[
  {
    "path": ".editorconfig",
    "content": "[*.{kt,kts}]\nindent_size=2\ninsert_final_newline=true\nmax_line_length=150\ndisabled_rules=import-ordering"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-problem-report.md",
    "content": "---\nname: Bug/Problem Report\nabout: Create a report of bug/problem you have encountered\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\nIf you have an idea for a new feature or how to improve the app please use [Discussions](https://github.com/michaldrabik/showly-2.0/discussions) for that purpose.\n\n**Describe the bug**\nA clear description of what the problem is.\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n"
  },
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\nktlint2.jar\n/app/keystore.properties\n/app/keystore\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 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 General Public License is a free, copyleft license for\nsoftware and other kinds of works.\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,\nthe GNU General Public License is 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.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\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  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\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 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. Use with the GNU Affero General Public License.\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 Affero 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 special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe 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 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 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 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 General Public License as published by\n    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 General Public License for more details.\n\n    You should have received a copy of the GNU 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 the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\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 GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "![Version](https://img.shields.io/github/v/tag/1RandomDev/showly-oss?style=flat&label=Version)\n![Downloads](https://img.shields.io/github/downloads/1RandomDev/showly-oss/total?style=flat&label=Downloads)\n\n> [!NOTE]  \n> Since the official Showly app is now open source (only APK on GitHub, not the PlayStore version), this fork becomes obsolete and will therefor be archived. You can download the latest OSS version from the [official repo](https://github.com/michaldrabik/showly/releases).\n\n# Showly OSS\n<img src=\"https://i.ibb.co/ChBN7Lg/ic-launcher.png\" align=\"left\" width=\"180\" hspace=\"10\" vspace=\"10\" />\n\nShowly 2.0 is modern, slick, open source Android TV Shows Tracker.\n\nThis fork gets rid of all proprietary tracking libraries.\n\n<a href=\"https://github.com/1RandomDev/showly-oss/releases/latest\">\n  <img\n    alt=\"Get it on GitHub\"\n    height=\"80\"\n    src=\"badge_github.png\"/>\n</a>\n<a href=\"https://apt.izzysoft.de/fdroid/index/apk/com.michaldrabik.showly_oss\">\n  <img\n    alt=\"Get it on IzzyOnDroid\"\n    height=\"80\"\n    src=\"https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png\"/>\n</a>\n\n## Screenshots\n\n<div>\n   <img src=\"assets/screenshots/github1a.jpg\" width=\"150\" alt=\"Screenshot 1\">\n   <img src=\"assets/screenshots/github2a.jpg\" width=\"150\" alt=\"Screenshot 2\">\n   <img src=\"assets/screenshots/github3a.jpg\" width=\"150\" alt=\"Screenshot 3\">\n   <img src=\"assets/screenshots/github4a.jpg\" width=\"150\" alt=\"Screenshot 4\">\n</div>\n\n## Project Setup\n\n1. Clone repository and open project in the latest version of Android Studio.\n2. Create `keystore.properties` file and put it in the `/app` folder.\n3. Add following properties into `keystore.properties` file (values are not important at this moment):\n```\nkeyAlias=github\nkeyPassword=github\nstorePassword=github\n```\n4. Add your [Trakt.tv](https://trakt.tv/oauth/applications), [TMDB](https://developers.themoviedb.org/3/), [OMDB](http://www.omdbapi.com) and [Reddit](https://www.reddit.com/prefs/apps) API keys as following properties into your `local.properties` file located in the root directory of the project:\n```\ntraktClientId=\"your trakt client id\"\ntraktClientSecret=\"your trakt client secret\"\ntmdbApiKey=\"your tmdb api key (v4)\"\nomdbApiKey=\"your omdb api key\"\nredditClientId=\"your reddit client id\"\n```\n5. Rebuild and start the app.\n\n## Issues & Contributions\n\nFeel free to post problems with the app as Github [Issues](https://github.com/1RandomDev/showly-oss/issues).\n\nFeatures ideas should be posted as new Github [Discussion](https://github.com/michaldrabik/showly-2.0/discussions).\n\nPull requests are welcome. Remember about leaving a comment in the relevant issue if you are working on something.\n\n### Language Translations\n\nWe're always looking for help with translating app into more languages.<br>\nIf you are interested in helping now or in the future, please visit our CrowdIn project and join:<br>\nhttps://crwd.in/showly-android-app\n\n## FAQ\n\n**1. Can I watch/stream/download shows and movies with Showly app?**\n\n  No, that is not possible. Showly is a progress tracking type of app - not a streaming service.\n\n**2. Show/Episode/Movie I'm looking for seems to be missing. What can I do?**\n\n  Showly uses [Trakt.tv](https://trakt.tv) as its main data source.\n  If something is missing please use \"Import Show\" / \"Import Movie\" option located at the bottom of Trakt.tv website.\n  It's also possible to contact Trakt.tv support about any related issue.\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n/release\n"
  },
  {
    "path": "app/build.gradle",
    "content": "plugins {\n  id 'com.android.application'\n  id 'kotlin-android'\n  id 'com.google.devtools.ksp'\n  id 'dagger.hilt.android.plugin'\n}\n\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    buildConfig = true\n    viewBinding true\n  }\n\n  hilt {\n    enableExperimentalClasspathAggregation = true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    applicationId \"com.michaldrabik.showly_oss\"\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n    compileSdkVersion versions.compileSdk\n    versionCode versions.versionCode\n    versionName versions.versionName\n    resourceConfigurations += ['en', 'ar', 'de', 'es', 'fi', 'fr', 'it', 'pl', 'pt', 'ru', 'tr', 'zh', 'uk', 'vi']\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    debug {\n      applicationIdSuffix '.debug'\n      versionNameSuffix '-debug'\n      minifyEnabled true\n      proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n      manifestPlaceholders = [\n          appIcon: \"@mipmap/ic_launcher_gray\",\n          appIconRound: \"@mipmap/ic_launcher_round_gray\"\n      ]\n    }\n    release {\n      minifyEnabled true\n      proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n      manifestPlaceholders = [\n              appIcon: \"@mipmap/ic_launcher\",\n              appIconRound: \"@mipmap/ic_launcher_round\"\n      ]\n    }\n  }\n\n  lint {\n    checkReleaseBuilds false\n  }\n\n  namespace 'com.michaldrabik.showly_oss'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-remote')\n  implementation project(':data-local')\n  implementation project(':repository')\n  implementation project(':ui-base')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n  implementation project(':ui-trakt-sync')\n  implementation project(':ui-discover')\n  implementation project(':ui-discover-movies')\n  implementation project(':ui-episodes')\n  implementation project(':ui-comments')\n  implementation project(':ui-lists')\n  implementation project(':ui-show')\n  implementation project(':ui-movie')\n  implementation project(':ui-gallery')\n  implementation project(':ui-my-shows')\n  implementation project(':ui-my-movies')\n  implementation project(':ui-search')\n  implementation project(':ui-statistics')\n  implementation project(':ui-statistics-movies')\n  implementation project(':ui-settings')\n  implementation project(':ui-progress')\n  implementation project(':ui-progress-movies')\n  implementation project(':ui-premium')\n  implementation project(':ui-news')\n  implementation project(':ui-widgets')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n  implementation libs.hilt.work\n  ksp libs.hilt.work.compiler\n\n  testImplementation libs.bundles.testing\n  androidTestImplementation libs.android.test.runner\n\n  //  debugImplementation \"com.squareup.leakcanary:leakcanary-android:$versions.leakCanary\"\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-dontwarn okhttp3.internal.platform.ConscryptPlatform"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  >\n\n  <uses-permission android:name=\"android.permission.INTERNET\" />\n  <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />\n  <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n  <uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\" />\n  <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n\n  <application\n    android:name=\".App\"\n    android:allowBackup=\"true\"\n    android:icon=\"${appIcon}\"\n    android:label=\"@string/app_name\"\n    android:localeConfig=\"@xml/locales_config\"\n    android:roundIcon=\"${appIconRound}\"\n    android:supportsRtl=\"true\"\n    android:theme=\"@style/AppTheme\"\n    tools:ignore=\"GoogleAppIndexingWarning\"\n    >\n\n    <!-- Activities -->\n\n    <activity\n      android:name=\".ui.main.MainActivity\"\n      android:configChanges=\"orientation|screenSize|keyboardHidden|keyboard\"\n      android:exported=\"true\"\n      android:launchMode=\"singleInstance\"\n      android:screenOrientation=\"portrait\"\n      tools:ignore=\"LockedOrientationActivity\"\n      >\n      <intent-filter>\n        <action android:name=\"android.intent.action.MAIN\" />\n\n        <category android:name=\"android.intent.category.LAUNCHER\" />\n        <category android:name=\"android.intent.category.DEFAULT\" />\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=\"trakt\"\n          android:scheme=\"showlyoss\"\n          />\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:pathPrefix=\"/title/tt\"\n          android:scheme=\"https\"\n          />\n\n        <data android:host=\"imdb.com\" />\n        <data android:host=\"www.imdb.com\" />\n        <data android:host=\"m.imdb.com\" />\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\n        <data android:scheme=\"https\" />\n\n        <data android:pathPrefix=\"/tv/\" />\n        <data android:pathPrefix=\"/movie/\" />\n\n        <data android:host=\"themoviedb.org\" />\n        <data android:host=\"www.themoviedb.org\" />\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 android:scheme=\"https\" />\n\n        <data android:host=\"trakt.tv\" />\n        <data android:host=\"www.trakt.tv\" />\n\n        <data android:pathPrefix=\"/shows/\" />\n        <data android:pathPrefix=\"/movies/\" />\n      </intent-filter>\n\n      <meta-data\n        android:name=\"android.app.shortcuts\"\n        android:resource=\"@xml/shortcuts\"\n        />\n\n    </activity>\n\n    <!--  Work Manager  -->\n\n    <provider\n      android:name=\"androidx.startup.InitializationProvider\"\n      android:authorities=\"${applicationId}.androidx-startup\"\n      tools:node=\"remove\"\n      />\n\n    <!-- Services -->\n\n    <service\n      android:name=\"androidx.appcompat.app.AppLocalesMetadataHolderService\"\n      android:enabled=\"false\"\n      android:exported=\"false\"\n      >\n      <meta-data\n        android:name=\"autoStoreLocales\"\n        android:value=\"true\"\n        />\n    </service>\n  </application>\n</manifest>\n"
  },
  {
    "path": "app/src/main/assets/release_notes.txt",
    "content": "* Fix issue where actors list of shows and movies would not return to its scrolled position\n* Other bugfixes\n\nDo you enjoy Showly and want to support the one and only developer?\nBecome a Premium user and gain access to cool bonus features! Check Settings to learn more!\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/App.kt",
    "content": "package com.michaldrabik.showly_oss\n\nimport android.app.Application\nimport android.app.NotificationChannel\nimport android.os.Build\nimport android.os.StrictMode\nimport androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode\nimport androidx.hilt.work.HiltWorkerFactory\nimport androidx.work.Configuration\nimport com.jakewharton.processphoenix.ProcessPhoenix\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.AppScopeProvider\nimport com.michaldrabik.ui_base.common.WidgetsProvider\nimport com.michaldrabik.ui_base.utilities.extensions.notificationManager\nimport com.michaldrabik.ui_model.Settings\nimport com.michaldrabik.ui_widgets.calendar.CalendarWidgetProvider\nimport com.michaldrabik.ui_widgets.calendar_movies.CalendarMoviesWidgetProvider\nimport com.michaldrabik.ui_widgets.progress.ProgressWidgetProvider\nimport com.michaldrabik.ui_widgets.progress_movies.ProgressMoviesWidgetProvider\nimport dagger.hilt.android.HiltAndroidApp\nimport kotlinx.coroutines.MainScope\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runBlocking\nimport timber.log.Timber\nimport javax.inject.Inject\nimport com.michaldrabik.ui_base.fcm.NotificationChannel as AppNotificationChannel\n\n@HiltAndroidApp\nclass App :\n  Application(),\n  AppScopeProvider,\n  Configuration.Provider,\n  WidgetsProvider {\n\n  override val appScope = MainScope()\n\n  @Inject lateinit var workerFactory: HiltWorkerFactory\n  @Inject lateinit var settingsRepository: SettingsRepository\n\n  override fun onCreate() {\n\n    fun setupSettings() = runBlocking {\n      if (!settingsRepository.isInitialized()) {\n        settingsRepository.update(Settings.createInitial())\n      }\n    }\n\n    fun setupStrictMode() {\n      if (BuildConfig.DEBUG) {\n        StrictMode\n          .setThreadPolicy(\n            StrictMode.ThreadPolicy.Builder()\n              .detectAll()\n              .penaltyLog()\n              .build()\n          )\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n          StrictMode.setVmPolicy(\n            StrictMode.VmPolicy.Builder()\n              .detectUnsafeIntentLaunch()\n              .penaltyDeath()\n              .build()\n          )\n        }\n      }\n    }\n\n    fun setupNotificationChannels() {\n      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return\n\n      fun createChannel(channel: AppNotificationChannel) =\n        NotificationChannel(\n          /* id = */ channel.name,\n          /* name = */ channel.displayName,\n          /* importance = */ channel.importance\n        ).apply {\n          description = channel.description\n        }\n\n      notificationManager().run {\n        createNotificationChannel(createChannel(AppNotificationChannel.GENERAL_INFO))\n        createNotificationChannel(createChannel(AppNotificationChannel.SHOWS_INFO))\n        createNotificationChannel(createChannel(AppNotificationChannel.EPISODES_ANNOUNCEMENTS))\n        createNotificationChannel(createChannel(AppNotificationChannel.MOVIES_ANNOUNCEMENTS))\n      }\n    }\n\n    fun setupTheme() {\n      setDefaultNightMode(settingsRepository.theme)\n    }\n\n    super.onCreate()\n\n    if (ProcessPhoenix.isPhoenixProcess(this)) return\n\n    if (BuildConfig.DEBUG) {\n      Timber.plant(Timber.DebugTree())\n    }\n\n    setupSettings()\n    setupStrictMode()\n    setupNotificationChannels()\n    setupTheme()\n  }\n\n  override fun requestShowsWidgetsUpdate() {\n    appScope.launch {\n      ProgressWidgetProvider.requestUpdate(applicationContext)\n      CalendarWidgetProvider.requestUpdate(applicationContext)\n    }\n  }\n\n  override fun requestMoviesWidgetsUpdate() {\n    appScope.launch {\n      ProgressMoviesWidgetProvider.requestUpdate(applicationContext)\n      CalendarMoviesWidgetProvider.requestUpdate(applicationContext)\n    }\n  }\n\n  override fun getWorkManagerConfiguration() =\n    Configuration.Builder()\n      .setWorkerFactory(workerFactory)\n      .build()\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/di/module/PreferencesModule.kt",
    "content": "package com.michaldrabik.showly_oss.di.module\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.components.SingletonComponent\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nclass PreferencesModule {\n\n  @Provides\n  @Singleton\n  @Named(\"tipsPreferences\")\n  fun providesTutorialsPreferences(@ApplicationContext context: Context): SharedPreferences =\n    context.applicationContext.getSharedPreferences(\n      \"PREFERENCES_TUTORIALS\",\n      Context.MODE_PRIVATE\n    )\n\n  @Provides\n  @Singleton\n  @Named(\"watchlistPreferences\")\n  fun providesProgressShowsPreferences(@ApplicationContext context: Context): SharedPreferences =\n    context.applicationContext.getSharedPreferences(\n      \"PREFERENCES_WATCHLIST\",\n      Context.MODE_PRIVATE\n    )\n\n  @Provides\n  @Singleton\n  @Named(\"progressOnHoldPreferences\")\n  fun providesProgressShowsOnHoldPreferences(@ApplicationContext context: Context): SharedPreferences =\n    context.applicationContext.getSharedPreferences(\n      \"PREFERENCES_PROGRESS_SHOWS_ON_HOLD\",\n      Context.MODE_PRIVATE\n    )\n\n  @Provides\n  @Singleton\n  @Named(\"progressMoviesPreferences\")\n  fun providesProgressMoviesPreferences(@ApplicationContext context: Context): SharedPreferences =\n    context.applicationContext.getSharedPreferences(\n      \"PREFERENCES_PROGRESS_MOVIES\",\n      Context.MODE_PRIVATE\n    )\n\n  @Provides\n  @Singleton\n  @Named(\"miscPreferences\")\n  fun providesMiscPreferences(@ApplicationContext context: Context): SharedPreferences =\n    context.applicationContext.getSharedPreferences(\n      \"PREFERENCES_MISC\",\n      Context.MODE_PRIVATE\n    )\n\n  @Provides\n  @Singleton\n  @Named(\"spoilersPreferences\")\n  fun providesSpoilersPreferences(@ApplicationContext context: Context): SharedPreferences =\n    context.applicationContext.getSharedPreferences(\n      \"PREFERENCES_SPOILERS\",\n      Context.MODE_PRIVATE\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/di/module/ServicesModule.kt",
    "content": "package com.michaldrabik.showly_oss.di.module\n\nimport android.content.Context\nimport android.net.ConnectivityManager\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)\nclass ServicesModule {\n\n  @Provides\n  @Singleton\n  fun providesConnectivityManager(@ApplicationContext context: Context) =\n    context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/di/module/WorkModule.kt",
    "content": "package com.michaldrabik.showly_oss.di.module\n\nimport android.content.Context\nimport androidx.work.WorkManager\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)\nclass WorkModule {\n\n  @Provides\n  @Singleton\n  fun providesWorkManager(@ApplicationContext context: Context) =\n    WorkManager.getInstance(context)\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/fcm/FcmExtra.kt",
    "content": "package com.michaldrabik.showly_oss.fcm\n\nenum class FcmExtra(val key: String) {\n  SHOW_ID(\"extra_show_id\")\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/BaseActivity.kt",
    "content": "package com.michaldrabik.showly_oss.ui\n\nimport android.annotation.SuppressLint\nimport android.net.Uri\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.navigation.fragment.NavHostFragment\nimport androidx.navigation.fragment.findNavController\nimport com.michaldrabik.showly_oss.R\nimport com.michaldrabik.showly_oss.fcm.FcmExtra\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.common.OnTraktAuthorizeListener\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_MOVIE_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SHOW_ID\nimport com.michaldrabik.ui_widgets.BaseWidgetProvider.Companion.EXTRA_MOVIE_ID\nimport com.michaldrabik.ui_widgets.BaseWidgetProvider.Companion.EXTRA_SHOW_ID\nimport com.michaldrabik.ui_widgets.search.SearchWidgetProvider\n\nabstract class BaseActivity : AppCompatActivity() {\n\n  private val actionKeys = arrayOf(\n    FcmExtra.SHOW_ID.key,\n    EXTRA_SHOW_ID,\n    EXTRA_MOVIE_ID\n  )\n\n  protected fun findNavHostFragment() = supportFragmentManager.findFragmentById(R.id.navigationHost) as? NavHostFragment\n\n  protected abstract fun handleSearchWidgetClick(bundle: Bundle?)\n\n  fun handleNotification(extras: Bundle?, action: () -> Unit = {}) {\n    if (extras == null) return\n    if (extras.containsKey(SearchWidgetProvider.EXTRA_WIDGET_SEARCH_CLICK)) {\n      handleSearchWidgetClick(extras)\n      return\n    }\n    actionKeys.forEach {\n      if (extras.containsKey(it)) {\n        handleShowMovieExtra(extras, it, action)\n      }\n    }\n  }\n\n  @SuppressLint(\"RestrictedApi\")\n  private fun handleShowMovieExtra(extras: Bundle, key: String, action: () -> Unit) {\n    val itemId = extras.getString(key)?.toLong() ?: -1\n    val bundle = Bundle().apply {\n      putLong(ARG_SHOW_ID, itemId)\n      putLong(ARG_MOVIE_ID, itemId)\n    }\n\n    findNavHostFragment()?.findNavController()?.run {\n      try {\n        val isShow = key in arrayOf(EXTRA_SHOW_ID, FcmExtra.SHOW_ID.key)\n        if (isShow) {\n          navigate(R.id.actionNavigateShowDetailsFragment, bundle)\n        } else {\n          navigate(R.id.actionNavigateMovieDetailsFragment, bundle)\n        }\n        extras.clear()\n        action()\n      } catch (error: Throwable) {\n        Logger.record(error, \"BaseActivity::handleShowMovieExtra()\")\n      }\n    }\n  }\n\n  protected fun handleTraktAuthorization(authData: Uri?) {\n    findNavHostFragment()?.findNavController()?.currentDestination?.id?.let {\n      val navHost = supportFragmentManager.findFragmentById(R.id.navigationHost)\n      navHost?.childFragmentManager?.primaryNavigationFragment?.let {\n        if (authData.toString().startsWith(\"showlyoss://trakt\")) {\n          (it as? OnTraktAuthorizeListener)?.onAuthorizationResult(authData)\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/MainActivity.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.ViewGroup\nimport android.view.animation.DecelerateInterpolator\nimport androidx.activity.addCallback\nimport androidx.activity.viewModels\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.navigation.fragment.findNavController\nimport androidx.work.WorkManager\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.Mode.MOVIES\nimport com.michaldrabik.common.Mode.SHOWS\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.showly_oss.R\nimport com.michaldrabik.showly_oss.databinding.ActivityMainBinding\nimport com.michaldrabik.showly_oss.ui.BaseActivity\nimport com.michaldrabik.showly_oss.ui.main.delegates.MainTipsDelegate\nimport com.michaldrabik.showly_oss.ui.main.delegates.TipsDelegate\nimport com.michaldrabik.showly_oss.ui.views.WhatsNewView\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkResolver\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.common.OnShowsMoviesSyncedListener\nimport com.michaldrabik.ui_base.common.OnTabReselectedListener\nimport com.michaldrabik.ui_base.events.Event\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.ShowsMoviesSyncComplete\nimport com.michaldrabik.ui_base.events.TraktQuickSyncSuccess\nimport com.michaldrabik.ui_base.events.TraktSyncAuthError\nimport com.michaldrabik.ui_base.network.NetworkStatusProvider\nimport com.michaldrabik.ui_base.sync.ShowsMoviesSyncWorker\nimport com.michaldrabik.ui_base.utilities.ModeHost\nimport com.michaldrabik.ui_base.utilities.MoviesStatusHost\nimport com.michaldrabik.ui_base.utilities.NavigationHost\nimport com.michaldrabik.ui_base.utilities.SnackbarHost\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.openWebUrl\nimport com.michaldrabik.ui_base.utilities.extensions.showErrorSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_settings.helpers.AppLanguage\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass MainActivity :\n  BaseActivity(),\n  SnackbarHost,\n  NavigationHost,\n  ModeHost,\n  MoviesStatusHost,\n  TipsDelegate by MainTipsDelegate() {\n\n  companion object {\n    private const val NAVIGATION_TRANSITION_DURATION_MS = 350L\n    private const val ARG_NAVIGATION_VISIBLE = \"ARG_NAVIGATION_VISIBLE\"\n  }\n\n  private val viewModel by viewModels<MainViewModel>()\n  private lateinit var binding: ActivityMainBinding\n\n  private val navigationHeightPad by lazy { dimenToPx(R.dimen.bottomNavigationHeightPadded) }\n  private val navigationHeight by lazy { dimenToPx(R.dimen.bottomNavigationHeight) }\n  private val decelerateInterpolator by lazy { DecelerateInterpolator(2F) }\n\n  @Inject lateinit var workManager: WorkManager\n  @Inject lateinit var eventsManager: EventsManager\n  @Inject lateinit var deepLinkResolver: DeepLinkResolver\n  @Inject lateinit var settingsRepository: SettingsRepository\n  @Inject lateinit var networkStatusProvider: NetworkStatusProvider\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    binding = ActivityMainBinding.inflate(layoutInflater)\n    setContentView(binding.root)\n\n    //onUpdateDownloaded() TODO: Implement own updater from GitHub\n\n    registerTipsDelegate(viewModel, binding)\n\n    setupViewModel()\n    setupNavigation()\n    setupView()\n    setupNetworkObserver()\n\n    restoreState(savedInstanceState)\n    onNewIntent(intent)\n  }\n\n  override fun onStart() {\n    super.onStart()\n    ShowsMoviesSyncWorker.schedule(workManager)\n  }\n\n  override fun onResume() {\n    super.onResume()\n    setupBackPressed()\n  }\n\n  override fun onNewIntent(intent: Intent?) {\n    super.onNewIntent(intent)\n    handleAppShortcut(intent)\n    handleNotification(intent?.extras) { hideNavigation(false) }\n    handleTraktAuthorization(intent?.data)\n    handleDeepLink(intent)\n  }\n\n  override fun onDestroy() {\n    lifecycle.removeObserver(networkStatusProvider)\n    super.onDestroy()\n  }\n\n  private fun setupViewModel() {\n    lifecycleScope.launch {\n      repeatOnLifecycle(Lifecycle.State.STARTED) {\n        launch { viewModel.uiState.collect { render(it) } }\n        launch { eventsManager.events.collect { handleEvent(it) } }\n      }\n    }\n    viewModel.initialize()\n    viewModel.refreshTraktSyncSchedule()\n  }\n\n  private fun setupView() {\n    with(binding.bottomMenuView) {\n      isModeMenuEnabled = hasMoviesEnabled()\n      onModeSelected = { setMode(it) }\n    }\n    binding.viewMask.onClick { /* NOOP */ }\n  }\n\n  private fun setupNetworkObserver() {\n    lifecycle.addObserver(networkStatusProvider)\n    lifecycleScope.launch {\n      repeatOnLifecycle(Lifecycle.State.STARTED) {\n        launch {\n          networkStatusProvider.status.collect {\n            binding.statusView.visibleIf(!it)\n            binding.statusView.text = getString(R.string.errorNoInternetConnection)\n          }\n        }\n      }\n    }\n  }\n\n  private fun setupNavigation() {\n    findNavControl()?.run {\n      val graph = navInflater.inflate(R.navigation.navigation_graph).apply {\n        val destination = when (viewModel.getMode()) {\n          SHOWS -> R.id.progressMainFragment\n          MOVIES -> R.id.progressMoviesMainFragment\n          else -> throw IllegalStateException()\n        }\n        setStartDestination(destination)\n      }\n      setGraph(graph, Bundle.EMPTY)\n    }\n    with(binding.bottomMenuView.binding.bottomNavigationView) {\n      setOnItemSelectedListener { item ->\n        if (selectedItemId == item.itemId) {\n          doForFragments { (it as? OnTabReselectedListener)?.onTabReselected() }\n          return@setOnItemSelectedListener true\n        }\n\n        val target = when (item.itemId) {\n          R.id.menuProgress -> getMenuProgressAction()\n          R.id.menuDiscover -> getMenuDiscoverAction()\n          R.id.menuCollection -> getMenuCollectionAction()\n          R.id.menuNews -> R.id.actionNavigateNewsFragment\n          else -> throw IllegalStateException(\"Invalid menu item.\")\n        }\n\n        findNavControl()?.navigate(target)\n        showNavigation(true)\n\n        if (item.itemId == R.id.menuDiscover) {\n          // Try showing rate app dialog when user navigates to Discover section.\n          viewModel.checkRateApp()\n        }\n\n        return@setOnItemSelectedListener true\n      }\n      menu.findItem(R.id.menuNews).isVisible = viewModel.hasNewsEnabled()\n    }\n  }\n\n  private fun setupBackPressed() {\n    with(binding) {\n      onBackPressedDispatcher.addCallback(this@MainActivity) {\n        if (tutorialView.isVisible) {\n          tutorialView.fadeOut()\n          return@addCallback\n        }\n        findNavControl()?.run {\n          when (currentDestination?.id) {\n            R.id.discoverFragment,\n            R.id.discoverMoviesFragment,\n            R.id.followedShowsFragment,\n            R.id.followedMoviesFragment,\n            R.id.listsFragment,\n            R.id.newsFragment,\n            -> {\n              bottomMenuView.binding.bottomNavigationView.selectedItemId = R.id.menuProgress\n            }\n            else -> {\n              remove()\n              super.onBackPressed()\n            }\n          }\n        }\n      }\n    }\n  }\n\n  override fun hideNavigation(animate: Boolean) {\n    with(binding) {\n      hideAllTips()\n      bottomMenuView.binding.bottomNavigationView.run {\n        isEnabled = false\n        isClickable = false\n      }\n      snackbarHost.translationY = navigationHeight.toFloat()\n      bottomNavigationWrapper.animate().translationYBy(navigationHeightPad.toFloat())\n        .setDuration(if (animate) NAVIGATION_TRANSITION_DURATION_MS else 0)\n        .setInterpolator(decelerateInterpolator).start()\n    }\n  }\n\n  override fun showNavigation(animate: Boolean) {\n    showAllTips()\n    binding.bottomMenuView.binding.bottomNavigationView.run {\n      isEnabled = true\n      isClickable = true\n    }\n    binding.snackbarHost.translationY = 0F\n    binding.bottomNavigationWrapper\n      .animate()\n      .translationY(0F)\n      .setDuration(if (animate) NAVIGATION_TRANSITION_DURATION_MS else 0)\n      .setInterpolator(decelerateInterpolator).start()\n  }\n\n  override fun navigateToDiscover() {\n    binding.bottomMenuView.binding.bottomNavigationView.selectedItemId = R.id.menuDiscover\n  }\n\n  override fun setMode(mode: Mode, force: Boolean) {\n    if (force || viewModel.getMode() != mode) {\n      viewModel.setMode(mode)\n      val target = when (binding.bottomMenuView.binding.bottomNavigationView.selectedItemId) {\n        R.id.menuDiscover -> getMenuDiscoverAction()\n        R.id.menuCollection -> getMenuCollectionAction()\n        R.id.menuProgress -> getMenuProgressAction()\n        R.id.menuNews -> R.id.actionNavigateNewsFragment\n        else -> 0\n      }\n      if (target != 0) {\n        findNavControl()?.navigate(target)\n      }\n    }\n  }\n\n  override fun getMode() = viewModel.getMode()\n\n  override fun hasMoviesEnabled() = viewModel.hasMoviesEnabled()\n\n  private fun render(uiState: MainUiState) {\n    with(binding) {\n      uiState.run {\n        isLoading.let {\n          mainProgress.visibleIf(it)\n        }\n        showMask.let {\n          viewMask.visibleIf(it)\n        }\n        isInitialRun?.let {\n          if (it.consume() == true) {\n            viewModel.checkInitialLanguage()\n          }\n        }\n        showWhatsNew?.let {\n          if (it.consume() == true) showWhatsNewDialog()\n        }\n        initialLanguage?.let { event ->\n          event.consume()?.let {\n            showWelcomeDialog(it)\n          }\n        }\n        openLink?.let { event ->\n          event.consume()?.let { bundle ->\n            findNavHostFragment()?.findNavController()?.let { nav ->\n              bundle.show?.let {\n                deepLinkResolver.resolveDestination(nav, bottomMenuView.binding.bottomNavigationView, it)\n              }\n              bundle.movie?.let {\n                deepLinkResolver.resolveDestination(nav, bottomMenuView.binding.bottomNavigationView, it)\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  private fun showWelcomeDialog(language: AppLanguage) {\n    navigateToDiscover()\n    with(binding.welcomeView) {\n      setLanguage(language)\n      fadeIn()\n      onOkClickListener = {\n        fadeOut()\n        showMask(false)\n        if (language != AppLanguage.ENGLISH) {\n          showWelcomeLanguageDialog(language)\n        }\n      }\n    }\n    showMask(true)\n  }\n\n  private fun showWelcomeLanguageDialog(language: AppLanguage) {\n    with(binding.welcomeLanguageView) {\n      setLanguage(language)\n      fadeIn()\n      onYesClick = {\n        viewModel.setLanguage(language)\n        fadeOut()\n        showMask(false)\n      }\n      onNoClick = {\n        viewModel.setLanguage(AppLanguage.ENGLISH)\n        fadeOut()\n        showMask(false)\n      }\n    }\n    showMask(true)\n  }\n\n  private fun showMask(show: Boolean) {\n    binding.viewMask.visibleIf(show)\n    if (!show) viewModel.clearMask()\n  }\n\n  @SuppressLint(\"MissingSuperCall\")\n  override fun onSaveInstanceState(outState: Bundle) {\n    outState.putBoolean(ARG_NAVIGATION_VISIBLE, binding.bottomNavigationWrapper.translationY == 0F)\n    super.onSaveInstanceState(outState)\n  }\n\n  private fun restoreState(savedInstanceState: Bundle?) {\n    val isNavigationVisible = savedInstanceState?.getBoolean(ARG_NAVIGATION_VISIBLE, true) ?: true\n    if (!isNavigationVisible) hideNavigation(true)\n  }\n\n  private fun doForFragments(action: (Fragment) -> Unit) {\n    findNavControl()?.currentDestination?.id?.let {\n      val navHost = supportFragmentManager.findFragmentById(R.id.navigationHost)\n      navHost?.childFragmentManager?.primaryNavigationFragment?.let { action(it) }\n    }\n  }\n\n  private fun handleEvent(event: Event) {\n    when (event) {\n      is ShowsMoviesSyncComplete -> {\n        if (event.count > 0) {\n          doForFragments { (it as? OnShowsMoviesSyncedListener)?.onShowsMoviesSyncFinished() }\n        }\n        viewModel.refreshAnnouncements()\n      }\n      is TraktQuickSyncSuccess -> {\n        val message = resources.getQuantityString(R.plurals.textTraktQuickSyncComplete, event.count, event.count)\n        provideSnackbarLayout().showInfoSnackbar(message)\n      }\n      is TraktSyncAuthError -> {\n        provideSnackbarLayout().showErrorSnackbar(getString(R.string.errorTraktAuthorization))\n      }\n      else -> Timber.d(\"Event ignored. Noop.\")\n    }\n  }\n\n  private fun handleAppShortcut(intent: Intent?) {\n    when {\n      intent == null -> return\n\n      intent.extras?.containsKey(\"extraShortcutProgress\") == true ->\n        binding.bottomMenuView.binding.bottomNavigationView.selectedItemId = R.id.menuProgress\n\n      intent.extras?.containsKey(\"extraShortcutDiscover\") == true ->\n        binding.bottomMenuView.binding.bottomNavigationView.selectedItemId = R.id.menuDiscover\n\n      intent.extras?.containsKey(\"extraShortcutCollection\") == true ->\n        binding.bottomMenuView.binding.bottomNavigationView.selectedItemId = R.id.menuCollection\n\n      intent.extras?.containsKey(\"extraShortcutSearch\") == true -> {\n        binding.bottomMenuView.binding.bottomNavigationView.selectedItemId = R.id.menuDiscover\n        val action = when (viewModel.getMode()) {\n          SHOWS -> R.id.actionDiscoverFragmentToSearchFragment\n          MOVIES -> R.id.actionDiscoverMoviesFragmentToSearchFragment\n          else -> throw IllegalStateException()\n        }\n        findNavControl()?.navigate(action)\n      }\n    }\n  }\n\n  override fun handleSearchWidgetClick(bundle: Bundle?) {\n    findNavHostFragment()?.findNavController()?.run {\n      try {\n        when (currentDestination?.id) {\n          R.id.searchFragment -> return@run\n          R.id.showDetailsFragment, R.id.movieDetailsFragment -> navigateUp()\n        }\n        if (currentDestination?.id != R.id.discoverFragment) {\n          binding.bottomMenuView.binding.bottomNavigationView.selectedItemId = R.id.menuDiscover\n        }\n        when (currentDestination?.id) {\n          R.id.discoverFragment -> navigate(R.id.actionDiscoverFragmentToSearchFragment)\n          R.id.discoverMoviesFragment -> navigate(R.id.actionDiscoverMoviesFragmentToSearchFragment)\n        }\n        bundle?.clear()\n      } catch (error: Throwable) {\n        Logger.record(error, \"BaseActivity::handleSearchWidgetClick()\")\n      }\n    }\n  }\n\n  private fun showWhatsNewDialog() {\n    MaterialAlertDialogBuilder(this, R.style.AlertDialog).setBackground(ContextCompat.getDrawable(this, R.drawable.bg_dialog))\n      .setView(WhatsNewView(this)).setCancelable(false).setPositiveButton(R.string.textClose) { _, _ -> }\n      .setNeutralButton(\"GitHub\") { _, _ -> openWebUrl(Config.GITHUB_URL) }.show()\n  }\n\n  private fun getMenuDiscoverAction() = when (viewModel.getMode()) {\n    SHOWS -> R.id.actionNavigateDiscoverFragment\n    MOVIES -> R.id.actionNavigateDiscoverMoviesFragment\n    else -> throw IllegalStateException()\n  }\n\n  private fun getMenuCollectionAction() = when (viewModel.getMode()) {\n    SHOWS -> R.id.actionNavigateFollowedShowsFragment\n    MOVIES -> R.id.actionNavigateFollowedMoviesFragment\n    else -> throw IllegalStateException()\n  }\n\n  private fun getMenuProgressAction() = when (viewModel.getMode()) {\n    SHOWS -> R.id.actionNavigateProgressFragment\n    MOVIES -> R.id.actionNavigateProgressMoviesFragment\n    else -> throw IllegalStateException()\n  }\n\n  private fun onUpdateDownloaded() {\n    provideSnackbarLayout().showInfoSnackbar(\n      message = getString(R.string.textUpdateDownloaded), actionText = R.string.textUpdateInstall, length = LENGTH_INDEFINITE\n    ) {\n      // TODO: Implement own updater from GitHub\n    }\n  }\n\n  private fun handleDeepLink(intent: Intent?) {\n    deepLinkResolver.findSource(intent)?.let {\n      viewModel.openDeepLink(it)\n    }\n  }\n\n  override fun findNavControl() = findNavHostFragment()?.findNavController()\n\n  override fun provideSnackbarLayout(): ViewGroup = binding.snackbarHost\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/MainUiState.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main\n\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkBundle\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_settings.helpers.AppLanguage\n\n// TODO Split events into their Channel\ndata class MainUiState(\n  val isLoading: Boolean = false,\n  val isInitialRun: Event<Boolean>? = null,\n  val showWhatsNew: Event<Boolean>? = null,\n  val initialLanguage: Event<AppLanguage>? = null,\n  val showRateApp: Event<Boolean>? = null,\n  val showMask: Boolean = false,\n  val openLink: Event<DeepLinkBundle>? = null,\n)\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/MainViewModel.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main\n\nimport android.annotation.SuppressLint\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.core.os.LocaleListCompat\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.showly_oss.ui.main.cases.MainAnnouncementsCase\nimport com.michaldrabik.showly_oss.ui.main.cases.MainClearingCase\nimport com.michaldrabik.showly_oss.ui.main.cases.MainInitialsCase\nimport com.michaldrabik.showly_oss.ui.main.cases.MainModesCase\nimport com.michaldrabik.showly_oss.ui.main.cases.MainRateAppCase\nimport com.michaldrabik.showly_oss.ui.main.cases.MainSettingsCase\nimport com.michaldrabik.showly_oss.ui.main.cases.MainTipsCase\nimport com.michaldrabik.showly_oss.ui.main.cases.MainTraktCase\nimport com.michaldrabik.showly_oss.ui.main.cases.deeplink.MainDeepLinksCase\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkBundle\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkSource\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.combine\nimport com.michaldrabik.ui_base.utilities.extensions.launchDelayed\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_model.Tip\nimport com.michaldrabik.ui_settings.helpers.AppLanguage\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@SuppressLint(\"StaticFieldLeak\")\n@HiltViewModel\nclass MainViewModel @Inject constructor(\n  private val initCase: MainInitialsCase,\n  private val tipsCase: MainTipsCase,\n  private val traktCase: MainTraktCase,\n  private val clearingCase: MainClearingCase,\n  private val settingsCase: MainSettingsCase,\n  private val announcementsCase: MainAnnouncementsCase,\n  private val modesCase: MainModesCase,\n  private val rateAppCase: MainRateAppCase,\n  private val linksCase: MainDeepLinksCase,\n  private val settingsRepository: SettingsRepository,\n) : ViewModel() {\n\n  private val loadingState = MutableStateFlow(false)\n  private val maskState = MutableStateFlow(false)\n  private val initialRunEvent = MutableStateFlow<Event<Boolean>?>(null)\n  private val initialLanguageEvent = MutableStateFlow<Event<AppLanguage>?>(null)\n  private val whatsNewEvent = MutableStateFlow<Event<Boolean>?>(null)\n  private val rateAppEvent = MutableStateFlow<Event<Boolean>?>(null)\n  private val openLinkEvent = MutableStateFlow<Event<DeepLinkBundle>?>(null)\n\n  fun initialize() {\n    viewModelScope.launch {\n      val isInitialRun = checkInitialRun()\n      with(initCase) {\n        preloadRatings()\n        saveInstallTimestamp()\n      }\n      checkApi13Locale(isInitialRun)\n    }\n  }\n\n  private suspend fun checkInitialRun(): Boolean {\n    val isInitialRun = initCase.isInitialRun()\n    if (isInitialRun) {\n      initCase.setInitialRun(false)\n      initCase.setInitialNotifications()\n      initCase.setInitialCountry()\n    }\n\n    val showWhatsNew = initCase.showWhatsNew(isInitialRun)\n\n    initialRunEvent.value = Event(isInitialRun)\n    whatsNewEvent.value = Event(showWhatsNew)\n\n    return isInitialRun\n  }\n\n  fun checkRateApp() {\n    val showRateApp = rateAppCase.shouldShowRateApp()\n    rateAppEvent.value = Event(showRateApp)\n  }\n\n  fun setLanguage(appLanguage: AppLanguage) = initCase.setLanguage(appLanguage)\n\n  fun checkInitialLanguage() {\n    viewModelScope.launch {\n      val initialLanguage = initCase.checkInitialLanguage()\n      initialLanguageEvent.value = Event(initialLanguage)\n      maskState.value = true\n    }\n  }\n\n  private fun checkApi13Locale(isInitialRun: Boolean) {\n    if (!isInitialRun && !settingsRepository.isLocaleInitialised) {\n      settingsRepository.isLocaleInitialised = true\n      val locale = LocaleListCompat.forLanguageTags(settingsRepository.language)\n      AppCompatDelegate.setApplicationLocales(locale)\n    }\n  }\n\n  fun refreshAnnouncements() {\n    viewModelScope.launch {\n      announcementsCase.refreshAnnouncements()\n    }\n  }\n\n  fun refreshTraktSyncSchedule() {\n    viewModelScope.launch {\n      traktCase.run {\n        refreshTraktSyncSchedule()\n        refreshTraktQuickSync()\n      }\n    }\n  }\n\n  fun setMode(mode: Mode) = modesCase.setMode(mode)\n  fun getMode(): Mode = modesCase.getMode()\n\n  fun isTipShown(tip: Tip) = tipsCase.isTipShown(tip)\n  fun setTipShown(tip: Tip) = tipsCase.setTipShown(tip)\n\n  fun hasMoviesEnabled(): Boolean = settingsCase.hasMoviesEnabled()\n  fun hasNewsEnabled(): Boolean = settingsCase.hasNewsEnabled()\n\n  fun completeAppRate() = rateAppCase.complete()\n\n  fun clearMask() {\n    maskState.value = false\n  }\n\n  fun openDeepLink(source: DeepLinkSource) {\n    viewModelScope.launch {\n      val progressJob = launchDelayed(750) {\n        loadingState.value = true\n        maskState.value = true\n      }\n      try {\n        val result = when (source) {\n          is DeepLinkSource.ImdbSource -> linksCase.findById(source.id)\n          is DeepLinkSource.TmdbSource -> linksCase.findById(source.id, source.type)\n          is DeepLinkSource.TraktSource -> linksCase.findById(source.id, source.type)\n        }\n        loadingState.value = false\n        maskState.value = false\n        openLinkEvent.value = Event(result)\n      } catch (error: Throwable) {\n        Logger.record(error, \"MainViewModel::openDeepLink:$source\")\n        rethrowCancellation(error)\n      } finally {\n        progressJob.cancelAndJoin()\n      }\n    }\n  }\n\n  override fun onCleared() {\n    clearingCase.clear()\n    super.onCleared()\n  }\n\n  val uiState = combine(\n    initialRunEvent,\n    initialLanguageEvent,\n    whatsNewEvent,\n    rateAppEvent,\n    openLinkEvent,\n    loadingState,\n    maskState\n  ) { s1, s2, s3, s4, s5, s6, s7 ->\n    MainUiState(\n      isInitialRun = s1,\n      initialLanguage = s2,\n      showWhatsNew = s3,\n      showRateApp = s4,\n      openLink = s5,\n      isLoading = s6,\n      showMask = s7\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = MainUiState()\n  )\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/cases/MainAnnouncementsCase.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MainAnnouncementsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val announcementManager: AnnouncementManager,\n) {\n\n  suspend fun refreshAnnouncements() {\n    withContext(dispatchers.IO) {\n      announcementManager.refreshShowsAnnouncements()\n      announcementManager.refreshMoviesAnnouncements()\n    }\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/cases/MainClearingCase.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main.cases\n\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MainClearingCase @Inject constructor(\n  private val showImagesProvider: ShowImagesProvider,\n  private val movieImagesProvider: MovieImagesProvider,\n) {\n\n  fun clear() {\n    showImagesProvider.clear()\n    movieImagesProvider.clear()\n    Timber.d(\"Clearing...\")\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/cases/MainInitialsCase.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main.cases\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport android.telephony.TelephonyManager\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.core.content.edit\nimport androidx.core.os.LocaleListCompat\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.showly_oss.BuildConfig\nimport com.michaldrabik.ui_base.common.AppCountry\nimport com.michaldrabik.ui_base.utilities.extensions.withApiAtLeast\nimport com.michaldrabik.ui_settings.helpers.AppLanguage\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.CoroutineExceptionHandler\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.supervisorScope\nimport timber.log.Timber\nimport java.util.Locale\nimport javax.inject.Inject\nimport javax.inject.Named\n\n@ViewModelScoped\nclass MainInitialsCase @Inject constructor(\n  @ApplicationContext private val context: Context,\n  private val userTraktManager: UserTraktManager,\n  private val ratingsRepository: RatingsRepository,\n  private val settingsRepository: SettingsRepository,\n  @Named(\"miscPreferences\") private var miscPreferences: SharedPreferences,\n) {\n\n  suspend fun setInitialRun(value: Boolean) {\n    val settings = settingsRepository.load()\n    settings.let {\n      settingsRepository.update(it.copy(isInitialRun = value))\n    }\n  }\n\n  suspend fun isInitialRun(): Boolean {\n    val settings = settingsRepository.load()\n    return settings.isInitialRun\n  }\n\n  fun setInitialCountry() {\n    var country = (context.getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager)?.simCountryIso\n    if (country == null) {\n      val locale = LocaleListCompat.getAdjustedDefault()\n      country = if (locale.size() > 1) {\n        locale.get(1)?.country\n      } else {\n        locale.get(0)?.country\n      }\n    }\n    if (!country.isNullOrBlank()) {\n      AppCountry.values().forEach { appCountry ->\n        if (appCountry.code.equals(country, ignoreCase = true)) {\n          settingsRepository.country = appCountry.code\n          return\n        }\n      }\n    }\n  }\n\n  suspend fun setInitialNotifications() {\n    withApiAtLeast(33) {\n      val settings = settingsRepository.load()\n      settings.let {\n        settingsRepository.update(it.copy(episodesNotificationsEnabled = false))\n      }\n    }\n  }\n\n  fun setLanguage(appLanguage: AppLanguage) {\n    settingsRepository.language = appLanguage.code\n    val locales = LocaleListCompat.forLanguageTags(appLanguage.code)\n    AppCompatDelegate.setApplicationLocales(locales)\n  }\n\n  fun checkInitialLanguage(): AppLanguage {\n    val locales = LocaleListCompat.getAdjustedDefault()\n    val appLanguages = AppLanguage.values()\n\n    if (locales.size() == 1 && !locales[0]?.language.equals(Locale(\"en\").language)) {\n      appLanguages.forEach { appLanguage ->\n        if (appLanguage.code.equals(locales[0]?.language, ignoreCase = true)) {\n          return appLanguage\n        }\n      }\n    }\n\n    if (locales.size() > 1) {\n      val languagesCodes = arrayOf(locales[0], locales[1])\n        .filterNotNull()\n        .map { it.language.lowercase() }\n      if (languagesCodes.any { it != Locale(Config.DEFAULT_LANGUAGE).language }) {\n        val languageCodes = appLanguages.map { it.code }\n        languagesCodes.forEach { language ->\n          if (language in languageCodes) {\n            return appLanguages.first { it.code == language }\n          }\n        }\n        appLanguages\n          .filter { it.code != Config.DEFAULT_LANGUAGE }\n          .forEach { appLanguage ->\n            if (appLanguage.code in languagesCodes) {\n              return appLanguage\n            }\n          }\n      }\n    }\n\n    return AppLanguage.ENGLISH\n  }\n\n  suspend fun preloadRatings() = supervisorScope {\n    val errorHandler = CoroutineExceptionHandler { _, _ -> Timber.e(\"Failed to preload.\") }\n\n    if (!userTraktManager.isAuthorized()) {\n      return@supervisorScope\n    }\n\n    userTraktManager.checkAuthorization()\n    launch(errorHandler) { ratingsRepository.shows.preloadRatings() }\n    if (settingsRepository.isMoviesEnabled) {\n      launch(errorHandler) { ratingsRepository.movies.preloadRatings() }\n    }\n  }\n\n  fun showWhatsNew(isInitialRun: Boolean): Boolean {\n    val keyAppVersion = \"APP_VERSION\"\n    val keyAppVersionName = \"APP_VERSION_NAME\"\n\n    val version = miscPreferences.getInt(keyAppVersion, 0)\n    val name = miscPreferences.getString(keyAppVersionName, \"\")\n\n    fun isPatchUpdate(): Boolean {\n      if (name.isNullOrBlank()) return false\n\n      val major = name.split(\".\").getOrNull(0)?.toIntOrNull()\n      val minor = name.split(\".\").getOrNull(1)?.toIntOrNull()\n\n      val currentMajor = BuildConfig.VERSION_NAME.split(\".\").getOrNull(0)?.toIntOrNull()\n      val currentMinor = BuildConfig.VERSION_NAME.split(\".\").getOrNull(1)?.toIntOrNull()\n\n      if (major == currentMajor && minor == currentMinor) return true\n      return false\n    }\n\n    miscPreferences.edit {\n      putInt(keyAppVersion, BuildConfig.VERSION_CODE).apply()\n      putString(keyAppVersionName, BuildConfig.VERSION_NAME).apply()\n    }\n\n    if (Config.SHOW_WHATS_NEW &&\n      BuildConfig.VERSION_CODE > version &&\n      BuildConfig.VERSION_NAME != name &&\n      !isInitialRun &&\n      !isPatchUpdate()\n    ) {\n      return true\n    }\n    return false\n  }\n\n  fun saveInstallTimestamp() {\n    if (settingsRepository.installTimestamp == 0L) {\n      settingsRepository.installTimestamp = nowUtcMillis()\n      Timber.d(\"Installation timestamp saved: ${nowUtc()}\")\n    }\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/cases/MainModesCase.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main.cases\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.Mode.MOVIES\nimport com.michaldrabik.common.Mode.SHOWS\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MainModesCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setMode(mode: Mode) {\n    settingsRepository.mode = mode\n  }\n\n  fun getMode(): Mode {\n    val isMoviesEnabled = settingsRepository.isMoviesEnabled\n    val isMovies = settingsRepository.mode == MOVIES\n    return if (isMoviesEnabled && isMovies) MOVIES else SHOWS\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/cases/MainRateAppCase.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main.cases\n\nimport android.content.SharedPreferences\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\nimport javax.inject.Named\n\n@ViewModelScoped\nclass MainRateAppCase @Inject constructor(\n  @Named(\"miscPreferences\") private var miscPreferences: SharedPreferences\n) {\n\n  companion object {\n    const val KEY_RATE_APP_COUNT = \"KEY_RATE_APP_COUNT\"\n    const val KEY_RATE_APP_TIMESTAMP = \"KEY_RATE_APP_TIMESTAMP\"\n\n    const val MAX_COUNT = 3\n  }\n\n  fun shouldShowRateApp(): Boolean {\n    val count = miscPreferences.getInt(KEY_RATE_APP_COUNT, 0)\n    val timestamp = miscPreferences.getLong(KEY_RATE_APP_TIMESTAMP, -1)\n    val isPastTwoWeeks = nowUtcMillis() - timestamp > TimeUnit.DAYS.toMillis(14)\n\n    if (timestamp == -1L) {\n      updateTimestamp(count)\n      return false\n    }\n\n    if (count < MAX_COUNT && isPastTwoWeeks) {\n      updateTimestamp(count)\n      return true\n    }\n\n    return false\n  }\n\n  private fun updateTimestamp(count: Int) {\n    miscPreferences.edit().apply {\n      putInt(KEY_RATE_APP_COUNT, count)\n      putLong(KEY_RATE_APP_TIMESTAMP, nowUtcMillis())\n      apply()\n    }\n  }\n\n  fun complete() {\n    val count = miscPreferences.getInt(KEY_RATE_APP_COUNT, 0)\n    updateTimestamp(count + 1)\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/cases/MainSettingsCase.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.showly_oss.BuildConfig\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MainSettingsCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun hasMoviesEnabled() = settingsRepository.isMoviesEnabled\n\n  fun hasNewsEnabled(): Boolean {\n    if (BuildConfig.DEBUG) return true\n    return settingsRepository.isNewsEnabled && settingsRepository.isPremium\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/cases/MainTipsCase.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main.cases\n\nimport android.content.SharedPreferences\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.showly_oss.BuildConfig\nimport com.michaldrabik.ui_model.Tip\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\nimport javax.inject.Named\n\n@ViewModelScoped\nclass MainTipsCase @Inject constructor(\n  @Named(\"tipsPreferences\") private val sharedPreferences: SharedPreferences\n) {\n\n  fun isTipShown(tip: Tip) = when {\n    BuildConfig.DEBUG -> !Config.SHOW_TIPS_DEBUG\n    else -> sharedPreferences.getBoolean(tip.name, false)\n  }\n\n  fun setTipShown(tip: Tip) {\n    sharedPreferences.edit().putBoolean(tip.name, true).apply()\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/cases/MainTraktCase.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main.cases\n\nimport androidx.work.WorkManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.trakt.TraktSyncWorker\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncWorker\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MainTraktCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n  private val quickSyncManager: QuickSyncManager,\n  private val workManager: WorkManager,\n) {\n\n  suspend fun refreshTraktSyncSchedule() {\n    if (!settingsRepository.isInitialized()) return\n    val schedule = settingsRepository.load().traktSyncSchedule\n    TraktSyncWorker.schedulePeriodic(workManager, schedule, cancelExisting = false)\n  }\n\n  suspend fun refreshTraktQuickSync() {\n    if (quickSyncManager.isAnyScheduled()) {\n      QuickSyncWorker.schedule(workManager)\n    }\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/cases/deeplink/ImdbDeepLinkCase.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main.cases.deeplink\n\nimport com.michaldrabik.data_local.sources.MoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.ShowsLocalDataSource\nimport com.michaldrabik.data_remote.trakt.TraktRemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.movies.MovieDetailsRepository\nimport com.michaldrabik.repository.shows.ShowDetailsRepository\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkBundle\nimport com.michaldrabik.ui_model.IdImdb\nimport javax.inject.Inject\n\nclass ImdbDeepLinkCase @Inject constructor(\n  private val traktRemoteSource: TraktRemoteDataSource,\n  private val showsLocalSource: ShowsLocalDataSource,\n  private val moviesLocalSource: MoviesLocalDataSource,\n  private val showDetailsRepository: ShowDetailsRepository,\n  private val movieDetailsRepository: MovieDetailsRepository,\n  private val mappers: Mappers\n) {\n\n  companion object {\n    private const val SEARCH_ID_TYPE = \"imdb\"\n  }\n\n  suspend fun findById(imdbId: IdImdb): DeepLinkBundle {\n    val show = showDetailsRepository.find(imdbId)\n    if (show != null) {\n      return DeepLinkBundle(show = show)\n    }\n\n    val movie = movieDetailsRepository.find(imdbId)\n    if (movie != null) {\n      return DeepLinkBundle(movie = movie)\n    }\n\n    val searchResult = traktRemoteSource.fetchSearchId(SEARCH_ID_TYPE, imdbId.id)\n    if (searchResult.size == 1) {\n      val showSearch = searchResult[0].show\n      val movieSearch = searchResult[0].movie\n      when {\n        showSearch != null -> {\n          val uiShow = mappers.show.fromNetwork(showSearch)\n          showsLocalSource.upsert(listOf(mappers.show.toDatabase(uiShow)))\n          return DeepLinkBundle(show = uiShow)\n        }\n        movieSearch != null -> {\n          val uiMovie = mappers.movie.fromNetwork(movieSearch)\n          moviesLocalSource.upsert(listOf(mappers.movie.toDatabase(uiMovie)))\n          return DeepLinkBundle(movie = uiMovie)\n        }\n      }\n    }\n\n    return DeepLinkBundle.EMPTY\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/cases/deeplink/MainDeepLinksCase.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main.cases.deeplink\n\nimport com.michaldrabik.ui_model.IdImdb\nimport com.michaldrabik.ui_model.IdSlug\nimport com.michaldrabik.ui_model.IdTmdb\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MainDeepLinksCase @Inject constructor(\n  private val imdbDeepLinkCase: ImdbDeepLinkCase,\n  private val tmdbDeepLinkCase: TmdbDeepLinkCase,\n  private val traktDeepLinkCase: TraktDeepLinkCase,\n) {\n\n  suspend fun findById(imdbId: IdImdb) =\n    imdbDeepLinkCase.findById(imdbId)\n\n  suspend fun findById(tmdbId: IdTmdb, type: String) =\n    tmdbDeepLinkCase.findById(tmdbId, type)\n\n  suspend fun findById(traktSlug: IdSlug, type: String) =\n    traktDeepLinkCase.findById(traktSlug, type)\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/cases/deeplink/TmdbDeepLinkCase.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main.cases.deeplink\n\nimport com.michaldrabik.data_local.sources.MoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.ShowsLocalDataSource\nimport com.michaldrabik.data_remote.trakt.TraktRemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.movies.MovieDetailsRepository\nimport com.michaldrabik.repository.shows.ShowDetailsRepository\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkBundle\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkResolver.Companion.TMDB_TYPE_MOVIE\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkResolver.Companion.TMDB_TYPE_TV\nimport com.michaldrabik.ui_model.IdTmdb\nimport javax.inject.Inject\n\nclass TmdbDeepLinkCase @Inject constructor(\n  private val traktRemoteSource: TraktRemoteDataSource,\n  private val showsLocalSource: ShowsLocalDataSource,\n  private val moviesLocalSource: MoviesLocalDataSource,\n  private val showDetailsRepository: ShowDetailsRepository,\n  private val movieDetailsRepository: MovieDetailsRepository,\n  private val mappers: Mappers\n) {\n\n  companion object {\n    private const val SEARCH_ID_TYPE = \"tmdb\"\n  }\n\n  suspend fun findById(tmdbId: IdTmdb, type: String): DeepLinkBundle {\n    val localShow = showDetailsRepository.find(tmdbId)\n    if (localShow != null && type == TMDB_TYPE_TV) {\n      return DeepLinkBundle(show = localShow)\n    }\n\n    val localMovie = movieDetailsRepository.find(tmdbId)\n    if (localMovie != null && type == TMDB_TYPE_MOVIE) {\n      return DeepLinkBundle(movie = localMovie)\n    }\n\n    val searchResult = traktRemoteSource.fetchSearchId(SEARCH_ID_TYPE, tmdbId.id.toString())\n    if (searchResult.isNotEmpty()) {\n      searchResult\n        .filter { it.show != null || it.movie != null }\n        .forEach { result ->\n          val show = result.show\n          val movie = result.movie\n          if (show != null && type == TMDB_TYPE_TV) {\n            val uiShow = mappers.show.fromNetwork(show)\n            showsLocalSource.upsert(listOf(mappers.show.toDatabase(uiShow)))\n            return DeepLinkBundle(show = uiShow)\n          }\n          if (movie != null && type == TMDB_TYPE_MOVIE) {\n            val uiMovie = mappers.movie.fromNetwork(movie)\n            moviesLocalSource.upsert(listOf(mappers.movie.toDatabase(uiMovie)))\n            return DeepLinkBundle(movie = uiMovie)\n          }\n        }\n    }\n\n    return DeepLinkBundle.EMPTY\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/cases/deeplink/TraktDeepLinkCase.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main.cases.deeplink\n\nimport com.michaldrabik.data_local.sources.MoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.ShowsLocalDataSource\nimport com.michaldrabik.data_remote.trakt.TraktRemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.movies.MovieDetailsRepository\nimport com.michaldrabik.repository.shows.ShowDetailsRepository\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkBundle\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkResolver.Companion.TRAKT_TYPE_MOVIE\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkResolver.Companion.TRAKT_TYPE_TV\nimport com.michaldrabik.ui_model.IdSlug\nimport javax.inject.Inject\n\nclass TraktDeepLinkCase @Inject constructor(\n  private val traktRemoteSource: TraktRemoteDataSource,\n  private val showsLocalSource: ShowsLocalDataSource,\n  private val moviesLocalSource: MoviesLocalDataSource,\n  private val showDetailsRepository: ShowDetailsRepository,\n  private val movieDetailsRepository: MovieDetailsRepository,\n  private val mappers: Mappers\n) {\n\n  suspend fun findById(traktSlug: IdSlug, type: String) = when (type) {\n    TRAKT_TYPE_TV -> {\n      val localShow = showDetailsRepository.find(traktSlug)\n      if (localShow != null) {\n        DeepLinkBundle(show = localShow)\n      }\n      try {\n        val show = traktRemoteSource.fetchShow(traktSlug.id)\n        val uiShow = mappers.show.fromNetwork(show)\n        showsLocalSource.upsert(listOf(mappers.show.toDatabase(uiShow)))\n        DeepLinkBundle(show = uiShow)\n      } catch (error: Throwable) {\n        DeepLinkBundle.EMPTY\n      }\n    }\n    TRAKT_TYPE_MOVIE -> {\n      val localMovie = movieDetailsRepository.find(traktSlug)\n      if (localMovie != null) {\n        DeepLinkBundle(movie = localMovie)\n      }\n      try {\n        val movie = traktRemoteSource.fetchMovie(traktSlug.id)\n        val uiMovie = mappers.movie.fromNetwork(movie)\n        moviesLocalSource.upsert(listOf(mappers.movie.toDatabase(uiMovie)))\n        DeepLinkBundle(movie = uiMovie)\n      } catch (error: Throwable) {\n        DeepLinkBundle.EMPTY\n      }\n    }\n    else -> DeepLinkBundle.EMPTY\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/main/delegates/TipsDelegate.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main.delegates\n\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport com.michaldrabik.showly_oss.databinding.ActivityMainBinding\nimport com.michaldrabik.showly_oss.ui.main.MainViewModel\nimport com.michaldrabik.ui_base.utilities.TipsHost\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.Tip\n\ninterface TipsDelegate : TipsHost {\n  fun registerTipsDelegate(\n    viewModel: MainViewModel,\n    binding: ActivityMainBinding,\n  )\n\n  fun showAllTips()\n  fun hideAllTips()\n}\n\nclass MainTipsDelegate : TipsDelegate, DefaultLifecycleObserver {\n\n  private lateinit var viewModel: MainViewModel\n  private lateinit var binding: ActivityMainBinding\n\n  private val tips by lazy {\n    mapOf(\n      Tip.MENU_DISCOVER to binding.tutorialTipDiscover,\n      Tip.MENU_MY_SHOWS to binding.tutorialTipMyShows,\n      Tip.MENU_MODES to binding.tutorialTipModeMenu\n    )\n  }\n\n  override fun registerTipsDelegate(\n    viewModel: MainViewModel,\n    binding: ActivityMainBinding,\n  ) {\n    this.viewModel = viewModel\n    this.binding = binding\n    setupTips()\n  }\n\n  private fun setupTips() {\n    tips.entries.forEach { (tip, view) ->\n      view.visibleIf(!isTipShown(tip))\n      view.onClick {\n        it.gone()\n        showTip(tip)\n      }\n    }\n  }\n\n  override fun setTipShow(tip: Tip) = viewModel.setTipShown(tip)\n\n  override fun isTipShown(tip: Tip) = viewModel.isTipShown(tip)\n\n  override fun showTip(tip: Tip) {\n    binding.tutorialView.showTip(tip)\n    setTipShow(tip)\n  }\n\n  override fun showAllTips() {\n    tips.entries.forEach { (tip, view) -> view.visibleIf(!isTipShown(tip)) }\n  }\n\n  override fun hideAllTips() {\n    tips.values.forEach { it.gone() }\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/views/BottomMenuView.kt",
    "content": "package com.michaldrabik.showly_oss.ui.views\n\nimport android.animation.Animator\nimport android.animation.AnimatorListenerAdapter\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.MotionEvent\nimport android.view.MotionEvent.ACTION_CANCEL\nimport android.view.MotionEvent.ACTION_DOWN\nimport android.view.MotionEvent.ACTION_MOVE\nimport android.view.MotionEvent.ACTION_UP\nimport android.view.ViewGroup\nimport android.view.ViewPropertyAnimator\nimport android.widget.FrameLayout\nimport androidx.core.view.children\nimport com.google.android.material.bottomnavigation.BottomNavigationItemView\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.showly_oss.R\nimport com.michaldrabik.showly_oss.databinding.ViewBottomMenuBinding\nimport com.michaldrabik.ui_base.utilities.extensions.add\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport kotlin.math.abs\n\nclass BottomMenuView : FrameLayout {\n\n  companion object {\n    private const val SWIPE_MIN_THRESHOLD = 150F\n    private const val FADE_DELAY = 150L\n  }\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  val binding = ViewBottomMenuBinding.inflate(LayoutInflater.from(context), this)\n\n  var isModeMenuEnabled = true\n  var onModeSelected: ((Mode) -> Unit)? = null\n\n  private val screenWidth by lazy { screenWidth() }\n  private val itemIdleColor by lazy { context.colorFromAttr(R.attr.colorBottomMenuItem) }\n  private val itemSelectedColor by lazy { context.colorFromAttr(R.attr.colorBottomMenuItemChecked) }\n  private val animations = mutableListOf<ViewPropertyAnimator?>()\n\n  private var touchX = 0F\n  private var isModeMenu = false\n\n  override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {\n    if (!isModeMenuEnabled) return super.onInterceptTouchEvent(ev)\n\n    when (ev?.actionMasked) {\n      ACTION_DOWN -> {\n        touchX = ev.x\n        isModeMenu = false\n        disableTooltips()\n      }\n      ACTION_MOVE -> {\n        val delta = ev.x - touchX\n        if (!isModeMenu && abs(delta) > SWIPE_MIN_THRESHOLD) {\n          isModeMenu = true\n          showModeMenu()\n        }\n        with(binding) {\n          if (isModeMenu) {\n            if (ev.x > screenWidth / 2) {\n              bottomMenuModeShows.setTextColor(itemIdleColor)\n              bottomMenuModeMovies.setTextColor(itemSelectedColor)\n            } else {\n              bottomMenuModeShows.setTextColor(itemSelectedColor)\n              bottomMenuModeMovies.setTextColor(itemIdleColor)\n            }\n          }\n        }\n      }\n      ACTION_UP, ACTION_CANCEL -> {\n        if (isModeMenu) {\n          hideModeMenu()\n          isModeMenu = false\n          when {\n            ev.x > screenWidth / 2 -> onModeSelected?.invoke(Mode.MOVIES)\n            else -> onModeSelected?.invoke(Mode.SHOWS)\n          }\n        }\n      }\n    }\n\n    return super.onInterceptTouchEvent(ev)\n  }\n\n  private fun showModeMenu() {\n    with(binding) {\n      bottomMenuModeShows.setTextColor(itemIdleColor)\n      bottomMenuModeMovies.setTextColor(itemIdleColor)\n      bottomNavigationView.fadeOut(FADE_DELAY).add(animations)\n      bottomMenuModeLayout.fadeIn(FADE_DELAY).add(animations)\n    }\n  }\n\n  private fun hideModeMenu() {\n    with(binding) {\n      with(animations) {\n        forEach {\n          it?.setListener(object : AnimatorListenerAdapter() {\n            override fun onAnimationCancel(animation: Animator) {\n              bottomNavigationView.visible()\n              bottomNavigationView.alpha = 1F\n            }\n          })\n          it?.cancel()\n        }\n        clear()\n      }\n      bottomNavigationView.fadeIn(FADE_DELAY).add(animations)\n      bottomMenuModeLayout.fadeOut(FADE_DELAY).add(animations)\n    }\n  }\n\n  private fun disableTooltips() {\n    val content = binding.bottomNavigationView.getChildAt(0)\n    if (content is ViewGroup) {\n      content.children.forEach {\n        if (it is BottomNavigationItemView) {\n          it.setOnLongClickListener(null)\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/views/WelcomeLanguageView.kt",
    "content": "package com.michaldrabik.showly_oss.ui.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.showly_oss.R\nimport com.michaldrabik.showly_oss.databinding.ViewWelcomeLanguageBinding\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_settings.helpers.AppLanguage\n\nclass WelcomeLanguageView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewWelcomeLanguageBinding.inflate(LayoutInflater.from(context), this)\n\n  var onYesClick: (() -> Unit)? = null\n  var onNoClick: (() -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    with(binding) {\n      viewWelcomeLanguageYesButton.onClick { onYesClick?.invoke() }\n      viewWelcomeLanguageLeaveButton.onClick { onNoClick?.invoke() }\n    }\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  fun setLanguage(appLanguage: AppLanguage) {\n    // This text will always be English.\n    binding.viewWelcomeLanguageMessage.text = \"It seems like your device\\'s language is ${appLanguage.displayNameRaw}.\\n\" +\n      \"Would you like to use it in Showly app?\"\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/views/WelcomeNoteView.kt",
    "content": "package com.michaldrabik.showly_oss.ui.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.showly_oss.R\nimport com.michaldrabik.showly_oss.databinding.ViewWelcomeNoteBinding\nimport com.michaldrabik.ui_base.utilities.extensions.getLocaleStringResource\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_settings.helpers.AppLanguage\nimport java.util.Locale\n\nclass WelcomeNoteView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewWelcomeNoteBinding.inflate(LayoutInflater.from(context), this)\n\n  var onOkClickListener: (() -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    binding.viewWelcomeNoteYesButton.onClick { onOkClickListener?.invoke() }\n  }\n\n  fun setLanguage(language: AppLanguage) {\n    if (language == AppLanguage.ENGLISH) return\n    val locale = Locale(language.code)\n    with(binding) {\n      viewWelcomeNoteTitle.text = context.getLocaleStringResource(locale, R.string.textDisclaimerTitle)\n      viewWelcomeNoteMessage.text = context.getLocaleStringResource(locale, R.string.textDisclaimerText)\n      viewWelcomeNoteYesButton.text = context.getLocaleStringResource(locale, R.string.textDisclaimerConfirmText)\n    }\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/ui/views/WhatsNewView.kt",
    "content": "package com.michaldrabik.showly_oss.ui.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.widget.ScrollView\nimport com.michaldrabik.showly_oss.BuildConfig\nimport com.michaldrabik.showly_oss.R\nimport com.michaldrabik.showly_oss.databinding.ViewWhatsNewBinding\n\n@SuppressLint(\"SetTextI18n\")\nclass WhatsNewView : ScrollView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewWhatsNewBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n\n    val whatsNew = context.assets.open(\"release_notes.txt\")\n      .bufferedReader()\n      .use { it.readText() }\n\n    with(binding) {\n      viewWhatsNewMessage.text = whatsNew\n      viewWhatsNewSubtitle.text = context.getString(R.string.textWhatsNewSubtitle, BuildConfig.VERSION_NAME)\n    }\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/utilities/deeplink/DeepLinkBundle.kt",
    "content": "package com.michaldrabik.showly_oss.utilities.deeplink\n\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\n\ndata class DeepLinkBundle(\n  val show: Show? = null,\n  val movie: Movie? = null\n) {\n\n  companion object {\n    val EMPTY = DeepLinkBundle()\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/utilities/deeplink/DeepLinkResolver.kt",
    "content": "package com.michaldrabik.showly_oss.utilities.deeplink\n\nimport android.content.Intent\nimport androidx.core.os.bundleOf\nimport androidx.navigation.NavController\nimport com.google.android.material.bottomnavigation.BottomNavigationView\nimport com.michaldrabik.showly_oss.R\nimport com.michaldrabik.showly_oss.utilities.deeplink.resolvers.ImdbSourceResolver\nimport com.michaldrabik.showly_oss.utilities.deeplink.resolvers.TmdbSourceResolver\nimport com.michaldrabik.showly_oss.utilities.deeplink.resolvers.TraktSourceResolver\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass DeepLinkResolver @Inject constructor() {\n\n  companion object {\n    const val TMDB_TYPE_TV = \"tv\"\n    const val TMDB_TYPE_MOVIE = \"movie\"\n\n    const val TRAKT_TYPE_TV = \"shows\"\n    const val TRAKT_TYPE_MOVIE = \"movies\"\n  }\n\n  private val sourceResolvers = setOf(\n    TraktSourceResolver(),\n    ImdbSourceResolver(),\n    TmdbSourceResolver()\n  )\n\n  private val progressDestinations = arrayOf(\n    R.id.progressMainFragment,\n    R.id.progressMoviesMainFragment,\n  )\n\n  private val mainDestinations = arrayOf(\n    *progressDestinations,\n    R.id.discoverFragment,\n    R.id.discoverMoviesFragment,\n    R.id.followedShowsFragment,\n    R.id.followedMoviesFragment,\n    R.id.listsFragment,\n    R.id.newsFragment,\n  )\n\n  fun findSource(intent: Intent?): DeepLinkSource? {\n    val path = intent?.data?.pathSegments ?: emptyList()\n    return sourceResolvers.firstNotNullOfOrNull { it.resolve(path) }\n  }\n\n  fun resolveDestination(\n    navController: NavController,\n    navigationView: BottomNavigationView,\n    show: Show,\n  ) {\n    try {\n      resetNavigation(navController, navigationView)\n\n      val navBundle = bundleOf(NavigationArgs.ARG_SHOW_ID to show.traktId)\n      val actionId = when (navController.currentDestination?.id) {\n        R.id.progressMainFragment -> R.id.actionProgressFragmentToShowDetailsFragment\n        R.id.progressMoviesMainFragment -> R.id.actionProgressMoviesFragmentToShowDetailsFragment\n        else -> error(\"Unknown actionId. ActionId: ${navController.currentDestination?.id}\")\n      }\n      navController.navigate(actionId, navBundle)\n    } catch (error: Throwable) {\n      Logger.record(error, \"DeepLinkResolver::resolveDestination(show:${show.traktId})\")\n    }\n  }\n\n  fun resolveDestination(\n    navController: NavController,\n    navigationView: BottomNavigationView,\n    movie: Movie,\n  ) {\n    try {\n      resetNavigation(navController, navigationView)\n\n      val navBundle = bundleOf(NavigationArgs.ARG_MOVIE_ID to movie.traktId)\n      val actionId = when (navController.currentDestination?.id) {\n        R.id.progressMainFragment -> R.id.actionProgressFragmentToMovieDetailsFragment\n        R.id.progressMoviesMainFragment -> R.id.actionProgressMoviesFragmentToMovieDetailsFragment\n        else -> error(\"Unknown actionId. ActionId: $navController.currentDestination?.id\")\n      }\n      navController.navigate(actionId, navBundle)\n    } catch (error: Throwable) {\n      Logger.record(error, \"DeepLinkResolver::resolveDestination(movie:${movie.traktId})\")\n    }\n  }\n\n  private fun resetNavigation(navController: NavController, navigationView: BottomNavigationView) {\n    while (navController.currentDestination?.id !in mainDestinations) {\n      navController.popBackStack()\n    }\n\n    if (navController.currentDestination?.id !in progressDestinations) {\n      navigationView.selectedItemId = R.id.menuProgress\n    }\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/utilities/deeplink/DeepLinkSource.kt",
    "content": "package com.michaldrabik.showly_oss.utilities.deeplink\n\nimport com.michaldrabik.ui_model.IdImdb\nimport com.michaldrabik.ui_model.IdSlug\nimport com.michaldrabik.ui_model.IdTmdb\n\nsealed class DeepLinkSource {\n\n  data class ImdbSource(val id: IdImdb) : DeepLinkSource()\n\n  data class TmdbSource(val id: IdTmdb, val type: String) : DeepLinkSource()\n\n  data class TraktSource(val id: IdSlug, val type: String) : DeepLinkSource()\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/utilities/deeplink/resolvers/ImdbSourceResolver.kt",
    "content": "package com.michaldrabik.showly_oss.utilities.deeplink.resolvers\n\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkSource\nimport com.michaldrabik.ui_model.IdImdb\n\nclass ImdbSourceResolver : SourceResolver {\n\n  override fun resolve(linkPath: List<String>): DeepLinkSource? {\n    if (linkPath.size < 2 || !linkPath[1].startsWith(\"tt\") || linkPath[1].length <= 2) {\n      return null\n    }\n\n    return DeepLinkSource.ImdbSource(IdImdb(linkPath[1]))\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/utilities/deeplink/resolvers/SourceResolver.kt",
    "content": "package com.michaldrabik.showly_oss.utilities.deeplink.resolvers\n\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkSource\n\ninterface SourceResolver {\n  fun resolve(linkPath: List<String>): DeepLinkSource?\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/utilities/deeplink/resolvers/TmdbSourceResolver.kt",
    "content": "package com.michaldrabik.showly_oss.utilities.deeplink.resolvers\n\nimport androidx.core.text.isDigitsOnly\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkResolver\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkSource\nimport com.michaldrabik.ui_model.IdTmdb\n\nclass TmdbSourceResolver : SourceResolver {\n\n  override fun resolve(linkPath: List<String>): DeepLinkSource? {\n    if (linkPath.size < 2 ||\n      !(linkPath[0] == DeepLinkResolver.TMDB_TYPE_TV || linkPath[0] == DeepLinkResolver.TMDB_TYPE_MOVIE) ||\n      linkPath[1].length <= 1\n    ) {\n      return null\n    }\n\n    val id = linkPath[1].substringBefore(\"-\").trim()\n    val type = linkPath[0]\n    return if (id.isDigitsOnly()) {\n      DeepLinkSource.TmdbSource(IdTmdb(id.toLong()), type)\n    } else {\n      null\n    }\n  }\n}\n"
  },
  {
    "path": "app/src/main/java/com/michaldrabik/showly_oss/utilities/deeplink/resolvers/TraktSourceResolver.kt",
    "content": "package com.michaldrabik.showly_oss.utilities.deeplink.resolvers\n\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkResolver\nimport com.michaldrabik.showly_oss.utilities.deeplink.DeepLinkSource\nimport com.michaldrabik.ui_model.IdSlug\n\nclass TraktSourceResolver : SourceResolver {\n\n  override fun resolve(linkPath: List<String>): DeepLinkSource? {\n    if (linkPath.size < 2 || !(linkPath[0] == DeepLinkResolver.TRAKT_TYPE_TV || linkPath[0] == DeepLinkResolver.TRAKT_TYPE_MOVIE)) {\n      return null\n    }\n\n    return DeepLinkSource.TraktSource(IdSlug(linkPath[1]), linkPath[0])\n  }\n}\n"
  },
  {
    "path": "app/src/main/play/release-notes/en-GB/default.txt",
    "content": "* Fix issue where actors list of shows and movies would not return to its scrolled position\n* Other bugfixes"
  },
  {
    "path": "app/src/main/res/drawable/bg_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <solid android:color=\"?attr/colorPrimary\" />\n  <corners android:radius=\"8dp\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/ic_eye_off.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_languages.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"512dp\"\n    android:height=\"512dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n  <path\n      android:pathData=\"m504.5,273.101c0,-70.961 -31.942,-134.462 -82.232,-176.909 -40.279,-33.998 -153.438,-37.892 -153.438,-37.892l-82.012,0.473c-0.61,0.258 -1.347,0.079 -1.954,0.342 -80.382,34.882 -132.545,117.531 -132.545,213.605 0,127.798 95.283,214.76 223.081,214.76 38.422,0 72.357,7.656 104.247,-8.915 74.182,-38.546 124.853,-116.088 124.853,-205.464z\"\n      android:fillColor=\"#94ccd4\"/>\n  <path\n      android:pathData=\"m422.27,96.19c-23.53,15.39 -48.79,30.66 -58.26,32.73 -25.99,7.6 -59.82,-8.5 -66.77,29.83 -0.2,7.4 -0.85,14.43 -1.96,20.99 -1.24,7.32 -3.96,14.21 -7.91,20.33s-14.463,7.906 -20.583,12.106c-16.85,11.58 -30.541,12.274 -48.681,2.824 -0.46,-0.24 -0.93,-0.49 -1.4,-0.74 -30.22,-14.81 -47.973,-48.87 -16.643,-73.76 20.59,-17 17.476,-46.987 3.086,-65.727l-8.66,-10.02c6.723,-2.777 3.986,-10.878 11.01,-13.021 21.385,-6.524 44.084,-10.032 67.6,-10.032 56.841,0 108.891,20.49 149.171,54.49z\"\n      android:fillColor=\"#b7e546\"/>\n  <path\n      android:pathData=\"m452.566,215.821c-0.129,-19.101 -7.841,-37.381 -21.444,-50.801 -11.605,-13.278 -15.619,-21.189 -31.569,-6.53 -15.118,18.805 -0.207,42.145 -32.34,74.213 -5.691,5.793 -8.884,13.723 -8.563,21.852 -0.012,-0.002 -0.007,-1.436 -0.018,-1.438 1.155,20.135 14.908,36.366 34.51,36.366 25.18,-0.001 59.424,-29.559 59.424,-73.662z\"\n      android:fillColor=\"#b7e546\"/>\n  <path\n      android:pathData=\"m380.12,231.21c-5.69,5.8 -8.89,13.73 -8.57,21.86 -0.01,-0.01 -0.01,-0.02 -0.02,-0.02 0.96,16.67 15.41,30.7 31.48,35.04 -3.37,0.92 -6.69,1.39 -9.87,1.39 -19.6,0 -40.45,-16.29 -41.61,-36.43 0.01,0 0.01,0.01 0.02,0.02 -0.32,-8.13 2.88,-16.06 8.57,-21.86 32.13,-32.06 13.51,-57.11 28.63,-75.91 9.11,-8.38 17.86,-8.34 25.84,-4.29 -1.93,1.07 -3.87,2.48 -5.84,4.29 -15.12,18.8 3.5,43.85 -28.63,75.91z\"\n      android:fillColor=\"#abd641\"/>\n  <path\n      android:pathData=\"m474.303,25.001v115.506l-7.36,6.988c0,9.665 -10.988,11.121 -20.654,11.121l-52.829,-0.397c-2.458,0 -4.854,0.776 -6.844,2.218l-43.595,30.271c-3.857,2.794 -9.256,0.038 -9.256,-4.724l4.092,-14.559c0,-6.444 -5.224,-11.667 -11.667,-11.667h-14c-9.665,0 -17.501,-7.835 -17.501,-17.501l-2.11,-108.357c0,-9.665 3.839,-19.859 13.504,-19.859l4.878,-6.541h145.841c9.666,0 17.501,7.835 17.501,17.501z\"\n      android:fillColor=\"#dc4955\"/>\n  <path\n      android:pathData=\"m474.303,140.507v5.834c0,9.666 -7.835,17.501 -17.501,17.501h-62.63c-2.462,0 -4.854,0.782 -6.837,2.217l-47.288,34.243c-3.85,2.8 -9.252,0.047 -9.252,-4.725v-20.068c0,-6.444 -5.224,-11.667 -11.667,-11.667h-14.001c-9.665,0 -17.501,-7.835 -17.501,-17.501v-121.34c0,-9.665 7.835,-17.501 17.501,-17.501h5.834v130.674c0,1.289 1.045,2.333 2.333,2.333h5.834c13.429,0 25.112,7.595 30.979,18.725 1,1.897 3.425,2.496 5.162,1.237l18.368,-13.312c6.009,-4.352 13.114,-6.65 20.534,-6.65z\"\n      android:fillColor=\"#d82f3c\"/>\n  <path\n      android:pathData=\"m328.1,484.28c-130.86,0 -236.95,-105.78 -236.95,-236.26 0,-77.71 35.628,-145.845 93.718,-188.905 -84.029,34.685 -143.168,117.43 -143.168,213.985 0,39.347 9.82,76.4 27.144,108.842 0,0 54.989,43.55 82.795,64.014 38.358,28.23 112.89,46.439 112.89,46.439s82.932,2.814 114.732,-13.646c-16.481,3.621 -33.591,5.531 -51.161,5.531z\"\n      android:fillColor=\"#6fbbc6\"/>\n  <path\n      android:pathData=\"m379.635,478.541c-18.595,-25.899 -52.793,-54.451 -51.323,-88.84 0.501,-11.724 -1.046,-23.487 -4.906,-34.574 -0.061,-0.15 -0.11,-0.297 -0.159,-0.443 -1.952,-5.491 -4.884,-10.572 -8.535,-15.101 -10.078,-12.52 -31.583,-45.184 -42.098,-49.764 -20.079,-12.419 -47.263,-3.171 -58.005,17.22 -9.324,23.587 -11.896,55.905 -36.253,68.627 -14.56,7.896 -35.545,6.122 -46.621,-6.952 -19.217,-24.96 -46.122,-0.826 -62.868,13.218 6.654,12.431 33.803,21.544 42.583,32.57 36.43,45.761 68.853,63.401 122.234,72.98 49.652,9.01 100.602,14.614 145.41,-8.666 0.18,-0.094 0.361,-0.182 0.541,-0.275z\"\n      android:fillColor=\"#b7e546\"/>\n  <path\n      android:pathData=\"m379.17,478.77c-39.78,20.697 -85.715,29.138 -130.28,24.46v-0.01c-75.319,-7.365 -144.829,-54.348 -180.02,-121.29 13.24,-11.11 32.85,-28.54 49.98,-22.97 39.85,74.57 118.61,125.32 209.25,125.32 17.54,0 34.62,-1.9 51.07,-5.51z\"\n      android:fillColor=\"#abd641\"/>\n  <path\n      android:pathData=\"m430.775,278.427v115.506l-7.36,6.988c0,9.665 -10.988,11.121 -20.654,11.121l-52.829,-0.397c-2.458,0 -4.854,0.776 -6.844,2.218l-43.595,30.271c-3.857,2.794 -9.256,0.038 -9.256,-4.724l4.092,-14.559c0,-6.444 -5.224,-11.667 -11.667,-11.667h-14.001c-9.665,0 -17.501,-7.835 -17.501,-17.501l-2.11,-108.356c0,-9.665 3.839,-19.859 13.504,-19.859l4.878,-6.541h145.841c9.666,-0.001 17.502,7.835 17.502,17.5z\"\n      android:fillColor=\"#ffbf54\"/>\n  <path\n      android:pathData=\"m430.775,393.933v5.834c0,9.665 -7.835,17.501 -17.501,17.501h-62.63c-2.462,0 -4.854,0.782 -6.837,2.217l-47.288,34.243c-3.85,2.8 -9.252,0.047 -9.252,-4.725v-20.068c0,-6.444 -5.224,-11.667 -11.667,-11.667h-14.001c-9.665,0 -17.501,-7.835 -17.501,-17.501v-121.34c0,-9.665 7.835,-17.501 17.501,-17.501h5.834v130.674c0,1.289 1.045,2.333 2.333,2.333h5.834c13.429,0 25.112,7.595 30.979,18.725 1,1.897 3.425,2.496 5.162,1.237l18.368,-13.312c6.009,-4.352 13.114,-6.65 20.534,-6.65z\"\n      android:fillColor=\"#ffa442\"/>\n  <path\n      android:pathData=\"m207.66,153.42 l-0.29,0.24c-3.86,3.07 -17.26,15.34 -26.76,25.86 -3.82,-14.43 -0.5,-29.75 14.32,-41.52 20.59,-17 12.99,-50.12 -1.4,-68.86l-8.666,-10.025c6.72,-2.78 13.606,-5.245 20.636,-7.385 6.22,9.45 11.75,17.41 11.75,17.41 14.91,27.77 15.58,63.51 -9.59,84.28z\"\n      android:fillColor=\"#abd641\"/>\n  <path\n      android:pathData=\"m288.21,198.71c-0.27,0.46 -0.55,0.91 -0.84,1.36 -3.95,6.12 -9.11,11.46 -15.23,15.66 -16.85,11.58 -38.7,12.88 -56.84,3.43 -0.46,-0.24 -0.93,-0.49 -1.4,-0.74 -28.44,-13.94 -47.89,-50.81 -23.98,-75.85 -7.61,22.98 10.36,49.51 34.55,60.85 0.49,0.25 0.98,0.5 1.46,0.74 18.96,9.45 41.8,8.15 59.41,-3.43 0.98,-0.64 1.94,-1.31 2.87,-2.02z\"\n      android:fillColor=\"#abd641\"/>\n  <path\n      android:pathData=\"m246.362,502.945c0.064,0.007 0.128,0.015 0.192,0.022 -0.064,-0.007 -0.128,-0.014 -0.192,-0.022z\"\n      android:fillColor=\"#94ccd4\"/>\n  <path\n      android:pathData=\"m174.635,482.535c0.227,0.107 0.456,0.211 0.683,0.317 -0.228,-0.106 -0.456,-0.21 -0.683,-0.317z\"\n      android:fillColor=\"#94ccd4\"/>\n  <path\n      android:pathData=\"m379.26,478.75c-4.13,2.14 -8.32,4.15 -12.59,6.03 4.2,-1.86 8.34,-3.85 12.42,-5.97 0.03,-0.01 0.05,-0.03 0.08,-0.04s0.06,-0.01 0.09,-0.02z\"\n      android:fillColor=\"#94ccd4\"/>\n  <path\n      android:pathData=\"m15.553,144.502 l1.468,100.637c0,9.855 8.03,17.844 17.936,17.844l56.305,1.84c2.519,0 4.974,0.792 7.014,2.262l38.427,32.486c3.953,2.849 9.486,0.039 9.486,-4.817l2.428,-15.705c0,-6.57 5.353,-11.896 11.957,-11.896l18.584,-0.068c9.906,0 19.66,-2.352 19.66,-12.207v-123.721c0,-9.855 -8.03,-17.844 -17.936,-17.844h-155.446c-9.906,0 -9.883,21.334 -9.883,31.189z\"\n      android:fillColor=\"#faf8f8\"/>\n  <path\n      android:pathData=\"m198.82,252.72v2.16c0,9.85 -8.03,17.84 -17.94,17.84h-14.35c-6.6,0 -11.95,5.33 -11.95,11.9v20.47c0,4.85 -5.54,7.66 -9.49,4.81l-48.45,-34.92c-2.04,-1.47 -4.5,-2.26 -7.02,-2.26h-64.18c-9.91,0 -17.94,-7.99 -17.94,-17.84v-123.72c0,-9.86 8.03,-17.85 17.94,-17.85h2.06v134.41c0,2.761 2.239,5 5,5h57.12c6.72,0 13.27,2.12 18.71,6.04l22.35,16.11c2.14,1.54 5.12,0.6 6.08,-1.85 4.66,-11.87 16.25,-20.3 29.77,-20.3z\"\n      android:fillColor=\"#f0e9e6\"/>\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"m481.888,156.912c0.81,-2.472 1.254,-5.108 1.254,-7.846v-123.722c0,-13.975 -11.411,-25.344 -25.436,-25.344h-155.446c-14.025,0 -25.436,11.369 -25.436,25.344v8.902c-33.533,-0.672 -68.387,6.287 -99.202,19.801 -28.212,12.313 -53.438,29.731 -75.046,51.765h-26.576c-4.142,0 -7.5,3.358 -7.5,7.5s3.358,7.5 7.5,7.5h104.882c5.754,0 10.436,4.641 10.436,10.344v123.721c0,5.704 -4.682,10.345 -10.436,10.345h-14.349c-10.729,0 -19.458,8.701 -19.458,19.396v17.47l-46.054,-33.188c-3.338,-2.406 -7.28,-3.677 -11.399,-3.677h-64.186c-5.754,0 -10.436,-4.641 -10.436,-10.345v-123.721c0,-5.704 4.682,-10.344 10.436,-10.344h15.564c4.142,0 7.5,-3.358 7.5,-7.5s-3.358,-7.5 -7.5,-7.5h-15.564c-14.025,0 -25.436,11.369 -25.436,25.344v123.721c0,13.975 11.411,25.345 25.436,25.345h8.881c1.778,61.143 26.418,118.367 69.857,161.806 45.122,45.121 105.115,69.971 168.927,69.971 33.177,0 65.293,-6.674 95.458,-19.836 29.134,-12.712 55.096,-30.852 77.166,-53.915 2.864,-2.993 2.76,-7.74 -0.233,-10.604 -2.994,-2.864 -7.74,-2.76 -10.604,0.233 -16.479,17.22 -35.175,31.437 -55.462,42.419 -9.532,-12.686 -23.519,-27.95 -32.87,-42.58l1.788,-1.289c0.768,-0.554 1.677,-0.846 2.628,-0.846h64.186c14.025,0 25.436,-11.369 25.436,-25.344v-123.721c0,-1.675 -0.168,-3.31 -0.481,-4.895 12.89,-15.429 19.951,-35.097 19.951,-55.852 -0.1,-14.759 -4.27,-29.019 -11.927,-41.36h9.568c5.264,0 10.16,-1.602 14.223,-4.342 16.427,31.673 25.072,67.079 25.072,103.033 0,46.297 -14.006,90.723 -40.504,128.476 -2.38,3.39 -1.561,8.068 1.83,10.447 2.238,1.608 7.05,2.252 10.447,-1.83 28.279,-40.29 43.227,-87.696 43.227,-137.093 0,-40.738 -10.402,-80.827 -30.112,-116.189zM180.882,105.813h-56.612c17.613,-15.735 37.295,-28.388 58.426,-37.713l5.014,5.785c8.417,11.05 13.671,26.913 11.948,40.194 -4.654,-5.076 -11.348,-8.266 -18.776,-8.266zM355.558,464.428c3.586,4.368 7.034,8.573 10.2,12.678 -28.669,13.037 -60.073,19.894 -92.657,19.894 -83.92,0 -157.19,-46.417 -195.523,-114.917 10.574,-8.721 24.195,-19.32 35.077,-18.058 4.441,0.516 8.373,3.111 12.02,7.937 9.126,12.064 24.526,14.415 35.315,15.134 16.11,-0.996 30.466,-9.225 39.429,-22.493 2.343,-3.416 1.474,-8.084 -1.942,-10.427 -3.416,-2.344 -8.084,-1.473 -10.427,1.942 -6.445,9.396 -14.957,14.48 -26.746,15.963 -12.244,-0.899 -19.559,-3.738 -23.665,-9.167 -6.161,-8.15 -13.648,-12.79 -22.254,-13.79 -15.885,-1.841 -31.508,9.32 -43.835,19.326 -12.724,-26.921 -20.235,-56.765 -21.224,-88.228h40.295c0.952,0 1.861,0.292 2.629,0.846l48.453,34.918c2.352,1.695 5.108,2.557 7.883,2.557 2.1,0 4.21,-0.494 6.163,-1.493 4.519,-2.313 7.325,-6.897 7.325,-11.965v-20.467c0,-2.424 2,-4.396 4.458,-4.396h14.349c14.025,0 25.436,-11.37 25.436,-25.345v-32.007c8.927,5.505 23.066,10.392 34.576,10.079 12.446,0 24.833,-3.712 35.494,-11.034 13.914,-9.556 23.494,-24.472 26.285,-40.923 0.362,-2.136 0.679,-4.336 0.951,-6.583h12.985c2.458,0 4.457,1.972 4.457,4.396v20.467c0,5.067 2.807,9.652 7.325,11.965 3.812,1.784 8.457,2.389 14.045,-1.063l30.874,-22.249c-1.193,11.615 -4.317,23.836 -18.543,38.033 -6.681,6.801 -10.516,15.886 -10.72,25.213h-84.335c-14.025,0 -25.436,11.37 -25.436,25.345v3.242c-13.924,5.308 -25.072,17.024 -30.068,31.782 -1.089,3.218 -2.794,8.699 -4.378,13.934 -1.211,3.961 1.018,8.154 4.979,9.365 3.02,0.848 7.296,-0.198 9.365,-4.979 1.063,-3.478 2.521,-8.328 4.243,-13.511 2.888,-8.532 8.6,-15.61 15.86,-19.975v103.862c0,13.975 11.411,25.344 25.436,25.344h14.349c2.458,0 4.457,1.972 4.457,4.396v20.466c0,5.067 2.807,9.651 7.325,11.965 1.953,1 4.063,1.493 6.164,1.493 2.775,0 5.532,-0.862 7.883,-2.556l34.488,-24.853c6.324,9.818 13.849,19.01 21.18,27.937zM425.596,400.237c0,5.704 -4.682,10.344 -10.436,10.344h-64.186c-4.12,0 -8.062,1.272 -11.399,3.678l-46.055,33.189v-17.471c0,-10.695 -8.729,-19.396 -19.457,-19.396h-14.349c-5.754,0 -10.436,-4.641 -10.436,-10.344v-123.72c0,-5.704 4.682,-10.345 10.436,-10.345h92.635c0.008,0 0.015,0.002 0.023,0.002 0.011,0 0.022,-0.002 0.033,-0.002h62.756c5.754,0 10.436,4.641 10.436,10.345v123.72zM445.066,215.821c0,15.236 -4.597,29.757 -13.056,41.732 -4.491,-3.966 -10.391,-6.381 -16.85,-6.381h-56.106c0.2,-5.417 2.48,-10.697 6.362,-14.648 21.123,-21.079 22.407,-39.759 23.438,-54.768 0.126,-1.844 0.253,-3.615 0.409,-5.327l1.626,-1.172c0.769,-0.554 1.678,-0.847 2.63,-0.847h36.092c9.878,11.564 15.351,26.181 15.455,41.411zM457.706,159.41h-64.186c-4.119,0 -8.061,1.271 -11.399,3.677l-4.481,3.229c-0.017,0.012 -0.034,0.024 -0.051,0.036l-41.523,29.924v-17.471c0,-10.695 -8.729,-19.396 -19.457,-19.396h-14.349c-5.754,0 -10.436,-4.641 -10.436,-10.344v-28.065c0,-4.142 -3.358,-7.5 -7.5,-7.5s-7.5,3.358 -7.5,7.5v28.065c0,9.115 4.856,17.118 12.123,21.584 -0.286,2.694 -0.636,5.321 -1.063,7.836 -2.115,12.472 -9.401,23.795 -19.988,31.066 -18.677,13.146 -43.842,9.827 -61.578,-4.852v-67.815c16.092,-21.223 8.041,-54.981 -8.872,-74.67 24.144,-8.66 52.191,-13.541 79.377,-12.969v36.755c0,4.142 3.358,7.5 7.5,7.5s7.5,-3.358 7.5,-7.5v-60.656c0,-5.704 4.682,-10.344 10.436,-10.344h155.446c5.754,0 10.436,4.641 10.436,10.344v123.721c0.001,5.704 -4.681,10.345 -10.435,10.345z\"/>\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"m426.308,49.753h-38.825v-12.753c0,-4.142 -3.358,-7.5 -7.5,-7.5s-7.5,3.358 -7.5,7.5v12.753h-38.825c-4.142,0 -7.5,3.358 -7.5,7.5s3.358,7.5 7.5,7.5h15.551c3.601,15.961 10.724,30.639 20.644,43.123 -7.591,7.247 -16.472,13.255 -26.375,17.669l-10.333,4.605c-3.784,1.686 -5.484,6.12 -3.798,9.903 1.244,2.792 3.984,4.449 6.855,4.449 1.02,0 2.057,-0.209 3.048,-0.651l10.333,-4.605c11.399,-5.08 21.635,-11.977 30.4,-20.293 8.765,8.316 19.001,15.213 30.4,20.293l10.333,4.605c2.636,1.092 7.58,0.786 9.903,-3.798 1.686,-3.783 -0.014,-8.217 -3.798,-9.903l-10.333,-4.605c-9.904,-4.413 -18.784,-10.422 -26.375,-17.669 9.92,-12.484 17.042,-27.162 20.643,-43.123h15.551c4.142,0 7.5,-3.358 7.5,-7.5s-3.357,-7.5 -7.499,-7.5zM379.983,96.446c-7.028,-9.387 -12.272,-20.113 -15.335,-31.693h30.669c-3.063,11.58 -8.306,22.306 -15.334,31.693z\"/>\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"m64.983,245.017c3.876,1.462 8.204,-0.494 9.665,-4.369l6.509,-17.247h43.945l6.577,17.269c1.14,2.993 3.988,4.833 7.01,4.833 0.887,0 1.79,-0.159 2.668,-0.493 3.871,-1.474 5.813,-5.807 4.339,-9.678l-33.794,-88.732c-0.022,-0.057 -0.044,-0.114 -0.067,-0.171 -1.472,-3.597 -4.934,-5.923 -8.82,-5.927 -0.004,0 -0.006,0 -0.01,0 -3.881,0 -7.344,2.319 -8.822,5.909 -0.028,0.068 -0.055,0.137 -0.082,0.207l-33.487,88.736c-1.463,3.873 0.493,8.201 4.369,9.663zM103.028,165.444 L119.389,208.401h-32.572z\"/>\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"m394.886,293h-34.891c-4.142,0 -7.5,3.358 -7.5,7.5s3.358,7.5 7.5,7.5h9.945v23.5h-29.833c1.743,-3.549 2.724,-7.537 2.724,-11.75 0,-14.75 -12,-26.75 -26.75,-26.75h-21.656c-4.142,0 -7.5,3.358 -7.5,7.5s3.358,7.5 7.5,7.5h21.656c6.479,0 11.75,5.271 11.75,11.75s-5.271,11.75 -11.75,11.75h-24.063c-4.142,0 -7.5,3.358 -7.5,7.5s3.358,7.5 7.5,7.5h24.063c6.479,0 11.75,5.271 11.75,11.75 0,6.589 -5.161,11.75 -11.75,11.75 -10.362,0 -22.125,-1.168 -30.791,-9.834 -2.929,-2.929 -7.678,-2.929 -10.606,0 -2.929,2.929 -2.929,7.678 0,10.606 12.611,12.611 28.748,14.228 41.397,14.228 14.75,0 26.75,-12 26.75,-26.75 0,-4.213 -0.981,-8.201 -2.724,-11.75h29.833v31c0,4.142 3.358,7.5 7.5,7.5s7.5,-3.358 7.5,-7.5v-69.5h9.945c4.142,0 7.5,-3.358 7.5,-7.5s-3.357,-7.5 -7.499,-7.5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_foreground.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"108dp\"\n  android:height=\"108dp\"\n  android:viewportWidth=\"78\"\n  android:viewportHeight=\"165.456\"\n  >\n  <group\n    android:scaleX=\"0.2893104\"\n    android:scaleY=\"0.61369413\"\n    android:translateX=\"27.716894\"\n    android:translateY=\"31.018133\"\n    >\n    <group android:translateY=\"134.208\">\n      <path\n        android:fillColor=\"#F44336\"\n        android:pathData=\"M36.144,-13.536Q45.936,-13.536,50.4,-16.848Q54.864,-20.16,54.864,-26.208Q54.864,-29.808,53.352,-32.4Q51.84,-34.992,49.032,-37.08Q46.224,-39.168,42.192,-40.968Q38.16,-42.768,32.976,-44.496Q27.792,-46.368,22.968,-48.6Q18.144,-50.832,14.472,-54.144Q10.8,-57.456,8.568,-62.064Q6.336,-66.672,6.336,-73.152Q6.336,-86.688,15.696,-94.392Q25.056,-102.096,41.184,-102.096Q50.544,-102.096,57.816,-100.008Q65.088,-97.92,69.264,-95.472L63.648,-80.784Q58.752,-83.52,52.92,-84.96Q47.088,-86.4,40.896,-86.4Q33.552,-86.4,29.448,-83.376Q25.344,-80.352,25.344,-74.88Q25.344,-71.568,26.712,-69.192Q28.08,-66.816,30.6,-64.944Q33.12,-63.072,36.504,-61.488Q39.888,-59.904,43.92,-58.464Q50.976,-55.872,56.52,-53.208Q62.064,-50.544,65.88,-46.872Q69.696,-43.2,71.712,-38.232Q73.728,-33.264,73.728,-26.208Q73.728,-12.672,64.152,-5.256Q54.576,2.16,36.144,2.16Q29.952,2.16,24.84,1.368Q19.728,0.576,15.768,-0.576Q11.808,-1.728,8.928,-2.952Q6.048,-4.176,4.32,-5.184L9.648,-20.016Q13.536,-17.856,20.16,-15.696Q26.784,-13.536,36.144,-13.536Z\"\n        />\n    </group>\n  </group>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_mono.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"108dp\"\n  android:height=\"108dp\"\n  android:viewportWidth=\"78\"\n  android:viewportHeight=\"165.456\"\n  >\n  <group\n    android:scaleX=\"0.2893104\"\n    android:scaleY=\"0.61369413\"\n    android:translateX=\"27.716894\"\n    android:translateY=\"31.018133\"\n    >\n    <group android:translateY=\"134.208\">\n      <path\n        android:fillColor=\"#FFFFFF\"\n        android:pathData=\"M36.144,-13.536Q45.936,-13.536,50.4,-16.848Q54.864,-20.16,54.864,-26.208Q54.864,-29.808,53.352,-32.4Q51.84,-34.992,49.032,-37.08Q46.224,-39.168,42.192,-40.968Q38.16,-42.768,32.976,-44.496Q27.792,-46.368,22.968,-48.6Q18.144,-50.832,14.472,-54.144Q10.8,-57.456,8.568,-62.064Q6.336,-66.672,6.336,-73.152Q6.336,-86.688,15.696,-94.392Q25.056,-102.096,41.184,-102.096Q50.544,-102.096,57.816,-100.008Q65.088,-97.92,69.264,-95.472L63.648,-80.784Q58.752,-83.52,52.92,-84.96Q47.088,-86.4,40.896,-86.4Q33.552,-86.4,29.448,-83.376Q25.344,-80.352,25.344,-74.88Q25.344,-71.568,26.712,-69.192Q28.08,-66.816,30.6,-64.944Q33.12,-63.072,36.504,-61.488Q39.888,-59.904,43.92,-58.464Q50.976,-55.872,56.52,-53.208Q62.064,-50.544,65.88,-46.872Q69.696,-43.2,71.712,-38.232Q73.728,-33.264,73.728,-26.208Q73.728,-12.672,64.152,-5.256Q54.576,2.16,36.144,2.16Q29.952,2.16,24.84,1.368Q19.728,0.576,15.768,-0.576Q11.808,-1.728,8.928,-2.952Q6.048,-4.176,4.32,-5.184L9.648,-20.016Q13.536,-17.856,20.16,-15.696Q26.784,-13.536,36.144,-13.536Z\"\n        />\n    </group>\n  </group>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/selector_bottom_menu.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?attr/colorBottomMenuItemChecked\" android:state_checked=\"true\" />\n  <item android:color=\"?attr/colorBottomMenuItem\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:background=\"?android:windowBackground\"\n  android:clipChildren=\"false\"\n  tools:context=\".ui.main.MainActivity\"\n  >\n\n  <androidx.fragment.app.FragmentContainerView\n    android:id=\"@+id/navigationHost\"\n    android:name=\"androidx.navigation.fragment.NavHostFragment\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n  <LinearLayout\n    android:id=\"@+id/bottomNavigationWrapper\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"bottom\"\n    android:background=\"?attr/colorBottomMenuBackground\"\n    android:elevation=\"@dimen/elevationNormal\"\n    android:orientation=\"vertical\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_constraintVertical_bias=\"1\"\n    >\n\n    <TextView\n      android:id=\"@+id/statusView\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"@dimen/bottomNoInternetViewHeight\"\n      android:background=\"?colorErrorSnackbar\"\n      android:gravity=\"center\"\n      android:text=\"@string/errorNoInternetConnection\"\n      android:textColor=\"?textColorOnSurface\"\n      android:textSize=\"12sp\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n    <View\n      android:id=\"@+id/bottomNavigationSeparator\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"1dp\"\n      android:background=\"?attr/colorBottomMenuSeparator\"\n      />\n\n    <com.michaldrabik.showly_oss.ui.views.BottomMenuView\n      android:id=\"@+id/bottomMenuView\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"@dimen/bottomNavigationHeight\"\n      />\n\n  </LinearLayout>\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/snackbarHost\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"0dp\"\n    app:layout_constraintBottom_toTopOf=\"@+id/bottomNavigationWrapper\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.tips.TipView\n    android:id=\"@+id/tutorialTipModeMenu\"\n    android:layout_width=\"@dimen/tutorialTipSize\"\n    android:layout_height=\"@dimen/tutorialTipSize\"\n    android:layout_marginBottom=\"54dp\"\n    app:layout_constraintBottom_toBottomOf=\"@id/bottomNavigationWrapper\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"@dimen/tipModeMenuRatio\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.tips.TipView\n    android:id=\"@+id/tutorialTipDiscover\"\n    android:layout_width=\"@dimen/tutorialTipSize\"\n    android:layout_height=\"@dimen/tutorialTipSize\"\n    android:layout_marginBottom=\"54dp\"\n    android:translationZ=\"10dp\"\n    app:layout_constraintBottom_toBottomOf=\"@id/bottomNavigationWrapper\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"@dimen/tipDiscoverRatio\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.tips.TipView\n    android:id=\"@+id/tutorialTipMyShows\"\n    android:layout_width=\"@dimen/tutorialTipSize\"\n    android:layout_height=\"@dimen/tutorialTipSize\"\n    android:layout_marginBottom=\"54dp\"\n    android:translationZ=\"10dp\"\n    app:layout_constraintBottom_toBottomOf=\"@id/bottomNavigationWrapper\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"@dimen/tipMyShows\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.tips.TipOverlayView\n    android:id=\"@+id/tutorialView\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"0dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <View\n    android:id=\"@+id/viewMask\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"0dp\"\n    android:background=\"@color/colorBlackTranslucent\"\n    android:translationZ=\"12dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.michaldrabik.showly_oss.ui.views.WelcomeNoteView\n    android:id=\"@+id/welcomeView\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"@dimen/welcomeDialogSpace\"\n    android:translationZ=\"13dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:visibility=\"gone\"\n    />\n\n  <com.michaldrabik.showly_oss.ui.views.WelcomeLanguageView\n    android:id=\"@+id/welcomeLanguageView\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"@dimen/welcomeDialogSpace\"\n    android:translationZ=\"15dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/mainProgress\"\n    style=\"@style/ProgressBar.Accent\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:translationZ=\"20dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/view_bottom_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <com.google.android.material.bottomnavigation.BottomNavigationView\n    android:id=\"@+id/bottomNavigationView\"\n    style=\"@style/ShowlyBottomBar\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/bottomNavigationHeight\"\n    app:backgroundTint=\"?attr/colorBottomMenuBackground\"\n    app:elevation=\"0dp\"\n    app:itemActiveIndicatorStyle=\"@style/ShowlyBottomBar.Indicator\"\n    app:itemIconTint=\"@drawable/selector_bottom_menu\"\n    app:itemRippleColor=\"@null\"\n    app:itemTextColor=\"@drawable/selector_bottom_menu\"\n    app:labelVisibilityMode=\"labeled\"\n    app:menu=\"@menu/bottom_navigation_menu\"\n    />\n\n  <LinearLayout\n    android:id=\"@+id/bottomMenuModeLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"horizontal\"\n    android:translationZ=\"10dp\"\n    android:visibility=\"gone\"\n    tools:visibility=\"gone\"\n    >\n\n    <TextView\n      android:id=\"@+id/bottomMenuModeShows\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center\"\n      android:layout_weight=\"1\"\n      android:gravity=\"center\"\n      android:text=\"@string/textShows\"\n      android:textColor=\"?attr/colorBottomMenuItem\"\n      android:textSize=\"22sp\"\n      android:textStyle=\"bold\"\n      />\n\n    <View\n      android:layout_width=\"1dp\"\n      android:layout_height=\"match_parent\"\n      android:layout_gravity=\"center\"\n      android:layout_marginTop=\"@dimen/spaceMedium\"\n      android:layout_marginBottom=\"@dimen/spaceMedium\"\n      android:background=\"?attr/colorBottomMenuItem\"\n      />\n\n    <TextView\n      android:id=\"@+id/bottomMenuModeMovies\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center\"\n      android:layout_weight=\"1\"\n      android:gravity=\"center\"\n      android:text=\"@string/textMovies\"\n      android:textColor=\"?attr/colorBottomMenuItem\"\n      android:textSize=\"22sp\"\n      android:textStyle=\"bold\"\n      />\n\n  </LinearLayout>\n\n</merge>"
  },
  {
    "path": "app/src/main/res/layout/view_welcome_language.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@drawable/bg_dialog\"\n    android:elevation=\"@dimen/elevationNormal\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingTop=\"@dimen/spaceBig\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <ImageView\n      android:id=\"@+id/viewWelcomeLanguageIcon\"\n      android:layout_width=\"100dp\"\n      android:layout_height=\"100dp\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_languages\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewWelcomeLanguageMessage\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceBig\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:maxLines=\"10\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"16sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewWelcomeLanguageMessage2\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewWelcomeLanguageIcon\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewWelcomeLanguageMessage2\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceNormal\"\n      android:text=\"@string/textLanguagesChoose2\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      android:textStyle=\"italic\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewWelcomeLanguageYesButton\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewWelcomeLanguageMessage\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/viewWelcomeLanguageYesButton\"\n      style=\"@style/RoundTextButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:text=\"@string/textChangeLanguage\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/viewWelcomeLanguageLeaveButton\"\n      style=\"@style/RoundTextButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:text=\"@string/textLeaveEnglish\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewWelcomeLanguageYesButton\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "app/src/main/res/layout/view_welcome_note.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@drawable/bg_dialog\"\n    android:elevation=\"@dimen/elevationNormal\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingTop=\"@dimen/spaceNormal\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewWelcomeNoteTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:text=\"@string/textDisclaimerTitle\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"28sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewWelcomeEyeIcon\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewWelcomeEyeIcon\"\n      android:layout_width=\"80dp\"\n      android:layout_height=\"80dp\"\n      android:layout_margin=\"@dimen/spaceBig\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewWelcomeNoteMessage\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewWelcomeNoteTitle\"\n      app:srcCompat=\"@drawable/ic_eye_off\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewWelcomeNoteMessage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceNormal\"\n      android:text=\"@string/textDisclaimerText\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"15sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewWelcomeNoteYesButton\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewWelcomeEyeIcon\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/viewWelcomeNoteYesButton\"\n      style=\"@style/RoundTextButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:text=\"@string/textDisclaimerConfirmText\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "app/src/main/res/layout/view_whats_new.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:layout_gravity=\"center\"\n  android:overScrollMode=\"never\"\n  tools:parentTag=\"android.widget.ScrollView\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"@dimen/spaceNormal\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewWhatsNewTitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:text=\"@string/textWhatsNewTitle\"\n      android:textAllCaps=\"true\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"20sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewWhatsNewSubtitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:text=\"@string/textWhatsNewSubtitle\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewWhatsNewTitle\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewWhatsNewMessage\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceNormal\"\n      android:lineSpacingExtra=\"1dp\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewWhatsNewSubtitle\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "app/src/main/res/menu/bottom_navigation_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n  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  >\n\n  <item\n    android:id=\"@+id/menuProgress\"\n    android:enabled=\"true\"\n    android:icon=\"@drawable/ic_check\"\n    android:title=\"@string/menuProgress\"\n    app:showAsAction=\"always\"\n    tools:ignore=\"AlwaysShowAction\"\n    />\n\n  <item\n    android:id=\"@+id/menuDiscover\"\n    android:enabled=\"true\"\n    android:icon=\"@drawable/ic_search\"\n    android:title=\"@string/menuDiscover\"\n    app:showAsAction=\"always\"\n    />\n\n  <item\n    android:id=\"@+id/menuCollection\"\n    android:enabled=\"true\"\n    android:icon=\"@drawable/ic_bookmark\"\n    android:title=\"@string/menuCollection\"\n    app:showAsAction=\"always\"\n    />\n\n  <item\n    android:id=\"@+id/menuNews\"\n    android:enabled=\"true\"\n    android:icon=\"@drawable/ic_news\"\n    android:title=\"@string/menuNews\"\n    android:visible=\"false\"\n    app:showAsAction=\"always\"\n    />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon\n  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_mono\" />\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\n  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_mono\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"welcomeDialogSpace\">@dimen/spaceBig</dimen>\n\n  <item name=\"tipModeMenuRatio\" format=\"float\" type=\"dimen\">0.2</item>\n  <item name=\"tipDiscoverRatio\" format=\"float\" type=\"dimen\">0.55</item>\n  <item name=\"tipMyShows\" format=\"float\" type=\"dimen\">0.925</item>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <color name=\"ic_launcher_background\">#27282C</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n  <string name=\"app_name\" translatable=\"false\">Showly OSS</string>\n\n  <string name=\"menuProgress\">Progress</string>\n  <string name=\"menuDiscover\">Discover</string>\n  <string name=\"menuCollection\">Collection</string>\n  <string name=\"menuNews\">News</string>\n\n  <string name=\"textRateAppTitle\">Enjoying Showly 2.0?</string>\n  <string name=\"textRateAppMessage\">Would you like to rate the app?\\nIt only takes a few seconds. Thanks!\\n\\n<i>The app will not be closed.</i></string>\n  <string name=\"textRateAppYesButton\">Sure, why not?</string>\n  <string name=\"textRateAppNoButton\">Not now</string>\n\n  <string name=\"textDisclaimerTitle\">Welcome!</string>\n  <string name=\"textDisclaimerText\">Please note that you <b>CAN\\'T</b> watch, stream or download shows and movies with this app.\\n\\nUse apps like <b>Netflix</b> to stream and watch content.</string>\n  <string name=\"textDisclaimerConfirmText\">OK, I understand</string>\n  <string name=\"textLanguagesChoose2\" translatable=\"false\">Language can always be changed later via Settings.</string>\n  <string name=\"textLeaveEnglish\" translatable=\"false\">No, I prefer English</string>\n  <string name=\"textChangeLanguage\" translatable=\"false\">Yes</string>\n\n  <string name=\"textWhatsNewTitle\">What\\'s New</string>\n  <string name=\"textWhatsNewSubtitle\">Version %s</string>\n\n  <string name=\"shortcutDiscover\">Discover</string>\n  <string name=\"shortcutCollection\">Collection</string>\n  <string name=\"shortcutSearch\">Search</string>\n  <string name=\"shortcutProgress\">Progress</string>\n\n  <string name=\"textUpdateDownloaded\" translatable=\"false\">Update is now available.</string>\n  <string name=\"textUpdateInstall\" translatable=\"false\">Install</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">مستوى التقدم</string>\n  <string name=\"menuDiscover\">اكتشف</string>\n  <string name=\"menuCollection\">المجموعة</string>\n  <string name=\"menuNews\">الأخبار</string>\n  <string name=\"textRateAppTitle\">هل تستمتع باستخدام Showly 2.0 ؟</string>\n  <string name=\"textRateAppMessage\">أترغب بِتقييم التطبيق؟\\nلن يستغرق التقييم سِوى ثوانٍ قليلة مِن وقتك. شكرًا!\\n\\n<i>لن يُغلق التطبيق.</i></string>\n  <string name=\"textRateAppYesButton\">بالطبع، لما لا؟</string>\n  <string name=\"textRateAppNoButton\">ليس الآن</string>\n  <string name=\"textDisclaimerTitle\">أهلًا بك!</string>\n  <string name=\"textDisclaimerText\">يرجى مُلاحظة أن هذا التطبيق <b>غير مُخصص</b> لِمُشاهدة أو تحميل الأفلام والمُسلسلات.\\n\\nيُمكنك اِستخدام تطبيقات مثل <b>Netflix</b> لهذه الأمور.</string>\n  <string name=\"textDisclaimerConfirmText\">حسنًا، فهمت</string>\n  <string name=\"textWhatsNewTitle\">ما الجديد</string>\n  <string name=\"textWhatsNewSubtitle\">الإصدار %s</string>\n  <string name=\"shortcutDiscover\">إكتشف</string>\n  <string name=\"shortcutCollection\">المجموعة</string>\n  <string name=\"shortcutSearch\">البحث</string>\n  <string name=\"shortcutProgress\">مستوى التقدم</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">Fortschritt</string>\n  <string name=\"menuDiscover\">Entdecken</string>\n  <string name=\"menuCollection\">Sammlung</string>\n  <string name=\"menuNews\">News</string>\n  <string name=\"textRateAppTitle\">Gefallt dir Showly 2.0?</string>\n  <string name=\"textRateAppMessage\">Möchtest du die App bewerten?\\nEs dauert nur wenige Sekunden. Danke!\\n\\n<i>Die App wird dabei nicht geschlossen.</i></string>\n  <string name=\"textRateAppYesButton\">Sicher, wieso nicht?</string>\n  <string name=\"textRateAppNoButton\">Nicht jetzt</string>\n  <string name=\"textDisclaimerTitle\">Willkommen!</string>\n  <string name=\"textDisclaimerText\">Bitte beachte, dass du mit dieser App <b>keine</b> Serien und Filme ansehen, streamen oder herunterladen kannst.\\n\\nVerwende Apps wie <b>Netflix</b>, um Inhalte zu streamen und anzusehen.</string>\n  <string name=\"textDisclaimerConfirmText\">OK, ich verstehe</string>\n  <string name=\"textWhatsNewTitle\">Was gibts Neues?</string>\n  <string name=\"textWhatsNewSubtitle\">Version %s</string>\n  <string name=\"shortcutDiscover\">Entdecken</string>\n  <string name=\"shortcutCollection\">Sammlung</string>\n  <string name=\"shortcutSearch\">Suche</string>\n  <string name=\"shortcutProgress\">Fortschritt</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">Progreso</string>\n  <string name=\"menuDiscover\">Descubrir</string>\n  <string name=\"menuCollection\">Colección</string>\n  <string name=\"menuNews\">Noticias</string>\n  <string name=\"textRateAppTitle\">¿Disfrutando Showly 2.0?</string>\n  <string name=\"textRateAppMessage\">¿Le gustaría calificar la aplicación?\\nSolo toma unos segundos. ¡Gracias!\\n\\n<i>La aplicación no se cerrará.</i></string>\n  <string name=\"textRateAppYesButton\">Sí, ¿por qué no?</string>\n  <string name=\"textRateAppNoButton\">Ahora no</string>\n  <string name=\"textDisclaimerTitle\">Bienvenido!</string>\n  <string name=\"textDisclaimerText\">Ten en cuenta que <b>NO PUEDES</b> ver, transmitir o descargar series y películas con esta aplicación.\\n\\nUsa aplicaciones como <b>Netflix</b> para transmitir y ver el contenido.</string>\n  <string name=\"textDisclaimerConfirmText\">Vale, lo entiendo</string>\n  <string name=\"textWhatsNewTitle\">Novedades</string>\n  <string name=\"textWhatsNewSubtitle\">Versión %s</string>\n  <string name=\"shortcutDiscover\">Descubrir</string>\n  <string name=\"shortcutCollection\">Colección</string>\n  <string name=\"shortcutSearch\">Buscar</string>\n  <string name=\"shortcutProgress\">Progreso</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">Katselutila</string>\n  <string name=\"menuDiscover\">Etsi</string>\n  <string name=\"menuCollection\">Kokoelma</string>\n  <string name=\"menuNews\">Uutiset</string>\n  <string name=\"textRateAppTitle\">Nautitko Showly 2.0:sta?</string>\n  <string name=\"textRateAppMessage\">Haluaisitko arvioida sovelluksen?\\nTämä kestää vain muutaman sekunnin. Kiitos!\\n\\n<i>Sovellusta ei suljeta.</i></string>\n  <string name=\"textRateAppYesButton\">Toki, miksipä en?</string>\n  <string name=\"textRateAppNoButton\">En nyt</string>\n  <string name=\"textDisclaimerTitle\">Tervetuloa!</string>\n  <string name=\"textDisclaimerText\">Huomioi, että tällä sovelluksella <b>ET VOI</b> katsella, suoratoistaa tai ladata sarjoja ja elokuvia.\\n\\nKäytä suoratoistoon ja katseluun <b>Netflixin</b> kaltaisia sovelluksia.</string>\n  <string name=\"textDisclaimerConfirmText\">OK, ymmärrän</string>\n  <string name=\"textWhatsNewTitle\">Mitä uutta</string>\n  <string name=\"textWhatsNewSubtitle\">Versio %s</string>\n  <string name=\"shortcutDiscover\">Etsi</string>\n  <string name=\"shortcutCollection\">Kokoelma</string>\n  <string name=\"shortcutSearch\">Haku</string>\n  <string name=\"shortcutProgress\">Katselutila</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">Progression</string>\n  <string name=\"menuDiscover\">Découvrir</string>\n  <string name=\"menuCollection\">Collection</string>\n  <string name=\"menuNews\">Actualités</string>\n  <string name=\"textRateAppTitle\">Aimez-vous Showly 2.0 ?</string>\n  <string name=\"textRateAppMessage\">Souhaitez-vous noter l\\'application ?\\nCela ne prendrait que quelques secondes. Merci !\\n\\n<i>L\\'application ne se fermera pas.</i></string>\n  <string name=\"textRateAppYesButton\">Bien sûr, pourquoi pas ?</string>\n  <string name=\"textRateAppNoButton\">Pas maintenant</string>\n  <string name=\"textDisclaimerTitle\">Bienvenue !</string>\n  <string name=\"textDisclaimerText\">Veuillez prendre note que vous <b>NE POUVEZ PAS</b> regarder ou télécharger des séries ou des films avec cette application.\\n\\nUtilisez des applications telles que <b>Netflix</b> pour regarder du contenu.</string>\n  <string name=\"textDisclaimerConfirmText\">Compris</string>\n  <string name=\"textWhatsNewTitle\">Quoi de neuf</string>\n  <string name=\"textWhatsNewSubtitle\">Version %s</string>\n  <string name=\"shortcutDiscover\">Découvrir</string>\n  <string name=\"shortcutCollection\">Collection</string>\n  <string name=\"shortcutSearch\">Recherche</string>\n  <string name=\"shortcutProgress\">Progression</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">Progressi</string>\n  <string name=\"menuDiscover\">Scopri</string>\n  <string name=\"menuCollection\">Raccolta</string>\n  <string name=\"menuNews\">Notizie</string>\n  <string name=\"textRateAppTitle\">Ti piace Showly 2.0?</string>\n  <string name=\"textRateAppMessage\">Ti piacerebbe valutare questa app?\\nCi vorranno solo pochi secondi. Grazie!\\n\\n<i>L\\'app non verrà chiusa.</i></string>\n  <string name=\"textRateAppYesButton\">Certo, perché no?</string>\n  <string name=\"textRateAppNoButton\">Non ora</string>\n  <string name=\"textDisclaimerTitle\">Benvenuto/a!</string>\n  <string name=\"textDisclaimerText\">Si prega di notare che <b>NON È POSSIBILE</b> guardare, riprodurre o scaricare show e film con questa app.\\n\\nUsa app come <b>Netflix</b> per riprodurre e guardare i contenuti.</string>\n  <string name=\"textDisclaimerConfirmText\">OK, ho capito</string>\n  <string name=\"textWhatsNewTitle\">Novità</string>\n  <string name=\"textWhatsNewSubtitle\">Versione %s</string>\n  <string name=\"shortcutDiscover\">Scopri</string>\n  <string name=\"shortcutCollection\">Raccolta</string>\n  <string name=\"shortcutSearch\">Cerca</string>\n  <string name=\"shortcutProgress\">Progressi</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">Postęp</string>\n  <string name=\"menuDiscover\">Odkrywaj</string>\n  <string name=\"menuCollection\">Kolekcja</string>\n  <string name=\"menuNews\">Newsy</string>\n  <string name=\"textRateAppTitle\">Podoba Ci się Showly 2.0?</string>\n  <string name=\"textRateAppMessage\">Czy chciałbyś ocenić aplikację?\\nZajmie to tylko kilka sekund. Dziękuję!\\n\\n<i>Aplikacja pozostanie otwarta.</i></string>\n  <string name=\"textRateAppYesButton\">Jasne, czemu nie?</string>\n  <string name=\"textRateAppNoButton\">Nie teraz</string>\n  <string name=\"textDisclaimerTitle\">Witaj!</string>\n  <string name=\"textDisclaimerText\">Pamiętaj, że <b>NIE MOŻESZ</b> oglądać, streamować lub pobierać seriali i filmów z pomocą tej aplikacji.\\n\\nMożesz użyć aplikacji takich jak <b>Netflix</b> aby streamować i oglądać treści.</string>\n  <string name=\"textDisclaimerConfirmText\">OK, rozumiem</string>\n  <string name=\"textWhatsNewTitle\">Co Nowego?</string>\n  <string name=\"textWhatsNewSubtitle\">Wersja %s</string>\n  <string name=\"shortcutDiscover\">Odkrywaj</string>\n  <string name=\"shortcutCollection\">Kolekcja</string>\n  <string name=\"shortcutSearch\">Szukaj</string>\n  <string name=\"shortcutProgress\">Postęp</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">Progresso</string>\n  <string name=\"menuDiscover\">Explorar</string>\n  <string name=\"menuCollection\">Coleção</string>\n  <string name=\"menuNews\">Notícias</string>\n  <string name=\"textRateAppTitle\">Curtindo o Showly 2.0?</string>\n  <string name=\"textRateAppMessage\">Gostaria de avaliar o aplicativo?\\nLeva apenas alguns segundos. Obrigado!\\n\\n<i>O aplicativo não será fechado.</i></string>\n  <string name=\"textRateAppYesButton\">Claro, por que não?</string>\n  <string name=\"textRateAppNoButton\">Agora não</string>\n  <string name=\"textDisclaimerTitle\">Bem-vindo!</string>\n  <string name=\"textDisclaimerText\">Observe que você não pode assistir, transmitir ou baixar programas e filmes com este aplicativo.\\n\\nUse aplicativos como o Netflix para transmitir e assistir conteúdo.</string>\n  <string name=\"textDisclaimerConfirmText\">OK, eu entendo</string>\n  <string name=\"textWhatsNewTitle\">O que há de novo</string>\n  <string name=\"textWhatsNewSubtitle\">Versão %s</string>\n  <string name=\"shortcutDiscover\">Explorar</string>\n  <string name=\"shortcutCollection\">Coleção</string>\n  <string name=\"shortcutSearch\">Pesquisar</string>\n  <string name=\"shortcutProgress\">Progresso</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">Прогресс</string>\n  <string name=\"menuDiscover\">Открытия</string>\n  <string name=\"menuCollection\">Коллекция</string>\n  <string name=\"menuNews\">Новости</string>\n  <string name=\"textRateAppTitle\">Вам нравится Showly 2.0?</string>\n  <string name=\"textRateAppMessage\">Вы хотите оценить приложение?\\nЭто займёт всего несколько секунд. Спасибо!\\n\\n<i>Приложение не будет закрыто.</i></string>\n  <string name=\"textRateAppYesButton\">Конечно, почему нет?</string>\n  <string name=\"textRateAppNoButton\">Позже</string>\n  <string name=\"textDisclaimerTitle\">Добро пожаловать!</string>\n  <string name=\"textDisclaimerText\">Обратите внимание, что приложение <b>НЕ ПРЕДНАЗНАЧЕНО</b> для просмотра или скачивания сериалов и фильмов.\\n\\nИспользуйте приложения по типу <b>Netflix</b>, чтобы скачивать и просматривать контент.</string>\n  <string name=\"textDisclaimerConfirmText\">Ок, я понял</string>\n  <string name=\"textWhatsNewTitle\">Что нового</string>\n  <string name=\"textWhatsNewSubtitle\">Версия %s</string>\n  <string name=\"shortcutDiscover\">Открытия</string>\n  <string name=\"shortcutCollection\">Коллекция</string>\n  <string name=\"shortcutSearch\">Поиск</string>\n  <string name=\"shortcutProgress\">Прогресс</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"welcomeDialogSpace\">112dp</dimen>\n\n  <item name=\"tipModeMenuRatio\" format=\"float\" type=\"dimen\">0.32</item>\n  <item name=\"tipDiscoverRatio\" format=\"float\" type=\"dimen\">0.55</item>\n  <item name=\"tipMyShows\" format=\"float\" type=\"dimen\">0.77</item>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">İlerleme</string>\n  <string name=\"menuDiscover\">Keşfet</string>\n  <string name=\"menuCollection\">Koleksiyon</string>\n  <string name=\"menuNews\">Haberler</string>\n  <string name=\"textRateAppTitle\">Showly 2.0 uygulamasından memnun musunuz?</string>\n  <string name=\"textRateAppMessage\">Uygulamayı derecelendirmek ister misiniz?\\nSadece birkaç saniye sürer. Teşekkürler!\\n\\n <i>Uygulama kapatılmayacaktır.</i></string>\n  <string name=\"textRateAppYesButton\">Tabii ki, neden olmasın?</string>\n  <string name=\"textRateAppNoButton\">Şimdi değil</string>\n  <string name=\"textDisclaimerTitle\">Hoş geldiniz!</string>\n  <string name=\"textDisclaimerText\">Lütfen bu uygulama ile dizileri ve filmleri izleyemeyeceğinizi veya indiremeyeceğinizi unutmayın!\\n\\n İçerikleri izlemek için <b>Netflix</b> gibi uygulamaları kullanın.</string>\n  <string name=\"textDisclaimerConfirmText\">Tamam, anladım</string>\n  <string name=\"textWhatsNewTitle\">Neler Yeni</string>\n  <string name=\"textWhatsNewSubtitle\">%s Sürümü</string>\n  <string name=\"shortcutDiscover\">Keşfet</string>\n  <string name=\"shortcutCollection\">Koleksiyon</string>\n  <string name=\"shortcutSearch\">Ara</string>\n  <string name=\"shortcutProgress\">İlerleme</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">Прогрес</string>\n  <string name=\"menuDiscover\">Огляд</string>\n  <string name=\"menuCollection\">Колекція</string>\n  <string name=\"menuNews\">Новини</string>\n  <string name=\"textRateAppTitle\">Подобається Showly 2.0?</string>\n  <string name=\"textRateAppMessage\">Ви хотіли б оцінити додаток?\\nЦе займає лише декілька секунд. Дякуємо!\\n\\n<i>Додаток не буде закрито.</i></string>\n  <string name=\"textRateAppYesButton\">Авжеж, чому ні?</string>\n  <string name=\"textRateAppNoButton\">Не зараз</string>\n  <string name=\"textDisclaimerTitle\">Вітаємо!</string>\n  <string name=\"textDisclaimerText\">Будь ласка, зверніть увагу, що ви <b>НЕ МОЖЕТЕ</b> дивитися або завантажувати серіали та фільми з цим додатком.\\n\\nВикористовуйте додатки, такі як <b>Netflix</b> для завантаження та перегляду вмісту.</string>\n  <string name=\"textDisclaimerConfirmText\">ОК, зрозуміло</string>\n  <string name=\"textWhatsNewTitle\">Що нового</string>\n  <string name=\"textWhatsNewSubtitle\">Версія: %s</string>\n  <string name=\"shortcutDiscover\">Огляд</string>\n  <string name=\"shortcutCollection\">Колекція</string>\n  <string name=\"shortcutSearch\">Пошук</string>\n  <string name=\"shortcutProgress\">Прогрес</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-vi/strings.xml",
    "content": "<resources>\n  <string name=\"app_name\" translatable=\"false\">Showly OSS</string>\n\n  <string name=\"menuProgress\">Tiến độ</string>\n  <string name=\"menuDiscover\">Khám phá</string>\n  <string name=\"menuCollection\">Bộ sưu tập</string>\n  <string name=\"menuNews\">Tin tức</string>\n\n  <string name=\"textRateAppTitle\">Bạn có thích Showly 2.0 không?</string>\n  <string name=\"textRateAppMessage\">Bạn có muốn xếp hạng ứng dụng không?\\nViệc này chỉ mất vài giây. Cảm ơn!\\n\\n<i>Ứng dụng sẽ không bị đóng.</i></string>\n  <string name=\"textRateAppYesButton\">Chắc chắn rồi, tại sao không?</string>\n  <string name=\"textRateAppNoButton\">Không phải bây giờ</string>\n\n  <string name=\"textDisclaimerTitle\">Chào mừng!</string>\n  <string name=\"textDisclaimerText\">Xin lưu ý rằng bạn <b>KHÔNG THỂ</b> xem, phát trực tuyến hoặc tải xuống các chương trình và phim bằng ứng dụng này.\\n\\nSử dụng các ứng dụng như <b>Netflix</b> để phát trực tuyến và xem nội dung.</string >\n  <string name=\"textDisclaimerConfirmText\">Được rồi, tôi hiểu</string>\n\n  <string name=\"textWhatsNewTitle\">Có gì mới</string>\n  <string name=\"textWhatsNewSubtitle\">Phiên bản %s</string>\n\n  <string name=\"shortcutDiscover\">Khám phá</string>\n  <string name=\"shortcutCollection\">Bộ sưu tập</string>\n  <string name=\"shortcutSearch\">Tìm kiếm</string>\n  <string name=\"shortcutProgress\">Tiến độ</string>\n\n  <string name=\"textUpdateDownloaded\" translatable=\"false\">Hiện đã có bản cập nhật.</string>\n  <string name=\"textUpdateInstall\" translatable=\"false\">Cài đặt</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">进度</string>\n  <string name=\"menuDiscover\">发现</string>\n  <string name=\"menuCollection\">合集</string>\n  <string name=\"menuNews\">新闻</string>\n  <string name=\"textRateAppTitle\">喜欢 Showly 2.0 吗？</string>\n  <string name=\"textRateAppMessage\">您愿意给应用评分吗？\\n仅需几秒，感谢！\\n\\n<i>应用不会被关闭。</i></string>\n  <string name=\"textRateAppYesButton\">好，没问题。</string>\n  <string name=\"textRateAppNoButton\">稍后</string>\n  <string name=\"textDisclaimerTitle\">欢迎!</string>\n  <string name=\"textDisclaimerText\">请注意您 <b>不能</b> 使用此应用观看或下载剧集和电影。\\n\\n使用 <b>Netflix</b> 等应用来观看。</string>\n  <string name=\"textDisclaimerConfirmText\">好的，我已了解</string>\n  <string name=\"textWhatsNewTitle\">更新内容</string>\n  <string name=\"textWhatsNewSubtitle\">版本：%s</string>\n  <string name=\"shortcutDiscover\">发现</string>\n  <string name=\"shortcutCollection\">合集</string>\n  <string name=\"shortcutSearch\">搜索</string>\n  <string name=\"shortcutProgress\">进度</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/xml/locales_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<locale-config xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <locale android:name=\"en\" />\n  <locale android:name=\"ar\" />\n  <locale android:name=\"de\" />\n  <locale android:name=\"es\" />\n  <locale android:name=\"fi\" />\n  <locale android:name=\"fr\" />\n  <locale android:name=\"it\" />\n  <locale android:name=\"pl\" />\n  <locale android:name=\"pt\" />\n  <locale android:name=\"ru\" />\n  <locale android:name=\"tr\" />\n  <locale android:name=\"zh\" />\n  <locale android:name=\"vi\" />\n</locale-config>\n"
  },
  {
    "path": "app/src/main/res/xml/shortcuts.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shortcuts\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  tools:targetApi=\"n_mr1\"\n  >\n  <shortcut\n    android:enabled=\"true\"\n    android:icon=\"@drawable/ic_shortcut_search\"\n    android:shortcutId=\"shortcutSearch\"\n    android:shortcutLongLabel=\"@string/shortcutSearch\"\n    android:shortcutShortLabel=\"@string/shortcutSearch\"\n    >\n    <intent\n      android:action=\"android.intent.action.VIEW\"\n      android:targetClass=\"com.michaldrabik.showly_oss.ui.main.MainActivity\"\n      android:targetPackage=\"com.michaldrabik.showly_oss\"\n      >\n      <extra\n        android:name=\"extraShortcutSearch\"\n        android:value=\"extraShortcutSearch\"\n        />\n    </intent>\n  </shortcut>\n\n  <shortcut\n    android:enabled=\"true\"\n    android:icon=\"@drawable/ic_shortcut_bookmark\"\n    android:shortcutId=\"shortcutCollection\"\n    android:shortcutLongLabel=\"@string/shortcutCollection\"\n    android:shortcutShortLabel=\"@string/shortcutCollection\"\n    >\n    <intent\n      android:action=\"android.intent.action.VIEW\"\n      android:targetClass=\"com.michaldrabik.showly_oss.ui.main.MainActivity\"\n      android:targetPackage=\"com.michaldrabik.showly_oss\"\n      >\n      <extra\n        android:name=\"extraShortcutCollection\"\n        android:value=\"extraShortcutCollection\"\n        />\n    </intent>\n  </shortcut>\n\n  <shortcut\n    android:enabled=\"true\"\n    android:icon=\"@drawable/ic_shortcut_search\"\n    android:shortcutId=\"shortcutDiscover\"\n    android:shortcutLongLabel=\"@string/shortcutDiscover\"\n    android:shortcutShortLabel=\"@string/shortcutDiscover\"\n    >\n    <intent\n      android:action=\"android.intent.action.VIEW\"\n      android:targetClass=\"com.michaldrabik.showly_oss.ui.main.MainActivity\"\n      android:targetPackage=\"com.michaldrabik.showly_oss\"\n      >\n      <extra\n        android:name=\"extraShortcutDiscover\"\n        android:value=\"extraShortcutDiscover\"\n        />\n    </intent>\n  </shortcut>\n\n  <shortcut\n    android:enabled=\"true\"\n    android:icon=\"@drawable/ic_shortcut_check\"\n    android:shortcutId=\"shortcutWatchlist\"\n    android:shortcutLongLabel=\"@string/shortcutProgress\"\n    android:shortcutShortLabel=\"@string/shortcutProgress\"\n    >\n    <intent\n      android:action=\"android.intent.action.VIEW\"\n      android:targetClass=\"com.michaldrabik.showly_oss.ui.main.MainActivity\"\n      android:targetPackage=\"com.michaldrabik.showly_oss\"\n      >\n      <extra\n        android:name=\"extraShortcutProgress\"\n        android:value=\"extraShortcutProgress\"\n        />\n    </intent>\n  </shortcut>\n</shortcuts>"
  },
  {
    "path": "app/src/test/java/BaseMockTest.kt",
    "content": "import io.mockk.MockKAnnotations\nimport io.mockk.mockkStatic\nimport org.junit.Before\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nabstract class BaseMockTest {\n\n  @Before\n  open fun setUp() {\n    MockKAnnotations.init(this)\n    mockkStatic(\"androidx.room.RoomDatabaseKt\")\n  }\n}\n"
  },
  {
    "path": "app/src/test/java/com/michaldrabik/showly_oss/ui/main/cases/MainRateAppCaseTest.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main.cases\n\nimport BaseMockTest\nimport android.content.SharedPreferences\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.showly_oss.ui.main.cases.MainRateAppCase.Companion.KEY_RATE_APP_COUNT\nimport com.michaldrabik.showly_oss.ui.main.cases.MainRateAppCase.Companion.KEY_RATE_APP_TIMESTAMP\nimport io.mockk.Runs\nimport io.mockk.confirmVerified\nimport io.mockk.every\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.just\nimport io.mockk.verify\nimport org.junit.Before\nimport org.junit.Test\nimport java.util.concurrent.TimeUnit\n\nclass MainRateAppCaseTest : BaseMockTest() {\n\n  @MockK\n  lateinit var sharedPreferences: SharedPreferences\n\n  @MockK\n  lateinit var sharedPreferencesEditor: SharedPreferences.Editor\n\n  private lateinit var SUT: MainRateAppCase\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    every { sharedPreferencesEditor.apply() } just Runs\n    every { sharedPreferencesEditor.putInt(any(), any()) } returns sharedPreferencesEditor\n    every { sharedPreferencesEditor.putLong(any(), any()) } returns sharedPreferencesEditor\n    every { sharedPreferences.edit() } returns sharedPreferencesEditor\n\n    SUT = MainRateAppCase(sharedPreferences)\n  }\n\n  @Test\n  fun `Should return false if check is initial`() {\n    every { sharedPreferences.getInt(KEY_RATE_APP_COUNT, any()) } returns 0\n    every { sharedPreferences.getLong(KEY_RATE_APP_TIMESTAMP, any()) } returns -1L\n\n    val result = SUT.shouldShowRateApp()\n\n    assertThat(result).isFalse()\n    verify { sharedPreferencesEditor.putInt(KEY_RATE_APP_COUNT, 0) }\n    verify { sharedPreferencesEditor.putLong(KEY_RATE_APP_TIMESTAMP, any()) }\n    verify { sharedPreferencesEditor.apply() }\n    confirmVerified(sharedPreferencesEditor)\n  }\n\n  @Test\n  fun `Should return false if not enough days have passed`() {\n    every { sharedPreferences.getInt(KEY_RATE_APP_COUNT, any()) } returns 1\n    every { sharedPreferences.getLong(KEY_RATE_APP_TIMESTAMP, any()) } returns (nowUtcMillis() - TimeUnit.DAYS.toMillis(5))\n\n    val result = SUT.shouldShowRateApp()\n\n    assertThat(result).isFalse()\n    verify(exactly = 0) { sharedPreferencesEditor.putInt(KEY_RATE_APP_COUNT, 2) }\n    verify(exactly = 0) { sharedPreferencesEditor.putLong(KEY_RATE_APP_TIMESTAMP, any()) }\n    verify(exactly = 0) { sharedPreferencesEditor.apply() }\n\n    confirmVerified(sharedPreferencesEditor)\n  }\n\n  @Test\n  fun `Should return false if not enough days have passed before last reminder`() {\n    every { sharedPreferences.getInt(KEY_RATE_APP_COUNT, any()) } returns 2\n    every { sharedPreferences.getLong(KEY_RATE_APP_TIMESTAMP, any()) } returns (nowUtcMillis() - TimeUnit.DAYS.toMillis(12))\n\n    val result = SUT.shouldShowRateApp()\n\n    assertThat(result).isFalse()\n    verify(exactly = 0) { sharedPreferencesEditor.putInt(KEY_RATE_APP_COUNT, 3) }\n    verify(exactly = 0) { sharedPreferencesEditor.putLong(KEY_RATE_APP_TIMESTAMP, any()) }\n    verify(exactly = 0) { sharedPreferencesEditor.apply() }\n\n    confirmVerified(sharedPreferencesEditor)\n  }\n\n  @Test\n  fun `Should return true if enough days have passed before another reminder`() {\n    every { sharedPreferences.getInt(KEY_RATE_APP_COUNT, any()) } returns 2\n    every { sharedPreferences.getLong(KEY_RATE_APP_TIMESTAMP, any()) } returns (nowUtcMillis() - TimeUnit.DAYS.toMillis(15))\n\n    val result = SUT.shouldShowRateApp()\n\n    assertThat(result).isTrue()\n    verify(exactly = 1) { sharedPreferencesEditor.putInt(KEY_RATE_APP_COUNT, 2) }\n    verify(exactly = 1) { sharedPreferencesEditor.putLong(KEY_RATE_APP_TIMESTAMP, any()) }\n    verify(exactly = 1) { sharedPreferencesEditor.apply() }\n\n    confirmVerified(sharedPreferencesEditor)\n  }\n\n//  @Test\n//  fun `Should return true if count is one before last one and 10 days passed`() {\n//    every { sharedPreferences.getInt(KEY_RATE_APP_COUNT, any()) } returns 1\n//    every { sharedPreferences.getLong(KEY_RATE_APP_TIMESTAMP, any()) } returns (nowUtcMillis() - TimeUnit.DAYS.toMillis(11))\n//\n//    val result = SUT.shouldShowRateApp()\n//\n//    assertThat(result).isTrue()\n//    verify { sharedPreferencesEditor.putInt(KEY_RATE_APP_COUNT, 2) }\n//    verify { sharedPreferencesEditor.putLong(KEY_RATE_APP_TIMESTAMP, any()) }\n//    verify { sharedPreferencesEditor.apply() }\n//\n//    confirmVerified(sharedPreferencesEditor)\n//  }\n//\n//  @Test\n//  fun `Should return true if count is last one and 14 days passed`() {\n//    every { sharedPreferences.getInt(KEY_RATE_APP_COUNT, any()) } returns 2\n//    every { sharedPreferences.getLong(KEY_RATE_APP_TIMESTAMP, any()) } returns (nowUtcMillis() - TimeUnit.DAYS.toMillis(15))\n//\n//    val result = SUT.shouldShowRateApp()\n//\n//    assertThat(result).isTrue()\n//    verify { sharedPreferencesEditor.putInt(KEY_RATE_APP_COUNT, 3) }\n//    verify { sharedPreferencesEditor.putLong(KEY_RATE_APP_TIMESTAMP, any()) }\n//    verify { sharedPreferencesEditor.apply() }\n//    confirmVerified(sharedPreferencesEditor)\n//  }\n}\n"
  },
  {
    "path": "app/src/test/java/com/michaldrabik/showly_oss/ui/main/cases/MainTipsCaseTest.kt",
    "content": "package com.michaldrabik.showly_oss.ui.main.cases\n\nimport BaseMockTest\nimport android.content.SharedPreferences\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.showly_oss.BuildConfig\nimport com.michaldrabik.ui_model.Tip\nimport io.mockk.Runs\nimport io.mockk.every\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.just\nimport io.mockk.verifyAll\nimport org.junit.Before\nimport org.junit.Test\n\nclass MainTipsCaseTest : BaseMockTest() {\n\n  @MockK\n  lateinit var sharedPreferences: SharedPreferences\n\n  @MockK\n  lateinit var sharedPreferencesEditor: SharedPreferences.Editor\n\n  private lateinit var SUT: MainTipsCase\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    every { sharedPreferencesEditor.apply() } just Runs\n    every { sharedPreferencesEditor.putBoolean(any(), any()) } returns sharedPreferencesEditor\n    every { sharedPreferences.edit() } returns sharedPreferencesEditor\n\n    SUT = MainTipsCase(sharedPreferences)\n  }\n\n  @Test\n  fun `Should return true if tip has been show`() {\n    val tip = Tip.MENU_DISCOVER\n    every { sharedPreferences.getBoolean(tip.name, false) } returns true\n\n    assertThat(SUT.isTipShown(tip)).isTrue()\n  }\n\n  @Test\n  fun `Should return false if tips has not been shown`() {\n    val tip = Tip.MENU_DISCOVER\n    every { sharedPreferences.getBoolean(tip.name, false) } returns false\n\n    if (BuildConfig.DEBUG) {\n      assertThat(SUT.isTipShown(tip)).isTrue()\n    } else {\n      assertThat(SUT.isTipShown(tip)).isFalse()\n    }\n  }\n\n  @Test\n  fun `Should store tips shown info properly`() {\n    val tip = Tip.MENU_DISCOVER\n\n    SUT.setTipShown(tip)\n\n    verifyAll {\n      sharedPreferencesEditor.putBoolean(tip.name, true)\n      sharedPreferencesEditor.apply()\n    }\n  }\n}\n"
  },
  {
    "path": "assets/codestyle.xml",
    "content": "<code_scheme name=\"Showly 2.0\" version=\"173\">\n  <option name=\"RIGHT_MARGIN\" value=\"150\" />\n  <option name=\"SOFT_MARGINS\" value=\"150\" />\n  <AndroidXmlCodeStyleSettings>\n    <option name=\"ARRANGEMENT_SETTINGS_MIGRATED_TO_191\" value=\"true\" />\n    <option name=\"LAYOUT_SETTINGS\">\n      <value>\n        <option name=\"INSERT_LINE_BREAK_BEFORE_NAMESPACE_DECLARATION\" value=\"true\" />\n        <option name=\"INSERT_LINE_BREAK_AFTER_LAST_ATTRIBUTE\" value=\"true\" />\n      </value>\n    </option>\n    <option name=\"OTHER_SETTINGS\">\n      <value>\n        <option name=\"INSERT_LINE_BREAK_BEFORE_NAMESPACE_DECLARATION\" value=\"true\" />\n        <option name=\"INSERT_LINE_BREAK_AFTER_LAST_ATTRIBUTE\" value=\"true\" />\n      </value>\n    </option>\n  </AndroidXmlCodeStyleSettings>\n  <DBN-PSQL>\n    <case-options enabled=\"true\">\n      <option name=\"KEYWORD_CASE\" value=\"lower\" />\n      <option name=\"FUNCTION_CASE\" value=\"lower\" />\n      <option name=\"PARAMETER_CASE\" value=\"lower\" />\n      <option name=\"DATATYPE_CASE\" value=\"lower\" />\n      <option name=\"OBJECT_CASE\" value=\"preserve\" />\n    </case-options>\n    <formatting-settings enabled=\"false\" />\n  </DBN-PSQL>\n  <DBN-SQL>\n    <case-options enabled=\"true\">\n      <option name=\"KEYWORD_CASE\" value=\"lower\" />\n      <option name=\"FUNCTION_CASE\" value=\"lower\" />\n      <option name=\"PARAMETER_CASE\" value=\"lower\" />\n      <option name=\"DATATYPE_CASE\" value=\"lower\" />\n      <option name=\"OBJECT_CASE\" value=\"preserve\" />\n    </case-options>\n    <formatting-settings enabled=\"false\">\n      <option name=\"STATEMENT_SPACING\" value=\"one_line\" />\n      <option name=\"CLAUSE_CHOP_DOWN\" value=\"chop_down_if_statement_long\" />\n      <option name=\"ITERATION_ELEMENTS_WRAPPING\" value=\"chop_down_if_not_single\" />\n    </formatting-settings>\n  </DBN-SQL>\n  <JetCodeStyleSettings>\n    <option name=\"NAME_COUNT_TO_USE_STAR_IMPORT\" value=\"2147483647\" />\n    <option name=\"NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS\" value=\"2147483647\" />\n    <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n  </JetCodeStyleSettings>\n  <DBN-PSQL>\n    <case-options enabled=\"true\">\n      <option name=\"KEYWORD_CASE\" value=\"lower\" />\n      <option name=\"FUNCTION_CASE\" value=\"lower\" />\n      <option name=\"PARAMETER_CASE\" value=\"lower\" />\n      <option name=\"DATATYPE_CASE\" value=\"lower\" />\n      <option name=\"OBJECT_CASE\" value=\"preserve\" />\n    </case-options>\n    <formatting-settings enabled=\"false\" />\n  </DBN-PSQL>\n  <DBN-SQL>\n    <case-options enabled=\"true\">\n      <option name=\"KEYWORD_CASE\" value=\"lower\" />\n      <option name=\"FUNCTION_CASE\" value=\"lower\" />\n      <option name=\"PARAMETER_CASE\" value=\"lower\" />\n      <option name=\"DATATYPE_CASE\" value=\"lower\" />\n      <option name=\"OBJECT_CASE\" value=\"preserve\" />\n    </case-options>\n    <formatting-settings enabled=\"false\">\n      <option name=\"STATEMENT_SPACING\" value=\"one_line\" />\n      <option name=\"CLAUSE_CHOP_DOWN\" value=\"chop_down_if_statement_long\" />\n      <option name=\"ITERATION_ELEMENTS_WRAPPING\" value=\"chop_down_if_not_single\" />\n    </formatting-settings>\n  </DBN-SQL>\n  <codeStyleSettings language=\"Dart\">\n    <option name=\"RIGHT_MARGIN\" value=\"100\" />\n  </codeStyleSettings>\n  <codeStyleSettings language=\"Groovy\">\n    <indentOptions>\n      <option name=\"INDENT_SIZE\" value=\"2\" />\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n      <option name=\"TAB_SIZE\" value=\"2\" />\n    </indentOptions>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"XML\">\n    <indentOptions>\n      <option name=\"INDENT_SIZE\" value=\"2\" />\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"2\" />\n      <option name=\"TAB_SIZE\" value=\"2\" />\n    </indentOptions>\n    <arrangement>\n      <rules>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>xmlns:android</NAME>\n                <XML_ATTRIBUTE />\n                <XML_NAMESPACE>^$</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>xmlns:.*</NAME>\n                <XML_ATTRIBUTE />\n                <XML_NAMESPACE>^$</XML_NAMESPACE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:id</NAME>\n                <XML_ATTRIBUTE />\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:name</NAME>\n                <XML_ATTRIBUTE />\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>name</NAME>\n                <XML_ATTRIBUTE />\n                <XML_NAMESPACE>^$</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>style</NAME>\n                <XML_ATTRIBUTE />\n                <XML_NAMESPACE>^$</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*</NAME>\n                <XML_ATTRIBUTE />\n                <XML_NAMESPACE>^$</XML_NAMESPACE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*</NAME>\n                <XML_ATTRIBUTE />\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n            <order>ANDROID_ATTRIBUTE_ORDER</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*</NAME>\n                <XML_ATTRIBUTE />\n                <XML_NAMESPACE>.*</XML_NAMESPACE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n      </rules>\n    </arrangement>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"kotlin\">\n    <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n    <option name=\"RIGHT_MARGIN\" value=\"150\" />\n    <option name=\"FIELD_ANNOTATION_WRAP\" value=\"1\" />\n    <indentOptions>\n      <option name=\"INDENT_SIZE\" value=\"2\" />\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n      <option name=\"TAB_SIZE\" value=\"2\" />\n    </indentOptions>\n  </codeStyleSettings>\n</code_scheme>"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nbuildscript {\n  apply from: \"./versions.gradle\"\n\n  repositories {\n    google()\n    mavenCentral()\n    maven { url \"https://plugins.gradle.org/m2/\" }\n  }\n\n  dependencies {\n    classpath libs.gradle\n    classpath libs.gradle.kotlin.plugin\n    classpath libs.gradle.ktlint\n    classpath libs.hilt.plugin\n  }\n}\n\nplugins {\n  id \"com.github.triplet.play\" version \"3.8.1\" apply false\n  id 'com.google.devtools.ksp' version '1.9.0-1.0.13' apply false\n}\n\nallprojects {\n  repositories {\n    google()\n    mavenCentral()\n    maven { url 'https://jitpack.io' }\n  }\n\n  apply plugin: \"org.jlleitschuh.gradle.ktlint\"\n  apply plugin: \"com.google.devtools.ksp\"\n}\n\ntask clean(type: Delete) {\n  delete rootProject.buildDir\n}\n"
  },
  {
    "path": "common/.gitignore",
    "content": "/build"
  },
  {
    "path": "common/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.common'\n}\n\ndependencies {\n  api libs.coroutines\n  implementation libs.retrofit\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  implementation libs.coroutinesTest\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "common/src/debug/java/com/michaldrabik/common/ConfigVariant.kt",
    "content": "package com.michaldrabik.common\n\nimport java.util.concurrent.TimeUnit.MINUTES\n\nobject ConfigVariant {\n  val SHOW_SYNC_COOLDOWN by lazy { MINUTES.toMillis(5) }\n  val MOVIE_SYNC_COOLDOWN by lazy { MINUTES.toMillis(5) }\n  val TRANSLATION_SYNC_SHOW_MOVIE_COOLDOWN by lazy { MINUTES.toMillis(5) }\n  val TRANSLATION_SYNC_EPISODE_COOLDOWN by lazy { MINUTES.toMillis(5) }\n\n  val RATINGS_CACHE_DURATION by lazy { MINUTES.toMillis(3) }\n  val STREAMINGS_CACHE_DURATION by lazy { MINUTES.toMillis(3) }\n  val COLLECTIONS_CACHE_DURATION by lazy { MINUTES.toMillis(3) }\n\n  val TWITTER_AD_DELAY by lazy { MINUTES.toMillis(1) }\n  val PREMIUM_AD_DELAY by lazy { MINUTES.toMillis(1) }\n}\n"
  },
  {
    "path": "common/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "common/src/main/java/com/michaldrabik/common/Config.kt",
    "content": "package com.michaldrabik.common\n\nimport java.util.concurrent.TimeUnit.DAYS\nimport java.util.concurrent.TimeUnit.HOURS\n\nobject Config {\n  const val TVDB_IMAGE_BASE_BANNERS_URL = \"https://artworks.thetvdb.com/banners/\"\n  const val TVDB_IMAGE_BASE_POSTER_URL = \"${TVDB_IMAGE_BASE_BANNERS_URL}posters/\"\n  const val TVDB_IMAGE_BASE_FANART_URL = \"${TVDB_IMAGE_BASE_BANNERS_URL}fanart/original/\"\n\n  private const val TMDB_IMAGE_BASE_URL = \"https://image.tmdb.org/t/p/\"\n  const val TMDB_IMAGE_BASE_POSTER_URL = \"${TMDB_IMAGE_BASE_URL}w342\"\n  const val TMDB_IMAGE_BASE_FANART_URL = \"${TMDB_IMAGE_BASE_URL}w1280\"\n  const val TMDB_IMAGE_BASE_PROFILE_URL = \"${TMDB_IMAGE_BASE_URL}w1280\"\n  const val TMDB_IMAGE_BASE_PROFILE_THUMB_URL = \"${TMDB_IMAGE_BASE_URL}w342\"\n  const val TMDB_IMAGE_BASE_STILL_URL = \"${TMDB_IMAGE_BASE_URL}original\"\n  const val TMDB_IMAGE_BASE_ACTOR_URL = \"${TMDB_IMAGE_BASE_URL}h632\"\n  const val TMDB_IMAGE_BASE_LOGO_URL = \"${TMDB_IMAGE_BASE_URL}original\"\n\n  const val AWS_IMAGE_BASE_URL = \"https://showly2.s3.eu-west-2.amazonaws.com/images/\"\n\n  const val TRAKT_URL = \"https://www.trakt.tv/\"\n  const val JUST_WATCH_URL = \"https://www.justwatch.com/\"\n  const val TMDB_URL = \"https://www.themoviedb.org/\"\n  const val GITHUB_RELEASE_URL = \"https://github.com/1RandomDev/showly-oss/releases/latest\"\n  const val GITHUB_ISSUE_URL = \"https://github.com/1RandomDev/showly-oss/issues\"\n  const val GITHUB_URL = \"https://github.com/1RandomDev/showly-oss\"\n\n  const val MAIN_GRID_SPAN = 3\n  const val MAIN_GRID_SPAN_TABLET = 6\n  const val LISTS_GRID_SPAN = 4\n  const val LISTS_GRID_SPAN_TABLET = 6\n  const val IMAGE_FADE_DURATION_MS = 150\n  const val SEARCH_RECENTS_AMOUNT = 5\n  const val FANART_GALLERY_IMAGES_LIMIT = 20\n  const val PULL_TO_REFRESH_COOLDOWN_MS = 10_000\n  const val DEFAULT_LANGUAGE = \"en\"\n  const val DEFAULT_COUNTRY = \"us\"\n  const val DEFAULT_DATE_FORMAT = \"DEFAULT_24\"\n  const val DEFAULT_NEWS_VIEW_TYPE = \"ROW\"\n  const val DEFAULT_LIST_VIEW_MODE = \"LIST_NORMAL\"\n  const val DEFAULT_LISTS_GRID_SPAN = 2\n  const val HOST_ACTIVITY_NAME = \"com.michaldrabik.showly_oss.ui.main.MainActivity\"\n\n  const val SHOW_WHATS_NEW = true\n  const val SHOW_TIPS_DEBUG = false\n  const val SHOW_PREMIUM = true\n\n  val PROGRESS_UPCOMING_OPTIONS = arrayOf(0, 7, 14, 30, 45, 60, 90)\n  val MY_SHOWS_RECENTS_OPTIONS = arrayOf(0, 2, 4, 6, 8)\n  val DISCOVER_SHOWS_CACHE_DURATION by lazy { HOURS.toMillis(12) }\n  val DISCOVER_MOVIES_CACHE_DURATION by lazy { HOURS.toMillis(12) }\n  val RELATED_CACHE_DURATION by lazy { DAYS.toMillis(7) }\n  val SHOW_DETAILS_CACHE_DURATION by lazy { DAYS.toMillis(3) }\n  val MOVIE_DETAILS_CACHE_DURATION by lazy { DAYS.toMillis(3) }\n  val ACTORS_CACHE_DURATION by lazy { DAYS.toMillis(5) }\n  val NEW_BADGE_DURATION by lazy { DAYS.toMillis(3) }\n  val PEOPLE_CREDITS_CACHE_DURATION by lazy { DAYS.toMillis(7) }\n  val PEOPLE_IMAGES_CACHE_DURATION by lazy { DAYS.toMillis(7) }\n\n  const val SPOILERS_HIDE_SYMBOL = \"•\"\n  const val SPOILERS_RATINGS_HIDE_SYMBOL = \"•.•\"\n  const val SPOILERS_RATINGS_VOTES_HIDE_SYMBOL = \"•.• (•••••)\"\n  val SPOILERS_REGEX = \"\\\\S\".toRegex()\n}\n"
  },
  {
    "path": "common/src/main/java/com/michaldrabik/common/Mode.kt",
    "content": "package com.michaldrabik.common\n\nenum class Mode(val type: String) {\n  SHOWS(\"show\"),\n  MOVIES(\"movie\");\n\n  companion object {\n    fun fromType(type: String) = values().first { it.type == type }\n\n    fun getAll() = listOf(SHOWS, MOVIES)\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/michaldrabik/common/di/CommonBindingModule.kt",
    "content": "package com.michaldrabik.common.di\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.dispatchers.DefaultCoroutineDispatchers\nimport dagger.Binds\nimport dagger.Module\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\n\n@Module\n@InstallIn(SingletonComponent::class)\ninternal abstract class CommonBindingModule {\n\n  @Binds\n  abstract fun bindCoroutineDispatchers(\n    dispatchers: DefaultCoroutineDispatchers\n  ): CoroutineDispatchers\n}\n"
  },
  {
    "path": "common/src/main/java/com/michaldrabik/common/dispatchers/CoroutineDispatchers.kt",
    "content": "package com.michaldrabik.common.dispatchers\n\nimport kotlinx.coroutines.CoroutineDispatcher\n\ninterface CoroutineDispatchers {\n  val Main: CoroutineDispatcher\n  val IO: CoroutineDispatcher\n  val Default: CoroutineDispatcher\n  val Unconfined: CoroutineDispatcher\n}\n"
  },
  {
    "path": "common/src/main/java/com/michaldrabik/common/dispatchers/DefaultCoroutineDispatchers.kt",
    "content": "package com.michaldrabik.common.dispatchers\n\nimport kotlinx.coroutines.Dispatchers\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\ninternal class DefaultCoroutineDispatchers @Inject constructor() : CoroutineDispatchers {\n  override val Main = Dispatchers.Main\n  override val IO = Dispatchers.IO\n  override val Default = Dispatchers.Default\n  override val Unconfined = Dispatchers.Unconfined\n}\n"
  },
  {
    "path": "common/src/main/java/com/michaldrabik/common/errors/ErrorHelper.kt",
    "content": "package com.michaldrabik.common.errors\n\nimport com.michaldrabik.common.errors.ShowlyError.AccountLimitsError\nimport com.michaldrabik.common.errors.ShowlyError.AccountLockedError\nimport com.michaldrabik.common.errors.ShowlyError.CoroutineCancellation\nimport com.michaldrabik.common.errors.ShowlyError.ResourceConflictError\nimport com.michaldrabik.common.errors.ShowlyError.ResourceNotFoundError\nimport com.michaldrabik.common.errors.ShowlyError.UnauthorizedError\nimport com.michaldrabik.common.errors.ShowlyError.UnknownError\nimport com.michaldrabik.common.errors.ShowlyError.UnknownHttpError\nimport com.michaldrabik.common.errors.ShowlyError.ValidationError\nimport retrofit2.HttpException\nimport kotlin.coroutines.cancellation.CancellationException\n\nobject ErrorHelper {\n\n  fun parse(error: Throwable): ShowlyError =\n    when (error) {\n      is ShowlyError -> error\n      is HttpException -> {\n        when (error.code()) {\n          in arrayOf(401, 403) -> UnauthorizedError(error.message)\n          404 -> ResourceNotFoundError\n          409 -> ResourceConflictError\n          420 -> AccountLimitsError\n          422 -> ValidationError\n          423 -> AccountLockedError\n          else -> UnknownHttpError(error.message)\n        }\n      }\n      is CancellationException -> CoroutineCancellation\n      else -> UnknownError(error.message)\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/michaldrabik/common/errors/ShowlyError.kt",
    "content": "package com.michaldrabik.common.errors\n\nsealed class ShowlyError(errorMessage: String?) : Throwable(errorMessage) {\n\n  object ValidationError : ShowlyError(\"ValidationError\")\n\n  object ResourceConflictError : ShowlyError(\"ResourceConflictError\")\n\n  object ResourceNotFoundError : ShowlyError(\"ResourceNotFoundError\")\n\n  object AccountLockedError : ShowlyError(\"AccountLockedError\")\n\n  object AccountLimitsError : ShowlyError(\"AccountLimitsError\")\n\n  data class UnauthorizedError(val errorMessage: String?) : ShowlyError(errorMessage)\n\n  data class UnknownHttpError(\n    val errorMessage: String?\n  ) : ShowlyError(errorMessage)\n\n  data class UnknownError(\n    val errorMessage: String?\n  ) : ShowlyError(errorMessage)\n\n  object CoroutineCancellation : ShowlyError(\"\")\n}\n"
  },
  {
    "path": "common/src/main/java/com/michaldrabik/common/extensions/DateExtensions.kt",
    "content": "package com.michaldrabik.common.extensions\n\nimport java.time.Instant\nimport java.time.LocalDate\nimport java.time.ZoneId\nimport java.time.ZoneOffset\nimport java.time.ZonedDateTime\nimport java.time.format.DateTimeFormatter\nimport java.time.temporal.ChronoUnit.DAYS\n\nfun nowUtc(): ZonedDateTime = ZonedDateTime.now(ZoneOffset.UTC)\n\nfun nowUtcDay(): LocalDate = LocalDate.now()\n\nfun nowUtcMillis(): Long = nowUtc().toMillis()\n\nfun dateFromMillis(millis: Long): ZonedDateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.of(\"UTC\"))\n\nfun dateIsoStringFromMillis(millis: Long): String = dateFromMillis(millis).format(DateTimeFormatter.ISO_INSTANT)\n\nfun ZonedDateTime.toMillis() = this.toInstant().toEpochMilli()\n\nfun ZonedDateTime.toLocalZone(): ZonedDateTime = this.withZoneSameInstant(ZoneId.systemDefault())\n\nfun ZonedDateTime.isSameDayOrAfter(date: ZonedDateTime): Boolean =\n  this.isEqual(date.truncatedTo(DAYS)) || this.isAfter(date.truncatedTo(DAYS))\n\nfun String?.toZonedDateTime(): ZonedDateTime? = if (this.isNullOrBlank()) null else ZonedDateTime.parse(this)\n"
  },
  {
    "path": "common/src/release/java/com/michaldrabik/common/ConfigVariant.kt",
    "content": "package com.michaldrabik.common\n\nimport java.util.concurrent.TimeUnit.DAYS\nimport java.util.concurrent.TimeUnit.HOURS\nimport java.util.concurrent.TimeUnit.MINUTES\n\nobject ConfigVariant {\n  val SHOW_SYNC_COOLDOWN by lazy { HOURS.toMillis(12) }\n  val MOVIE_SYNC_COOLDOWN by lazy { DAYS.toMillis(3) }\n  val TRANSLATION_SYNC_SHOW_MOVIE_COOLDOWN by lazy { DAYS.toMillis(5) }\n  val TRANSLATION_SYNC_EPISODE_COOLDOWN by lazy { DAYS.toMillis(3) }\n\n  val RATINGS_CACHE_DURATION by lazy { DAYS.toMillis(3) }\n  val STREAMINGS_CACHE_DURATION by lazy { DAYS.toMillis(3) }\n  val COLLECTIONS_CACHE_DURATION by lazy { DAYS.toMillis(7) }\n\n  val REMOTE_CONFIG_FETCH_INTERVAL by lazy { MINUTES.toSeconds(60) }\n  val TWITTER_AD_DELAY by lazy { DAYS.toMillis(5) }\n  val PREMIUM_AD_DELAY by lazy { DAYS.toMillis(10) }\n}\n"
  },
  {
    "path": "common-test/.gitignore",
    "content": "/build"
  },
  {
    "path": "common-test/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply from: '../versions.gradle'\n\nandroid {\n    kotlinOptions { jvmTarget = \"17\" }\n    compileOptions {\n        coreLibraryDesugaringEnabled true\n        sourceCompatibility JavaVersion.VERSION_17\n        targetCompatibility JavaVersion.VERSION_17\n    }\n\n    compileSdkVersion versions.compileSdk\n\n    defaultConfig {\n        minSdkVersion versions.minSdk\n        targetSdkVersion versions.targetSdk\n        compileSdkVersion versions.compileSdk\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n        }\n    }\n\n    namespace 'com.michaldrabik.test_base'\n}\n\ndependencies {\n    implementation project(':common')\n\n    implementation libs.coroutinesTest\n    implementation libs.junit\n    coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "common-test/src/main/java/com/michaldrabik/common_test/MainDispatcherRule.kt",
    "content": "package com.michaldrabik.common_test\n\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.test.TestDispatcher\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.resetMain\nimport kotlinx.coroutines.test.setMain\nimport org.junit.rules.TestWatcher\nimport org.junit.runner.Description\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass MainDispatcherRule(\n  private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),\n) : TestWatcher() {\n\n  override fun starting(description: Description) {\n    super.starting(description)\n    Dispatchers.setMain(testDispatcher)\n  }\n\n  override fun finished(description: Description) {\n    super.finished(description)\n    Dispatchers.resetMain()\n  }\n}\n"
  },
  {
    "path": "common-test/src/main/java/com/michaldrabik/common_test/UnconfinedCoroutineDispatchers.kt",
    "content": "package com.michaldrabik.common_test\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass UnconfinedCoroutineDispatchers : CoroutineDispatchers {\n  override val Main = UnconfinedTestDispatcher()\n  override val IO = UnconfinedTestDispatcher()\n  override val Default = UnconfinedTestDispatcher()\n  override val Unconfined = UnconfinedTestDispatcher()\n}\n"
  },
  {
    "path": "crowdin.yml",
    "content": "files:\n  - source: /**/values/strings.xml\n    translation: /**/values-%two_letters_code%/%original_file_name%\n    translate_attributes: 0\n    content_segmentation: 0\n"
  },
  {
    "path": "data-local/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "data-local/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.data_local'\n}\n\ndependencies {\n  api libs.android.room.ktx\n  api libs.android.room.runtime\n  ksp libs.android.room.compiler\n\n  implementation libs.timber\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  testImplementation libs.junit\n  androidTestImplementation libs.truth\n  androidTestImplementation libs.android.test.runner\n  androidTestImplementation libs.android.test.truth\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "data-local/src/androidTest/java/com/michaldrabik/data_local/database/dao/BaseDaoTest.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Room\nimport androidx.test.platform.app.InstrumentationRegistry\nimport com.michaldrabik.data_local.database.AppDatabase\nimport org.junit.After\nimport org.junit.Before\n\nabstract class BaseDaoTest {\n\n  protected lateinit var database: AppDatabase\n\n  @Before\n  fun initDb() {\n    database = Room.inMemoryDatabaseBuilder(\n      InstrumentationRegistry.getInstrumentation().targetContext.applicationContext,\n      AppDatabase::class.java\n    ).build()\n  }\n\n  @After\n  fun closeDb() = database.close()\n}\n"
  },
  {
    "path": "data-local/src/androidTest/java/com/michaldrabik/data_local/database/dao/DiscoverShowsDaoTest.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.test.runner.AndroidJUnit4\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.database.dao.helpers.TestData\nimport com.michaldrabik.data_local.database.model.DiscoverShow\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Before\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n@RunWith(AndroidJUnit4::class)\nclass DiscoverShowsDaoTest : BaseDaoTest() {\n\n  private val show by lazy { TestData.createShow().copy(idTrakt = 11) }\n  private val discoverShow by lazy { DiscoverShow(1, show.idTrakt, 999, 999) }\n\n  @Before\n  fun setUp() {\n    runBlocking {\n      database.showsDao().upsert(listOf(show))\n    }\n  }\n\n  @Test\n  fun shouldInsertSingleEntity() {\n    runBlocking {\n      database.discoverShowsDao().upsert(listOf(discoverShow))\n      val result = database.discoverShowsDao().getAll()\n      assertThat(result).hasSize(1)\n      assertThat(result.first()).isEqualTo(discoverShow)\n    }\n  }\n\n  @Test\n  fun shouldInsertMultipleEntities() {\n    runBlocking {\n      val show1 = discoverShow.copy(id = 11)\n      val show2 = discoverShow.copy(id = 12)\n\n      database.discoverShowsDao().upsert(listOf(show1, show2))\n      val result = database.discoverShowsDao().getAll()\n      assertThat(result).containsExactlyElementsIn(listOf(show1, show2))\n    }\n  }\n\n  @Test\n  fun shouldUpdateEntitiesIfExist() {\n    runBlocking {\n      val show1 = discoverShow.copy(id = 99)\n      val show2 = discoverShow.copy(id = 23)\n\n      val show1Updated = show1.copy(updatedAt = 1000)\n      val show2Updated = show2.copy(updatedAt = 1000)\n\n      database.discoverShowsDao().upsert(listOf(show1, show2))\n      database.discoverShowsDao().upsert(listOf(show1Updated, show2Updated))\n      val result = database.discoverShowsDao().getAll()\n      assertThat(result).containsExactlyElementsIn(listOf(show1Updated, show2Updated))\n    }\n  }\n\n  @Test\n  fun shouldReplaceEntities() {\n    runBlocking {\n      val show1 = discoverShow.copy(id = 11)\n      val show2 = discoverShow.copy(id = 22)\n      val show3 = discoverShow.copy(id = 33)\n\n      database.discoverShowsDao().upsert(listOf(show1, show2, show3))\n      assertThat(database.discoverShowsDao().getAll()).containsExactly(show1, show2, show3)\n\n      val show4 = discoverShow.copy(id = 44)\n\n      database.discoverShowsDao().replace(listOf(show4))\n      assertThat(database.discoverShowsDao().getAll()).containsExactly(show4)\n    }\n  }\n\n  @Test\n  fun shouldReturnMostRecentEntity() {\n    runBlocking {\n      val show1 = discoverShow.copy(id = 11, createdAt = 2)\n      val show2 = discoverShow.copy(id = 22, createdAt = 3)\n      val show3 = discoverShow.copy(id = 33, createdAt = 1)\n\n      database.discoverShowsDao().upsert(listOf(show1, show2, show3))\n      val mostRecent = database.discoverShowsDao().getMostRecent()\n\n      assertThat(mostRecent).isEqualTo(show2)\n    }\n  }\n}\n"
  },
  {
    "path": "data-local/src/androidTest/java/com/michaldrabik/data_local/database/dao/EpisodesDaoTest.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.test.runner.AndroidJUnit4\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.database.dao.helpers.TestData\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n@RunWith(AndroidJUnit4::class)\nclass EpisodesDaoTest : BaseDaoTest() {\n\n  @Test\n  fun shouldStoreEpisodesForSeason() {\n    runBlocking {\n      val season = TestData.createSeason()\n      val episode1 = TestData.createEpisode().copy(idTrakt = 1)\n      val episode2 = TestData.createEpisode().copy(idTrakt = 2)\n\n      database.seasonsDao().upsert(listOf(season))\n      database.episodesDao().upsert(listOf(episode1, episode2))\n\n      val result = database.episodesDao().getAllForSeason(1)\n      assertThat(result).containsExactlyElementsIn(listOf(episode1, episode2))\n    }\n  }\n\n  @Test\n  fun shouldUpdateEpisodeIfAlreadyExists() {\n    runBlocking {\n      val season = TestData.createSeason()\n      val episode1 = TestData.createEpisode().copy(idTrakt = 1)\n      val episode2 = TestData.createEpisode().copy(idTrakt = 2)\n\n      database.seasonsDao().upsert(listOf(season))\n      database.episodesDao().upsert(listOf(episode1, episode2))\n\n      val result = database.episodesDao().getAllForSeason(1)\n      assertThat(result).containsExactlyElementsIn(listOf(episode1, episode2))\n\n      val updated = episode2.copy(title = \"Updated\")\n      database.episodesDao().upsert(listOf(episode1, updated))\n\n      val result2 = database.episodesDao().getAllForSeason(1)\n      assertThat(result2).containsExactlyElementsIn(listOf(episode1, updated))\n    }\n  }\n\n  @Test\n  fun shouldStoreEpisodesForShows() {\n    runBlocking {\n      val show1 = TestData.createShow().copy(idTrakt = 1)\n      val show2 = TestData.createShow().copy(idTrakt = 2)\n\n      val season1 = TestData.createSeason().copy(idShowTrakt = show1.idTrakt)\n      val season2 = TestData.createSeason().copy(idShowTrakt = show2.idTrakt)\n\n      val episode1 = TestData.createEpisode().copy(\n        idTrakt = 1,\n        idShowTrakt = show1.idTrakt,\n        idSeason = season1.idTrakt\n      )\n      val episode2 = TestData.createEpisode().copy(\n        idTrakt = 2,\n        idShowTrakt = show2.idTrakt,\n        idSeason = season2.idTrakt\n      )\n\n      database.showsDao().upsert(listOf(show1, show2))\n      database.seasonsDao().upsert(listOf(season1, season2))\n      database.episodesDao().upsert(listOf(episode1, episode2))\n\n      val result2 = database.episodesDao().getAllByShowId(2)\n      assertThat(result2).containsExactlyElementsIn(listOf(episode2))\n    }\n  }\n\n  @Test\n  fun shouldReturnWatchedIdsForShow() {\n    runBlocking {\n      val show = TestData.createShow().copy(idTrakt = 1)\n\n      val season1 = TestData.createSeason().copy(idShowTrakt = show.idTrakt)\n      val season2 = TestData.createSeason().copy(idShowTrakt = show.idTrakt)\n\n      val episode1 = TestData.createEpisode().copy(\n        idTrakt = 1,\n        idShowTrakt = show.idTrakt,\n        idSeason = season1.idTrakt,\n        isWatched = true\n      )\n      val episode2 = TestData.createEpisode().copy(\n        idTrakt = 2,\n        idShowTrakt = show.idTrakt,\n        idSeason = season2.idTrakt,\n        isWatched = false\n      )\n\n      database.showsDao().upsert(listOf(show))\n      database.seasonsDao().upsert(listOf(season1, season2))\n      database.episodesDao().upsert(listOf(episode1, episode2))\n\n      val result = database.episodesDao().getAllWatchedIdsForShows(listOf(show.idTrakt))\n      assertThat(result).containsExactlyElementsIn(listOf(episode1.idTrakt))\n    }\n  }\n\n  @Test\n  fun shouldDeleteAllUnwatchedForShow() {\n    runBlocking {\n      val show = TestData.createShow().copy(idTrakt = 1)\n\n      val season1 = TestData.createSeason().copy(idShowTrakt = show.idTrakt)\n      val season2 = TestData.createSeason().copy(idShowTrakt = show.idTrakt)\n\n      val episode1 = TestData.createEpisode().copy(\n        idTrakt = 1,\n        idShowTrakt = show.idTrakt,\n        idSeason = season1.idTrakt,\n        isWatched = true\n      )\n      val episode2 = TestData.createEpisode().copy(\n        idTrakt = 2,\n        idShowTrakt = show.idTrakt,\n        idSeason = season2.idTrakt,\n        isWatched = false\n      )\n\n      database.showsDao().upsert(listOf(show))\n      database.seasonsDao().upsert(listOf(season1, season2))\n      database.episodesDao().upsert(listOf(episode1, episode2))\n\n      val result = database.episodesDao().getAllByShowId(show.idTrakt)\n      assertThat(result).containsExactlyElementsIn(listOf(episode1, episode2))\n\n      database.episodesDao().deleteAllUnwatchedForShow(show.idTrakt)\n\n      val result2 = database.episodesDao().getAllByShowId(show.idTrakt)\n      assertThat(result2).containsExactlyElementsIn(listOf(episode1))\n    }\n  }\n}\n"
  },
  {
    "path": "data-local/src/androidTest/java/com/michaldrabik/data_local/database/dao/EpisodesSyncLogDaoTest.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.test.runner.AndroidJUnit4\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.database.model.EpisodesSyncLog\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n@RunWith(AndroidJUnit4::class)\nclass EpisodesSyncLogDaoTest : BaseDaoTest() {\n\n  @Test\n  fun shouldInsertAndSaveData() {\n    runBlocking {\n      val testLog = EpisodesSyncLog(1, 999)\n      database.episodesSyncLogDao().upsert(testLog)\n      val result = database.episodesSyncLogDao().getAll().first()\n      assertThat(result).isEqualTo(testLog)\n    }\n  }\n\n  @Test\n  fun shouldUpdateRowIfAlreadyExists() {\n    runBlocking {\n      val testLog = EpisodesSyncLog(1, 999)\n\n      database.episodesSyncLogDao().upsert(testLog)\n      val result = database.episodesSyncLogDao().getAll().first()\n      assertThat(result).isEqualTo(testLog)\n\n      val testLog2 = testLog.copy(syncedAt = 888)\n      database.episodesSyncLogDao().upsert(testLog2)\n      val result2 = database.episodesSyncLogDao().getAll().first()\n      assertThat(result2).isEqualTo(testLog2)\n    }\n  }\n}\n"
  },
  {
    "path": "data-local/src/androidTest/java/com/michaldrabik/data_local/database/dao/MyShowsDaoTest.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.test.runner.AndroidJUnit4\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.database.dao.helpers.TestData\nimport com.michaldrabik.data_local.database.model.MyShow\nimport com.michaldrabik.data_local.database.model.Show\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Before\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n@RunWith(AndroidJUnit4::class)\nclass MyShowsDaoTest : BaseDaoTest() {\n\n  private val shows = mutableListOf<Show>()\n\n  @Before\n  fun setUp() = runBlocking {\n    shows.add(TestData.createShow().copy(idTrakt = 1))\n    shows.add(TestData.createShow().copy(idTrakt = 2))\n    shows.add(TestData.createShow().copy(idTrakt = 3))\n\n    database.showsDao().upsert(shows)\n  }\n\n  @Test\n  fun shouldInsertAndStoreEntities() {\n    runBlocking {\n      val myShow = MyShow.fromTraktId(shows[0].idTrakt, 0, 0)\n\n      database.myShowsDao().insert(listOf(myShow))\n\n      val result = database.myShowsDao().getAll()\n      assertThat(result).containsExactlyElementsIn(listOf(shows[0]))\n    }\n  }\n\n  @Test\n  fun shouldReturnIdsOnly() {\n    runBlocking {\n      val myShow1 = MyShow.fromTraktId(shows[0].idTrakt, 0, 0)\n      val myShow2 = MyShow.fromTraktId(shows[1].idTrakt, 0, 0)\n\n      database.myShowsDao().insert(listOf(myShow1))\n      database.myShowsDao().insert(listOf(myShow2))\n\n      val result = database.myShowsDao().getAllTraktIds()\n      assertThat(result).containsExactlyElementsIn(listOf(shows[0].idTrakt, shows[1].idTrakt))\n    }\n  }\n\n  @Test\n  fun shouldReturnMostRecentAddedShows() {\n    runBlocking {\n      val myShow1 = MyShow.fromTraktId(shows[0].idTrakt, 0, 0)\n      val myShow2 = MyShow.fromTraktId(shows[1].idTrakt, 999, 999)\n\n      database.myShowsDao().insert(listOf(myShow1))\n      database.myShowsDao().insert(listOf(myShow2))\n\n      val result = database.myShowsDao().getAllRecent(10)\n      assertThat(result[0]).isEqualTo(shows[1])\n      assertThat(result[1]).isEqualTo(shows[0])\n    }\n  }\n\n  @Test\n  fun shouldReturnById() {\n    runBlocking {\n      val myShow1 = MyShow.fromTraktId(shows[0].idTrakt, 0, 0)\n      val myShow2 = MyShow.fromTraktId(shows[1].idTrakt, 0, 0)\n\n      database.myShowsDao().insert(listOf(myShow1))\n      database.myShowsDao().insert(listOf(myShow2))\n\n      val result = database.myShowsDao().getById(shows[1].idTrakt)\n      assertThat(result).isEqualTo(shows[1])\n    }\n  }\n\n  @Test\n  fun shouldDeleteByIdWithoutDeletingParent() {\n    runBlocking {\n      val myShow1 = MyShow.fromTraktId(shows[1].idTrakt, 0, 0)\n\n      val showsSize = shows.size\n      database.myShowsDao().insert(listOf(myShow1))\n      database.myShowsDao().deleteById(shows[1].idTrakt)\n      val result = database.myShowsDao().getById(shows[1].idTrakt)\n\n      assertThat(result).isNull()\n      assertThat(shows).hasSize(showsSize)\n    }\n  }\n}\n"
  },
  {
    "path": "data-local/src/androidTest/java/com/michaldrabik/data_local/database/dao/RecentSearchDaoTest.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.test.runner.AndroidJUnit4\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.database.model.RecentSearch\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Before\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n@RunWith(AndroidJUnit4::class)\nclass RecentSearchDaoTest : BaseDaoTest() {\n\n  private val items = mutableListOf<RecentSearch>()\n\n  @Before\n  fun setUp() {\n    val item = RecentSearch(1, \"Text1\", 1, 1)\n    val item2 = RecentSearch(2, \"Text2\", 2, 2)\n    val item3 = RecentSearch(3, \"Text3\", 3, 3)\n    items.addAll(listOf(item, item2, item3))\n  }\n\n  @Test\n  fun shouldInsertAndStoreEntities() {\n    runBlocking {\n      database.recentSearchDao().upsert(items)\n\n      val result = database.recentSearchDao().getAll(10)\n      assertThat(result).containsExactlyElementsIn(items)\n    }\n  }\n\n  @Test\n  fun shouldReturnMostRecentSearchesFirst() {\n    runBlocking {\n      database.recentSearchDao().upsert(items)\n\n      val result = database.recentSearchDao().getAll(10)\n      assertThat(result).containsExactlyElementsIn(items)\n      assertThat(result).isInOrder(compareByDescending<RecentSearch> { it.createdAt })\n    }\n  }\n\n  @Test\n  fun shouldReturnLimitedResults() {\n    runBlocking {\n      database.recentSearchDao().upsert(items)\n\n      val result = database.recentSearchDao().getAll(2)\n      assertThat(result).hasSize(2)\n    }\n  }\n}\n"
  },
  {
    "path": "data-local/src/androidTest/java/com/michaldrabik/data_local/database/dao/RelatedShowsDaoTest.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.test.runner.AndroidJUnit4\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.database.dao.helpers.TestData\nimport com.michaldrabik.data_local.database.model.RelatedShow\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n@RunWith(AndroidJUnit4::class)\nclass RelatedShowsDaoTest : BaseDaoTest() {\n\n  @Test\n  fun shouldInsertAndDeleteSingleEntity() {\n    runBlocking {\n      val show = TestData.createShow().copy(idTrakt = 99)\n      val relatedShow = RelatedShow(1, 1, 99, 999)\n\n      database.showsDao().upsert(listOf(show))\n      database.relatedShowsDao().insert(listOf(relatedShow))\n      val result = database.relatedShowsDao().getAll()\n      assertThat(result).hasSize(1)\n      assertThat(result.first()).isEqualTo(relatedShow)\n\n      database.relatedShowsDao().deleteById(99)\n      val result2 = database.relatedShowsDao().getAll()\n      assertThat(result2).isEmpty()\n      assertThat(database.showsDao().getById(99)).isEqualTo(show)\n    }\n  }\n\n  @Test\n  fun shouldReturnRelatedShowsForId() {\n    runBlocking {\n      val show1 = TestData.createShow().copy(idTrakt = 1, updatedAt = 100)\n      val show2 = TestData.createShow().copy(idTrakt = 2, updatedAt = 100)\n      val show3 = TestData.createShow().copy(idTrakt = 3, updatedAt = 100)\n\n      val relatedShow1 = RelatedShow(1, 2, 1, 200)\n      val relatedShow2 = RelatedShow(2, 3, 1, 200)\n      val relatedShow3 = RelatedShow(3, 1, 2, 200)\n\n      database.showsDao().upsert(listOf(show1, show2, show3))\n      database.relatedShowsDao().insert(listOf(relatedShow1, relatedShow2, relatedShow3))\n      val result = database.relatedShowsDao().getAllById(1)\n      assertThat(result).containsExactlyElementsIn(listOf(relatedShow1, relatedShow2))\n    }\n  }\n}\n"
  },
  {
    "path": "data-local/src/androidTest/java/com/michaldrabik/data_local/database/dao/SeasonsDaoTest.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.test.runner.AndroidJUnit4\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.database.dao.helpers.TestData\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n@RunWith(AndroidJUnit4::class)\nclass SeasonsDaoTest : BaseDaoTest() {\n\n  @Test\n  fun shouldInsertAndStoreSingleEntity() {\n    runBlocking {\n      val season = TestData.createSeason()\n\n      database.seasonsDao().upsert(listOf(season))\n      val result = database.seasonsDao().getAllByShowId(1)\n      assertThat(result).containsExactlyElementsIn(listOf(season))\n    }\n  }\n\n  @Test\n  fun shouldInsertAndStoreMultipleEntities() {\n    runBlocking {\n      val season1 = TestData.createSeason().copy(idTrakt = 1)\n      val season2 = TestData.createSeason().copy(idTrakt = 2)\n\n      database.seasonsDao().upsert(listOf(season1, season2))\n      val result = database.seasonsDao().getAllByShowId(1)\n      assertThat(result).containsExactlyElementsIn(listOf(season1, season2))\n    }\n  }\n\n  @Test\n  fun shouldReturnEntityById() {\n    runBlocking {\n      val season1 = TestData.createSeason().copy(idTrakt = 1)\n      val season2 = TestData.createSeason().copy(idTrakt = 2)\n\n      database.seasonsDao().upsert(listOf(season1, season2))\n      val result = database.seasonsDao().getById(2)\n      assertThat(result).isEqualTo(season2)\n    }\n  }\n\n  @Test\n  fun shouldReturnEntitiesByIds() {\n    runBlocking {\n      val season1 = TestData.createSeason().copy(idTrakt = 1)\n      val season2 = TestData.createSeason().copy(idTrakt = 2, idShowTrakt = 2)\n      val season3 = TestData.createSeason().copy(idTrakt = 3)\n\n      database.seasonsDao().upsert(listOf(season1, season2, season3))\n      val result = database.seasonsDao().getAllByShowId(1)\n      assertThat(result).containsExactlyElementsIn(listOf(season1, season3))\n    }\n  }\n\n  @Test\n  fun shouldOnlyReturnWatchedSeasons() {\n    runBlocking {\n      val season1 = TestData.createSeason().copy(idTrakt = 1)\n      val season2 = TestData.createSeason().copy(idTrakt = 2)\n      val season3 = TestData.createSeason().copy(idTrakt = 3, isWatched = true)\n\n      database.seasonsDao().upsert(listOf(season1, season2, season3))\n      val result = database.seasonsDao().getAllWatchedIdsForShows(listOf(1))\n      assertThat(result).hasSize(1)\n      assertThat(result[0]).isEqualTo(3)\n    }\n  }\n\n  @Test\n  fun shouldReturnNullIfNoEntity() {\n    runBlocking {\n      val season1 = TestData.createSeason()\n\n      database.seasonsDao().upsert(listOf(season1))\n      val result = database.seasonsDao().getById(2)\n      assertThat(result).isNull()\n    }\n  }\n}\n"
  },
  {
    "path": "data-local/src/androidTest/java/com/michaldrabik/data_local/database/dao/SettingsDaoTest.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.test.runner.AndroidJUnit4\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.database.dao.helpers.TestData\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n@RunWith(AndroidJUnit4::class)\nclass SettingsDaoTest : BaseDaoTest() {\n\n  @Test\n  fun shouldInsertAndSaveData() {\n    runBlocking {\n      val settings = TestData.createSettings()\n\n      database.settingsDao().upsert(settings)\n      val result = database.settingsDao().getAll()\n      assertThat(result).isEqualTo(settings)\n    }\n  }\n\n  @Test\n  fun shouldReturnNullIfNoEntity() {\n    runBlocking {\n      val settings = database.settingsDao().getAll()\n      assertThat(settings).isNull()\n    }\n  }\n\n  @Test\n  fun shouldUpdateRowIfAlreadyExists() {\n    runBlocking {\n      val settings = TestData.createSettings()\n\n      database.settingsDao().upsert(settings)\n      assertThat(database.settingsDao().getAll()).isEqualTo(settings)\n\n      val settings2 = settings.copy(myShowsEndedSortBy = \"sort\")\n      database.settingsDao().upsert(settings2)\n      assertThat(database.settingsDao().getAll()).isEqualTo(settings2)\n    }\n  }\n}\n"
  },
  {
    "path": "data-local/src/androidTest/java/com/michaldrabik/data_local/database/dao/ShowImagesDaoTest.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.test.runner.AndroidJUnit4\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.database.model.ShowImage\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Before\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n@RunWith(AndroidJUnit4::class)\nclass ShowImagesDaoTest : BaseDaoTest() {\n\n  private val image1 = ShowImage(1, 1, 1, \"poster\", \"show\", \"url\", \"thumbUrl\", \"tmdb\")\n  private val image2 = ShowImage(2, 2, 2, \"poster\", \"episode\", \"url\", \"thumbUrl\", \"tmdb\")\n  private val image3 = ShowImage(3, 1, 1, \"fanart\", \"show\", \"url\", \"thumbUrl\", \"tmdb\")\n  private val image4 = ShowImage(4, 2, 2, \"fanart\", \"episode\", \"url\", \"thumbUrl\", \"tmdb\")\n\n  @Before\n  fun setUp() = runBlocking {\n    database.showImagesDao().upsert(image1)\n    database.showImagesDao().upsert(image2)\n    database.showImagesDao().upsert(image3)\n    database.showImagesDao().upsert(image4)\n  }\n\n  @Test\n  fun shouldReturnImageByTypeForShow() = runBlocking {\n    val result = database.showImagesDao().getByShowId(1, \"poster\")\n    val result2 = database.showImagesDao().getByShowId(1, \"fanart\")\n\n    assertThat(result).isEqualTo(image1)\n    assertThat(result2).isEqualTo(image3)\n  }\n\n  @Test\n  fun shouldReturnImageByTypeForEpisode() = runBlocking {\n    val result = database.showImagesDao().getByEpisodeId(2, \"poster\")\n    val result2 = database.showImagesDao().getByEpisodeId(2, \"fanart\")\n\n    assertThat(result).isEqualTo(image2)\n    assertThat(result2).isEqualTo(image4)\n  }\n\n  @Test\n  fun shouldUpsertShowImage() = runBlocking {\n    val result = database.showImagesDao().getByShowId(10, \"fanart\")\n    assertThat(result).isNull()\n\n    val image = ShowImage(10, 10, 10, \"fanart\", \"show\", \"url\", \"thumbUrl\", \"tmdb\")\n    database.showImagesDao().insertShowImage(image)\n\n    val result2 = database.showImagesDao().getByShowId(10, \"fanart\")\n    assertThat(result2).isEqualTo(image)\n\n    val image2 = ShowImage(10, 10, 10, \"fanart\", \"show\", \"url2\", \"thumbUrl2\", \"tmdb\")\n    database.showImagesDao().insertShowImage(image2)\n\n    val result3 = database.showImagesDao().getByShowId(10, \"fanart\")\n    assertThat(result3).isEqualTo(image2)\n  }\n\n  @Test\n  fun shouldUpsertEpisodeImage() = runBlocking {\n    val result = database.showImagesDao().getByEpisodeId(10, \"fanart\")\n    assertThat(result).isNull()\n\n    val image = ShowImage(10, 10, 10, \"fanart\", \"episode\", \"url\", \"thumbUrl\", \"tmdb\")\n    database.showImagesDao().insertEpisodeImage(image)\n\n    val result2 = database.showImagesDao().getByEpisodeId(10, \"fanart\")\n    assertThat(result2).isEqualTo(image)\n\n    val image2 = ShowImage(10, 10, 10, \"fanart\", \"episode\", \"url2\", \"thumbUrl2\", \"tmdb\")\n    database.showImagesDao().insertEpisodeImage(image2)\n\n    val result3 = database.showImagesDao().getByEpisodeId(10, \"fanart\")\n    assertThat(result3).isEqualTo(image2)\n  }\n\n  @Test\n  fun shouldDeleteByShowId() = runBlocking {\n    assertThat(database.showImagesDao().getByShowId(1, \"poster\")).isEqualTo(image1)\n    assertThat(database.showImagesDao().getByShowId(1, \"fanart\")).isEqualTo(image3)\n\n    database.showImagesDao().deleteByShowId(1, \"poster\")\n    database.showImagesDao().deleteByShowId(1, \"fanart\")\n\n    assertThat(database.showImagesDao().getByShowId(1, \"poster\")).isNull()\n    assertThat(database.showImagesDao().getByShowId(1, \"fanart\")).isNull()\n\n    // Ensure nothing else is deleted\n    assertThat(database.showImagesDao().getByEpisodeId(2, \"poster\")).isEqualTo(image2)\n    assertThat(database.showImagesDao().getByEpisodeId(2, \"fanart\")).isEqualTo(image4)\n  }\n\n  @Test\n  fun shouldDeleteByEpisodeId() = runBlocking {\n    assertThat(database.showImagesDao().getByEpisodeId(2, \"poster\")).isEqualTo(image2)\n    assertThat(database.showImagesDao().getByEpisodeId(2, \"fanart\")).isEqualTo(image4)\n\n    database.showImagesDao().deleteByEpisodeId(2, \"poster\")\n    database.showImagesDao().deleteByEpisodeId(2, \"fanart\")\n\n    assertThat(database.showImagesDao().getByEpisodeId(2, \"poster\")).isNull()\n    assertThat(database.showImagesDao().getByEpisodeId(2, \"fanart\")).isNull()\n\n    // Ensure nothing else is deleted\n    assertThat(database.showImagesDao().getByShowId(1, \"poster\")).isEqualTo(image1)\n    assertThat(database.showImagesDao().getByShowId(1, \"fanart\")).isEqualTo(image3)\n  }\n}\n"
  },
  {
    "path": "data-local/src/androidTest/java/com/michaldrabik/data_local/database/dao/ShowsDaoTest.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.test.runner.AndroidJUnit4\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.database.dao.helpers.TestData\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n@RunWith(AndroidJUnit4::class)\nclass ShowsDaoTest : BaseDaoTest() {\n\n  @Test\n  fun shouldInsertAndStoreSingleEntity() {\n    runBlocking {\n      val show = TestData.createShow()\n\n      database.showsDao().upsert(listOf(show))\n      val result = database.showsDao().getAll()\n      assertThat(result).hasSize(1)\n      assertThat(result.first()).isEqualTo(show)\n    }\n  }\n\n  @Test\n  fun shouldInsertAndStoreMultipleEntities() {\n    runBlocking {\n      val show1 = TestData.createShow().copy(idTrakt = 1)\n      val show2 = TestData.createShow().copy(idTrakt = 2)\n\n      database.showsDao().upsert(listOf(show1, show2))\n      val result = database.showsDao().getAll()\n      assertThat(result).hasSize(2)\n      assertThat(result[0]).isEqualTo(show1)\n      assertThat(result[1]).isEqualTo(show2)\n    }\n  }\n\n  @Test\n  fun shouldReturnEntityById() {\n    runBlocking {\n      val show1 = TestData.createShow().copy(idTrakt = 1)\n      val show2 = TestData.createShow().copy(idTrakt = 2)\n\n      database.showsDao().upsert(listOf(show1, show2))\n      val result = database.showsDao().getById(2)\n      assertThat(result).isEqualTo(show2)\n    }\n  }\n\n  @Test\n  fun shouldReturnEntitiesByIds() {\n    runBlocking {\n      val show1 = TestData.createShow().copy(idTrakt = 1)\n      val show2 = TestData.createShow().copy(idTrakt = 2)\n      val show3 = TestData.createShow().copy(idTrakt = 3)\n\n      database.showsDao().upsert(listOf(show1, show2, show3))\n      val result = database.showsDao().getAll(listOf(1, 3))\n      assertThat(result).containsExactlyElementsIn(listOf(show1, show3))\n    }\n  }\n\n  @Test\n  fun shouldReturnNullIfNoEntity() {\n    runBlocking {\n      val show1 = TestData.createShow()\n\n      database.showsDao().upsert(listOf(show1))\n      val result = database.showsDao().getById(2)\n      assertThat(result).isNull()\n    }\n  }\n}\n"
  },
  {
    "path": "data-local/src/androidTest/java/com/michaldrabik/data_local/database/dao/UserDaoTest.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.test.runner.AndroidJUnit4\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.database.model.User\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n@RunWith(AndroidJUnit4::class)\nclass UserDaoTest : BaseDaoTest() {\n\n  @Test\n  fun shouldInsertAndSaveData() {\n    runBlocking {\n      val testUser = User(1, \"tvdbToken\", 999, \"test\", \"test\", 999, \"\", \"\", 999)\n      database.userDao().upsert(testUser)\n      val result = database.userDao().get()\n      assertThat(result).isEqualTo(testUser)\n    }\n  }\n\n  @Test\n  fun shouldUpdateRowIfAlreadyExists() {\n    runBlocking {\n      val testUser = User(1, \"tvdbToken\", 999, \"test\", \"test\", 999, \"\", \"\", 999)\n\n      database.userDao().upsert(testUser)\n      assertThat(database.userDao().get()).isEqualTo(testUser)\n\n      val testUser2 = testUser.copy(tvdbToken = \"otherToken\", tvdbTokenTimestamp = 888)\n      database.userDao().upsert(testUser2)\n      assertThat(database.userDao().get()).isEqualTo(testUser2)\n    }\n  }\n}\n"
  },
  {
    "path": "data-local/src/androidTest/java/com/michaldrabik/data_local/database/dao/WatchlistShowsDaoTest.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.test.runner.AndroidJUnit4\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.database.dao.helpers.TestData\nimport com.michaldrabik.data_local.database.model.WatchlistShow\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n@RunWith(AndroidJUnit4::class)\nclass WatchlistShowsDaoTest : BaseDaoTest() {\n\n  @Test\n  fun shouldInsertAndStoreSingleEntity() {\n    runBlocking {\n      val show = TestData.createShow()\n      val seeLaterShow = WatchlistShow.fromTraktId(show.idTrakt, 999)\n\n      database.showsDao().upsert(listOf(show))\n      database.watchlistShowsDao().insert(seeLaterShow)\n      val result = database.watchlistShowsDao().getAll()\n      assertThat(result).containsExactlyElementsIn(listOf(show.copy(updatedAt = 999, createdAt = 999)))\n    }\n  }\n\n  @Test\n  fun shouldInsertAndStoreSingleEntityById() {\n    runBlocking {\n      val show = TestData.createShow()\n      val show2 = TestData.createShow().copy(idTrakt = 2)\n      val seeLaterShow = WatchlistShow.fromTraktId(show.idTrakt, 999)\n      val seeLaterShow2 = WatchlistShow.fromTraktId(show2.idTrakt, 999)\n\n      database.showsDao().upsert(listOf(show, show2))\n      database.watchlistShowsDao().insert(seeLaterShow)\n      database.watchlistShowsDao().insert(seeLaterShow2)\n\n      val result = database.watchlistShowsDao().getById(2)\n      assertThat(result).isEqualTo(show2)\n    }\n  }\n\n  @Test\n  fun shouldDeleteSingleEntityById() {\n    runBlocking {\n      val show = TestData.createShow()\n      val show2 = TestData.createShow().copy(idTrakt = 2)\n      val seeLaterShow = WatchlistShow.fromTraktId(show.idTrakt, 999)\n      val seeLaterShow2 = WatchlistShow.fromTraktId(show2.idTrakt, 999)\n\n      database.showsDao().upsert(listOf(show, show2))\n      database.watchlistShowsDao().insert(seeLaterShow)\n      database.watchlistShowsDao().insert(seeLaterShow2)\n      database.watchlistShowsDao().deleteById(2)\n\n      val result = database.watchlistShowsDao().getAll()\n      assertThat(result).containsExactlyElementsIn(listOf(show.copy(updatedAt = 999, createdAt = 999)))\n    }\n  }\n}\n"
  },
  {
    "path": "data-local/src/androidTest/java/com/michaldrabik/data_local/database/dao/converters/DateConverterTest.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.data_local.database.dao.converters\n\nimport com.google.common.truth.Truth.assertThat\nimport androidx.test.runner.AndroidJUnit4\nimport com.michaldrabik.data_local.database.converters.DateConverter\nimport org.junit.Before\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport java.time.ZoneId\nimport java.time.ZonedDateTime\n\n@RunWith(AndroidJUnit4::class)\nclass DateConverterTest {\n\n  private val SUT by lazy { DateConverter() }\n\n  @Before\n  fun setUp() {\n  }\n\n  @Test\n  fun shouldConvertTimestampToDate() {\n    val date = SUT.stringToDate(1573120000000) // Thu Nov 07 2019 09:46:40\n    assertThat(date).isEqualTo(ZonedDateTime.of(2019, 11, 7, 9, 46, 40, 0, ZoneId.of(\"UTC\")))\n  }\n\n  @Test\n  fun shouldConvertDateToTimestamp() {\n    val timestamp = SUT.dateToString(ZonedDateTime.of(2019, 11, 7, 9, 46, 40, 0, ZoneId.of(\"UTC\")))\n    assertThat(timestamp).isEqualTo(1573120000000)\n  }\n}\n"
  },
  {
    "path": "data-local/src/androidTest/java/com/michaldrabik/data_local/database/dao/helpers/TestData.kt",
    "content": "package com.michaldrabik.data_local.database.dao.helpers\n\nimport com.michaldrabik.data_local.database.model.Episode\nimport com.michaldrabik.data_local.database.model.Season\nimport com.michaldrabik.data_local.database.model.Settings\nimport com.michaldrabik.data_local.database.model.Show\n\nobject TestData {\n\n  fun createShow() = Show(\n    1,\n    1,\n    1,\n    \"idImdb\",\n    \"idSlug\",\n    1,\n    \"Title\",\n    2000,\n    \"Overview\",\n    \"FirstAired\",\n    60,\n    \"AirtimeDay\",\n    \"AirtimeTime\",\n    \"AirtimeTimezone\",\n    \"Certification\",\n    \"Network\",\n    \"Country\",\n    \"Trailer\",\n    \"Homepage\",\n    \"Status\",\n    0F,\n    0L,\n    0L,\n    \"Genres\",\n    0,\n    0,\n    0\n  )\n\n  fun createSettings() = Settings(\n    1,\n    false,\n    true,\n    true,\n    0,\n    2,\n    \"\",\n    \"\",\n    \"\",\n    \"\",\n    false,\n    false,\n    false,\n    false,\n    false,\n    false,\n    false,\n    \"\",\n    true,\n    \"OFF\",\n    \"\",\n    \"\",\n    false,\n    false,\n    \"\",\n    \"\",\n    false,\n    false,\n    false,\n    \"\",\n    \"\",\n    \"\",\n    \"\",\n    \"\",\n    true,\n    true,\n    true,\n    true,\n    true,\n    \"\",\n    true\n  )\n\n  fun createEpisode() = Episode(1, 1, 1, 1, \"\", 1, 1, 1, 1, \"\", \"\", null, 0, 0F, 60, 0, false)\n\n  fun createSeason() = Season(1, 1, 1, \"\", \"\", null, 0, 0, 0f, false)\n}\n"
  },
  {
    "path": "data-local/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/LocalDataSource.kt",
    "content": "package com.michaldrabik.data_local\n\nimport com.michaldrabik.data_local.sources.ArchiveMoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.ArchiveShowsLocalDataSource\nimport com.michaldrabik.data_local.sources.CustomImagesLocalDataSource\nimport com.michaldrabik.data_local.sources.CustomListsItemsLocalDataSource\nimport com.michaldrabik.data_local.sources.CustomListsLocalDataSource\nimport com.michaldrabik.data_local.sources.DiscoverMoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.DiscoverShowsLocalDataSource\nimport com.michaldrabik.data_local.sources.EpisodeTranslationsLocalDataSource\nimport com.michaldrabik.data_local.sources.EpisodesLocalDataSource\nimport com.michaldrabik.data_local.sources.EpisodesSyncLogLocalDataSource\nimport com.michaldrabik.data_local.sources.MovieImagesLocalDataSource\nimport com.michaldrabik.data_local.sources.MovieRatingsLocalDataSource\nimport com.michaldrabik.data_local.sources.MovieStreamingsLocalDataSource\nimport com.michaldrabik.data_local.sources.MovieTranslationsLocalDataSource\nimport com.michaldrabik.data_local.sources.MoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.MoviesSyncLogLocalDataSource\nimport com.michaldrabik.data_local.sources.MyMoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.MyShowsLocalDataSource\nimport com.michaldrabik.data_local.sources.NewsLocalDataSource\nimport com.michaldrabik.data_local.sources.PeopleCreditsLocalDataSource\nimport com.michaldrabik.data_local.sources.PeopleImagesLocalDataSource\nimport com.michaldrabik.data_local.sources.PeopleLocalDataSource\nimport com.michaldrabik.data_local.sources.PeopleShowsMoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.RatingsLocalDataSource\nimport com.michaldrabik.data_local.sources.RecentSearchLocalDataSource\nimport com.michaldrabik.data_local.sources.RelatedMoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.RelatedShowsLocalDataSource\nimport com.michaldrabik.data_local.sources.SeasonsLocalDataSource\nimport com.michaldrabik.data_local.sources.SettingsLocalDataSource\nimport com.michaldrabik.data_local.sources.ShowImagesLocalDataSource\nimport com.michaldrabik.data_local.sources.ShowRatingsLocalDataSource\nimport com.michaldrabik.data_local.sources.ShowStreamingsLocalDataSource\nimport com.michaldrabik.data_local.sources.ShowTranslationsLocalDataSource\nimport com.michaldrabik.data_local.sources.ShowsLocalDataSource\nimport com.michaldrabik.data_local.sources.TraktSyncLogLocalDataSource\nimport com.michaldrabik.data_local.sources.TraktSyncQueueLocalDataSource\nimport com.michaldrabik.data_local.sources.TranslationsMoviesSyncLogLocalDataSource\nimport com.michaldrabik.data_local.sources.TranslationsShowsSyncLogLocalDataSource\nimport com.michaldrabik.data_local.sources.UserLocalDataSource\nimport com.michaldrabik.data_local.sources.WatchlistMoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.WatchlistShowsLocalDataSource\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * Provides local data sources access points.\n */\n// TODO Refactor. Split or remove this wrapper at all. Clients do not need to be exposed to everything.\ninterface LocalDataSource {\n  val archiveMovies: ArchiveMoviesLocalDataSource\n  val archiveShows: ArchiveShowsLocalDataSource\n  val customImages: CustomImagesLocalDataSource\n  val customLists: CustomListsLocalDataSource\n  val customListsItems: CustomListsItemsLocalDataSource\n  val discoverMovies: DiscoverMoviesLocalDataSource\n  val discoverShows: DiscoverShowsLocalDataSource\n  val episodes: EpisodesLocalDataSource\n  val episodesSyncLog: EpisodesSyncLogLocalDataSource\n  val episodesTranslations: EpisodeTranslationsLocalDataSource\n  val movieImages: MovieImagesLocalDataSource\n  val movieRatings: MovieRatingsLocalDataSource\n  val movieStreamings: MovieStreamingsLocalDataSource\n  val movieTranslations: MovieTranslationsLocalDataSource\n  val movies: MoviesLocalDataSource\n  val moviesSyncLog: MoviesSyncLogLocalDataSource\n  val myMovies: MyMoviesLocalDataSource\n  val myShows: MyShowsLocalDataSource\n  val news: NewsLocalDataSource\n  val people: PeopleLocalDataSource\n  val peopleCredits: PeopleCreditsLocalDataSource\n  val peopleImages: PeopleImagesLocalDataSource\n  val peopleShowsMovies: PeopleShowsMoviesLocalDataSource\n  val ratings: RatingsLocalDataSource\n  val recentSearch: RecentSearchLocalDataSource\n  val relatedMovies: RelatedMoviesLocalDataSource\n  val relatedShows: RelatedShowsLocalDataSource\n  val seasons: SeasonsLocalDataSource\n  val settings: SettingsLocalDataSource\n  val showImages: ShowImagesLocalDataSource\n  val showRatings: ShowRatingsLocalDataSource\n  val showStreamings: ShowStreamingsLocalDataSource\n  val showTranslations: ShowTranslationsLocalDataSource\n  val shows: ShowsLocalDataSource\n  val traktSyncLog: TraktSyncLogLocalDataSource\n  val traktSyncQueue: TraktSyncQueueLocalDataSource\n  val translationsMoviesSyncLog: TranslationsMoviesSyncLogLocalDataSource\n  val translationsShowsSyncLog: TranslationsShowsSyncLogLocalDataSource\n  val user: UserLocalDataSource\n  val watchlistMovies: WatchlistMoviesLocalDataSource\n  val watchlistShows: WatchlistShowsLocalDataSource\n}\n\n@Singleton\ninternal class MainLocalDataSource @Inject constructor(\n  override val archiveMovies: ArchiveMoviesLocalDataSource,\n  override val archiveShows: ArchiveShowsLocalDataSource,\n  override val customImages: CustomImagesLocalDataSource,\n  override val customLists: CustomListsLocalDataSource,\n  override val customListsItems: CustomListsItemsLocalDataSource,\n  override val discoverMovies: DiscoverMoviesLocalDataSource,\n  override val discoverShows: DiscoverShowsLocalDataSource,\n  override val episodes: EpisodesLocalDataSource,\n  override val episodesSyncLog: EpisodesSyncLogLocalDataSource,\n  override val episodesTranslations: EpisodeTranslationsLocalDataSource,\n  override val movieImages: MovieImagesLocalDataSource,\n  override val movieRatings: MovieRatingsLocalDataSource,\n  override val movieStreamings: MovieStreamingsLocalDataSource,\n  override val movieTranslations: MovieTranslationsLocalDataSource,\n  override val movies: MoviesLocalDataSource,\n  override val moviesSyncLog: MoviesSyncLogLocalDataSource,\n  override val myMovies: MyMoviesLocalDataSource,\n  override val myShows: MyShowsLocalDataSource,\n  override val news: NewsLocalDataSource,\n  override val people: PeopleLocalDataSource,\n  override val peopleCredits: PeopleCreditsLocalDataSource,\n  override val peopleImages: PeopleImagesLocalDataSource,\n  override val peopleShowsMovies: PeopleShowsMoviesLocalDataSource,\n  override val ratings: RatingsLocalDataSource,\n  override val recentSearch: RecentSearchLocalDataSource,\n  override val relatedMovies: RelatedMoviesLocalDataSource,\n  override val relatedShows: RelatedShowsLocalDataSource,\n  override val seasons: SeasonsLocalDataSource,\n  override val settings: SettingsLocalDataSource,\n  override val showImages: ShowImagesLocalDataSource,\n  override val showRatings: ShowRatingsLocalDataSource,\n  override val showStreamings: ShowStreamingsLocalDataSource,\n  override val showTranslations: ShowTranslationsLocalDataSource,\n  override val shows: ShowsLocalDataSource,\n  override val traktSyncLog: TraktSyncLogLocalDataSource,\n  override val traktSyncQueue: TraktSyncQueueLocalDataSource,\n  override val translationsMoviesSyncLog: TranslationsMoviesSyncLogLocalDataSource,\n  override val translationsShowsSyncLog: TranslationsShowsSyncLogLocalDataSource,\n  override val user: UserLocalDataSource,\n  override val watchlistMovies: WatchlistMoviesLocalDataSource,\n  override val watchlistShows: WatchlistShowsLocalDataSource\n) : LocalDataSource\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/AppDatabase.kt",
    "content": "package com.michaldrabik.data_local.database\n\nimport androidx.room.Database\nimport androidx.room.RoomDatabase\nimport com.michaldrabik.data_local.database.dao.ArchiveMoviesDao\nimport com.michaldrabik.data_local.database.dao.ArchiveShowsDao\nimport com.michaldrabik.data_local.database.dao.CustomImagesDao\nimport com.michaldrabik.data_local.database.dao.CustomListsDao\nimport com.michaldrabik.data_local.database.dao.CustomListsItemsDao\nimport com.michaldrabik.data_local.database.dao.DiscoverMoviesDao\nimport com.michaldrabik.data_local.database.dao.DiscoverShowsDao\nimport com.michaldrabik.data_local.database.dao.EpisodeTranslationsDao\nimport com.michaldrabik.data_local.database.dao.EpisodesDao\nimport com.michaldrabik.data_local.database.dao.EpisodesSyncLogDao\nimport com.michaldrabik.data_local.database.dao.MovieCollectionsDao\nimport com.michaldrabik.data_local.database.dao.MovieCollectionsItemsDao\nimport com.michaldrabik.data_local.database.dao.MovieImagesDao\nimport com.michaldrabik.data_local.database.dao.MovieRatingsDao\nimport com.michaldrabik.data_local.database.dao.MovieStreamingsDao\nimport com.michaldrabik.data_local.database.dao.MovieTranslationsDao\nimport com.michaldrabik.data_local.database.dao.MoviesDao\nimport com.michaldrabik.data_local.database.dao.MoviesSyncLogDao\nimport com.michaldrabik.data_local.database.dao.MyMoviesDao\nimport com.michaldrabik.data_local.database.dao.MyShowsDao\nimport com.michaldrabik.data_local.database.dao.NewsDao\nimport com.michaldrabik.data_local.database.dao.PeopleCreditsDao\nimport com.michaldrabik.data_local.database.dao.PeopleDao\nimport com.michaldrabik.data_local.database.dao.PeopleImagesDao\nimport com.michaldrabik.data_local.database.dao.PeopleShowsMoviesDao\nimport com.michaldrabik.data_local.database.dao.RatingsDao\nimport com.michaldrabik.data_local.database.dao.RecentSearchDao\nimport com.michaldrabik.data_local.database.dao.RelatedMoviesDao\nimport com.michaldrabik.data_local.database.dao.RelatedShowsDao\nimport com.michaldrabik.data_local.database.dao.SeasonsDao\nimport com.michaldrabik.data_local.database.dao.SettingsDao\nimport com.michaldrabik.data_local.database.dao.ShowImagesDao\nimport com.michaldrabik.data_local.database.dao.ShowRatingsDao\nimport com.michaldrabik.data_local.database.dao.ShowStreamingsDao\nimport com.michaldrabik.data_local.database.dao.ShowTranslationsDao\nimport com.michaldrabik.data_local.database.dao.ShowsDao\nimport com.michaldrabik.data_local.database.dao.TraktSyncLogDao\nimport com.michaldrabik.data_local.database.dao.TraktSyncQueueDao\nimport com.michaldrabik.data_local.database.dao.TranslationsMoviesSyncLogDao\nimport com.michaldrabik.data_local.database.dao.TranslationsSyncLogDao\nimport com.michaldrabik.data_local.database.dao.UserDao\nimport com.michaldrabik.data_local.database.dao.WatchlistMoviesDao\nimport com.michaldrabik.data_local.database.dao.WatchlistShowsDao\nimport com.michaldrabik.data_local.database.migrations.DATABASE_VERSION\nimport com.michaldrabik.data_local.database.model.ArchiveMovie\nimport com.michaldrabik.data_local.database.model.ArchiveShow\nimport com.michaldrabik.data_local.database.model.CustomImage\nimport com.michaldrabik.data_local.database.model.CustomList\nimport com.michaldrabik.data_local.database.model.CustomListItem\nimport com.michaldrabik.data_local.database.model.DiscoverMovie\nimport com.michaldrabik.data_local.database.model.DiscoverShow\nimport com.michaldrabik.data_local.database.model.Episode\nimport com.michaldrabik.data_local.database.model.EpisodeTranslation\nimport com.michaldrabik.data_local.database.model.EpisodesSyncLog\nimport com.michaldrabik.data_local.database.model.Movie\nimport com.michaldrabik.data_local.database.model.MovieCollection\nimport com.michaldrabik.data_local.database.model.MovieCollectionItem\nimport com.michaldrabik.data_local.database.model.MovieImage\nimport com.michaldrabik.data_local.database.model.MovieRatings\nimport com.michaldrabik.data_local.database.model.MovieStreaming\nimport com.michaldrabik.data_local.database.model.MovieTranslation\nimport com.michaldrabik.data_local.database.model.MoviesSyncLog\nimport com.michaldrabik.data_local.database.model.MyMovie\nimport com.michaldrabik.data_local.database.model.MyShow\nimport com.michaldrabik.data_local.database.model.News\nimport com.michaldrabik.data_local.database.model.Person\nimport com.michaldrabik.data_local.database.model.PersonCredits\nimport com.michaldrabik.data_local.database.model.PersonImage\nimport com.michaldrabik.data_local.database.model.PersonShowMovie\nimport com.michaldrabik.data_local.database.model.Rating\nimport com.michaldrabik.data_local.database.model.RecentSearch\nimport com.michaldrabik.data_local.database.model.RelatedMovie\nimport com.michaldrabik.data_local.database.model.RelatedShow\nimport com.michaldrabik.data_local.database.model.Season\nimport com.michaldrabik.data_local.database.model.Settings\nimport com.michaldrabik.data_local.database.model.Show\nimport com.michaldrabik.data_local.database.model.ShowImage\nimport com.michaldrabik.data_local.database.model.ShowRatings\nimport com.michaldrabik.data_local.database.model.ShowStreaming\nimport com.michaldrabik.data_local.database.model.ShowTranslation\nimport com.michaldrabik.data_local.database.model.TraktSyncLog\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue\nimport com.michaldrabik.data_local.database.model.TranslationsMoviesSyncLog\nimport com.michaldrabik.data_local.database.model.TranslationsSyncLog\nimport com.michaldrabik.data_local.database.model.User\nimport com.michaldrabik.data_local.database.model.WatchlistMovie\nimport com.michaldrabik.data_local.database.model.WatchlistShow\n\n@Database(\n  version = DATABASE_VERSION,\n  entities = [\n    Show::class,\n    Movie::class,\n    DiscoverShow::class,\n    DiscoverMovie::class,\n    MyShow::class,\n    MyMovie::class,\n    WatchlistShow::class,\n    WatchlistMovie::class,\n    ArchiveShow::class,\n    ArchiveMovie::class,\n    RelatedShow::class,\n    RelatedMovie::class,\n    ShowImage::class,\n    MovieImage::class,\n    User::class,\n    Season::class,\n    Person::class,\n    PersonShowMovie::class,\n    PersonCredits::class,\n    PersonImage::class,\n    Episode::class,\n    Settings::class,\n    RecentSearch::class,\n    EpisodesSyncLog::class,\n    MoviesSyncLog::class,\n    TranslationsSyncLog::class,\n    TranslationsMoviesSyncLog::class,\n    TraktSyncQueue::class,\n    TraktSyncLog::class,\n    ShowTranslation::class,\n    MovieTranslation::class,\n    EpisodeTranslation::class,\n    CustomImage::class,\n    CustomList::class,\n    CustomListItem::class,\n    News::class,\n    Rating::class,\n    ShowRatings::class,\n    MovieRatings::class,\n    ShowStreaming::class,\n    MovieStreaming::class,\n    MovieCollection::class,\n    MovieCollectionItem::class,\n  ],\n  exportSchema = false\n)\nabstract class AppDatabase : RoomDatabase() {\n\n  abstract fun showsDao(): ShowsDao\n\n  abstract fun moviesDao(): MoviesDao\n\n  abstract fun discoverShowsDao(): DiscoverShowsDao\n\n  abstract fun discoverMoviesDao(): DiscoverMoviesDao\n\n  abstract fun myShowsDao(): MyShowsDao\n\n  abstract fun myMoviesDao(): MyMoviesDao\n\n  abstract fun watchlistShowsDao(): WatchlistShowsDao\n\n  abstract fun watchlistMoviesDao(): WatchlistMoviesDao\n\n  abstract fun archiveShowsDao(): ArchiveShowsDao\n\n  abstract fun archiveMoviesDao(): ArchiveMoviesDao\n\n  abstract fun relatedShowsDao(): RelatedShowsDao\n\n  abstract fun relatedMoviesDao(): RelatedMoviesDao\n\n  abstract fun showImagesDao(): ShowImagesDao\n\n  abstract fun movieImagesDao(): MovieImagesDao\n\n  abstract fun customImagesDao(): CustomImagesDao\n\n  abstract fun userDao(): UserDao\n\n  abstract fun recentSearchDao(): RecentSearchDao\n\n  abstract fun episodesDao(): EpisodesDao\n\n  abstract fun seasonsDao(): SeasonsDao\n\n  abstract fun peopleDao(): PeopleDao\n\n  abstract fun peopleShowsMoviesDao(): PeopleShowsMoviesDao\n\n  abstract fun peopleCreditsDao(): PeopleCreditsDao\n\n  abstract fun peopleImagesDao(): PeopleImagesDao\n\n  abstract fun settingsDao(): SettingsDao\n\n  abstract fun traktSyncLogDao(): TraktSyncLogDao\n\n  abstract fun moviesSyncLogDao(): MoviesSyncLogDao\n\n  abstract fun episodesSyncLogDao(): EpisodesSyncLogDao\n\n  abstract fun translationsSyncLogDao(): TranslationsSyncLogDao\n\n  abstract fun translationsMoviesSyncLogDao(): TranslationsMoviesSyncLogDao\n\n  abstract fun traktSyncQueueDao(): TraktSyncQueueDao\n\n  abstract fun showTranslationsDao(): ShowTranslationsDao\n\n  abstract fun movieTranslationsDao(): MovieTranslationsDao\n\n  abstract fun ratingsDao(): RatingsDao\n\n  abstract fun showRatingsDao(): ShowRatingsDao\n\n  abstract fun movieRatingsDao(): MovieRatingsDao\n\n  abstract fun showStreamingsDao(): ShowStreamingsDao\n\n  abstract fun movieStreamingsDao(): MovieStreamingsDao\n\n  abstract fun movieCollectionsDao(): MovieCollectionsDao\n\n  abstract fun movieCollectionsItemsDao(): MovieCollectionsItemsDao\n\n  abstract fun episodeTranslationsDao(): EpisodeTranslationsDao\n\n  abstract fun customListsDao(): CustomListsDao\n\n  abstract fun customListsItemsDao(): CustomListsItemsDao\n\n  abstract fun newsDao(): NewsDao\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/converters/DateConverter.kt",
    "content": "package com.michaldrabik.data_local.database.converters\n\nimport androidx.room.TypeConverter\nimport java.time.Instant\nimport java.time.ZoneId\nimport java.time.ZonedDateTime\n\nclass DateConverter {\n\n  @TypeConverter\n  fun stringToDate(value: Long?) =\n    value?.let { ZonedDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneId.of(\"UTC\")) }\n\n  @TypeConverter\n  fun dateToString(date: ZonedDateTime?) =\n    date?.toInstant()?.toEpochMilli()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/ArchiveMoviesDao.kt",
    "content": "// ktlint-disable max-line-length\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.ArchiveMovie\nimport com.michaldrabik.data_local.database.model.Movie\nimport com.michaldrabik.data_local.sources.ArchiveMoviesLocalDataSource\n\n@Dao\ninterface ArchiveMoviesDao : ArchiveMoviesLocalDataSource {\n\n  @Query(\"SELECT movies.*, movies_archive.created_at AS created_at, movies_archive.updated_at AS updated_at FROM movies INNER JOIN movies_archive USING(id_trakt)\")\n  override suspend fun getAll(): List<Movie>\n\n  @Query(\"SELECT movies.*, movies_archive.created_at AS created_at, movies_archive.updated_at AS updated_at FROM movies INNER JOIN movies_archive USING(id_trakt) WHERE id_trakt IN (:ids)\")\n  override suspend fun getAll(ids: List<Long>): List<Movie>\n\n  @Query(\"SELECT movies.id_trakt FROM movies INNER JOIN movies_archive USING(id_trakt)\")\n  override suspend fun getAllTraktIds(): List<Long>\n\n  @Query(\"SELECT movies.* FROM movies INNER JOIN movies_archive USING(id_trakt) WHERE id_trakt == :traktId\")\n  override suspend fun getById(traktId: Long): Movie?\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun insert(movie: ArchiveMovie)\n\n  @Query(\"DELETE FROM movies_archive WHERE id_trakt == :traktId\")\n  override suspend fun deleteById(traktId: Long)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/ArchiveShowsDao.kt",
    "content": "// ktlint-disable max-line-length\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.ArchiveShow\nimport com.michaldrabik.data_local.database.model.Show\nimport com.michaldrabik.data_local.sources.ArchiveShowsLocalDataSource\n\n@Dao\ninterface ArchiveShowsDao : ArchiveShowsLocalDataSource {\n\n  @Query(\"SELECT shows.*, shows_archive.created_at AS created_at, shows_archive.updated_at AS updated_at FROM shows INNER JOIN shows_archive USING(id_trakt)\")\n  override suspend fun getAll(): List<Show>\n\n  @Query(\"SELECT shows.*, shows_archive.created_at AS created_at, shows_archive.updated_at AS updated_at FROM shows INNER JOIN shows_archive USING(id_trakt) WHERE id_trakt IN (:ids)\")\n  override suspend fun getAll(ids: List<Long>): List<Show>\n\n  @Query(\"SELECT shows.id_trakt FROM shows INNER JOIN shows_archive USING(id_trakt)\")\n  override suspend fun getAllTraktIds(): List<Long>\n\n  @Query(\"SELECT shows.* FROM shows INNER JOIN shows_archive USING(id_trakt) WHERE id_trakt == :traktId\")\n  override suspend fun getById(traktId: Long): Show?\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun insert(show: ArchiveShow)\n\n  @Query(\"DELETE FROM shows_archive WHERE id_trakt == :traktId\")\n  override suspend fun deleteById(traktId: Long)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/BaseDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Delete\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Update\n\ninterface BaseDao<T> {\n\n  @Insert(onConflict = OnConflictStrategy.IGNORE)\n  suspend fun insert(items: List<T>): List<Long>\n\n  @Update(onConflict = OnConflictStrategy.REPLACE)\n  suspend fun update(items: List<T>)\n\n  @Delete\n  suspend fun delete(items: List<T>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/CustomImagesDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.CustomImage\nimport com.michaldrabik.data_local.sources.CustomImagesLocalDataSource\n\n@Dao\ninterface CustomImagesDao : CustomImagesLocalDataSource {\n\n  @Query(\"SELECT * FROM custom_images WHERE id_trakt = :traktId AND type = :type AND family = :family\")\n  override suspend fun getById(traktId: Long, family: String, type: String): CustomImage?\n\n  @Query(\"DELETE FROM custom_images WHERE id_trakt = :traktId AND type = :type AND family = :family\")\n  override suspend fun deleteById(traktId: Long, family: String, type: String)\n\n  @Transaction\n  override suspend fun insertImage(image: CustomImage) {\n    val localImage = getById(image.idTrakt, image.family, image.type)\n    if (localImage != null) {\n      val updated = image.copy(id = localImage.id)\n      upsert(updated)\n      return\n    }\n    upsert(image)\n  }\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun upsert(image: CustomImage)\n\n  @Query(\"DELETE FROM custom_images\")\n  override suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/CustomListsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Update\nimport com.michaldrabik.data_local.database.model.CustomList\nimport com.michaldrabik.data_local.sources.CustomListsLocalDataSource\n\n@Dao\ninterface CustomListsDao : CustomListsLocalDataSource {\n\n  @Insert(onConflict = OnConflictStrategy.IGNORE)\n  override suspend fun insert(items: List<CustomList>): List<Long>\n\n  @Update(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun update(items: List<CustomList>)\n\n  @Query(\"SELECT * FROM custom_lists ORDER BY created_at DESC\")\n  override suspend fun getAll(): List<CustomList>\n\n  @Query(\"SELECT * FROM custom_lists WHERE id == :id\")\n  override suspend fun getById(id: Long): CustomList?\n\n  @Query(\"UPDATE custom_lists SET id_trakt = :idTrakt, id_slug = :idSlug, updated_at = :timestamp WHERE id == :id\")\n  override suspend fun updateTraktId(id: Long, idTrakt: Long, idSlug: String, timestamp: Long)\n\n  @Query(\"UPDATE custom_lists SET updated_at = :timestamp WHERE id == :id\")\n  override suspend fun updateTimestamp(id: Long, timestamp: Long)\n\n  @Query(\"UPDATE custom_lists SET sort_by_local = :sortBy, sort_how_local = :sortHow, updated_at = :timestamp WHERE id == :id\")\n  override suspend fun updateSortByLocal(id: Long, sortBy: String, sortHow: String, timestamp: Long)\n\n  @Query(\"UPDATE custom_lists SET filter_type_local = :filterType, updated_at = :timestamp WHERE id == :id\")\n  override suspend fun updateFilterTypeLocal(id: Long, filterType: String, timestamp: Long)\n\n  @Query(\"DELETE FROM custom_lists WHERE id == :id\")\n  override suspend fun deleteById(id: Long)\n\n  @Query(\"DELETE FROM custom_lists\")\n  override suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/CustomListsItemsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport androidx.room.Update\nimport com.michaldrabik.data_local.database.model.CustomListItem\nimport com.michaldrabik.data_local.sources.CustomListsItemsLocalDataSource\n\n@Dao\ninterface CustomListsItemsDao : CustomListsItemsLocalDataSource {\n\n  @Insert(onConflict = OnConflictStrategy.IGNORE)\n  suspend fun insert(items: List<CustomListItem>): List<Long>\n\n  @Update(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun update(items: List<CustomListItem>)\n\n  @Query(\"SELECT id_list FROM custom_list_item WHERE id_trakt = :idTrakt AND type = :type\")\n  override suspend fun getListsForItem(idTrakt: Long, type: String): List<Long>\n\n  @Query(\"SELECT * FROM custom_list_item WHERE id_list = :idList AND id_trakt = :idTrakt AND type = :type\")\n  override suspend fun getByIdTrakt(idList: Long, idTrakt: Long, type: String): CustomListItem?\n\n  @Query(\"SELECT * FROM custom_list_item WHERE id_list = :idList ORDER BY rank ASC\")\n  override suspend fun getItemsById(idList: Long): List<CustomListItem>\n\n  @Query(\"SELECT * FROM custom_list_item WHERE id_list = :idList ORDER BY rank ASC LIMIT :limit\")\n  override suspend fun getItemsForListImages(idList: Long, limit: Int): List<CustomListItem>\n\n  @Query(\"SELECT rank FROM custom_list_item WHERE id_list = :idList ORDER BY rank DESC LIMIT 1\")\n  override suspend fun getRankForList(idList: Long): Long?\n\n  @Transaction\n  override suspend fun insertItem(item: CustomListItem) {\n    val localItem = getByIdTrakt(item.idList, item.idTrakt, item.type)\n    if (localItem != null) return\n    val rank = getRankForList(item.idList) ?: 0L\n    val rankedItem = item.copy(rank = rank + 1L)\n    insert(listOf(rankedItem))\n  }\n\n  @Query(\"DELETE FROM custom_list_item WHERE id_list = :idList AND id_trakt == :idTrakt AND type = :type\")\n  override suspend fun deleteItem(idList: Long, idTrakt: Long, type: String)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/DiscoverMoviesDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.DiscoverMovie\nimport com.michaldrabik.data_local.sources.DiscoverMoviesLocalDataSource\n\n@Dao\ninterface DiscoverMoviesDao : DiscoverMoviesLocalDataSource {\n\n  @Query(\"SELECT * FROM movies_discover ORDER BY id\")\n  override suspend fun getAll(): List<DiscoverMovie>\n\n  @Query(\"SELECT * from movies_discover ORDER BY created_at DESC LIMIT 1\")\n  override suspend fun getMostRecent(): DiscoverMovie?\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun upsert(movies: List<DiscoverMovie>)\n\n  @Query(\"DELETE FROM movies_discover\")\n  override suspend fun deleteAll()\n\n  @Transaction\n  override suspend fun replace(movies: List<DiscoverMovie>) {\n    deleteAll()\n    upsert(movies)\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/DiscoverShowsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.DiscoverShow\nimport com.michaldrabik.data_local.sources.DiscoverShowsLocalDataSource\n\n@Dao\ninterface DiscoverShowsDao : DiscoverShowsLocalDataSource {\n\n  @Query(\"SELECT * FROM shows_discover ORDER BY id\")\n  override suspend fun getAll(): List<DiscoverShow>\n\n  @Query(\"SELECT * from shows_discover ORDER BY created_at DESC LIMIT 1\")\n  override suspend fun getMostRecent(): DiscoverShow?\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun upsert(shows: List<DiscoverShow>)\n\n  @Query(\"DELETE FROM shows_discover\")\n  override suspend fun deleteAll()\n\n  @Transaction\n  override suspend fun replace(shows: List<DiscoverShow>) {\n    deleteAll()\n    upsert(shows)\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/EpisodeTranslationsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.EpisodeTranslation\nimport com.michaldrabik.data_local.sources.EpisodeTranslationsLocalDataSource\n\n@Dao\ninterface EpisodeTranslationsDao : BaseDao<EpisodeTranslation>, EpisodeTranslationsLocalDataSource {\n\n  @Query(\"SELECT * FROM episodes_translations WHERE id_trakt == :traktEpisodeId AND id_trakt_show == :traktShowId AND language == :language\")\n  override suspend fun getById(traktEpisodeId: Long, traktShowId: Long, language: String): EpisodeTranslation?\n\n  @Query(\"SELECT * FROM episodes_translations WHERE id_trakt IN (:traktEpisodeIds) AND id_trakt_show == :traktShowId AND language == :language\")\n  override suspend fun getByIds(traktEpisodeIds: List<Long>, traktShowId: Long, language: String): List<EpisodeTranslation>\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun insertSingle(translation: EpisodeTranslation)\n\n  @Query(\"DELETE FROM episodes_translations WHERE language IN (:languages)\")\n  override suspend fun deleteByLanguage(languages: List<String>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/EpisodesDao.kt",
    "content": "// ktlint-disable max-line-length\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Delete\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy.Companion.REPLACE\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.Episode\nimport com.michaldrabik.data_local.sources.EpisodesLocalDataSource\n\n@Dao\ninterface EpisodesDao : EpisodesLocalDataSource {\n\n  @Insert(onConflict = REPLACE)\n  override suspend fun upsert(episodes: List<Episode>)\n\n  @Transaction\n  override suspend fun upsertChunked(items: List<Episode>) {\n    val chunks = items.chunked(500)\n    chunks.forEach { chunk -> upsert(chunk) }\n  }\n\n  @Query(\"SELECT EXISTS(SELECT 1 FROM episodes WHERE id_show_trakt = :showTraktId AND id_trakt = :episodeTraktId AND is_watched = 1)\")\n  override suspend fun isEpisodeWatched(showTraktId: Long, episodeTraktId: Long): Boolean\n\n  @Query(\"SELECT * FROM episodes WHERE id_season = :seasonTraktId\")\n  override suspend fun getAllForSeason(seasonTraktId: Long): List<Episode>\n\n  @Query(\"SELECT * FROM episodes WHERE id_show_trakt = :showTraktId\")\n  override suspend fun getAllByShowId(showTraktId: Long): List<Episode>\n\n  @Query(\"SELECT * FROM episodes WHERE id_show_trakt = :showTraktId AND season_number = :seasonNumber\")\n  override suspend fun getAllByShowId(showTraktId: Long, seasonNumber: Int): List<Episode>\n\n  @Transaction\n  override suspend fun getAllByShowsIds(showTraktIds: List<Long>): List<Episode> {\n    val result = mutableListOf<Episode>()\n    val chunks = showTraktIds.chunked(50)\n    chunks.forEach { chunk ->\n      result += getAllByShowsIdsChunk(chunk)\n    }\n    return result\n  }\n\n  @Transaction\n  @Query(\"SELECT * FROM episodes WHERE id_show_trakt IN (:showTraktIds)\")\n  override suspend fun getAllByShowsIdsChunk(showTraktIds: List<Long>): List<Episode>\n\n  @Query(\"SELECT * from episodes where id_show_trakt = :showTraktId AND is_watched = 0 AND season_number != 0 AND first_aired <= :toTime ORDER BY season_number ASC, episode_number ASC LIMIT 1\")\n  override suspend fun getFirstUnwatched(showTraktId: Long, toTime: Long): Episode?\n\n  @Query(\"SELECT * from episodes where id_show_trakt = :showTraktId AND is_watched = 0 AND season_number != 0 AND first_aired > :fromTime AND first_aired <= :toTime ORDER BY season_number ASC, episode_number ASC LIMIT 1\")\n  override suspend fun getFirstUnwatched(showTraktId: Long, fromTime: Long, toTime: Long): Episode?\n\n  @Query(\n    \"SELECT * from episodes where id_show_trakt = :showTraktId \" +\n      \"AND is_watched = 0 \" +\n      \"AND season_number != 0 \" +\n      \"AND ((season_number * 10000) + episode_number) > ((:seasonNumber * 10000) + :episodeNumber) \" +\n      \"AND first_aired <= :toTime \" +\n      \"ORDER BY season_number ASC, episode_number ASC LIMIT 1\"\n  )\n  override suspend fun getFirstUnwatchedAfterEpisode(showTraktId: Long, seasonNumber: Int, episodeNumber: Int, toTime: Long): Episode?\n\n  @Query(\"SELECT * from episodes where id_show_trakt = :showTraktId AND is_watched = 1 AND season_number != 0 ORDER BY last_watched_at DESC LIMIT 1\")\n  override suspend fun getLastWatched(showTraktId: Long): Episode?\n\n  @Query(\"SELECT COUNT(id_trakt) FROM episodes WHERE id_show_trakt = :showTraktId AND first_aired < :toTime AND season_number != 0\")\n  override suspend fun getTotalCount(showTraktId: Long, toTime: Long): Int\n\n  @Query(\"SELECT COUNT(id_trakt) FROM episodes WHERE id_show_trakt = :showTraktId AND season_number != 0\")\n  override suspend fun getTotalCount(showTraktId: Long): Int\n\n  @Query(\"SELECT COUNT(id_trakt) FROM episodes WHERE id_show_trakt = :showTraktId AND is_watched = 1 AND first_aired < :toTime AND season_number != 0\")\n  override suspend fun getWatchedCount(showTraktId: Long, toTime: Long): Int\n\n  @Query(\"SELECT COUNT(id_trakt) FROM episodes WHERE id_show_trakt = :showTraktId AND is_watched = 1 AND season_number != 0\")\n  override suspend fun getWatchedCount(showTraktId: Long): Int\n\n  @Query(\"SELECT * FROM episodes WHERE id_show_trakt IN(:showsIds) AND is_watched = 1\")\n  override suspend fun getAllWatchedForShows(showsIds: List<Long>): List<Episode>\n\n  @Query(\"SELECT id_trakt FROM episodes WHERE id_show_trakt IN(:showsIds) AND is_watched = 1\")\n  override suspend fun getAllWatchedIdsForShows(showsIds: List<Long>): List<Long>\n\n  @Query(\"DELETE FROM episodes WHERE id_show_trakt = :showTraktId AND is_watched = 0\")\n  override suspend fun deleteAllUnwatchedForShow(showTraktId: Long)\n\n  @Query(\"DELETE FROM episodes WHERE id_show_trakt = :showTraktId\")\n  override suspend fun deleteAllForShow(showTraktId: Long)\n\n  @Delete\n  override suspend fun delete(items: List<Episode>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/EpisodesSyncLogDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.EpisodesSyncLog\nimport com.michaldrabik.data_local.sources.EpisodesSyncLogLocalDataSource\n\n@Dao\ninterface EpisodesSyncLogDao : EpisodesSyncLogLocalDataSource {\n\n  @Query(\"SELECT * from sync_episodes_log\")\n  override suspend fun getAll(): List<EpisodesSyncLog>\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun upsert(log: EpisodesSyncLog)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/MovieCollectionsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.MovieCollection\nimport com.michaldrabik.data_local.sources.MovieCollectionsLocalDataSource\n\n@Dao\ninterface MovieCollectionsDao : BaseDao<MovieCollection>, MovieCollectionsLocalDataSource {\n\n  @Query(\"SELECT * FROM movies_collections WHERE id_trakt == :traktId\")\n  override suspend fun getById(traktId: Long): MovieCollection?\n\n  @Query(\"SELECT * FROM movies_collections WHERE id_trakt_movie == :movieTraktId\")\n  override suspend fun getByMovieId(movieTraktId: Long): List<MovieCollection>\n\n  @Transaction\n  override suspend fun replaceByMovieId(\n    movieTraktId: Long,\n    entities: List<MovieCollection>,\n  ) {\n    val deleteCollections = getByMovieId(movieTraktId).map { it.idTrakt }\n\n    deleteCollectionsItems(deleteCollections)\n    deleteCollections(deleteCollections)\n\n    insert(entities)\n  }\n\n  override suspend fun insertAll(items: List<MovieCollection>) {\n    insert(items)\n  }\n\n  @Query(\"DELETE FROM movies_collections WHERE id_trakt IN (:collectionIds)\")\n  suspend fun deleteCollections(collectionIds: List<Long>)\n\n  @Query(\"DELETE FROM movies_collections_items WHERE id_trakt_collection IN (:collectionIds)\")\n  suspend fun deleteCollectionsItems(collectionIds: List<Long>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/MovieCollectionsItemsDao.kt",
    "content": "// ktlint-disable max-line-length\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.Movie\nimport com.michaldrabik.data_local.database.model.MovieCollectionItem\nimport com.michaldrabik.data_local.sources.MovieCollectionsItemsLocalDataSource\n\n@Dao\ninterface MovieCollectionsItemsDao : BaseDao<MovieCollectionItem>, MovieCollectionsItemsLocalDataSource {\n\n  @Query(\"SELECT movies.*, movies_collections_items.created_at, movies_collections_items.updated_at FROM movies INNER JOIN movies_collections_items USING(id_trakt) WHERE id_trakt_collection == :collectionId ORDER BY rank ASC\")\n  override suspend fun getById(collectionId: Long): List<Movie>\n\n  @Transaction\n  override suspend fun replace(\n    collectionId: Long,\n    items: List<MovieCollectionItem>,\n  ) {\n    deleteById(collectionId)\n    insert(items)\n  }\n\n  @Query(\"DELETE FROM movies_collections_items WHERE id_trakt_collection == :collectionId\")\n  override suspend fun deleteById(collectionId: Long)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/MovieImagesDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.MovieImage\nimport com.michaldrabik.data_local.sources.MovieImagesLocalDataSource\n\n@Dao\ninterface MovieImagesDao : MovieImagesLocalDataSource {\n\n  @Query(\"SELECT * FROM movies_images WHERE id_tmdb = :tmdbId AND type = :type\")\n  override suspend fun getByMovieId(tmdbId: Long, type: String): MovieImage?\n\n  @Transaction\n  override suspend fun insertMovieImage(image: MovieImage) {\n    val localImage = getByMovieId(image.idTmdb, image.type)\n    if (localImage != null) {\n      val updated = image.copy(id = localImage.id)\n      upsert(updated)\n      return\n    }\n    upsert(image)\n  }\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun upsert(image: MovieImage)\n\n  @Query(\"DELETE FROM movies_images WHERE id_tmdb = :id AND type = :type\")\n  override suspend fun deleteByMovieId(id: Long, type: String)\n\n  @Query(\"DELETE FROM movies_images WHERE type = 'poster'\")\n  override suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/MovieRatingsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.MovieRatings\nimport com.michaldrabik.data_local.sources.MovieRatingsLocalDataSource\n\n@Dao\ninterface MovieRatingsDao : BaseDao<MovieRatings>, MovieRatingsLocalDataSource {\n\n  @Transaction\n  override suspend fun upsert(entity: MovieRatings) {\n    val local = getById(entity.idTrakt)\n    if (local != null) {\n      update(\n        listOf(\n          local.copy(\n            trakt = entity.trakt,\n            imdb = entity.imdb,\n            metascore = entity.metascore,\n            rottenTomatoes = entity.rottenTomatoes,\n            rottenTomatoesUrl = entity.rottenTomatoesUrl,\n            updatedAt = entity.updatedAt\n          )\n        )\n      )\n      return\n    }\n    insert(listOf(entity))\n  }\n\n  @Query(\"SELECT * FROM movies_ratings WHERE id_trakt == :traktId\")\n  override suspend fun getById(traktId: Long): MovieRatings?\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/MovieStreamingsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.MovieStreaming\nimport com.michaldrabik.data_local.sources.MovieStreamingsLocalDataSource\n\n@Dao\ninterface MovieStreamingsDao : BaseDao<MovieStreaming>, MovieStreamingsLocalDataSource {\n\n  @Transaction\n  override suspend fun replace(traktId: Long, entities: List<MovieStreaming>) {\n    deleteById(traktId)\n    insert(entities)\n  }\n\n  @Query(\"SELECT * FROM movies_streamings WHERE id_trakt == :traktId\")\n  override suspend fun getById(traktId: Long): List<MovieStreaming>\n\n  @Query(\"DELETE FROM movies_streamings WHERE id_trakt == :traktId\")\n  override suspend fun deleteById(traktId: Long)\n\n  @Query(\"DELETE FROM movies_streamings\")\n  override suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/MovieTranslationsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.MovieTranslation\nimport com.michaldrabik.data_local.sources.MovieTranslationsLocalDataSource\n\n@Dao\ninterface MovieTranslationsDao : BaseDao<MovieTranslation>, MovieTranslationsLocalDataSource {\n\n  @Query(\"SELECT * FROM movies_translations WHERE id_trakt == :traktId AND language == :language\")\n  override suspend fun getById(traktId: Long, language: String): MovieTranslation?\n\n  @Query(\"SELECT * FROM movies_translations WHERE language == :language\")\n  override suspend fun getAll(language: String): List<MovieTranslation>\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun insertSingle(translation: MovieTranslation)\n\n  @Query(\"DELETE FROM movies_translations WHERE language IN (:languages)\")\n  override suspend fun deleteByLanguage(languages: List<String>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/MoviesDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.Movie\nimport com.michaldrabik.data_local.sources.MoviesLocalDataSource\n\n@Dao\ninterface MoviesDao : BaseDao<Movie>, MoviesLocalDataSource {\n\n  @Query(\"SELECT * FROM movies\")\n  override suspend fun getAll(): List<Movie>\n\n  @Query(\"SELECT * FROM movies WHERE id_trakt IN (:ids)\")\n  override suspend fun getAll(ids: List<Long>): List<Movie>\n\n  @Transaction\n  override suspend fun getAllChunked(ids: List<Long>): List<Movie> = ids\n    .chunked(500)\n    .fold(\n      mutableListOf()\n    ) { acc, chunk ->\n      acc += getAll(chunk)\n      acc\n    }\n\n  @Query(\"SELECT * FROM movies WHERE id_trakt == :traktId\")\n  override suspend fun getById(traktId: Long): Movie?\n\n  @Query(\"SELECT * FROM movies WHERE id_tmdb == :tmdbId\")\n  override suspend fun getByTmdbId(tmdbId: Long): Movie?\n\n  @Query(\"SELECT * FROM movies WHERE id_slug == :slug\")\n  override suspend fun getBySlug(slug: String): Movie?\n\n  @Query(\"SELECT * FROM movies WHERE id_imdb == :imdbId\")\n  override suspend fun getById(imdbId: String): Movie?\n\n  @Query(\"DELETE FROM movies where id_trakt == :traktId\")\n  override suspend fun deleteById(traktId: Long)\n\n  @Transaction\n  override suspend fun upsert(movies: List<Movie>) {\n    val result = insert(movies)\n\n    val updateList = mutableListOf<Movie>()\n    result.forEachIndexed { index, id ->\n      if (id == -1L) {\n        updateList.add(movies[index])\n      }\n    }\n    if (updateList.isNotEmpty()) {\n      update(updateList)\n    }\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/MoviesSyncLogDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.MoviesSyncLog\nimport com.michaldrabik.data_local.sources.MoviesSyncLogLocalDataSource\n\n@Dao\ninterface MoviesSyncLogDao : MoviesSyncLogLocalDataSource {\n\n  @Query(\"SELECT * from sync_movies_log\")\n  override suspend fun getAll(): List<MoviesSyncLog>\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun upsert(log: MoviesSyncLog)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/MyMoviesDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.Movie\nimport com.michaldrabik.data_local.database.model.MyMovie\nimport com.michaldrabik.data_local.sources.MyMoviesLocalDataSource\n\n@Dao\ninterface MyMoviesDao : MyMoviesLocalDataSource {\n\n  @Query(\"SELECT movies.*, movies_my_movies.updated_at AS updated_at FROM movies INNER JOIN movies_my_movies USING(id_trakt)\")\n  override suspend fun getAll(): List<Movie>\n\n  @Query(\n    \"SELECT movies.*, movies_my_movies.updated_at AS updated_at FROM movies \" +\n      \"INNER JOIN movies_my_movies USING(id_trakt) WHERE id_trakt IN (:ids)\"\n  )\n  override suspend fun getAll(ids: List<Long>): List<Movie>\n\n  @Query(\"SELECT movies.* FROM movies INNER JOIN movies_my_movies USING(id_trakt) ORDER BY movies_my_movies.updated_at DESC LIMIT :limit\")\n  override suspend fun getAllRecent(limit: Int): List<Movie>\n\n  @Query(\"SELECT movies.id_trakt FROM movies INNER JOIN movies_my_movies USING(id_trakt)\")\n  override suspend fun getAllTraktIds(): List<Long>\n\n  @Query(\"SELECT movies.* FROM movies INNER JOIN movies_my_movies USING(id_trakt) WHERE id_trakt == :traktId\")\n  override suspend fun getById(traktId: Long): Movie?\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun insert(movies: List<MyMovie>)\n\n  @Query(\"DELETE FROM movies_my_movies WHERE id_trakt == :traktId\")\n  override suspend fun deleteById(traktId: Long)\n\n  @Query(\"SELECT EXISTS(SELECT 1 FROM movies_my_movies WHERE id_trakt = :traktId LIMIT 1);\")\n  override suspend fun checkExists(traktId: Long): Boolean\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/MyShowsDao.kt",
    "content": "// ktlint-disable max-line-length\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.MyShow\nimport com.michaldrabik.data_local.database.model.Show\nimport com.michaldrabik.data_local.sources.MyShowsLocalDataSource\n\n@Dao\ninterface MyShowsDao : MyShowsLocalDataSource {\n\n  @Query(\"SELECT shows.*, shows_my_shows.created_at AS created_at, shows_my_shows.last_watched_at AS updated_at FROM shows INNER JOIN shows_my_shows USING(id_trakt)\")\n  override suspend fun getAll(): List<Show>\n\n  @Query(\"SELECT shows.*, shows_my_shows.created_at AS created_at, shows_my_shows.last_watched_at AS updated_at FROM shows INNER JOIN shows_my_shows USING(id_trakt) WHERE id_trakt IN (:ids)\")\n  override suspend fun getAll(ids: List<Long>): List<Show>\n\n  @Query(\"SELECT shows.* FROM shows INNER JOIN shows_my_shows USING(id_trakt) ORDER BY shows_my_shows.created_at DESC LIMIT :limit\")\n  override suspend fun getAllRecent(limit: Int): List<Show>\n\n  @Query(\"SELECT shows.id_trakt FROM shows INNER JOIN shows_my_shows USING(id_trakt)\")\n  override suspend fun getAllTraktIds(): List<Long>\n\n  @Query(\"SELECT shows.* FROM shows INNER JOIN shows_my_shows USING(id_trakt) WHERE id_trakt == :traktId\")\n  override suspend fun getById(traktId: Long): Show?\n\n  @Query(\"UPDATE shows_my_shows SET last_watched_at = :watchedAt WHERE id_trakt == :traktId\")\n  override suspend fun updateWatchedAt(traktId: Long, watchedAt: Long)\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun insert(shows: List<MyShow>)\n\n  @Query(\"DELETE FROM shows_my_shows WHERE id_trakt == :traktId\")\n  override suspend fun deleteById(traktId: Long)\n\n  @Query(\"SELECT EXISTS(SELECT 1 FROM shows_my_shows WHERE id_trakt = :traktId LIMIT 1);\")\n  override suspend fun checkExists(traktId: Long): Boolean\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/NewsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.News\nimport com.michaldrabik.data_local.sources.NewsLocalDataSource\n\n@Dao\ninterface NewsDao : BaseDao<News>, NewsLocalDataSource {\n\n  @Query(\"SELECT * FROM news WHERE type == :type ORDER BY dated_at DESC\")\n  override suspend fun getAllByType(type: String): List<News>\n\n  @Transaction\n  override suspend fun replaceForType(items: List<News>, type: String) {\n    deleteAllByType(type)\n    insert(items)\n  }\n\n  @Query(\"DELETE FROM news WHERE type == :type\")\n  override suspend fun deleteAllByType(type: String): Int\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/PeopleCreditsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\n/* ktlint-disable */\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.Movie\nimport com.michaldrabik.data_local.database.model.PersonCredits\nimport com.michaldrabik.data_local.database.model.Show\nimport com.michaldrabik.data_local.sources.PeopleCreditsLocalDataSource\n\n@Dao\ninterface PeopleCreditsDao : BaseDao<PersonCredits>, PeopleCreditsLocalDataSource {\n\n  @Query(\n    \"SELECT shows.*, people_credits.created_at AS created_at, people_credits.updated_at AS updated_at FROM shows \" +\n      \"INNER JOIN people_credits ON people_credits.id_trakt_show = shows.id_trakt WHERE people_credits.id_trakt_person = :personTraktId\"\n  )\n  override suspend fun getAllShowsForPerson(personTraktId: Long): List<Show>\n\n  @Query(\n    \"SELECT movies.*, people_credits.created_at AS created_at, people_credits.updated_at AS updated_at FROM movies \" +\n      \"INNER JOIN people_credits ON people_credits.id_trakt_movie = movies.id_trakt WHERE people_credits.id_trakt_person = :personTraktId\"\n  )\n  override suspend fun getAllMoviesForPerson(personTraktId: Long): List<Movie>\n\n  @Query(\"SELECT updated_at FROM people_credits WHERE id_trakt_person = :personTraktId LIMIT 1\")\n  override suspend fun getTimestampForPerson(personTraktId: Long): Long?\n\n  @Query(\"DELETE FROM people_credits WHERE id_trakt_person == :personTraktId\")\n  override suspend fun deleteAllForPerson(personTraktId: Long)\n\n  @Transaction\n  override suspend fun insertSingle(personTraktId: Long, credits: List<PersonCredits>) {\n    deleteAllForPerson(personTraktId)\n    insert(credits)\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/PeopleDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\n/* ktlint-disable */\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.Person\nimport com.michaldrabik.data_local.sources.PeopleLocalDataSource\n\n@Dao\ninterface PeopleDao : BaseDao<Person>, PeopleLocalDataSource {\n\n  @Transaction\n  override suspend fun upsert(people: List<Person>) {\n    val result = insert(people)\n    val updateList = mutableListOf<Person>()\n    result.forEachIndexed { index, id ->\n      if (id == -1L) {\n        updateList.add(people[index])\n      }\n    }\n    if (updateList.isNotEmpty()) update(updateList)\n  }\n\n  @Query(\"SELECT * FROM people WHERE id_tmdb = :tmdbId\")\n  override suspend fun getById(tmdbId: Long): Person?\n\n  @Query(\"SELECT people.*, people_shows_movies.department AS department, people_shows_movies.character AS character, people_shows_movies.job AS job, people_shows_movies.episodes_count AS episodes_count FROM people INNER JOIN people_shows_movies ON people_shows_movies.id_tmdb_person = people.id_tmdb WHERE people_shows_movies.id_trakt_show = :showTraktId\")\n  override suspend fun getAllForShow(showTraktId: Long): List<Person>\n\n  @Query(\"SELECT people.*, people_shows_movies.department AS department, people_shows_movies.character AS character, people_shows_movies.job AS job, people_shows_movies.episodes_count AS episodes_count FROM people INNER JOIN people_shows_movies ON people_shows_movies.id_tmdb_person = people.id_tmdb WHERE people_shows_movies.id_trakt_movie = :movieTraktId\")\n  override suspend fun getAllForMovie(movieTraktId: Long): List<Person>\n\n  @Query(\"SELECT * FROM people\")\n  override suspend fun getAll(): List<Person>\n\n  @Query(\"UPDATE people SET id_trakt = :idTrakt WHERE id_tmdb = :idTmdb\")\n  override suspend fun updateTraktId(idTrakt: Long, idTmdb: Long)\n\n  @Query(\"UPDATE people SET biography_translation = NULL, details_updated_at = NULL\")\n  override suspend fun deleteTranslations()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/PeopleImagesDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\n/* ktlint-disable */\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.PersonImage\nimport com.michaldrabik.data_local.sources.PeopleImagesLocalDataSource\n\n@Dao\ninterface PeopleImagesDao : BaseDao<PersonImage>, PeopleImagesLocalDataSource {\n\n  @Query(\"SELECT updated_at FROM people_images WHERE id_tmdb = :personTmdbId LIMIT 1\")\n  override suspend fun getTimestampForPerson(personTmdbId: Long): Long?\n\n  @Query(\"SELECT * FROM people_images WHERE id_tmdb = :personTmdbId\")\n  override suspend fun getAll(personTmdbId: Long): List<PersonImage>\n\n  @Query(\"DELETE FROM people_images WHERE id_tmdb == :personTmdbId\")\n  override suspend fun deleteAllForPerson(personTmdbId: Long)\n\n  @Transaction\n  override suspend fun insertSingle(personTmdbId: Long, images: List<PersonImage>) {\n    deleteAllForPerson(personTmdbId)\n    insert(images)\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/PeopleShowsMoviesDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.PersonShowMovie\nimport com.michaldrabik.data_local.sources.PeopleShowsMoviesLocalDataSource\n\n@Dao\ninterface PeopleShowsMoviesDao : BaseDao<PersonShowMovie>, PeopleShowsMoviesLocalDataSource {\n\n  @Query(\"SELECT updated_at FROM people_shows_movies WHERE id_trakt_show == :showTraktId LIMIT 1\")\n  override suspend fun getTimestampForShow(showTraktId: Long): Long?\n\n  @Query(\"SELECT updated_at FROM people_shows_movies WHERE id_trakt_movie == :movieTraktId LIMIT 1\")\n  override suspend fun getTimestampForMovie(movieTraktId: Long): Long?\n\n  @Query(\"DELETE FROM people_shows_movies WHERE id_trakt_show == :showTraktId\")\n  override suspend fun deleteAllForShow(showTraktId: Long)\n\n  @Query(\"DELETE FROM people_shows_movies WHERE id_trakt_movie == :movieTraktId\")\n  override suspend fun deleteAllForMovie(movieTraktId: Long)\n\n  @Transaction\n  override suspend fun insertForShow(people: List<PersonShowMovie>, showTraktId: Long) {\n    deleteAllForShow(showTraktId)\n    insert(people)\n  }\n\n  @Transaction\n  override suspend fun insertForMovie(people: List<PersonShowMovie>, movieTraktId: Long) {\n    deleteAllForMovie(movieTraktId)\n    insert(people)\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/RatingsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.Rating\nimport com.michaldrabik.data_local.sources.RatingsLocalDataSource\n\n@Dao\ninterface RatingsDao : BaseDao<Rating>, RatingsLocalDataSource {\n\n  @Query(\"SELECT * FROM ratings\")\n  override suspend fun getAll(): List<Rating>\n\n  @Query(\"SELECT * FROM ratings WHERE type == :type\")\n  override suspend fun getAllByType(type: String): List<Rating>\n\n  @Query(\"SELECT * FROM ratings WHERE id_trakt IN (:idsTrakt) AND type == :type\")\n  override suspend fun getAllByType(idsTrakt: List<Long>, type: String): List<Rating>\n\n  @Query(\"DELETE FROM ratings WHERE type == :type\")\n  override suspend fun deleteAllByType(type: String)\n\n  @Query(\"DELETE FROM ratings WHERE id_trakt == :traktId AND type == :type\")\n  override suspend fun deleteByType(traktId: Long, type: String)\n\n  @Transaction\n  override suspend fun replaceAll(ratings: List<Rating>, type: String) {\n    deleteAllByType(type)\n    insert(ratings)\n  }\n\n  @Transaction\n  override suspend fun replace(rating: Rating) {\n    deleteByType(rating.idTrakt, rating.type)\n    insert(listOf(rating))\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/RecentSearchDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.RecentSearch\nimport com.michaldrabik.data_local.sources.RecentSearchLocalDataSource\n\n@Dao\ninterface RecentSearchDao : RecentSearchLocalDataSource {\n\n  @Query(\"SELECT * FROM recent_searches ORDER BY created_at DESC LIMIT :limit\")\n  override suspend fun getAll(limit: Int): List<RecentSearch>\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun upsert(searches: List<RecentSearch>)\n\n  @Query(\"DELETE FROM recent_searches\")\n  override suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/RelatedMoviesDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.RelatedMovie\nimport com.michaldrabik.data_local.sources.RelatedMoviesLocalDataSource\n\n@Dao\ninterface RelatedMoviesDao : RelatedMoviesLocalDataSource {\n\n  @Insert(onConflict = OnConflictStrategy.IGNORE)\n  override suspend fun insert(items: List<RelatedMovie>): List<Long>\n\n  @Query(\"SELECT * FROM movies_related WHERE id_trakt_related_movie == :traktId\")\n  override suspend fun getAllById(traktId: Long): List<RelatedMovie>\n\n  @Query(\"SELECT * FROM movies_related\")\n  override suspend fun getAll(): List<RelatedMovie>\n\n  @Query(\"DELETE FROM movies_related WHERE id_trakt_related_movie == :traktId\")\n  override suspend fun deleteById(traktId: Long)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/RelatedShowsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.RelatedShow\nimport com.michaldrabik.data_local.sources.RelatedShowsLocalDataSource\n\n@Dao\ninterface RelatedShowsDao : RelatedShowsLocalDataSource {\n\n  @Insert(onConflict = OnConflictStrategy.IGNORE)\n  override suspend fun insert(items: List<RelatedShow>): List<Long>\n\n  @Query(\"SELECT * FROM shows_related WHERE id_trakt_related_show == :traktId\")\n  override suspend fun getAllById(traktId: Long): List<RelatedShow>\n\n  @Query(\"SELECT * FROM shows_related\")\n  override suspend fun getAll(): List<RelatedShow>\n\n  @Query(\"DELETE FROM shows_related WHERE id_trakt_related_show == :traktId\")\n  override suspend fun deleteById(traktId: Long)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/SeasonsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Delete\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport androidx.room.Update\nimport com.michaldrabik.data_local.database.model.Season\nimport com.michaldrabik.data_local.sources.SeasonsLocalDataSource\n\n@Dao\ninterface SeasonsDao : SeasonsLocalDataSource {\n\n  @Insert(onConflict = OnConflictStrategy.IGNORE)\n  suspend fun insert(items: List<Season>): List<Long>\n\n  @Update(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun update(items: List<Season>)\n\n  @Delete\n  override suspend fun delete(items: List<Season>)\n\n  @Transaction\n  override suspend fun getAllByShowsIds(traktIds: List<Long>): List<Season> {\n    val result = mutableListOf<Season>()\n    val chunks = traktIds.chunked(50)\n    chunks.forEach { chunk ->\n      result += getAllByShowsIdsChunk(chunk)\n    }\n    return result\n  }\n\n  @Query(\"SELECT * FROM seasons WHERE id_show_trakt IN (:traktIds)\")\n  override suspend fun getAllByShowsIdsChunk(traktIds: List<Long>): List<Season>\n\n  @Query(\"SELECT * FROM seasons WHERE id_show_trakt IN (:traktIds) AND is_watched = 1\")\n  override suspend fun getAllWatchedForShows(traktIds: List<Long>): List<Season>\n\n  @Query(\"SELECT id_trakt FROM seasons WHERE id_show_trakt IN (:traktIds) AND is_watched = 1\")\n  override suspend fun getAllWatchedIdsForShows(traktIds: List<Long>): List<Long>\n\n  @Query(\"SELECT * FROM seasons WHERE id_show_trakt = :traktId\")\n  override suspend fun getAllByShowId(traktId: Long): List<Season>\n\n  @Query(\"SELECT * FROM seasons WHERE id_trakt = :traktId\")\n  override suspend fun getById(traktId: Long): Season?\n\n  @Transaction\n  override suspend fun upsert(items: List<Season>) {\n    val result = insert(items)\n    val updateList = mutableListOf<Season>()\n\n    result.forEachIndexed { index, id ->\n      if (id == -1L) updateList.add(items[index])\n    }\n\n    if (updateList.isNotEmpty()) update(updateList)\n  }\n\n  @Query(\"DELETE FROM seasons WHERE id_show_trakt = :showTraktId\")\n  override suspend fun deleteAllForShow(showTraktId: Long)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/SettingsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.Settings\nimport com.michaldrabik.data_local.sources.SettingsLocalDataSource\n\n@Dao\ninterface SettingsDao : SettingsLocalDataSource {\n\n  @Query(\"SELECT * FROM settings\")\n  override suspend fun getAll(): Settings\n\n  @Query(\"SELECT COUNT(*) FROM settings\")\n  override suspend fun getCount(): Int\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun upsert(settings: Settings)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/ShowImagesDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.ShowImage\nimport com.michaldrabik.data_local.sources.ShowImagesLocalDataSource\n\n@Dao\ninterface ShowImagesDao : ShowImagesLocalDataSource {\n\n  @Query(\"SELECT * FROM shows_images WHERE id_tmdb = :tmdbId AND type = :type AND family = 'show'\")\n  override suspend fun getByShowId(tmdbId: Long, type: String): ShowImage?\n\n  @Query(\"SELECT * FROM shows_images WHERE id_tmdb = :tmdbId AND type = :type AND family = 'episode'\")\n  override suspend fun getByEpisodeId(tmdbId: Long, type: String): ShowImage?\n\n  @Transaction\n  override suspend fun insertShowImage(image: ShowImage) {\n    val localImage = getByShowId(image.idTmdb, image.type)\n    if (localImage != null) {\n      val updated = image.copy(id = localImage.id)\n      upsert(updated)\n      return\n    }\n    upsert(image)\n  }\n\n  @Transaction\n  override suspend fun insertEpisodeImage(image: ShowImage) {\n    val localImage = getByEpisodeId(image.idTmdb, image.type)\n    if (localImage != null) {\n      val updated = image.copy(id = localImage.id)\n      upsert(updated)\n      return\n    }\n    upsert(image)\n  }\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun upsert(image: ShowImage)\n\n  @Query(\"DELETE FROM shows_images WHERE id_tmdb = :id AND type = :type AND family = 'show'\")\n  override suspend fun deleteByShowId(id: Long, type: String)\n\n  @Query(\"DELETE FROM shows_images WHERE id_tmdb = :id AND type = :type AND family = 'episode'\")\n  override suspend fun deleteByEpisodeId(id: Long, type: String)\n\n  @Query(\"DELETE FROM shows_images WHERE type = 'poster'\")\n  override suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/ShowRatingsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.ShowRatings\nimport com.michaldrabik.data_local.sources.ShowRatingsLocalDataSource\n\n@Dao\ninterface ShowRatingsDao : BaseDao<ShowRatings>, ShowRatingsLocalDataSource {\n\n  @Transaction\n  override suspend fun upsert(entity: ShowRatings) {\n    val local = getById(entity.idTrakt)\n    if (local != null) {\n      update(\n        listOf(\n          local.copy(\n            trakt = entity.trakt,\n            imdb = entity.imdb,\n            metascore = entity.metascore,\n            rottenTomatoes = entity.rottenTomatoes,\n            rottenTomatoesUrl = entity.rottenTomatoesUrl,\n            updatedAt = entity.updatedAt\n          )\n        )\n      )\n      return\n    }\n    insert(listOf(entity))\n  }\n\n  @Query(\"SELECT * FROM shows_ratings WHERE id_trakt == :traktId\")\n  override suspend fun getById(traktId: Long): ShowRatings?\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/ShowStreamingsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.ShowStreaming\nimport com.michaldrabik.data_local.sources.ShowStreamingsLocalDataSource\n\n@Dao\ninterface ShowStreamingsDao : BaseDao<ShowStreaming>, ShowStreamingsLocalDataSource {\n\n  @Transaction\n  override suspend fun replace(traktId: Long, entities: List<ShowStreaming>) {\n    deleteById(traktId)\n    insert(entities)\n  }\n\n  @Query(\"SELECT * FROM shows_streamings WHERE id_trakt == :traktId\")\n  override suspend fun getById(traktId: Long): List<ShowStreaming>\n\n  @Query(\"DELETE FROM shows_streamings WHERE id_trakt == :traktId\")\n  override suspend fun deleteById(traktId: Long)\n\n  @Query(\"DELETE FROM shows_streamings\")\n  override suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/ShowTranslationsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy.Companion.REPLACE\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.ShowTranslation\nimport com.michaldrabik.data_local.sources.ShowTranslationsLocalDataSource\n\n@Dao\ninterface ShowTranslationsDao : BaseDao<ShowTranslation>, ShowTranslationsLocalDataSource {\n\n  @Query(\"SELECT * FROM shows_translations WHERE id_trakt == :traktId AND language == :language\")\n  override suspend fun getById(traktId: Long, language: String): ShowTranslation?\n\n  @Query(\"SELECT * FROM shows_translations WHERE language == :language\")\n  override suspend fun getAll(language: String): List<ShowTranslation>\n\n  @Insert(onConflict = REPLACE)\n  override suspend fun insertSingle(translation: ShowTranslation)\n\n  @Query(\"DELETE FROM shows_translations WHERE language IN (:languages)\")\n  override suspend fun deleteByLanguage(languages: List<String>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/ShowsDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.Show\nimport com.michaldrabik.data_local.sources.ShowsLocalDataSource\n\n@Dao\ninterface ShowsDao : BaseDao<Show>, ShowsLocalDataSource {\n\n  @Query(\"SELECT * FROM shows\")\n  override suspend fun getAll(): List<Show>\n\n  @Query(\"SELECT * FROM shows WHERE id_trakt IN (:ids)\")\n  override suspend fun getAll(ids: List<Long>): List<Show>\n\n  @Transaction\n  override suspend fun getAllChunked(ids: List<Long>): List<Show> = ids\n    .chunked(500)\n    .fold(mutableListOf()) { acc, chunk ->\n      acc += getAll(chunk)\n      acc\n    }\n\n  @Query(\"SELECT * FROM shows WHERE id_trakt == :traktId\")\n  override suspend fun getById(traktId: Long): Show?\n\n  @Query(\"SELECT * FROM shows WHERE id_tmdb == :tmdbId\")\n  override suspend fun getByTmdbId(tmdbId: Long): Show?\n\n  @Query(\"SELECT * FROM shows WHERE id_slug == :slug\")\n  override suspend fun getBySlug(slug: String): Show?\n\n  @Query(\"SELECT * FROM shows WHERE id_imdb == :imdbId\")\n  override suspend fun getById(imdbId: String): Show?\n\n  @Query(\"DELETE FROM shows where id_trakt == :traktId\")\n  override suspend fun deleteById(traktId: Long)\n\n  @Transaction\n  override suspend fun upsert(shows: List<Show>) {\n    val result = insert(shows)\n\n    val updateList = mutableListOf<Show>()\n    result.forEachIndexed { index, id ->\n      if (id == -1L) updateList.add(shows[index])\n    }\n\n    if (updateList.isNotEmpty()) update(updateList)\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/TraktSyncLogDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.michaldrabik.data_local.database.model.TraktSyncLog\nimport com.michaldrabik.data_local.sources.TraktSyncLogLocalDataSource\n\n@Dao\ninterface TraktSyncLogDao : TraktSyncLogLocalDataSource {\n\n  @Query(\"SELECT * FROM sync_trakt_log WHERE type == 'show'\")\n  override suspend fun getAllShows(): List<TraktSyncLog>\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun insert(log: TraktSyncLog)\n\n  @Query(\"UPDATE sync_trakt_log SET synced_at = :syncedAt WHERE id_trakt == :idTrakt AND type == :type\")\n  override suspend fun update(idTrakt: Long, type: String, syncedAt: Long): Int\n\n  @Query(\"DELETE FROM sync_trakt_log\")\n  override suspend fun deleteAll()\n\n  @Transaction\n  override suspend fun upsertShow(idTrakt: Long, syncedAt: Long) {\n    val result = update(idTrakt, \"show\", syncedAt)\n    if (result <= 0) {\n      insert(TraktSyncLog(0, idTrakt, \"show\", syncedAt))\n    }\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/TraktSyncQueueDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Delete\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue\nimport com.michaldrabik.data_local.sources.TraktSyncQueueLocalDataSource\n\n@Dao\ninterface TraktSyncQueueDao : TraktSyncQueueLocalDataSource {\n\n  @Insert(onConflict = OnConflictStrategy.IGNORE)\n  override suspend fun insert(items: List<TraktSyncQueue>): List<Long>\n\n  @Delete\n  override suspend fun delete(items: List<TraktSyncQueue>)\n\n  @Query(\"SELECT * FROM trakt_sync_queue ORDER BY created_at ASC\")\n  override suspend fun getAll(): List<TraktSyncQueue>\n\n  @Query(\"SELECT * FROM trakt_sync_queue WHERE type IN (:types) ORDER BY created_at ASC\")\n  override suspend fun getAll(types: List<String>): List<TraktSyncQueue>\n\n  @Query(\"DELETE FROM trakt_sync_queue WHERE id_trakt IN (:idsTrakt) AND type = :type\")\n  override suspend fun deleteAll(idsTrakt: List<Long>, type: String): Int\n\n  @Query(\"DELETE FROM trakt_sync_queue WHERE type = :type\")\n  override suspend fun deleteAll(type: String): Int\n\n  @Query(\"DELETE FROM trakt_sync_queue\")\n  override suspend fun deleteAll()\n\n  @Query(\"DELETE FROM trakt_sync_queue WHERE id_list = :idList\")\n  override suspend fun deleteAllForList(idList: Long): Int\n\n  @Query(\"DELETE FROM trakt_sync_queue WHERE id_trakt = :idTrakt AND id_list = :idList AND type = :type AND operation = :operation\")\n  override suspend fun delete(\n    idTrakt: Long,\n    idList: Long,\n    type: String,\n    operation: String\n  ): Int\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/TranslationsMoviesSyncLogDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.TranslationsMoviesSyncLog\nimport com.michaldrabik.data_local.sources.TranslationsMoviesSyncLogLocalDataSource\n\n@Dao\ninterface TranslationsMoviesSyncLogDao : TranslationsMoviesSyncLogLocalDataSource {\n\n  @Query(\"SELECT * from sync_movies_translations_log\")\n  override suspend fun getAll(): List<TranslationsMoviesSyncLog>\n\n  @Query(\"SELECT * from sync_movies_translations_log WHERE id_movie_trakt == :idTrakt\")\n  override suspend fun getById(idTrakt: Long): TranslationsMoviesSyncLog?\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun upsert(log: TranslationsMoviesSyncLog)\n\n  @Query(\"DELETE FROM sync_movies_translations_log\")\n  override suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/TranslationsSyncLogDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.TranslationsSyncLog\nimport com.michaldrabik.data_local.sources.TranslationsShowsSyncLogLocalDataSource\n\n@Dao\ninterface TranslationsSyncLogDao : TranslationsShowsSyncLogLocalDataSource {\n\n  @Query(\"SELECT * from sync_translations_log\")\n  override suspend fun getAll(): List<TranslationsSyncLog>\n\n  @Query(\"SELECT * from sync_translations_log WHERE id_show_trakt == :idTrakt\")\n  override suspend fun getById(idTrakt: Long): TranslationsSyncLog?\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun upsert(log: TranslationsSyncLog)\n\n  @Query(\"DELETE FROM sync_translations_log\")\n  override suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/UserDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.User\nimport com.michaldrabik.data_local.sources.UserLocalDataSource\n\n@Dao\ninterface UserDao : UserLocalDataSource {\n\n  @Query(\"SELECT * FROM user WHERE id == 1\")\n  override suspend fun get(): User?\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun upsert(user: User)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/WatchlistMoviesDao.kt",
    "content": "package com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.Movie\nimport com.michaldrabik.data_local.database.model.WatchlistMovie\nimport com.michaldrabik.data_local.sources.WatchlistMoviesLocalDataSource\n\n@Dao\ninterface WatchlistMoviesDao : WatchlistMoviesLocalDataSource {\n\n  @Query(\"SELECT movies.*, movies_see_later.created_at, movies_see_later.updated_at FROM movies INNER JOIN movies_see_later USING(id_trakt)\")\n  override suspend fun getAll(): List<Movie>\n\n  @Query(\"SELECT movies.id_trakt FROM movies INNER JOIN movies_see_later USING(id_trakt)\")\n  override suspend fun getAllTraktIds(): List<Long>\n\n  @Query(\"SELECT movies.* FROM movies INNER JOIN movies_see_later ON movies_see_later.id_trakt == movies.id_trakt WHERE movies.id_trakt == :traktId\")\n  override suspend fun getById(traktId: Long): Movie?\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun insert(movie: WatchlistMovie)\n\n  @Query(\"DELETE FROM movies_see_later WHERE id_trakt == :traktId\")\n  override suspend fun deleteById(traktId: Long)\n\n  @Query(\"SELECT EXISTS(SELECT 1 FROM movies_see_later WHERE id_trakt = :traktId LIMIT 1);\")\n  override suspend fun checkExists(traktId: Long): Boolean\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/dao/WatchlistShowsDao.kt",
    "content": "// ktlint-disable max-line-length\npackage com.michaldrabik.data_local.database.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.michaldrabik.data_local.database.model.Show\nimport com.michaldrabik.data_local.database.model.WatchlistShow\nimport com.michaldrabik.data_local.sources.WatchlistShowsLocalDataSource\n\n@Dao\ninterface WatchlistShowsDao : WatchlistShowsLocalDataSource {\n\n  @Query(\"SELECT shows.*, shows_see_later.created_at AS created_at, shows_see_later.updated_at AS updated_at FROM shows INNER JOIN shows_see_later USING(id_trakt)\")\n  override suspend fun getAll(): List<Show>\n\n  @Query(\"SELECT shows.id_trakt FROM shows INNER JOIN shows_see_later USING(id_trakt)\")\n  override suspend fun getAllTraktIds(): List<Long>\n\n  @Query(\"SELECT shows.* FROM shows INNER JOIN shows_see_later ON shows_see_later.id_trakt == shows.id_trakt WHERE shows.id_trakt == :traktId\")\n  override suspend fun getById(traktId: Long): Show?\n\n  @Insert(onConflict = OnConflictStrategy.REPLACE)\n  override suspend fun insert(show: WatchlistShow)\n\n  @Query(\"DELETE FROM shows_see_later WHERE id_trakt == :traktId\")\n  override suspend fun deleteById(traktId: Long)\n\n  @Query(\"SELECT EXISTS(SELECT 1 FROM shows_see_later WHERE id_trakt = :traktId LIMIT 1);\")\n  override suspend fun checkExists(traktId: Long): Boolean\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/migrations/Migrations.kt",
    "content": "package com.michaldrabik.data_local.database.migrations\n\nimport android.content.Context\nimport android.content.Context.MODE_PRIVATE\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nconst val DATABASE_VERSION = 38\nconst val DATABASE_NAME = \"SHOWLY2_DB_2\"\n\nclass Migrations(context: Context) {\n\n  private val migration2 = object : Migration(1, 2) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN show_anticipated_shows INTEGER NOT NULL DEFAULT 1\")\n    }\n  }\n\n  private val migration3 = object : Migration(2, 3) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"ALTER TABLE actors ADD COLUMN id_imdb TEXT\")\n    }\n  }\n\n  private val migration4 = object : Migration(3, 4) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN trakt_sync_schedule TEXT NOT NULL DEFAULT 'OFF'\")\n    }\n  }\n\n  private val migration5 = object : Migration(4, 5) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN my_shows_running_is_enabled INTEGER NOT NULL DEFAULT 1\")\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN my_shows_incoming_is_enabled INTEGER NOT NULL DEFAULT 1\")\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN my_shows_ended_is_enabled INTEGER NOT NULL DEFAULT 1\")\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN my_shows_recent_is_enabled INTEGER NOT NULL DEFAULT 1\")\n    }\n  }\n\n  private val migration6 = object : Migration(5, 6) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"CREATE INDEX index_episodes_id_show_trakt ON episodes(id_show_trakt)\")\n      database.execSQL(\"CREATE INDEX index_seasons_id_show_trakt ON seasons(id_show_trakt)\")\n    }\n  }\n\n  private val migration7 = object : Migration(6, 7) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN discover_filter_genres TEXT NOT NULL DEFAULT ''\")\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN discover_filter_feed TEXT NOT NULL DEFAULT 'HOT'\")\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN trakt_quick_sync_enabled INTEGER NOT NULL DEFAULT 0\")\n\n      database.execSQL(\n        \"CREATE TABLE IF NOT EXISTS `trakt_sync_queue` (\" +\n          \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n          \"`id_trakt` INTEGER NOT NULL, \" +\n          \"`type` TEXT NOT NULL, \" +\n          \"`created_at` INTEGER NOT NULL, \" +\n          \"`updated_at` INTEGER NOT NULL)\"\n      )\n    }\n  }\n\n  private val migration8 = object : Migration(7, 8) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN watchlist_sort_by TEXT NOT NULL DEFAULT 'NAME'\")\n    }\n  }\n\n  private val migration9 = object : Migration(8, 9) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN trakt_quick_remove_enabled INTEGER NOT NULL DEFAULT 0\")\n    }\n  }\n\n  private val migration10 = object : Migration(9, 10) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\n        \"CREATE TABLE IF NOT EXISTS `shows_archive` (\" +\n          \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n          \"`id_trakt` INTEGER NOT NULL, \" +\n          \"`created_at` INTEGER NOT NULL, \" +\n          \"`updated_at` INTEGER NOT NULL, \" +\n          \"FOREIGN KEY(`id_trakt`) REFERENCES `shows`(`id_trakt`) ON DELETE CASCADE)\"\n      )\n      database.execSQL(\"CREATE UNIQUE INDEX index_shows_archive_id_trakt ON shows_archive(id_trakt)\")\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN archive_shows_sort_by TEXT NOT NULL DEFAULT 'NAME'\")\n    }\n  }\n\n  private val migration11 = object : Migration(10, 11) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN archive_shows_include_statistics INTEGER NOT NULL DEFAULT 1\")\n    }\n  }\n\n  private val migration12 = object : Migration(11, 12) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN special_seasons_enabled INTEGER NOT NULL DEFAULT 0\")\n    }\n  }\n\n  private val migration13 = object : Migration(12, 13) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\n        \"CREATE TABLE IF NOT EXISTS `shows_translations` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n          \"`id_trakt` INTEGER NOT NULL, `title` TEXT NOT NULL, `language` TEXT NOT NULL, `overview` TEXT NOT NULL, \" +\n          \"`created_at` INTEGER NOT NULL, `updated_at` INTEGER NOT NULL, \" +\n          \"FOREIGN KEY(`id_trakt`) REFERENCES `shows`(`id_trakt`) ON DELETE CASCADE)\"\n      )\n      database.execSQL(\"CREATE UNIQUE INDEX index_shows_translations_id_trakt ON shows_translations(id_trakt)\")\n\n      database.execSQL(\n        \"CREATE TABLE IF NOT EXISTS `episodes_translations` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n          \"`id_trakt` INTEGER NOT NULL, `id_trakt_show` INTEGER NOT NULL, \" +\n          \"`title` TEXT NOT NULL, `language` TEXT NOT NULL, `overview` TEXT NOT NULL, \" +\n          \"`created_at` INTEGER NOT NULL, `updated_at` INTEGER NOT NULL, \" +\n          \"FOREIGN KEY(`id_trakt_show`) REFERENCES `shows`(`id_trakt`) ON DELETE CASCADE)\"\n      )\n      database.execSQL(\"CREATE UNIQUE INDEX index_episodes_translations_id_trakt ON episodes_translations(id_trakt)\")\n      database.execSQL(\"CREATE INDEX index_episodes_translations_id_trakt_show ON episodes_translations(id_trakt_show)\")\n\n      database.execSQL(\n        \"CREATE TABLE IF NOT EXISTS `sync_translations_log` (\" +\n          \"`id_show_trakt` INTEGER PRIMARY KEY NOT NULL, \" +\n          \"`synced_at` INTEGER NOT NULL)\"\n      )\n    }\n  }\n\n  private val migration14 = object : Migration(13, 14) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"ALTER TABLE shows_images ADD COLUMN source TEXT NOT NULL DEFAULT 'tvdb'\")\n    }\n  }\n\n  private val migration15 = object : Migration(14, 15) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN show_anticipated_movies INTEGER NOT NULL DEFAULT 0\")\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN discover_movies_filter_genres TEXT NOT NULL DEFAULT ''\")\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN discover_movies_filter_feed TEXT NOT NULL DEFAULT 'HOT'\")\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN my_movies_all_sort_by TEXT NOT NULL DEFAULT 'NAME'\")\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN see_later_movies_sort_by TEXT NOT NULL DEFAULT 'NAME'\")\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN progress_movies_sort_by TEXT NOT NULL DEFAULT 'NAME'\")\n\n      database.execSQL(\n        \"CREATE TABLE IF NOT EXISTS `movies` (\" +\n          \"`id_trakt` INTEGER PRIMARY KEY NOT NULL, \" +\n          \"`id_tmdb` INTEGER NOT NULL DEFAULT -1, \" +\n          \"`id_imdb` TEXT NOT NULL DEFAULT '', \" +\n          \"`id_slug` TEXT NOT NULL DEFAULT '', \" +\n          \"`title` TEXT NOT NULL DEFAULT '', \" +\n          \"`year` INTEGER NOT NULL DEFAULT -1, \" +\n          \"`overview` TEXT NOT NULL DEFAULT '', \" +\n          \"`released` TEXT NOT NULL DEFAULT '', \" +\n          \"`runtime` INTEGER NOT NULL DEFAULT -1, \" +\n          \"`country` TEXT NOT NULL DEFAULT '', \" +\n          \"`trailer` TEXT NOT NULL DEFAULT '', \" +\n          \"`language` TEXT NOT NULL DEFAULT '', \" +\n          \"`homepage` TEXT NOT NULL DEFAULT '', \" +\n          \"`status` TEXT NOT NULL DEFAULT '', \" +\n          \"`rating` REAL NOT NULL DEFAULT -1, \" +\n          \"`votes` INTEGER NOT NULL DEFAULT -1, \" +\n          \"`comment_count` INTEGER NOT NULL DEFAULT -1, \" +\n          \"`genres` TEXT NOT NULL DEFAULT '', \" +\n          \"`updated_at` INTEGER NOT NULL DEFAULT -1)\"\n      )\n\n      database.execSQL(\n        \"CREATE TABLE IF NOT EXISTS `movies_discover` (\" +\n          \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n          \"`id_trakt` INTEGER NOT NULL DEFAULT -1, \" +\n          \"`created_at` INTEGER NOT NULL DEFAULT -1, \" +\n          \"`updated_at` INTEGER NOT NULL DEFAULT -1, \" +\n          \"FOREIGN KEY(`id_trakt`) REFERENCES `movies`(`id_trakt`) ON DELETE CASCADE)\"\n      )\n      database.execSQL(\"CREATE INDEX index_discover_movies_id_trakt ON movies_discover(id_trakt)\")\n\n      database.execSQL(\n        \"CREATE TABLE IF NOT EXISTS `movies_images` (\" +\n          \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n          \"`id_tmdb` INTEGER NOT NULL DEFAULT -1, \" +\n          \"`type` TEXT NOT NULL DEFAULT '', \" +\n          \"`file_url` TEXT NOT NULL DEFAULT '', \" +\n          \"`source` TEXT NOT NULL DEFAULT 'tmdb')\"\n      )\n\n      database.execSQL(\n        \"CREATE TABLE IF NOT EXISTS `movies_translations` (\" +\n          \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n          \"`id_trakt` INTEGER NOT NULL, `title` TEXT NOT NULL, \" +\n          \"`language` TEXT NOT NULL, \" +\n          \"`overview` TEXT NOT NULL, \" +\n          \"`created_at` INTEGER NOT NULL, \" +\n          \"`updated_at` INTEGER NOT NULL, \" +\n          \"FOREIGN KEY(`id_trakt`) REFERENCES `movies`(`id_trakt`) ON DELETE CASCADE)\"\n      )\n      database.execSQL(\"CREATE UNIQUE INDEX index_movies_translations_id_trakt ON movies_translations(id_trakt)\")\n\n      database.execSQL(\n        \"CREATE TABLE IF NOT EXISTS `sync_movies_translations_log` (\" +\n          \"`id_movie_trakt` INTEGER PRIMARY KEY NOT NULL, \" +\n          \"`synced_at` INTEGER NOT NULL)\"\n      )\n\n      database.execSQL(\n        \"CREATE TABLE IF NOT EXISTS `movies_related` (\" +\n          \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n          \"`id_trakt` INTEGER NOT NULL  DEFAULT -1, \" +\n          \"`id_trakt_related_movie` INTEGER NOT NULL DEFAULT -1, \" +\n          \"`updated_at` INTEGER NOT NULL DEFAULT -1, \" +\n          \"FOREIGN KEY(`id_trakt_related_movie`) REFERENCES `movies`(`id_trakt`) ON DELETE CASCADE)\"\n      )\n      database.execSQL(\"CREATE INDEX index_movies_related_id_trakt ON movies_related(id_trakt_related_movie)\")\n\n      database.execSQL(\"ALTER TABLE actors ADD COLUMN id_tmdb_movie INTEGER NOT NULL DEFAULT -1\")\n      database.execSQL(\"ALTER TABLE actors ADD COLUMN id_tmdb INTEGER NOT NULL DEFAULT -1\")\n\n      database.execSQL(\n        \"CREATE TABLE IF NOT EXISTS `movies_my_movies` (\" +\n          \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n          \"`id_trakt` INTEGER NOT NULL DEFAULT -1, \" +\n          \"`created_at` INTEGER NOT NULL DEFAULT -1, \" +\n          \"`updated_at` INTEGER NOT NULL DEFAULT -1, \" +\n          \"FOREIGN KEY(`id_trakt`) REFERENCES `movies`(`id_trakt`) ON DELETE CASCADE)\"\n      )\n      database.execSQL(\"CREATE INDEX index_movies_my_movies_id_trakt ON movies_my_movies(id_trakt)\")\n\n      database.execSQL(\n        \"CREATE TABLE IF NOT EXISTS `movies_see_later` (\" +\n          \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n          \"`id_trakt` INTEGER NOT NULL DEFAULT -1, \" +\n          \"`created_at` INTEGER NOT NULL DEFAULT -1, \" +\n          \"`updated_at` INTEGER NOT NULL DEFAULT -1, \" +\n          \"FOREIGN KEY(`id_trakt`) REFERENCES `movies`(`id_trakt`) ON DELETE CASCADE)\"\n      )\n      database.execSQL(\"CREATE INDEX index_movies_see_later_id_trakt ON movies_see_later(id_trakt)\")\n\n      database.execSQL(\n        \"CREATE TABLE IF NOT EXISTS `sync_movies_log` (\" +\n          \"`id_movie_trakt` INTEGER PRIMARY KEY NOT NULL DEFAULT -1, \" +\n          \"`synced_at` INTEGER NOT NULL DEFAULT 0)\"\n      )\n\n      database.execSQL(\n        \"CREATE TABLE IF NOT EXISTS `sync_trakt_log` (\" +\n          \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n          \"`id_trakt` INTEGER NOT NULL, \" +\n          \"`type` TEXT NOT NULL, \" +\n          \"`synced_at` INTEGER NOT NULL)\"\n      )\n      database.execSQL(\"CREATE INDEX index_sync_trakt_log_id_trakt ON sync_trakt_log(id_trakt)\")\n      database.execSQL(\"CREATE INDEX index_sync_trakt_log_type ON sync_trakt_log(type)\")\n      database.execSQL(\"CREATE UNIQUE INDEX index_sync_trakt_log_id_trakt_type ON sync_trakt_log(id_trakt, type)\")\n    }\n  }\n\n  private val migration16 = object : Migration(15, 16) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN show_collection_shows INTEGER NOT NULL DEFAULT 1\")\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN show_collection_movies INTEGER NOT NULL DEFAULT 1\")\n    }\n  }\n\n  private val migration17 = object : Migration(16, 17) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN widgets_show_label INTEGER NOT NULL DEFAULT 1\")\n    }\n  }\n\n  private val migration18 = object : Migration(17, 18) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\"ALTER TABLE actors ADD COLUMN id_tmdb_show INTEGER NOT NULL DEFAULT -1\")\n        execSQL(\"DELETE FROM actors\")\n\n        execSQL(\"ALTER TABLE shows_images ADD COLUMN id_tmdb INTEGER NOT NULL DEFAULT -1\")\n        execSQL(\"DELETE FROM shows_images WHERE source = 'tvdb' OR family = 'movie'\")\n\n        execSQL(\"CREATE INDEX index_shows_images_tmdb_id_type_family ON shows_images(id_tmdb, type, family)\")\n        execSQL(\"CREATE INDEX index_movies_images_tmdb_id_type ON movies_images(id_tmdb, type)\")\n      }\n    }\n  }\n\n  private val migration19 = object : Migration(18, 19) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\"ALTER TABLE settings ADD COLUMN my_movies_recent_is_enabled INTEGER NOT NULL DEFAULT 1\")\n      }\n    }\n  }\n\n  private val migration20 = object : Migration(19, 20) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `custom_images` (\" +\n            \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n            \"`id_trakt` INTEGER NOT NULL, \" +\n            \"`family` TEXT NOT NULL, \" +\n            \"`type` TEXT NOT NULL, \" +\n            \"`file_url` TEXT NOT NULL)\"\n        )\n        execSQL(\"CREATE INDEX index_custom_images_trakt_id_family_type ON custom_images(id_trakt, family, type)\")\n      }\n    }\n  }\n\n  private val migration21 = object : Migration(20, 21) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\"ALTER TABLE settings ADD COLUMN quick_rate_enabled INTEGER NOT NULL DEFAULT 0\")\n      }\n    }\n  }\n\n  private val migration22 = object : Migration(21, 22) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\"ALTER TABLE shows ADD COLUMN created_at INTEGER NOT NULL DEFAULT -1\")\n        val cursor = database.query(\"SELECT id_trakt, updated_at FROM shows\")\n        while (cursor.moveToNext()) {\n          val id = cursor.getLong(cursor.getColumnIndexOrThrow(\"id_trakt\"))\n          val updatedAt = cursor.getLong(cursor.getColumnIndexOrThrow(\"updated_at\"))\n          execSQL(\"UPDATE shows SET created_at = $updatedAt WHERE id_trakt == $id\")\n        }\n      }\n    }\n  }\n\n  private val migration23 = object : Migration(22, 23) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `custom_lists` (\" +\n            \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n            \"`id_trakt` INTEGER, \" +\n            \"`id_slug` TEXT NOT NULL, \" +\n            \"`name` TEXT NOT NULL, \" +\n            \"`description` TEXT, \" +\n            \"`privacy` TEXT NOT NULL, \" +\n            \"`display_numbers` INTEGER NOT NULL, \" +\n            \"`allow_comments` INTEGER NOT NULL, \" +\n            \"`sort_by` TEXT NOT NULL, \" +\n            \"`sort_how` TEXT NOT NULL, \" +\n            \"`sort_by_local` TEXT NOT NULL, \" +\n            \"`sort_how_local` TEXT NOT NULL, \" +\n            \"`filter_type_local` TEXT NOT NULL, \" +\n            \"`item_count` INTEGER NOT NULL, \" +\n            \"`comment_count` INTEGER NOT NULL, \" +\n            \"`likes` INTEGER NOT NULL, \" +\n            \"`created_at` INTEGER NOT NULL, \" +\n            \"`updated_at` INTEGER NOT NULL\" +\n            \")\"\n        )\n        execSQL(\"CREATE UNIQUE INDEX index_custom_lists_id_trakt ON custom_lists(id_trakt)\")\n        execSQL(\"ALTER TABLE settings ADD COLUMN lists_sort_by TEXT NOT NULL DEFAULT 'DATE_UPDATED'\")\n      }\n    }\n  }\n\n  private val migration24 = object : Migration(23, 24) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `custom_list_item` (\" +\n            \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n            \"`id_list` INTEGER NOT NULL, \" +\n            \"`id_trakt` INTEGER NOT NULL, \" +\n            \"`type` TEXT NOT NULL, \" +\n            \"`rank` INTEGER NOT NULL, \" +\n            \"`listed_at` INTEGER NOT NULL, \" +\n            \"`created_at` INTEGER NOT NULL, \" +\n            \"`updated_at` INTEGER NOT NULL, \" +\n            \"FOREIGN KEY(`id_list`) REFERENCES `custom_lists`(`id`) ON DELETE CASCADE\" +\n            \")\"\n        )\n        execSQL(\"CREATE INDEX index_custom_list_item_id_list ON custom_list_item(id_list)\")\n        execSQL(\"CREATE INDEX index_custom_list_item_id_trakt_type ON custom_list_item(id_trakt, type)\")\n        execSQL(\"CREATE UNIQUE INDEX index_custom_list_item_id_list_id_trakt_type ON custom_list_item(id_list, id_trakt, type)\")\n\n        execSQL(\"ALTER TABLE trakt_sync_queue ADD COLUMN id_list INTEGER\")\n        execSQL(\"ALTER TABLE trakt_sync_queue ADD COLUMN operation TEXT NOT NULL DEFAULT ''\")\n      }\n    }\n  }\n\n  private val migration25 = object : Migration(24, 25) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `news` (\" +\n            \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n            \"`id_news` TEXT NOT NULL, \" +\n            \"`title` TEXT NOT NULL, \" +\n            \"`url` TEXT NOT NULL, \" +\n            \"`type` TEXT NOT NULL, \" +\n            \"`image` TEXT, \" +\n            \"`score` INTEGER NOT NULL, \" +\n            \"`dated_at` INTEGER NOT NULL, \" +\n            \"`created_at` INTEGER NOT NULL, \" +\n            \"`updated_at` INTEGER NOT NULL \" +\n            \")\"\n        )\n\n        execSQL(\"ALTER TABLE user ADD COLUMN reddit_token TEXT NOT NULL DEFAULT ''\")\n        execSQL(\"ALTER TABLE user ADD COLUMN reddit_token_timestamp INTEGER NOT NULL DEFAULT 0\")\n      }\n    }\n  }\n\n  private val migration26 = object : Migration(25, 26) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\"ALTER TABLE settings ADD COLUMN progress_upcoming_enabled INTEGER NOT NULL DEFAULT 1\")\n      }\n    }\n  }\n\n  private val migration27 = object : Migration(26, 27) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `movies_ratings` (\" +\n            \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n            \"`id_trakt` INTEGER NOT NULL, \" +\n            \"`trakt` TEXT, \" +\n            \"`imdb` TEXT, \" +\n            \"`metascore` TEXT, \" +\n            \"`rotten_tomatoes` TEXT, \" +\n            \"`rotten_tomatoes_url` TEXT, \" +\n            \"`created_at` INTEGER NOT NULL, \" +\n            \"`updated_at` INTEGER NOT NULL, \" +\n            \"FOREIGN KEY(`id_trakt`) REFERENCES `movies`(`id_trakt`) ON DELETE CASCADE)\"\n        )\n        execSQL(\"CREATE UNIQUE INDEX index_movies_ratings_id_trakt ON movies_ratings(id_trakt)\")\n\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `shows_ratings` (\" +\n            \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n            \"`id_trakt` INTEGER NOT NULL, \" +\n            \"`trakt` TEXT, \" +\n            \"`imdb` TEXT, \" +\n            \"`metascore` TEXT, \" +\n            \"`rotten_tomatoes` TEXT, \" +\n            \"`rotten_tomatoes_url` TEXT, \" +\n            \"`created_at` INTEGER NOT NULL, \" +\n            \"`updated_at` INTEGER NOT NULL, \" +\n            \"FOREIGN KEY(`id_trakt`) REFERENCES `shows`(`id_trakt`) ON DELETE CASCADE)\"\n        )\n        execSQL(\"CREATE UNIQUE INDEX index_shows_ratings_id_trakt ON shows_ratings(id_trakt)\")\n      }\n    }\n  }\n\n  private val migration28 = object : Migration(27, 28) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `movies_streamings` (\" +\n            \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n            \"`id_trakt` INTEGER NOT NULL, \" +\n            \"`id_tmdb` INTEGER NOT NULL, \" +\n            \"`type` TEXT, \" +\n            \"`provider_id` INTEGER, \" +\n            \"`provider_name` TEXT, \" +\n            \"`display_priority` INTEGER, \" +\n            \"`logo_path` TEXT, \" +\n            \"`link` TEXT, \" +\n            \"`created_at` INTEGER NOT NULL, \" +\n            \"`updated_at` INTEGER NOT NULL, \" +\n            \"FOREIGN KEY(`id_trakt`) REFERENCES `movies`(`id_trakt`) ON DELETE CASCADE)\"\n        )\n        execSQL(\"CREATE INDEX index_movies_streamings_id_trakt ON movies_streamings(id_trakt)\")\n        execSQL(\"CREATE INDEX index_movies_streamings_id_tmdb ON movies_streamings(id_tmdb)\")\n\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `shows_streamings` (\" +\n            \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n            \"`id_trakt` INTEGER NOT NULL, \" +\n            \"`id_tmdb` INTEGER NOT NULL, \" +\n            \"`type` TEXT, \" +\n            \"`provider_id` INTEGER, \" +\n            \"`provider_name` TEXT, \" +\n            \"`display_priority` INTEGER, \" +\n            \"`logo_path` TEXT, \" +\n            \"`link` TEXT, \" +\n            \"`created_at` INTEGER NOT NULL, \" +\n            \"`updated_at` INTEGER NOT NULL, \" +\n            \"FOREIGN KEY(`id_trakt`) REFERENCES `shows`(`id_trakt`) ON DELETE CASCADE)\"\n        )\n        execSQL(\"CREATE INDEX index_shows_streamings_id_trakt ON shows_streamings(id_trakt)\")\n        execSQL(\"CREATE INDEX index_shows_streamings_id_tmdb ON shows_streamings(id_tmdb)\")\n      }\n    }\n  }\n\n  private val migration29 = object : Migration(28, 29) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\"ALTER TABLE movies ADD COLUMN created_at INTEGER NOT NULL DEFAULT -1\")\n        val cursor = database.query(\"SELECT id_trakt, updated_at FROM movies\")\n        while (cursor.moveToNext()) {\n          val id = cursor.getLong(cursor.getColumnIndexOrThrow(\"id_trakt\"))\n          val updatedAt = cursor.getLong(cursor.getColumnIndexOrThrow(\"updated_at\"))\n          execSQL(\"UPDATE movies SET created_at = $updatedAt WHERE id_trakt == $id\")\n        }\n\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `movies_archive` (\" +\n            \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n            \"`id_trakt` INTEGER NOT NULL, \" +\n            \"`created_at` INTEGER NOT NULL, \" +\n            \"`updated_at` INTEGER NOT NULL, \" +\n            \"FOREIGN KEY(`id_trakt`) REFERENCES `movies`(`id_trakt`) ON DELETE CASCADE)\"\n        )\n        execSQL(\"CREATE UNIQUE INDEX index_movies_archive_id_trakt ON movies_archive(id_trakt)\")\n      }\n    }\n  }\n\n  private val migration30 = object : Migration(29, 30) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\"DROP TABLE actors\")\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `people` (\" +\n            \"`id_tmdb` INTEGER PRIMARY KEY NOT NULL, \" +\n            \"`id_trakt` INTEGER, \" +\n            \"`id_imdb` TEXT, \" +\n            \"`name` TEXT NOT NULL, \" +\n            \"`department` TEXT NOT NULL, \" +\n            \"`biography` TEXT, \" +\n            \"`biography_translation` TEXT, \" +\n            \"`birthday` TEXT, \" +\n            \"`character` TEXT, \" +\n            \"`job` TEXT, \" +\n            \"`episodes_count` INTEGER, \" +\n            \"`birthplace` TEXT, \" +\n            \"`deathday` TEXT, \" +\n            \"`image_path` TEXT, \" +\n            \"`homepage` TEXT, \" +\n            \"`created_at` INTEGER NOT NULL, \" +\n            \"`details_updated_at` INTEGER, \" +\n            \"`updated_at` INTEGER NOT NULL)\"\n        )\n        execSQL(\"CREATE INDEX index_people_id_trakt ON people(id_trakt)\")\n        execSQL(\"CREATE UNIQUE INDEX index_people_id_tmdb ON people(id_tmdb)\")\n\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `people_shows_movies` (\" +\n            \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n            \"`id_tmdb_person` INTEGER NOT NULL, \" +\n            \"`mode` TEXT NOT NULL, \" +\n            \"`department` TEXT NOT NULL, \" +\n            \"`character` TEXT, \" +\n            \"`job` TEXT, \" +\n            \"`episodes_count` INTEGER NOT NULL, \" +\n            \"`id_trakt_show` INTEGER, \" +\n            \"`id_trakt_movie` INTEGER, \" +\n            \"`created_at` INTEGER NOT NULL, \" +\n            \"`updated_at` INTEGER NOT NULL, \" +\n            \"FOREIGN KEY(`id_tmdb_person`) REFERENCES `people`(`id_tmdb`) ON DELETE CASCADE)\"\n        )\n        execSQL(\"CREATE INDEX index_people_shows_movies_id_show_mode ON people_shows_movies(id_trakt_show, mode)\")\n        execSQL(\"CREATE INDEX index_people_shows_movies_id_movie_mode ON people_shows_movies(id_trakt_movie, mode)\")\n\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `people_credits` (\" +\n            \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n            \"`id_trakt_person` INTEGER NOT NULL, \" +\n            \"`id_trakt_show` INTEGER, \" +\n            \"`id_trakt_movie` INTEGER, \" +\n            \"`type` TEXT NOT NULL, \" +\n            \"`created_at` INTEGER NOT NULL, \" +\n            \"`updated_at` INTEGER NOT NULL, \" +\n            \"FOREIGN KEY(`id_trakt_show`) REFERENCES `shows`(`id_trakt`) ON DELETE CASCADE, \" +\n            \"FOREIGN KEY(`id_trakt_movie`) REFERENCES `movies`(`id_trakt`) ON DELETE CASCADE)\"\n        )\n        execSQL(\"CREATE INDEX index_people_credits_id_person ON people_credits(id_trakt_person)\")\n\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `people_images` (\" +\n            \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n            \"`id_tmdb` INTEGER NOT NULL, \" +\n            \"`file_path` TEXT NOT NULL, \" +\n            \"`created_at` INTEGER NOT NULL, \" +\n            \"`updated_at` INTEGER NOT NULL)\"\n        )\n        execSQL(\"CREATE INDEX index_people_images_id_person ON people_images(id_tmdb)\")\n      }\n    }\n  }\n\n  private val migration31 = object : Migration(30, 31) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `ratings` (\" +\n            \"`id_trakt` INTEGER NOT NULL, \" +\n            \"`type` TEXT NOT NULL, \" +\n            \"`rating` INTEGER NOT NULL, \" +\n            \"`season_number` INTEGER, \" +\n            \"`episode_number` INTEGER, \" +\n            \"`rated_at` INTEGER NOT NULL, \" +\n            \"`created_at` INTEGER NOT NULL, \" +\n            \"`updated_at` INTEGER NOT NULL, \" +\n            \"PRIMARY KEY (id_trakt, type))\"\n        )\n        execSQL(\"CREATE INDEX index_ratings_id_trakt_type ON ratings(id_trakt, type)\")\n\n        execSQL(\"ALTER TABLE seasons ADD COLUMN rating REAL\")\n\n        execSQL(\"CREATE INDEX index_people_credits_show ON people_credits(id_trakt_show)\")\n        execSQL(\"CREATE INDEX index_people_credits_movies ON people_credits(id_trakt_movie)\")\n        execSQL(\"CREATE INDEX index_people_shows_movies_tmdb_person ON people_shows_movies(id_tmdb_person)\")\n      }\n    }\n  }\n\n  private val migration32 = object : Migration(31, 32) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"ALTER TABLE episodes ADD COLUMN episode_number_abs INTEGER\")\n    }\n  }\n\n  private val migration33 = object : Migration(32, 33) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      val preferences = context.applicationContext.getSharedPreferences(\"PREFERENCES_NETWORK\", MODE_PRIVATE)\n      val preferencesEditor = preferences.edit()\n      val cursor = database.query(\"SELECT trakt_token, trakt_refresh_token FROM user\")\n      while (cursor.moveToNext()) {\n        val accessToken = cursor.getString(cursor.getColumnIndexOrThrow(\"trakt_token\"))\n        val refreshToken = cursor.getString(cursor.getColumnIndexOrThrow(\"trakt_refresh_token\"))\n        accessToken?.let { preferencesEditor.putString(\"TRAKT_ACCESS_TOKEN\", it) }\n        refreshToken?.let { preferencesEditor.putString(\"TRAKT_REFRESH_TOKEN\", it) }\n      }\n      preferencesEditor.apply()\n    }\n  }\n\n  private val migration34 = object : Migration(33, 34) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\"ALTER TABLE episodes ADD COLUMN last_watched_at INTEGER\")\n        execSQL(\"ALTER TABLE shows_my_shows ADD COLUMN last_watched_at INTEGER\")\n        val cursor = database.query(\"SELECT id_trakt, updated_at FROM shows_my_shows\")\n        while (cursor.moveToNext()) {\n          val idShow = cursor.getLong(cursor.getColumnIndexOrThrow(\"id_trakt\"))\n          val updatedAt = cursor.getLong(cursor.getColumnIndexOrThrow(\"updated_at\"))\n\n          val epCursor = database.query(\n            \"SELECT season_number, episode_number FROM episodes WHERE id_show_trakt == $idShow AND is_watched == 1 \" +\n              \"ORDER BY season_number DESC, episode_number DESC LIMIT 1\"\n          )\n          while (epCursor.moveToNext()) {\n            val episodeNumber = epCursor.getLong(epCursor.getColumnIndexOrThrow(\"episode_number\"))\n            val seasonNumber = epCursor.getLong(epCursor.getColumnIndexOrThrow(\"season_number\"))\n            if (updatedAt > 0 && episodeNumber > 0 && seasonNumber > 0) {\n              execSQL(\n                \"UPDATE episodes SET last_watched_at = $updatedAt \" +\n                  \"WHERE id_show_trakt == $idShow \" +\n                  \"AND is_watched == 1 \" +\n                  \"AND episode_number == $episodeNumber AND season_number == $seasonNumber\"\n              )\n              execSQL(\"UPDATE shows_my_shows SET last_watched_at = $updatedAt WHERE id_trakt == $idShow\")\n            }\n          }\n        }\n      }\n    }\n  }\n\n  private val migration35 = object : Migration(34, 35) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      database.execSQL(\"ALTER TABLE settings ADD COLUMN discover_filter_networks TEXT NOT NULL DEFAULT ''\")\n    }\n  }\n\n  private val migration36 = object : Migration(35, 36) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `movies_collections` (\" +\n            \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n            \"`id_trakt` INTEGER NOT NULL, \" +\n            \"`id_trakt_movie` INTEGER NOT NULL, \" +\n            \"`name` TEXT NOT NULL, \" +\n            \"`description` TEXT NOT NULL, \" +\n            \"`item_count` INTEGER NOT NULL, \" +\n            \"`created_at` INTEGER NOT NULL, \" +\n            \"`updated_at` INTEGER NOT NULL, \" +\n            \"FOREIGN KEY(`id_trakt_movie`) REFERENCES `movies`(`id_trakt`) ON DELETE CASCADE)\"\n        )\n        execSQL(\"CREATE INDEX index_movies_collections_id_trakt ON movies_collections(id_trakt)\")\n        execSQL(\"CREATE INDEX index_movies_collections_id_trakt_movie ON movies_collections(id_trakt_movie)\")\n\n        execSQL(\n          \"CREATE TABLE IF NOT EXISTS `movies_collections_items` (\" +\n            \"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \" +\n            \"`id_trakt` INTEGER NOT NULL, \" +\n            \"`id_trakt_collection` INTEGER NOT NULL, \" +\n            \"`rank` INTEGER NOT NULL, \" +\n            \"`created_at` INTEGER NOT NULL, \" +\n            \"`updated_at` INTEGER NOT NULL, \" +\n            \"FOREIGN KEY(`id_trakt`) REFERENCES `movies`(`id_trakt`) ON DELETE CASCADE)\"\n        )\n        execSQL(\"CREATE INDEX index_movies_collections_items_id_trakt ON movies_collections_items(id_trakt)\")\n        execSQL(\"CREATE INDEX index_movies_collections_items_id_trakt_collection ON movies_collections_items(id_trakt_collection)\")\n      }\n    }\n  }\n\n  private val migration37 = object : Migration(36, 37) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        val cursor = database.query(\"SELECT my_shows_recent_is_enabled, my_movies_recent_is_enabled FROM settings\")\n        while (cursor.moveToNext()) {\n          val myShowsRecentsEnabled = cursor.getInt(cursor.getColumnIndexOrThrow(\"my_shows_recent_is_enabled\"))\n          val myMoviesRecentsEnabled = cursor.getInt(cursor.getColumnIndexOrThrow(\"my_movies_recent_is_enabled\"))\n\n          if (myShowsRecentsEnabled != 1 && myMoviesRecentsEnabled != 1) {\n            execSQL(\"UPDATE settings SET my_shows_recent_amount = 0\")\n          }\n        }\n      }\n    }\n  }\n\n  private val migration38 = object : Migration(37, 38) {\n    override fun migrate(database: SupportSQLiteDatabase) {\n      with(database) {\n        val cursor = database.query(\"SELECT progress_upcoming_enabled FROM settings\")\n        while (cursor.moveToNext()) {\n          val isEnabled = cursor.getInt(cursor.getColumnIndexOrThrow(\"progress_upcoming_enabled\"))\n          if (isEnabled != 1) {\n            val preferences = context.applicationContext.getSharedPreferences(\"PREFERENCES_MISC\", MODE_PRIVATE)\n            preferences.edit().apply {\n              putLong(\"PROGRESS_UPCOMING_DAYS\", 0)\n              apply()\n            }\n          }\n        }\n      }\n    }\n  }\n\n  fun getAll() = listOf(\n    migration2,\n    migration3,\n    migration4,\n    migration5,\n    migration6,\n    migration7,\n    migration8,\n    migration9,\n    migration10,\n    migration11,\n    migration12,\n    migration13,\n    migration14,\n    migration15,\n    migration16,\n    migration17,\n    migration18,\n    migration19,\n    migration20,\n    migration21,\n    migration22,\n    migration23,\n    migration24,\n    migration25,\n    migration26,\n    migration27,\n    migration28,\n    migration29,\n    migration30,\n    migration31,\n    migration32,\n    migration33,\n    migration34,\n    migration35,\n    migration36,\n    migration37,\n    migration38\n  )\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/ArchiveMovie.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"movies_archive\",\n  indices = [Index(value = [\"id_trakt\"], unique = true)],\n  foreignKeys = [\n    ForeignKey(\n      entity = Movie::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\ndata class ArchiveMovie(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"created_at\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: Long\n) {\n\n  companion object {\n    fun fromTraktId(traktId: Long, createdAt: Long) =\n      ArchiveMovie(\n        idTrakt = traktId,\n        createdAt = createdAt,\n        updatedAt = createdAt\n      )\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/ArchiveShow.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"shows_archive\",\n  indices = [Index(value = [\"id_trakt\"], unique = true)],\n  foreignKeys = [\n    ForeignKey(\n      entity = Show::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\ndata class ArchiveShow(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"created_at\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: Long\n) {\n\n  companion object {\n    fun fromTraktId(traktId: Long, createdAt: Long) =\n      ArchiveShow(\n        idTrakt = traktId,\n        createdAt = createdAt,\n        updatedAt = createdAt\n      )\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/CustomImage.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"custom_images\",\n  indices = [\n    Index(value = [\"id_trakt\", \"family\", \"type\"])\n  ]\n)\ndata class CustomImage(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"family\") val family: String,\n  @ColumnInfo(name = \"type\") val type: String,\n  @ColumnInfo(name = \"file_url\") val fileUrl: String\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/CustomList.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"custom_lists\",\n  indices = [\n    Index(value = [\"id_trakt\"], unique = true)\n  ]\n)\ndata class CustomList(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long?,\n  @ColumnInfo(name = \"id_slug\") val idSlug: String,\n  @ColumnInfo(name = \"name\") val name: String,\n  @ColumnInfo(name = \"description\") val description: String?,\n  @ColumnInfo(name = \"privacy\") val privacy: String,\n  @ColumnInfo(name = \"display_numbers\") val displayNumbers: Boolean,\n  @ColumnInfo(name = \"allow_comments\") val allowComments: Boolean,\n  @ColumnInfo(name = \"sort_by\") val sortBy: String,\n  @ColumnInfo(name = \"sort_how\") val sortHow: String,\n  @ColumnInfo(name = \"sort_by_local\") val sortByLocal: String,\n  @ColumnInfo(name = \"sort_how_local\") val sortHowLocal: String,\n  @ColumnInfo(name = \"filter_type_local\") val filterTypeLocal: String,\n  @ColumnInfo(name = \"item_count\") val itemCount: Long,\n  @ColumnInfo(name = \"comment_count\") val commentCount: Long,\n  @ColumnInfo(name = \"likes\") val likes: Long,\n  @ColumnInfo(name = \"created_at\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: Long\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/CustomListItem.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"custom_list_item\",\n  indices = [\n    Index(value = [\"id_list\"], unique = false),\n    Index(value = [\"id_trakt\", \"type\"], unique = false),\n    Index(value = [\"id_list\", \"id_trakt\", \"type\"], unique = true)\n  ],\n  foreignKeys = [\n    ForeignKey(\n      entity = CustomList::class,\n      parentColumns = arrayOf(\"id\"),\n      childColumns = arrayOf(\"id_list\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\ndata class CustomListItem(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_list\") val idList: Long,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"type\") val type: String,\n  @ColumnInfo(name = \"rank\") val rank: Long,\n  @ColumnInfo(name = \"listed_at\") val listedAt: Long,\n  @ColumnInfo(name = \"created_at\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: Long\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/DiscoverMovie.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"movies_discover\",\n  foreignKeys = [\n    ForeignKey(\n      entity = Movie::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\ndata class DiscoverMovie(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\", defaultValue = \"-1\", index = true) val idTrakt: Long,\n  @ColumnInfo(name = \"created_at\", defaultValue = \"-1\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\", defaultValue = \"-1\") val updatedAt: Long\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/DiscoverShow.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"shows_discover\",\n  foreignKeys = [\n    ForeignKey(\n      entity = Show::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\ndata class DiscoverShow(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\", defaultValue = \"-1\", index = true) val idTrakt: Long,\n  @ColumnInfo(name = \"created_at\", defaultValue = \"-1\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\", defaultValue = \"-1\") val updatedAt: Long\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/Episode.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.ForeignKey.Companion.CASCADE\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverters\nimport com.michaldrabik.data_local.database.converters.DateConverter\nimport java.time.ZonedDateTime\n\n@Entity(\n  tableName = \"episodes\",\n  foreignKeys = [\n    ForeignKey(\n      entity = Season::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_season\"),\n      onDelete = CASCADE\n    )\n  ],\n  indices = [\n    Index(\"id_season\"),\n    Index(\"id_show_trakt\")\n  ]\n)\n@TypeConverters(DateConverter::class)\ndata class Episode(\n  @PrimaryKey @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"id_season\") val idSeason: Long,\n  @ColumnInfo(name = \"id_show_trakt\") val idShowTrakt: Long,\n  @ColumnInfo(name = \"id_show_tvdb\") val idShowTvdb: Long,\n  @ColumnInfo(name = \"id_show_imdb\") val idShowImdb: String,\n  @ColumnInfo(name = \"id_show_tmdb\") val idShowTmdb: Long,\n  @ColumnInfo(name = \"season_number\") val seasonNumber: Int,\n  @ColumnInfo(name = \"episode_number\") val episodeNumber: Int,\n  @ColumnInfo(name = \"episode_number_abs\") val episodeNumberAbs: Int?,\n  @ColumnInfo(name = \"episode_overview\") val episodeOverview: String,\n  @ColumnInfo(name = \"episode_title\") val title: String,\n  @ColumnInfo(name = \"first_aired\") val firstAired: ZonedDateTime?,\n  @ColumnInfo(name = \"comments_count\") val commentsCount: Int,\n  @ColumnInfo(name = \"rating\") val rating: Float,\n  @ColumnInfo(name = \"runtime\") val runtime: Int,\n  @ColumnInfo(name = \"votes_count\") val votesCount: Int,\n  @ColumnInfo(name = \"is_watched\") val isWatched: Boolean,\n  @ColumnInfo(name = \"last_watched_at\") val lastWatchedAt: ZonedDateTime?,\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/EpisodeTranslation.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"episodes_translations\",\n  indices = [\n    Index(value = [\"id_trakt\"], unique = true),\n    Index(value = [\"id_trakt_show\"])\n  ],\n  foreignKeys = [\n    ForeignKey(\n      entity = Show::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt_show\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\ndata class EpisodeTranslation(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"id_trakt_show\") val idTraktShow: Long,\n  @ColumnInfo(name = \"title\") val title: String,\n  @ColumnInfo(name = \"language\") val language: String,\n  @ColumnInfo(name = \"overview\") val overview: String,\n  @ColumnInfo(name = \"created_at\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: Long\n) {\n\n  companion object {\n    fun fromTraktId(\n      traktEpisodeId: Long,\n      traktShowId: Long,\n      title: String,\n      language: String,\n      overview: String,\n      createdAt: Long\n    ) =\n      EpisodeTranslation(\n        idTrakt = traktEpisodeId,\n        idTraktShow = traktShowId,\n        title = title,\n        language = language,\n        overview = overview,\n        createdAt = createdAt,\n        updatedAt = createdAt\n      )\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/EpisodesSyncLog.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity(tableName = \"sync_episodes_log\")\ndata class EpisodesSyncLog(\n  @PrimaryKey @ColumnInfo(name = \"id_show_trakt\", defaultValue = \"-1\") val idTrakt: Long,\n  @ColumnInfo(name = \"synced_at\", defaultValue = \"0\") val syncedAt: Long\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/Movie.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity(tableName = \"movies\")\ndata class Movie(\n  @PrimaryKey @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"id_tmdb\", defaultValue = \"-1\") val idTmdb: Long,\n  @ColumnInfo(name = \"id_imdb\", defaultValue = \"\") val idImdb: String,\n  @ColumnInfo(name = \"id_slug\", defaultValue = \"\") val idSlug: String,\n  @ColumnInfo(name = \"title\", defaultValue = \"\") val title: String,\n  @ColumnInfo(name = \"year\", defaultValue = \"-1\") val year: Int,\n  @ColumnInfo(name = \"overview\", defaultValue = \"\") val overview: String,\n  @ColumnInfo(name = \"released\", defaultValue = \"\") val released: String,\n  @ColumnInfo(name = \"runtime\", defaultValue = \"-1\") val runtime: Int,\n  @ColumnInfo(name = \"country\", defaultValue = \"\") val country: String,\n  @ColumnInfo(name = \"trailer\", defaultValue = \"\") val trailer: String,\n  @ColumnInfo(name = \"language\", defaultValue = \"\") val language: String,\n  @ColumnInfo(name = \"homepage\", defaultValue = \"\") val homepage: String,\n  @ColumnInfo(name = \"status\", defaultValue = \"\") val status: String,\n  @ColumnInfo(name = \"rating\", defaultValue = \"-1\") val rating: Float,\n  @ColumnInfo(name = \"votes\", defaultValue = \"-1\") val votes: Long,\n  @ColumnInfo(name = \"comment_count\", defaultValue = \"-1\") val commentCount: Long,\n  @ColumnInfo(name = \"genres\", defaultValue = \"\") val genres: String,\n  @ColumnInfo(name = \"updated_at\", defaultValue = \"-1\") val updatedAt: Long,\n  @ColumnInfo(name = \"created_at\", defaultValue = \"-1\") val createdAt: Long\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/MovieCollection.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverters\nimport com.michaldrabik.data_local.database.converters.DateConverter\nimport java.time.ZonedDateTime\n\n@Entity(\n  tableName = \"movies_collections\",\n  indices = [\n    Index(value = [\"id_trakt\"]),\n    Index(value = [\"id_trakt_movie\"]),\n  ],\n  foreignKeys = [\n    ForeignKey(\n      entity = Movie::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt_movie\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\n@TypeConverters(DateConverter::class)\ndata class MovieCollection(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"id_trakt_movie\") val idTraktMovie: Long,\n  @ColumnInfo(name = \"name\") val name: String,\n  @ColumnInfo(name = \"description\") val description: String,\n  @ColumnInfo(name = \"item_count\") val itemCount: Int,\n  @ColumnInfo(name = \"created_at\") val createdAt: ZonedDateTime,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: ZonedDateTime,\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/MovieCollectionItem.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverters\nimport com.michaldrabik.data_local.database.converters.DateConverter\nimport java.time.ZonedDateTime\n\n@Entity(\n  tableName = \"movies_collections_items\",\n  indices = [\n    Index(value = [\"id_trakt\"]),\n    Index(value = [\"id_trakt_collection\"]),\n  ],\n  foreignKeys = [\n    ForeignKey(\n      entity = Movie::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\n@TypeConverters(DateConverter::class)\ndata class MovieCollectionItem(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"id_trakt_collection\") val idTraktCollection: Long,\n  @ColumnInfo(name = \"rank\") val rank: Int,\n  @ColumnInfo(name = \"created_at\") val createdAt: ZonedDateTime,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: ZonedDateTime,\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/MovieImage.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"movies_images\",\n  indices = [\n    Index(value = [\"id_tmdb\", \"type\"])\n  ]\n)\ndata class MovieImage(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_tmdb\", defaultValue = \"-1\") val idTmdb: Long,\n  @ColumnInfo(name = \"type\", defaultValue = \"\") val type: String,\n  @ColumnInfo(name = \"file_url\", defaultValue = \"\") val fileUrl: String,\n  @ColumnInfo(name = \"source\", defaultValue = \"tmdb\") val source: String\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/MovieRatings.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"movies_ratings\",\n  indices = [Index(value = [\"id_trakt\"], unique = true)],\n  foreignKeys = [\n    ForeignKey(\n      entity = Movie::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\ndata class MovieRatings(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"trakt\") val trakt: String?,\n  @ColumnInfo(name = \"imdb\") val imdb: String?,\n  @ColumnInfo(name = \"metascore\") val metascore: String?,\n  @ColumnInfo(name = \"rotten_tomatoes\") val rottenTomatoes: String?,\n  @ColumnInfo(name = \"rotten_tomatoes_url\") val rottenTomatoesUrl: String?,\n  @ColumnInfo(name = \"created_at\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: Long,\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/MovieStreaming.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverters\nimport com.michaldrabik.data_local.database.converters.DateConverter\nimport java.time.ZonedDateTime\n\n@Entity(\n  tableName = \"movies_streamings\",\n  indices = [\n    Index(value = [\"id_trakt\"]),\n    Index(value = [\"id_tmdb\"]),\n  ],\n  foreignKeys = [\n    ForeignKey(\n      entity = Movie::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\n@TypeConverters(DateConverter::class)\ndata class MovieStreaming(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"id_tmdb\") val idTmdb: Long,\n  @ColumnInfo(name = \"type\") val type: String?,\n  @ColumnInfo(name = \"provider_id\") val providerId: Long?,\n  @ColumnInfo(name = \"provider_name\") val providerName: String?,\n  @ColumnInfo(name = \"display_priority\") val displayPriority: Long?,\n  @ColumnInfo(name = \"logo_path\") val logoPath: String?,\n  @ColumnInfo(name = \"link\") val link: String?,\n  @ColumnInfo(name = \"created_at\") val createdAt: ZonedDateTime,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: ZonedDateTime,\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/MovieTranslation.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"movies_translations\",\n  indices = [Index(value = [\"id_trakt\"], unique = true)],\n  foreignKeys = [\n    ForeignKey(\n      entity = Movie::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\ndata class MovieTranslation(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"title\") val title: String,\n  @ColumnInfo(name = \"language\") val language: String,\n  @ColumnInfo(name = \"overview\") val overview: String,\n  @ColumnInfo(name = \"created_at\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: Long\n) {\n\n  companion object {\n    fun fromTraktId(\n      traktId: Long,\n      title: String,\n      language: String,\n      overview: String,\n      createdAt: Long\n    ) =\n      MovieTranslation(\n        idTrakt = traktId,\n        title = title,\n        language = language,\n        overview = overview,\n        createdAt = createdAt,\n        updatedAt = createdAt\n      )\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/MoviesSyncLog.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity(tableName = \"sync_movies_log\")\ndata class MoviesSyncLog(\n  @PrimaryKey @ColumnInfo(name = \"id_movie_trakt\", defaultValue = \"-1\") val idTrakt: Long,\n  @ColumnInfo(name = \"synced_at\", defaultValue = \"0\") val syncedAt: Long\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/MyMovie.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"movies_my_movies\",\n  foreignKeys = [\n    ForeignKey(\n      entity = Movie::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\ndata class MyMovie(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\", defaultValue = \"-1\", index = true) val idTrakt: Long,\n  @ColumnInfo(name = \"created_at\", defaultValue = \"-1\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\", defaultValue = \"-1\") val updatedAt: Long\n) {\n\n  companion object {\n    fun fromTraktId(traktId: Long, timestamp: Long) =\n      MyMovie(\n        idTrakt = traktId,\n        createdAt = timestamp,\n        updatedAt = timestamp\n      )\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/MyShow.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"shows_my_shows\",\n  foreignKeys = [\n    ForeignKey(\n      entity = Show::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\ndata class MyShow(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\", defaultValue = \"-1\", index = true) val idTrakt: Long,\n  @ColumnInfo(name = \"created_at\", defaultValue = \"-1\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\", defaultValue = \"-1\") val updatedAt: Long,\n  @ColumnInfo(name = \"last_watched_at\") val lastWatchedAt: Long?\n) {\n\n  companion object {\n    fun fromTraktId(\n      traktId: Long,\n      createdAt: Long,\n      updatedAt: Long,\n      watchedAt: Long\n    ) = MyShow(\n      idTrakt = traktId,\n      createdAt = createdAt,\n      updatedAt = updatedAt,\n      lastWatchedAt = watchedAt\n    )\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/News.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"news\"\n)\ndata class News(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_news\") val idNews: String,\n  @ColumnInfo(name = \"title\") val title: String,\n  @ColumnInfo(name = \"url\") val url: String,\n  @ColumnInfo(name = \"type\") val type: String,\n  @ColumnInfo(name = \"image\") val image: String?,\n  @ColumnInfo(name = \"score\") val score: Long,\n  @ColumnInfo(name = \"dated_at\") val datedAt: Long,\n  @ColumnInfo(name = \"created_at\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: Long,\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/Person.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverters\nimport com.michaldrabik.data_local.database.converters.DateConverter\nimport java.time.ZonedDateTime\n\n@Entity(\n  tableName = \"people\",\n  indices = [\n    Index(value = [\"id_trakt\"]),\n    Index(value = [\"id_tmdb\"], unique = true),\n  ]\n)\n@TypeConverters(DateConverter::class)\ndata class Person(\n  @PrimaryKey @ColumnInfo(name = \"id_tmdb\") val idTmdb: Long,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long?,\n  @ColumnInfo(name = \"id_imdb\") val idImdb: String?,\n  @ColumnInfo(name = \"name\") val name: String,\n  @ColumnInfo(name = \"department\") val department: String,\n  @ColumnInfo(name = \"biography\") val biography: String?,\n  @ColumnInfo(name = \"biography_translation\") val biographyTranslation: String?,\n  @ColumnInfo(name = \"birthday\") val birthday: String?,\n  @ColumnInfo(name = \"birthplace\") val birthplace: String?,\n  @ColumnInfo(name = \"character\") val character: String?,\n  @ColumnInfo(name = \"episodes_count\") val episodesCount: Int?,\n  @ColumnInfo(name = \"job\") val job: String?,\n  @ColumnInfo(name = \"deathday\") val deathday: String?,\n  @ColumnInfo(name = \"image_path\") val image: String?,\n  @ColumnInfo(name = \"homepage\") val homepage: String?,\n  @ColumnInfo(name = \"created_at\") val createdAt: ZonedDateTime,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: ZonedDateTime,\n  @ColumnInfo(name = \"details_updated_at\") val detailsUpdatedAt: ZonedDateTime?,\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/PersonCredits.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverters\nimport com.michaldrabik.data_local.database.converters.DateConverter\nimport java.time.ZonedDateTime\n\n@Entity(\n  tableName = \"people_credits\",\n  foreignKeys = [\n    ForeignKey(\n      entity = Show::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt_show\"),\n      onDelete = ForeignKey.CASCADE\n    ),\n    ForeignKey(\n      entity = Movie::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt_movie\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ],\n  indices = [\n    Index(value = [\"id_trakt_person\"]),\n    Index(value = [\"id_trakt_show\"]),\n    Index(value = [\"id_trakt_movie\"]),\n  ]\n)\n@TypeConverters(DateConverter::class)\ndata class PersonCredits(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long,\n  @ColumnInfo(name = \"id_trakt_person\") val idTraktPerson: Long,\n  @ColumnInfo(name = \"id_trakt_show\") val idTraktShow: Long?,\n  @ColumnInfo(name = \"id_trakt_movie\") val idTraktMovie: Long?,\n  @ColumnInfo(name = \"type\") val type: String,\n  @ColumnInfo(name = \"created_at\") val createdAt: ZonedDateTime,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: ZonedDateTime\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/PersonImage.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverters\nimport com.michaldrabik.data_local.database.converters.DateConverter\nimport java.time.ZonedDateTime\n\n@Entity(\n  tableName = \"people_images\",\n  indices = [\n    Index(value = [\"id_tmdb\"])\n  ]\n)\n@TypeConverters(DateConverter::class)\ndata class PersonImage(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_tmdb\") val idTmdb: Long,\n  @ColumnInfo(name = \"file_path\") val filePath: String,\n  @ColumnInfo(name = \"created_at\") val createdAt: ZonedDateTime,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: ZonedDateTime\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/PersonShowMovie.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverters\nimport com.michaldrabik.data_local.database.converters.DateConverter\nimport java.time.ZonedDateTime\n\n@Entity(\n  tableName = \"people_shows_movies\",\n  foreignKeys = [\n    ForeignKey(\n      entity = Person::class,\n      parentColumns = arrayOf(\"id_tmdb\"),\n      childColumns = arrayOf(\"id_tmdb_person\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ],\n  indices = [\n    Index(value = [\"id_tmdb_person\"]),\n    Index(value = [\"id_trakt_show\", \"mode\"]),\n    Index(value = [\"id_trakt_movie\", \"mode\"])\n  ]\n)\n@TypeConverters(DateConverter::class)\ndata class PersonShowMovie(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long,\n  @ColumnInfo(name = \"id_tmdb_person\") val idTmdbPerson: Long,\n  @ColumnInfo(name = \"mode\") val mode: String,\n  @ColumnInfo(name = \"department\") val department: String,\n  @ColumnInfo(name = \"character\") val character: String?,\n  @ColumnInfo(name = \"job\") val job: String?,\n  @ColumnInfo(name = \"episodes_count\") val episodesCount: Int,\n  @ColumnInfo(name = \"id_trakt_show\") val idTraktShow: Long?,\n  @ColumnInfo(name = \"id_trakt_movie\") val idTraktMovie: Long?,\n  @ColumnInfo(name = \"created_at\") val createdAt: ZonedDateTime,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: ZonedDateTime\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/Rating.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.TypeConverters\nimport com.michaldrabik.data_local.database.converters.DateConverter\nimport java.time.ZonedDateTime\n\n@Entity(\n  tableName = \"ratings\",\n  primaryKeys = [\"id_trakt\", \"type\"],\n  indices = [\n    Index(value = [\"id_trakt\", \"type\"], unique = false),\n  ]\n)\n@TypeConverters(DateConverter::class)\ndata class Rating(\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"type\") val type: String,\n  @ColumnInfo(name = \"rating\") val rating: Int,\n  @ColumnInfo(name = \"season_number\") val seasonNumber: Int?,\n  @ColumnInfo(name = \"episode_number\") val episodeNumber: Int?,\n  @ColumnInfo(name = \"rated_at\") val ratedAt: ZonedDateTime,\n  @ColumnInfo(name = \"created_at\") val createdAt: ZonedDateTime,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: ZonedDateTime\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/RecentSearch.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity(tableName = \"recent_searches\")\ndata class RecentSearch(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long,\n  @ColumnInfo(name = \"text\", defaultValue = \"\") val text: String,\n  @ColumnInfo(name = \"created_at\", defaultValue = \"-1\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\", defaultValue = \"-1\") val updatedAt: Long\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/RelatedMovie.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.ForeignKey.Companion.CASCADE\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"movies_related\",\n  foreignKeys = [\n    ForeignKey(\n      entity = Movie::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt_related_movie\"),\n      onDelete = CASCADE\n    )\n  ]\n)\ndata class RelatedMovie(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\", defaultValue = \"-1\") val idTrakt: Long,\n  @ColumnInfo(name = \"id_trakt_related_movie\", defaultValue = \"-1\", index = true) val idTraktRelatedMovie: Long,\n  @ColumnInfo(name = \"updated_at\", defaultValue = \"-1\") val updatedAt: Long\n) {\n\n  companion object {\n    fun fromTraktId(traktId: Long, relatedTraktId: Long, nowUtcMillis: Long) =\n      RelatedMovie(\n        idTrakt = traktId,\n        idTraktRelatedMovie = relatedTraktId,\n        updatedAt = nowUtcMillis\n      )\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/RelatedShow.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.ForeignKey.Companion.CASCADE\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"shows_related\",\n  foreignKeys = [\n    ForeignKey(\n      entity = Show::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt_related_show\"),\n      onDelete = CASCADE\n    )\n  ]\n)\ndata class RelatedShow(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\", defaultValue = \"-1\") val idTrakt: Long,\n  @ColumnInfo(name = \"id_trakt_related_show\", defaultValue = \"-1\", index = true) val idTraktRelatedShow: Long,\n  @ColumnInfo(name = \"updated_at\", defaultValue = \"-1\") val updatedAt: Long\n) {\n\n  companion object {\n    fun fromTraktId(traktId: Long, relatedShowTraktId: Long, nowUtcMillis: Long): RelatedShow {\n      return RelatedShow(\n        idTrakt = traktId,\n        idTraktRelatedShow = relatedShowTraktId,\n        updatedAt = nowUtcMillis\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/Season.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverters\nimport com.michaldrabik.data_local.database.converters.DateConverter\nimport java.time.ZonedDateTime\n\n@Entity(\n  tableName = \"seasons\",\n  indices = [Index(\"id_show_trakt\")]\n)\n@TypeConverters(DateConverter::class)\ndata class Season(\n  @PrimaryKey @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"id_show_trakt\") val idShowTrakt: Long,\n  @ColumnInfo(name = \"season_number\") val seasonNumber: Int,\n  @ColumnInfo(name = \"season_title\") val seasonTitle: String,\n  @ColumnInfo(name = \"season_overview\") val seasonOverview: String,\n  @ColumnInfo(name = \"season_first_aired\") val seasonFirstAired: ZonedDateTime?,\n  @ColumnInfo(name = \"episodes_count\") val episodesCount: Int,\n  @ColumnInfo(name = \"episodes_aired_count\") val episodesAiredCount: Int,\n  @ColumnInfo(name = \"rating\") val rating: Float?,\n  @ColumnInfo(name = \"is_watched\") val isWatched: Boolean\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/Settings.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity(tableName = \"settings\")\ndata class Settings(\n  @PrimaryKey @ColumnInfo(name = \"id\") val id: Long = 1,\n  @ColumnInfo(name = \"is_initial_run\", defaultValue = \"0\") val isInitialRun: Boolean,\n  @ColumnInfo(name = \"push_notifications_enabled\", defaultValue = \"1\") val pushNotificationsEnabled: Boolean, // Removed\n  @ColumnInfo(name = \"episodes_notifications_enabled\", defaultValue = \"1\") val episodesNotificationsEnabled: Boolean,\n  @ColumnInfo(name = \"episodes_notifications_delay\", defaultValue = \"0\") val episodesNotificationsDelay: Long,\n  @ColumnInfo(name = \"my_shows_recent_amount\", defaultValue = \"6\") val myShowsRecentsAmount: Int,\n  @ColumnInfo(name = \"my_shows_running_sort_by\", defaultValue = \"NAME\") val myShowsRunningSortBy: String,\n  @ColumnInfo(name = \"my_shows_incoming_sort_by\", defaultValue = \"NAME\") val myShowsIncomingSortBy: String,\n  @ColumnInfo(name = \"my_shows_ended_sort_by\", defaultValue = \"NAME\") val myShowsEndedSortBy: String,\n  @ColumnInfo(name = \"my_shows_all_sort_by\", defaultValue = \"NAME\") val myShowsAllSortBy: String,\n  @ColumnInfo(name = \"my_shows_running_is_collapsed\", defaultValue = \"0\") val myShowsRunningIsCollapsed: Boolean,\n  @ColumnInfo(name = \"my_shows_incoming_is_collapsed\", defaultValue = \"0\") val myShowsIncomingIsCollapsed: Boolean,\n  @ColumnInfo(name = \"my_shows_ended_is_collapsed\", defaultValue = \"0\") val myShowsEndedIsCollapsed: Boolean,\n  @ColumnInfo(name = \"my_shows_running_is_enabled\", defaultValue = \"1\") val myShowsRunningIsEnabled: Boolean,\n  @ColumnInfo(name = \"my_shows_incoming_is_enabled\", defaultValue = \"1\") val myShowsIncomingIsEnabled: Boolean,\n  @ColumnInfo(name = \"my_shows_ended_is_enabled\", defaultValue = \"1\") val myShowsEndedIsEnabled: Boolean,\n  @ColumnInfo(name = \"my_shows_recent_is_enabled\", defaultValue = \"1\") val myShowsRecentIsEnabled: Boolean,\n  @ColumnInfo(name = \"see_later_shows_sort_by\", defaultValue = \"NAME\") val seeLaterShowsSortBy: String,\n  @ColumnInfo(name = \"show_anticipated_shows\", defaultValue = \"1\") val showAnticipatedShows: Boolean,\n  @ColumnInfo(name = \"discover_filter_genres\", defaultValue = \"\") val discoverFilterGenres: String,\n  @ColumnInfo(name = \"discover_filter_networks\", defaultValue = \"\") val discoverFilterNetworks: String,\n  @ColumnInfo(name = \"discover_filter_feed\", defaultValue = \"HOT\") val discoverFilterFeed: String,\n  @ColumnInfo(name = \"trakt_sync_schedule\", defaultValue = \"OFF\") val traktSyncSchedule: String,\n  @ColumnInfo(name = \"trakt_quick_sync_enabled\", defaultValue = \"0\") val traktQuickSyncEnabled: Boolean,\n  @ColumnInfo(name = \"trakt_quick_remove_enabled\", defaultValue = \"0\") val traktQuickRemoveEnabled: Boolean,\n  @ColumnInfo(name = \"watchlist_sort_by\", defaultValue = \"NAME\") val watchlistSortBy: String,\n  @ColumnInfo(name = \"archive_shows_sort_by\", defaultValue = \"NAME\") val archiveShowsSortBy: String,\n  @ColumnInfo(name = \"archive_shows_include_statistics\", defaultValue = \"1\") val archiveShowsIncludeStatistics: Boolean,\n  @ColumnInfo(name = \"special_seasons_enabled\", defaultValue = \"0\") val specialSeasonsEnabled: Boolean,\n  @ColumnInfo(name = \"show_anticipated_movies\", defaultValue = \"0\") val showAnticipatedMovies: Boolean,\n  @ColumnInfo(name = \"discover_movies_filter_genres\", defaultValue = \"\") val discoverMoviesFilterGenres: String,\n  @ColumnInfo(name = \"discover_movies_filter_feed\", defaultValue = \"HOT\") val discoverMoviesFilterFeed: String,\n  @ColumnInfo(name = \"my_movies_all_sort_by\", defaultValue = \"NAME\") val myMoviesAllSortBy: String,\n  @ColumnInfo(name = \"see_later_movies_sort_by\", defaultValue = \"NAME\") val seeLaterMoviesSortBy: String,\n  @ColumnInfo(name = \"progress_movies_sort_by\", defaultValue = \"NAME\") val progressMoviesSortBy: String,\n  @ColumnInfo(name = \"show_collection_shows\", defaultValue = \"1\") val showCollectionShows: Boolean,\n  @ColumnInfo(name = \"show_collection_movies\", defaultValue = \"1\") val showCollectionMovies: Boolean,\n  @ColumnInfo(name = \"widgets_show_label\", defaultValue = \"1\") val widgetsShowLabel: Boolean,\n  @ColumnInfo(name = \"my_movies_recent_is_enabled\", defaultValue = \"1\") val myMoviesRecentIsEnabled: Boolean,\n  @ColumnInfo(name = \"quick_rate_enabled\", defaultValue = \"0\") val quickRateEnabled: Boolean,\n  @ColumnInfo(name = \"lists_sort_by\", defaultValue = \"DATE_UPDATED\") val listsSortBy: String,\n  @ColumnInfo(name = \"progress_upcoming_enabled\", defaultValue = \"1\") val progressUpcomingEnabled: Boolean,\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/Show.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity(tableName = \"shows\")\ndata class Show(\n  @PrimaryKey @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"id_tvdb\", defaultValue = \"-1\") val idTvdb: Long,\n  @ColumnInfo(name = \"id_tmdb\", defaultValue = \"-1\") val idTmdb: Long,\n  @ColumnInfo(name = \"id_imdb\", defaultValue = \"\") val idImdb: String,\n  @ColumnInfo(name = \"id_slug\", defaultValue = \"\") val idSlug: String,\n  @ColumnInfo(name = \"id_tvrage\", defaultValue = \"-1\") val idTvrage: Long,\n  @ColumnInfo(name = \"title\", defaultValue = \"\") val title: String,\n  @ColumnInfo(name = \"year\", defaultValue = \"-1\") val year: Int,\n  @ColumnInfo(name = \"overview\", defaultValue = \"\") val overview: String,\n  @ColumnInfo(name = \"first_aired\", defaultValue = \"\") val firstAired: String,\n  @ColumnInfo(name = \"runtime\", defaultValue = \"-1\") val runtime: Int,\n  @ColumnInfo(name = \"airtime_day\", defaultValue = \"\") val airtimeDay: String,\n  @ColumnInfo(name = \"airtime_time\", defaultValue = \"\") val airtimeTime: String,\n  @ColumnInfo(name = \"airtime_timezone\", defaultValue = \"\") val airtimeTimezone: String,\n  @ColumnInfo(name = \"certification\", defaultValue = \"\") val certification: String,\n  @ColumnInfo(name = \"network\", defaultValue = \"\") val network: String,\n  @ColumnInfo(name = \"country\", defaultValue = \"\") val country: String,\n  @ColumnInfo(name = \"trailer\", defaultValue = \"\") val trailer: String,\n  @ColumnInfo(name = \"homepage\", defaultValue = \"\") val homepage: String,\n  @ColumnInfo(name = \"status\", defaultValue = \"\") val status: String,\n  @ColumnInfo(name = \"rating\", defaultValue = \"-1\") val rating: Float,\n  @ColumnInfo(name = \"votes\", defaultValue = \"-1\") val votes: Long,\n  @ColumnInfo(name = \"comment_count\", defaultValue = \"-1\") val commentCount: Long,\n  @ColumnInfo(name = \"genres\", defaultValue = \"\") val genres: String,\n  @ColumnInfo(name = \"aired_episodes\", defaultValue = \"-1\") val airedEpisodes: Int,\n  @ColumnInfo(name = \"created_at\", defaultValue = \"-1\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\", defaultValue = \"-1\") val updatedAt: Long\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/ShowImage.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"shows_images\",\n  indices = [\n    Index(value = [\"id_tmdb\", \"type\", \"family\"])\n  ]\n)\n\ndata class ShowImage(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_tvdb\", defaultValue = \"-1\") val idTvdb: Long,\n  @ColumnInfo(name = \"id_tmdb\", defaultValue = \"-1\") val idTmdb: Long,\n  @ColumnInfo(name = \"type\", defaultValue = \"\") val type: String,\n  @ColumnInfo(name = \"family\", defaultValue = \"\") val family: String,\n  @ColumnInfo(name = \"file_url\", defaultValue = \"\") val fileUrl: String,\n  @ColumnInfo(name = \"thumbnail_url\", defaultValue = \"\") val thumbnailUrl: String,\n  @ColumnInfo(name = \"source\", defaultValue = \"tvdb\") val source: String\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/ShowRatings.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"shows_ratings\",\n  indices = [Index(value = [\"id_trakt\"], unique = true)],\n  foreignKeys = [\n    ForeignKey(\n      entity = Show::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\ndata class ShowRatings(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"trakt\") val trakt: String?,\n  @ColumnInfo(name = \"imdb\") val imdb: String?,\n  @ColumnInfo(name = \"metascore\") val metascore: String?,\n  @ColumnInfo(name = \"rotten_tomatoes\") val rottenTomatoes: String?,\n  @ColumnInfo(name = \"rotten_tomatoes_url\") val rottenTomatoesUrl: String?,\n  @ColumnInfo(name = \"created_at\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: Long,\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/ShowStreaming.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverters\nimport com.michaldrabik.data_local.database.converters.DateConverter\nimport java.time.ZonedDateTime\n\n@Entity(\n  tableName = \"shows_streamings\",\n  indices = [\n    Index(value = [\"id_trakt\"]),\n    Index(value = [\"id_tmdb\"]),\n  ],\n  foreignKeys = [\n    ForeignKey(\n      entity = Show::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\n@TypeConverters(DateConverter::class)\ndata class ShowStreaming(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"id_tmdb\") val idTmdb: Long,\n  @ColumnInfo(name = \"type\") val type: String?,\n  @ColumnInfo(name = \"provider_id\") val providerId: Long?,\n  @ColumnInfo(name = \"provider_name\") val providerName: String?,\n  @ColumnInfo(name = \"display_priority\") val displayPriority: Long?,\n  @ColumnInfo(name = \"logo_path\") val logoPath: String?,\n  @ColumnInfo(name = \"link\") val link: String?,\n  @ColumnInfo(name = \"created_at\") val createdAt: ZonedDateTime,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: ZonedDateTime,\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/ShowTranslation.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"shows_translations\",\n  indices = [Index(value = [\"id_trakt\"], unique = true)],\n  foreignKeys = [\n    ForeignKey(\n      entity = Show::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\ndata class ShowTranslation(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"title\") val title: String,\n  @ColumnInfo(name = \"language\") val language: String,\n  @ColumnInfo(name = \"overview\") val overview: String,\n  @ColumnInfo(name = \"created_at\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: Long\n) {\n\n  companion object {\n    fun fromTraktId(\n      traktId: Long,\n      title: String,\n      language: String,\n      overview: String,\n      createdAt: Long\n    ) =\n      ShowTranslation(\n        idTrakt = traktId,\n        title = title,\n        language = language,\n        overview = overview,\n        createdAt = createdAt,\n        updatedAt = createdAt\n      )\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/TraktSyncLog.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"sync_trakt_log\",\n  indices = [\n    Index(value = [\"id_trakt\", \"type\"], unique = true)\n  ]\n)\ndata class TraktSyncLog(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\", index = true) val idTrakt: Long,\n  @ColumnInfo(name = \"type\", index = true) val type: String,\n  @ColumnInfo(name = \"synced_at\") val syncedAt: Long\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/TraktSyncQueue.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity(tableName = \"trakt_sync_queue\")\ndata class TraktSyncQueue(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long,\n  @ColumnInfo(name = \"id_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"id_list\") val idList: Long?,\n  @ColumnInfo(name = \"type\") val type: String,\n  @ColumnInfo(name = \"operation\") val operation: String,\n  @ColumnInfo(name = \"created_at\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\") val updatedAt: Long\n) {\n\n  companion object {\n    fun createEpisode(\n      episodeTraktId: Long,\n      showTraktId: Long?,\n      createdAt: Long,\n      updatedAt: Long,\n      clearProgress: Boolean\n    ): TraktSyncQueue {\n      val operation = if (clearProgress) Operation.ADD_WITH_CLEAR else Operation.ADD\n      return TraktSyncQueue(0, episodeTraktId, showTraktId, Type.EPISODE.slug, operation.slug, createdAt, updatedAt)\n    }\n\n    fun createShowWatchlist(\n      idTrakt: Long,\n      createdAt: Long,\n      updatedAt: Long\n    ) = TraktSyncQueue(0, idTrakt, null, Type.SHOW_WATCHLIST.slug, Operation.ADD.slug, createdAt, updatedAt)\n\n    fun createMovie(\n      idTrakt: Long,\n      createdAt: Long,\n      updatedAt: Long\n    ) = TraktSyncQueue(0, idTrakt, null, Type.MOVIE.slug, Operation.ADD.slug, createdAt, updatedAt)\n\n    fun createMovieWatchlist(\n      idTrakt: Long,\n      createdAt: Long,\n      updatedAt: Long\n    ) = TraktSyncQueue(0, idTrakt, null, Type.MOVIE_WATCHLIST.slug, Operation.ADD.slug, createdAt, updatedAt)\n\n    fun createListShow(\n      idTrakt: Long,\n      idList: Long,\n      operation: Operation,\n      createdAt: Long,\n      updatedAt: Long\n    ) = TraktSyncQueue(0, idTrakt, idList, Type.LIST_ITEM_SHOW.slug, operation.slug, createdAt, updatedAt)\n\n    fun createListMovie(\n      idTrakt: Long,\n      idList: Long,\n      operation: Operation,\n      createdAt: Long,\n      updatedAt: Long\n    ) = TraktSyncQueue(0, idTrakt, idList, Type.LIST_ITEM_MOVIE.slug, operation.slug, createdAt, updatedAt)\n\n    fun createHiddenShow(\n      idTrakt: Long,\n      operation: Operation,\n      createdAt: Long,\n      updatedAt: Long\n    ) = TraktSyncQueue(0, idTrakt, null, Type.HIDDEN_SHOW.slug, operation.slug, createdAt, updatedAt)\n\n    fun createHiddenMovie(\n      idTrakt: Long,\n      operation: Operation,\n      createdAt: Long,\n      updatedAt: Long\n    ) = TraktSyncQueue(0, idTrakt, null, Type.HIDDEN_MOVIE.slug, operation.slug, createdAt, updatedAt)\n  }\n\n  enum class Type(val slug: String) {\n    EPISODE(\"episode\"),\n    SHOW_WATCHLIST(\"show_watchlist\"),\n    MOVIE(\"movie\"),\n    MOVIE_WATCHLIST(\"movie_watchlist\"),\n    LIST_ITEM_SHOW(\"list_item_show\"),\n    LIST_ITEM_MOVIE(\"list_item_movie\"),\n    HIDDEN_SHOW(\"hidden_show\"),\n    HIDDEN_MOVIE(\"hidden_movie\")\n  }\n\n  enum class Operation(val slug: String) {\n    ADD(\"add\"),\n    ADD_WITH_CLEAR(\"add_clear\"),\n    REMOVE(\"remove\")\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/TranslationsMoviesSyncLog.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity(tableName = \"sync_movies_translations_log\")\ndata class TranslationsMoviesSyncLog(\n  @PrimaryKey @ColumnInfo(name = \"id_movie_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"synced_at\") val syncedAt: Long\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/TranslationsSyncLog.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity(tableName = \"sync_translations_log\")\ndata class TranslationsSyncLog(\n  @PrimaryKey @ColumnInfo(name = \"id_show_trakt\") val idTrakt: Long,\n  @ColumnInfo(name = \"synced_at\") val syncedAt: Long\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/User.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity(tableName = \"user\")\ndata class User(\n  @PrimaryKey @ColumnInfo(name = \"id\") val id: Long = 1,\n  @ColumnInfo(name = \"tvdb_token\", defaultValue = \"\") val tvdbToken: String,\n  @ColumnInfo(name = \"tvdb_token_timestamp\", defaultValue = \"0\") val tvdbTokenTimestamp: Long,\n  @ColumnInfo(name = \"trakt_token\", defaultValue = \"\") val traktToken: String,\n  @ColumnInfo(name = \"trakt_refresh_token\", defaultValue = \"\") val traktRefreshToken: String,\n  @ColumnInfo(name = \"trakt_token_timestamp\", defaultValue = \"0\") val traktTokenTimestamp: Long,\n  @ColumnInfo(name = \"trakt_username\", defaultValue = \"\") val traktUsername: String,\n  @ColumnInfo(name = \"reddit_token\", defaultValue = \"\") val redditToken: String,\n  @ColumnInfo(name = \"reddit_token_timestamp\", defaultValue = \"0\") val redditTokenTimestamp: Long,\n)\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/WatchlistMovie.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"movies_see_later\",\n  foreignKeys = [\n    ForeignKey(\n      entity = Movie::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\ndata class WatchlistMovie(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\", defaultValue = \"-1\", index = true) val idTrakt: Long,\n  @ColumnInfo(name = \"created_at\", defaultValue = \"-1\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\", defaultValue = \"-1\") val updatedAt: Long\n) {\n\n  companion object {\n    fun fromTraktId(traktId: Long, nowUtcMillis: Long) =\n      WatchlistMovie(idTrakt = traktId, createdAt = nowUtcMillis, updatedAt = nowUtcMillis)\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/database/model/WatchlistShow.kt",
    "content": "package com.michaldrabik.data_local.database.model\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.PrimaryKey\n\n@Entity(\n  tableName = \"shows_see_later\",\n  foreignKeys = [\n    ForeignKey(\n      entity = Show::class,\n      parentColumns = arrayOf(\"id_trakt\"),\n      childColumns = arrayOf(\"id_trakt\"),\n      onDelete = ForeignKey.CASCADE\n    )\n  ]\n)\ndata class WatchlistShow(\n  @PrimaryKey(autoGenerate = true) @ColumnInfo(name = \"id\") val id: Long = 0,\n  @ColumnInfo(name = \"id_trakt\", defaultValue = \"-1\", index = true) val idTrakt: Long,\n  @ColumnInfo(name = \"created_at\", defaultValue = \"-1\") val createdAt: Long,\n  @ColumnInfo(name = \"updated_at\", defaultValue = \"-1\") val updatedAt: Long\n) {\n\n  companion object {\n    fun fromTraktId(traktId: Long, nowUtcMillis: Long) =\n      WatchlistShow(idTrakt = traktId, createdAt = nowUtcMillis, updatedAt = nowUtcMillis)\n  }\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/di/LocalDataModule.kt",
    "content": "package com.michaldrabik.data_local.di\n\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.MainLocalDataSource\nimport dagger.Binds\nimport dagger.Module\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nabstract class LocalDataModule {\n\n  @Binds\n  @Singleton\n  internal abstract fun providesLocalDataSource(source: MainLocalDataSource): LocalDataSource\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/di/SourcesModule.kt",
    "content": "package com.michaldrabik.data_local.di\n\nimport com.michaldrabik.data_local.database.AppDatabase\nimport com.michaldrabik.data_local.sources.ArchiveMoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.ArchiveShowsLocalDataSource\nimport com.michaldrabik.data_local.sources.CustomImagesLocalDataSource\nimport com.michaldrabik.data_local.sources.CustomListsItemsLocalDataSource\nimport com.michaldrabik.data_local.sources.CustomListsLocalDataSource\nimport com.michaldrabik.data_local.sources.DiscoverMoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.DiscoverShowsLocalDataSource\nimport com.michaldrabik.data_local.sources.EpisodeTranslationsLocalDataSource\nimport com.michaldrabik.data_local.sources.EpisodesLocalDataSource\nimport com.michaldrabik.data_local.sources.EpisodesSyncLogLocalDataSource\nimport com.michaldrabik.data_local.sources.MovieCollectionsItemsLocalDataSource\nimport com.michaldrabik.data_local.sources.MovieCollectionsLocalDataSource\nimport com.michaldrabik.data_local.sources.MovieImagesLocalDataSource\nimport com.michaldrabik.data_local.sources.MovieRatingsLocalDataSource\nimport com.michaldrabik.data_local.sources.MovieStreamingsLocalDataSource\nimport com.michaldrabik.data_local.sources.MovieTranslationsLocalDataSource\nimport com.michaldrabik.data_local.sources.MoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.MoviesSyncLogLocalDataSource\nimport com.michaldrabik.data_local.sources.MyMoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.MyShowsLocalDataSource\nimport com.michaldrabik.data_local.sources.NewsLocalDataSource\nimport com.michaldrabik.data_local.sources.PeopleCreditsLocalDataSource\nimport com.michaldrabik.data_local.sources.PeopleImagesLocalDataSource\nimport com.michaldrabik.data_local.sources.PeopleLocalDataSource\nimport com.michaldrabik.data_local.sources.PeopleShowsMoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.RatingsLocalDataSource\nimport com.michaldrabik.data_local.sources.RecentSearchLocalDataSource\nimport com.michaldrabik.data_local.sources.RelatedMoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.RelatedShowsLocalDataSource\nimport com.michaldrabik.data_local.sources.SeasonsLocalDataSource\nimport com.michaldrabik.data_local.sources.SettingsLocalDataSource\nimport com.michaldrabik.data_local.sources.ShowImagesLocalDataSource\nimport com.michaldrabik.data_local.sources.ShowRatingsLocalDataSource\nimport com.michaldrabik.data_local.sources.ShowStreamingsLocalDataSource\nimport com.michaldrabik.data_local.sources.ShowTranslationsLocalDataSource\nimport com.michaldrabik.data_local.sources.ShowsLocalDataSource\nimport com.michaldrabik.data_local.sources.TraktSyncLogLocalDataSource\nimport com.michaldrabik.data_local.sources.TraktSyncQueueLocalDataSource\nimport com.michaldrabik.data_local.sources.TranslationsMoviesSyncLogLocalDataSource\nimport com.michaldrabik.data_local.sources.TranslationsShowsSyncLogLocalDataSource\nimport com.michaldrabik.data_local.sources.UserLocalDataSource\nimport com.michaldrabik.data_local.sources.WatchlistMoviesLocalDataSource\nimport com.michaldrabik.data_local.sources.WatchlistShowsLocalDataSource\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nclass SourcesModule {\n\n  @Provides\n  @Singleton\n  internal fun providesShows(database: AppDatabase): ShowsLocalDataSource =\n    database.showsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesMovies(database: AppDatabase): MoviesLocalDataSource =\n    database.moviesDao()\n\n  @Provides\n  @Singleton\n  internal fun providesArchivesShows(database: AppDatabase): ArchiveShowsLocalDataSource =\n    database.archiveShowsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesArchivesMovies(database: AppDatabase): ArchiveMoviesLocalDataSource =\n    database.archiveMoviesDao()\n\n  @Provides\n  @Singleton\n  internal fun providesCustomListsItems(database: AppDatabase): CustomListsItemsLocalDataSource =\n    database.customListsItemsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesCustomImages(database: AppDatabase): CustomImagesLocalDataSource =\n    database.customImagesDao()\n\n  @Provides\n  @Singleton\n  internal fun providesCustomLists(database: AppDatabase): CustomListsLocalDataSource =\n    database.customListsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesDiscoverShows(database: AppDatabase): DiscoverShowsLocalDataSource =\n    database.discoverShowsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesDiscoverMovies(database: AppDatabase): DiscoverMoviesLocalDataSource =\n    database.discoverMoviesDao()\n\n  @Provides\n  @Singleton\n  internal fun providesEpisodes(database: AppDatabase): EpisodesLocalDataSource =\n    database.episodesDao()\n\n  @Provides\n  @Singleton\n  internal fun providesEpisodesSyncLog(database: AppDatabase): EpisodesSyncLogLocalDataSource =\n    database.episodesSyncLogDao()\n\n  @Provides\n  @Singleton\n  internal fun providesEpisodesTranslations(database: AppDatabase): EpisodeTranslationsLocalDataSource =\n    database.episodeTranslationsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesMovieImages(database: AppDatabase): MovieImagesLocalDataSource =\n    database.movieImagesDao()\n\n  @Provides\n  @Singleton\n  internal fun providesMovieRatings(database: AppDatabase): MovieRatingsLocalDataSource =\n    database.movieRatingsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesMovieSyncLog(database: AppDatabase): MoviesSyncLogLocalDataSource =\n    database.moviesSyncLogDao()\n\n  @Provides\n  @Singleton\n  internal fun providesMovieStreaming(database: AppDatabase): MovieStreamingsLocalDataSource =\n    database.movieStreamingsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesMovieCollections(database: AppDatabase): MovieCollectionsLocalDataSource =\n    database.movieCollectionsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesMovieCollectionsItems(database: AppDatabase): MovieCollectionsItemsLocalDataSource =\n    database.movieCollectionsItemsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesMovieTranslation(database: AppDatabase): MovieTranslationsLocalDataSource =\n    database.movieTranslationsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesMyMovies(database: AppDatabase): MyMoviesLocalDataSource =\n    database.myMoviesDao()\n\n  @Provides\n  @Singleton\n  internal fun providesMyShows(database: AppDatabase): MyShowsLocalDataSource =\n    database.myShowsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesNews(database: AppDatabase): NewsLocalDataSource =\n    database.newsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesPeopleCredits(database: AppDatabase): PeopleCreditsLocalDataSource =\n    database.peopleCreditsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesPeople(database: AppDatabase): PeopleLocalDataSource =\n    database.peopleDao()\n\n  @Provides\n  @Singleton\n  internal fun providesPeopleImages(database: AppDatabase): PeopleImagesLocalDataSource =\n    database.peopleImagesDao()\n\n  @Provides\n  @Singleton\n  internal fun providesPeopleShowsMovies(database: AppDatabase): PeopleShowsMoviesLocalDataSource =\n    database.peopleShowsMoviesDao()\n\n  @Provides\n  @Singleton\n  internal fun providesRatings(database: AppDatabase): RatingsLocalDataSource =\n    database.ratingsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesRecentSearch(database: AppDatabase): RecentSearchLocalDataSource =\n    database.recentSearchDao()\n\n  @Provides\n  @Singleton\n  internal fun providesSeasons(database: AppDatabase): SeasonsLocalDataSource =\n    database.seasonsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesSettings(database: AppDatabase): SettingsLocalDataSource =\n    database.settingsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesShowImages(database: AppDatabase): ShowImagesLocalDataSource =\n    database.showImagesDao()\n\n  @Provides\n  @Singleton\n  internal fun providesShowRatings(database: AppDatabase): ShowRatingsLocalDataSource =\n    database.showRatingsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesShowStreamings(database: AppDatabase): ShowStreamingsLocalDataSource =\n    database.showStreamingsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesShowTranslations(database: AppDatabase): ShowTranslationsLocalDataSource =\n    database.showTranslationsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesTraktSyncLog(database: AppDatabase): TraktSyncLogLocalDataSource =\n    database.traktSyncLogDao()\n\n  @Provides\n  @Singleton\n  internal fun providesTraktSyncQueue(database: AppDatabase): TraktSyncQueueLocalDataSource =\n    database.traktSyncQueueDao()\n\n  @Provides\n  @Singleton\n  internal fun providesTranslationsMovies(database: AppDatabase): TranslationsMoviesSyncLogLocalDataSource =\n    database.translationsMoviesSyncLogDao()\n\n  @Provides\n  @Singleton\n  internal fun providesTranslationsShows(database: AppDatabase): TranslationsShowsSyncLogLocalDataSource =\n    database.translationsSyncLogDao()\n\n  @Provides\n  @Singleton\n  internal fun providesUser(database: AppDatabase): UserLocalDataSource =\n    database.userDao()\n\n  @Provides\n  @Singleton\n  internal fun providesWatchlistMovies(database: AppDatabase): WatchlistMoviesLocalDataSource =\n    database.watchlistMoviesDao()\n\n  @Provides\n  @Singleton\n  internal fun providesWatchlistShows(database: AppDatabase): WatchlistShowsLocalDataSource =\n    database.watchlistShowsDao()\n\n  @Provides\n  @Singleton\n  internal fun providesRelatedMovies(database: AppDatabase): RelatedMoviesLocalDataSource =\n    database.relatedMoviesDao()\n\n  @Provides\n  @Singleton\n  internal fun providesRelatedShows(database: AppDatabase): RelatedShowsLocalDataSource =\n    database.relatedShowsDao()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/di/StorageModule.kt",
    "content": "package com.michaldrabik.data_local.di\n\nimport android.content.Context\nimport androidx.room.Room\nimport com.michaldrabik.data_local.database.AppDatabase\nimport com.michaldrabik.data_local.database.migrations.DATABASE_NAME\nimport com.michaldrabik.data_local.database.migrations.Migrations\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.components.SingletonComponent\nimport timber.log.Timber\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nclass StorageModule {\n\n  @Provides\n  @Singleton\n  internal fun providesDatabase(\n    @ApplicationContext context: Context,\n    migrations: Migrations\n  ): AppDatabase {\n    Timber.d(\"Creating database...\")\n    return Room.databaseBuilder(\n      context.applicationContext,\n      AppDatabase::class.java,\n      DATABASE_NAME\n    ).apply {\n      migrations.getAll().forEach { addMigrations(it) }\n    }.build()\n  }\n\n  @Provides\n  @Singleton\n  internal fun providesMigrations(@ApplicationContext context: Context): Migrations =\n    Migrations(context)\n\n  @Provides\n  @Singleton\n  internal fun providesTransactions(database: AppDatabase): TransactionsProvider =\n    TransactionsProvider(database)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/ArchiveMoviesLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.ArchiveMovie\nimport com.michaldrabik.data_local.database.model.Movie\n\ninterface ArchiveMoviesLocalDataSource {\n\n  suspend fun getAll(): List<Movie>\n\n  suspend fun getAll(ids: List<Long>): List<Movie>\n\n  suspend fun getAllTraktIds(): List<Long>\n\n  suspend fun getById(traktId: Long): Movie?\n\n  suspend fun insert(movie: ArchiveMovie)\n\n  suspend fun deleteById(traktId: Long)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/ArchiveShowsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.ArchiveShow\nimport com.michaldrabik.data_local.database.model.Show\n\ninterface ArchiveShowsLocalDataSource {\n\n  suspend fun getAll(): List<Show>\n\n  suspend fun getAll(ids: List<Long>): List<Show>\n\n  suspend fun getAllTraktIds(): List<Long>\n\n  suspend fun getById(traktId: Long): Show?\n\n  suspend fun insert(show: ArchiveShow)\n\n  suspend fun deleteById(traktId: Long)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/CustomImagesLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.CustomImage\n\ninterface CustomImagesLocalDataSource {\n\n  suspend fun getById(traktId: Long, family: String, type: String): CustomImage?\n\n  suspend fun deleteById(traktId: Long, family: String, type: String)\n\n  suspend fun insertImage(image: CustomImage)\n\n  suspend fun upsert(image: CustomImage)\n\n  suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/CustomListsItemsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.CustomListItem\n\ninterface CustomListsItemsLocalDataSource {\n\n  suspend fun update(items: List<CustomListItem>)\n\n  suspend fun getListsForItem(idTrakt: Long, type: String): List<Long>\n\n  suspend fun getByIdTrakt(idList: Long, idTrakt: Long, type: String): CustomListItem?\n\n  suspend fun getItemsById(idList: Long): List<CustomListItem>\n\n  suspend fun getItemsForListImages(idList: Long, limit: Int): List<CustomListItem>\n\n  suspend fun getRankForList(idList: Long): Long?\n\n  suspend fun insertItem(item: CustomListItem)\n\n  suspend fun deleteItem(idList: Long, idTrakt: Long, type: String)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/CustomListsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.CustomList\n\ninterface CustomListsLocalDataSource {\n\n  suspend fun insert(items: List<CustomList>): List<Long>\n\n  suspend fun update(items: List<CustomList>)\n\n  suspend fun getAll(): List<CustomList>\n\n  suspend fun getById(id: Long): CustomList?\n\n  suspend fun updateTraktId(id: Long, idTrakt: Long, idSlug: String, timestamp: Long)\n\n  suspend fun updateTimestamp(id: Long, timestamp: Long)\n\n  suspend fun updateSortByLocal(id: Long, sortBy: String, sortHow: String, timestamp: Long)\n\n  suspend fun updateFilterTypeLocal(id: Long, filterType: String, timestamp: Long)\n\n  suspend fun deleteById(id: Long)\n\n  suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/DiscoverMoviesLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.DiscoverMovie\n\ninterface DiscoverMoviesLocalDataSource {\n\n  suspend fun getAll(): List<DiscoverMovie>\n\n  suspend fun getMostRecent(): DiscoverMovie?\n\n  suspend fun upsert(movies: List<DiscoverMovie>)\n\n  suspend fun deleteAll()\n\n  suspend fun replace(movies: List<DiscoverMovie>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/DiscoverShowsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.DiscoverShow\n\ninterface DiscoverShowsLocalDataSource {\n\n  suspend fun getAll(): List<DiscoverShow>\n\n  suspend fun getMostRecent(): DiscoverShow?\n\n  suspend fun upsert(shows: List<DiscoverShow>)\n\n  suspend fun deleteAll()\n\n  suspend fun replace(shows: List<DiscoverShow>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/EpisodeTranslationsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.EpisodeTranslation\n\ninterface EpisodeTranslationsLocalDataSource {\n\n  suspend fun getById(traktEpisodeId: Long, traktShowId: Long, language: String): EpisodeTranslation?\n\n  suspend fun getByIds(traktEpisodeIds: List<Long>, traktShowId: Long, language: String): List<EpisodeTranslation>\n\n  suspend fun insertSingle(translation: EpisodeTranslation)\n\n  suspend fun deleteByLanguage(languages: List<String>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/EpisodesLocalDataSource.kt",
    "content": "// ktlint-disable max-line-length\npackage com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.Episode\n\ninterface EpisodesLocalDataSource {\n\n  suspend fun upsert(episodes: List<Episode>)\n\n  suspend fun delete(items: List<Episode>)\n\n  suspend fun upsertChunked(items: List<Episode>)\n\n  suspend fun isEpisodeWatched(showTraktId: Long, episodeTraktId: Long): Boolean\n\n  suspend fun getAllForSeason(seasonTraktId: Long): List<Episode>\n\n  suspend fun getAllByShowId(showTraktId: Long): List<Episode>\n\n  suspend fun getAllByShowId(showTraktId: Long, seasonNumber: Int): List<Episode>\n\n  suspend fun getAllByShowsIds(showTraktIds: List<Long>): List<Episode>\n\n  suspend fun getAllByShowsIdsChunk(showTraktIds: List<Long>): List<Episode>\n\n  suspend fun getFirstUnwatched(\n    showTraktId: Long,\n    toTime: Long\n  ): Episode?\n\n  suspend fun getFirstUnwatched(\n    showTraktId: Long,\n    fromTime: Long,\n    toTime: Long\n  ): Episode?\n\n  suspend fun getFirstUnwatchedAfterEpisode(\n    showTraktId: Long,\n    seasonNumber: Int,\n    episodeNumber: Int,\n    toTime: Long\n  ): Episode?\n\n  suspend fun getLastWatched(showTraktId: Long): Episode?\n\n  suspend fun getTotalCount(showTraktId: Long, toTime: Long): Int\n\n  suspend fun getTotalCount(showTraktId: Long): Int\n\n  suspend fun getWatchedCount(showTraktId: Long, toTime: Long): Int\n\n  suspend fun getWatchedCount(showTraktId: Long): Int\n\n  suspend fun getAllWatchedForShows(showsIds: List<Long>): List<Episode>\n\n  suspend fun getAllWatchedIdsForShows(showsIds: List<Long>): List<Long>\n\n  suspend fun deleteAllUnwatchedForShow(showTraktId: Long)\n\n  suspend fun deleteAllForShow(showTraktId: Long)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/EpisodesSyncLogLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.EpisodesSyncLog\n\ninterface EpisodesSyncLogLocalDataSource {\n\n  suspend fun getAll(): List<EpisodesSyncLog>\n\n  suspend fun upsert(log: EpisodesSyncLog)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/MovieCollectionsItemsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.Movie\nimport com.michaldrabik.data_local.database.model.MovieCollectionItem\n\ninterface MovieCollectionsItemsLocalDataSource {\n\n  suspend fun getById(collectionId: Long): List<Movie>\n\n  suspend fun deleteById(collectionId: Long)\n\n  suspend fun replace(\n    collectionId: Long,\n    items: List<MovieCollectionItem>,\n  )\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/MovieCollectionsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.MovieCollection\n\ninterface MovieCollectionsLocalDataSource {\n\n  suspend fun getById(traktId: Long): MovieCollection?\n\n  suspend fun getByMovieId(movieTraktId: Long): List<MovieCollection>\n\n  suspend fun replaceByMovieId(movieTraktId: Long, entities: List<MovieCollection>)\n\n  suspend fun insertAll(items: List<MovieCollection>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/MovieImagesLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.MovieImage\n\ninterface MovieImagesLocalDataSource {\n\n  suspend fun getByMovieId(tmdbId: Long, type: String): MovieImage?\n\n  suspend fun insertMovieImage(image: MovieImage)\n\n  suspend fun upsert(image: MovieImage)\n\n  suspend fun deleteByMovieId(id: Long, type: String)\n\n  suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/MovieRatingsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.MovieRatings\n\ninterface MovieRatingsLocalDataSource {\n\n  suspend fun upsert(entity: MovieRatings)\n\n  suspend fun getById(traktId: Long): MovieRatings?\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/MovieStreamingsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.MovieStreaming\n\ninterface MovieStreamingsLocalDataSource {\n\n  suspend fun replace(traktId: Long, entities: List<MovieStreaming>)\n\n  suspend fun getById(traktId: Long): List<MovieStreaming>\n\n  suspend fun deleteById(traktId: Long)\n\n  suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/MovieTranslationsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.MovieTranslation\n\ninterface MovieTranslationsLocalDataSource {\n\n  suspend fun getById(traktId: Long, language: String): MovieTranslation?\n\n  suspend fun getAll(language: String): List<MovieTranslation>\n\n  suspend fun insertSingle(translation: MovieTranslation)\n\n  suspend fun deleteByLanguage(languages: List<String>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/MoviesLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.Movie\n\ninterface MoviesLocalDataSource {\n\n  suspend fun getAll(): List<Movie>\n\n  suspend fun getAll(ids: List<Long>): List<Movie>\n\n  suspend fun getAllChunked(ids: List<Long>): List<Movie>\n\n  suspend fun getById(traktId: Long): Movie?\n\n  suspend fun getByTmdbId(tmdbId: Long): Movie?\n\n  suspend fun getBySlug(slug: String): Movie?\n\n  suspend fun getById(imdbId: String): Movie?\n\n  suspend fun deleteById(traktId: Long)\n\n  suspend fun upsert(movies: List<Movie>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/MoviesSyncLogLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.MoviesSyncLog\n\ninterface MoviesSyncLogLocalDataSource {\n\n  suspend fun getAll(): List<MoviesSyncLog>\n\n  suspend fun upsert(log: MoviesSyncLog)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/MyMoviesLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.Movie\nimport com.michaldrabik.data_local.database.model.MyMovie\n\ninterface MyMoviesLocalDataSource {\n\n  suspend fun getAll(): List<Movie>\n\n  suspend fun getAll(ids: List<Long>): List<Movie>\n\n  suspend fun getAllRecent(limit: Int): List<Movie>\n\n  suspend fun getAllTraktIds(): List<Long>\n\n  suspend fun getById(traktId: Long): Movie?\n\n  suspend fun insert(movies: List<MyMovie>)\n\n  suspend fun deleteById(traktId: Long)\n\n  suspend fun checkExists(traktId: Long): Boolean\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/MyShowsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.MyShow\nimport com.michaldrabik.data_local.database.model.Show\n\ninterface MyShowsLocalDataSource {\n\n  suspend fun getAll(): List<Show>\n\n  suspend fun getAll(ids: List<Long>): List<Show>\n\n  suspend fun getAllRecent(limit: Int): List<Show>\n\n  suspend fun getAllTraktIds(): List<Long>\n\n  suspend fun getById(traktId: Long): Show?\n\n  suspend fun updateWatchedAt(traktId: Long, watchedAt: Long)\n\n  suspend fun insert(shows: List<MyShow>)\n\n  suspend fun deleteById(traktId: Long)\n\n  suspend fun checkExists(traktId: Long): Boolean\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/NewsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.News\n\ninterface NewsLocalDataSource {\n\n  suspend fun getAllByType(type: String): List<News>\n\n  suspend fun replaceForType(items: List<News>, type: String)\n\n  suspend fun deleteAllByType(type: String): Int\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/PeopleCreditsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.Movie\nimport com.michaldrabik.data_local.database.model.PersonCredits\nimport com.michaldrabik.data_local.database.model.Show\n\ninterface PeopleCreditsLocalDataSource {\n\n  suspend fun getAllShowsForPerson(personTraktId: Long): List<Show>\n\n  suspend fun getAllMoviesForPerson(personTraktId: Long): List<Movie>\n\n  suspend fun getTimestampForPerson(personTraktId: Long): Long?\n\n  suspend fun deleteAllForPerson(personTraktId: Long)\n\n  suspend fun insertSingle(personTraktId: Long, credits: List<PersonCredits>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/PeopleImagesLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.PersonImage\n\ninterface PeopleImagesLocalDataSource {\n\n  suspend fun getTimestampForPerson(personTmdbId: Long): Long?\n\n  suspend fun getAll(personTmdbId: Long): List<PersonImage>\n\n  suspend fun deleteAllForPerson(personTmdbId: Long)\n\n  suspend fun insertSingle(personTmdbId: Long, images: List<PersonImage>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/PeopleLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.Person\n\ninterface PeopleLocalDataSource {\n\n  suspend fun upsert(people: List<Person>)\n\n  suspend fun getById(tmdbId: Long): Person?\n\n  suspend fun getAllForShow(showTraktId: Long): List<Person>\n\n  suspend fun getAllForMovie(movieTraktId: Long): List<Person>\n\n  suspend fun getAll(): List<Person>\n\n  suspend fun updateTraktId(idTrakt: Long, idTmdb: Long)\n\n  suspend fun deleteTranslations()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/PeopleShowsMoviesLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.PersonShowMovie\n\ninterface PeopleShowsMoviesLocalDataSource {\n\n  suspend fun getTimestampForShow(showTraktId: Long): Long?\n\n  suspend fun getTimestampForMovie(movieTraktId: Long): Long?\n\n  suspend fun deleteAllForShow(showTraktId: Long)\n\n  suspend fun deleteAllForMovie(movieTraktId: Long)\n\n  suspend fun insertForShow(people: List<PersonShowMovie>, showTraktId: Long)\n\n  suspend fun insertForMovie(people: List<PersonShowMovie>, movieTraktId: Long)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/RatingsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.Rating\n\ninterface RatingsLocalDataSource {\n\n  suspend fun getAll(): List<Rating>\n\n  suspend fun getAllByType(type: String): List<Rating>\n\n  suspend fun getAllByType(idsTrakt: List<Long>, type: String): List<Rating>\n\n  suspend fun deleteAllByType(type: String)\n\n  suspend fun deleteByType(traktId: Long, type: String)\n\n  suspend fun replaceAll(ratings: List<Rating>, type: String)\n\n  suspend fun replace(rating: Rating)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/RecentSearchLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.RecentSearch\n\ninterface RecentSearchLocalDataSource {\n\n  suspend fun getAll(limit: Int): List<RecentSearch>\n\n  suspend fun upsert(searches: List<RecentSearch>)\n\n  suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/RelatedMoviesLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.RelatedMovie\n\ninterface RelatedMoviesLocalDataSource {\n\n  suspend fun insert(items: List<RelatedMovie>): List<Long>\n\n  suspend fun getAllById(traktId: Long): List<RelatedMovie>\n\n  suspend fun getAll(): List<RelatedMovie>\n\n  suspend fun deleteById(traktId: Long)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/RelatedShowsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.RelatedShow\n\ninterface RelatedShowsLocalDataSource {\n\n  suspend fun insert(items: List<RelatedShow>): List<Long>\n\n  suspend fun getAllById(traktId: Long): List<RelatedShow>\n\n  suspend fun getAll(): List<RelatedShow>\n\n  suspend fun deleteById(traktId: Long)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/SeasonsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.Season\n\ninterface SeasonsLocalDataSource {\n\n  suspend fun getAllByShowsIds(traktIds: List<Long>): List<Season>\n\n  suspend fun getAllByShowsIdsChunk(traktIds: List<Long>): List<Season>\n\n  suspend fun getAllWatchedForShows(traktIds: List<Long>): List<Season>\n\n  suspend fun getAllWatchedIdsForShows(traktIds: List<Long>): List<Long>\n\n  suspend fun getAllByShowId(traktId: Long): List<Season>\n\n  suspend fun getById(traktId: Long): Season?\n\n  suspend fun update(items: List<Season>)\n\n  suspend fun upsert(items: List<Season>)\n\n  suspend fun delete(items: List<Season>)\n\n  suspend fun deleteAllForShow(showTraktId: Long)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/SettingsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.Settings\n\ninterface SettingsLocalDataSource {\n\n  suspend fun getAll(): Settings\n\n  suspend fun getCount(): Int\n\n  suspend fun upsert(settings: Settings)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/ShowImagesLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.ShowImage\n\ninterface ShowImagesLocalDataSource {\n\n  suspend fun getByShowId(tmdbId: Long, type: String): ShowImage?\n\n  suspend fun getByEpisodeId(tmdbId: Long, type: String): ShowImage?\n\n  suspend fun insertShowImage(image: ShowImage)\n\n  suspend fun insertEpisodeImage(image: ShowImage)\n\n  suspend fun upsert(image: ShowImage)\n\n  suspend fun deleteByShowId(id: Long, type: String)\n\n  suspend fun deleteByEpisodeId(id: Long, type: String)\n\n  suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/ShowRatingsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.ShowRatings\n\ninterface ShowRatingsLocalDataSource {\n\n  suspend fun upsert(entity: ShowRatings)\n\n  suspend fun getById(traktId: Long): ShowRatings?\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/ShowStreamingsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.ShowStreaming\n\ninterface ShowStreamingsLocalDataSource {\n\n  suspend fun replace(traktId: Long, entities: List<ShowStreaming>)\n\n  suspend fun getById(traktId: Long): List<ShowStreaming>\n\n  suspend fun deleteById(traktId: Long)\n\n  suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/ShowTranslationsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.ShowTranslation\n\ninterface ShowTranslationsLocalDataSource {\n\n  suspend fun getById(traktId: Long, language: String): ShowTranslation?\n\n  suspend fun getAll(language: String): List<ShowTranslation>\n\n  suspend fun insertSingle(translation: ShowTranslation)\n\n  suspend fun deleteByLanguage(languages: List<String>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/ShowsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.Show\n\ninterface ShowsLocalDataSource {\n\n  suspend fun getAll(): List<Show>\n\n  suspend fun getAll(ids: List<Long>): List<Show>\n\n  suspend fun getAllChunked(ids: List<Long>): List<Show>\n\n  suspend fun getById(traktId: Long): Show?\n\n  suspend fun getByTmdbId(tmdbId: Long): Show?\n\n  suspend fun getBySlug(slug: String): Show?\n\n  suspend fun getById(imdbId: String): Show?\n\n  suspend fun deleteById(traktId: Long)\n\n  suspend fun upsert(shows: List<Show>)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/TraktSyncLogLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.TraktSyncLog\n\ninterface TraktSyncLogLocalDataSource {\n\n  suspend fun getAllShows(): List<TraktSyncLog>\n\n  suspend fun insert(log: TraktSyncLog)\n\n  suspend fun update(idTrakt: Long, type: String, syncedAt: Long): Int\n\n  suspend fun deleteAll()\n\n  suspend fun upsertShow(idTrakt: Long, syncedAt: Long)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/TraktSyncQueueLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue\n\ninterface TraktSyncQueueLocalDataSource {\n\n  suspend fun insert(items: List<TraktSyncQueue>): List<Long>\n\n  suspend fun getAll(): List<TraktSyncQueue>\n\n  suspend fun getAll(types: List<String>): List<TraktSyncQueue>\n\n  suspend fun deleteAll(idsTrakt: List<Long>, type: String): Int\n\n  suspend fun deleteAll(type: String): Int\n\n  suspend fun deleteAll()\n\n  suspend fun deleteAllForList(idList: Long): Int\n\n  suspend fun delete(items: List<TraktSyncQueue>)\n\n  suspend fun delete(idTrakt: Long, idList: Long, type: String, operation: String): Int\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/TranslationsMoviesSyncLogLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.TranslationsMoviesSyncLog\n\ninterface TranslationsMoviesSyncLogLocalDataSource {\n\n  suspend fun getAll(): List<TranslationsMoviesSyncLog>\n\n  suspend fun getById(idTrakt: Long): TranslationsMoviesSyncLog?\n\n  suspend fun upsert(log: TranslationsMoviesSyncLog)\n\n  suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/TranslationsShowsSyncLogLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.TranslationsSyncLog\n\ninterface TranslationsShowsSyncLogLocalDataSource {\n\n  suspend fun getAll(): List<TranslationsSyncLog>\n\n  suspend fun getById(idTrakt: Long): TranslationsSyncLog?\n\n  suspend fun upsert(log: TranslationsSyncLog)\n\n  suspend fun deleteAll()\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/UserLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.User\n\ninterface UserLocalDataSource {\n\n  suspend fun get(): User?\n\n  suspend fun upsert(user: User)\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/WatchlistMoviesLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.Movie\nimport com.michaldrabik.data_local.database.model.WatchlistMovie\n\ninterface WatchlistMoviesLocalDataSource {\n\n  suspend fun getAll(): List<Movie>\n\n  suspend fun getAllTraktIds(): List<Long>\n\n  suspend fun getById(traktId: Long): Movie?\n\n  suspend fun insert(movie: WatchlistMovie)\n\n  suspend fun deleteById(traktId: Long)\n\n  suspend fun checkExists(traktId: Long): Boolean\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/sources/WatchlistShowsLocalDataSource.kt",
    "content": "package com.michaldrabik.data_local.sources\n\nimport com.michaldrabik.data_local.database.model.Show\nimport com.michaldrabik.data_local.database.model.WatchlistShow\n\ninterface WatchlistShowsLocalDataSource {\n\n  suspend fun getAll(): List<Show>\n\n  suspend fun getAllTraktIds(): List<Long>\n\n  suspend fun getById(traktId: Long): Show?\n\n  suspend fun insert(show: WatchlistShow)\n\n  suspend fun deleteById(traktId: Long)\n\n  suspend fun checkExists(traktId: Long): Boolean\n}\n"
  },
  {
    "path": "data-local/src/main/java/com/michaldrabik/data_local/utilities/TransactionsProvider.kt",
    "content": "package com.michaldrabik.data_local.utilities\n\nimport androidx.room.RoomDatabase\nimport androidx.room.withTransaction\n\nclass TransactionsProvider(\n  private val database: RoomDatabase\n) {\n  suspend fun <R> withTransaction(block: suspend () -> R): R {\n    return database.withTransaction(block)\n  }\n}\n"
  },
  {
    "path": "data-remote/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "data-remote/build.gradle",
    "content": "apply plugin: \"com.android.library\"\napply plugin: \"kotlin-android\"\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  buildFeatures {\n    buildConfig = true\n  }\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n    consumerProguardFiles \"consumer-rules.pro\"\n  }\n\n  buildTypes.all {\n    Properties properties = new Properties()\n    properties.load(project.rootProject.file('local.properties').newDataInputStream())\n\n    buildConfigField(\"String\", \"TRAKT_CLIENT_ID\", properties.getProperty(\"traktClientId\"))\n    buildConfigField(\"String\", \"TRAKT_CLIENT_SECRET\", properties.getProperty(\"traktClientSecret\"))\n    buildConfigField(\"String\", \"TMDB_API_KEY\", properties.getProperty(\"tmdbApiKey\"))\n    buildConfigField(\"String\", \"OMDB_API_KEY\", properties.getProperty(\"omdbApiKey\"))\n    buildConfigField(\"String\", \"REDDIT_CLIENT_ID\", properties.getProperty(\"redditClientId\"))\n    buildConfigField 'String', 'VER_NAME', \"\\\"${versions.versionName}\\\"\"\n  }\n\n  buildTypes {\n    debug {\n      minifyEnabled false\n    }\n\n    release {\n      minifyEnabled false\n      proguardFiles getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\"\n    }\n  }\n\n  namespace 'com.michaldrabik.data_remote'\n}\n\ndependencies {\n  api libs.retrofit\n  api libs.retrofit.moshi\n  api libs.loggingInterceptor\n\n  implementation libs.coroutines\n  implementation libs.timber\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  testImplementation libs.junit\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "data-remote/consumer-rules.pro",
    "content": "### Moshi\n\n# JSR 305 annotations are for embedding nullability information.\n-dontwarn javax.annotation.**\n\n-keepclasseswithmembers class * {\n    @com.squareup.moshi.* <methods>;\n}\n\n-keep @com.squareup.moshi.JsonQualifier interface *\n\n# Enum field names are used by the integrated EnumJsonAdapter.\n# Annotate enums with @JsonClass(generateAdapter = false) to use them with Moshi.\n-keepclassmembers @com.squareup.moshi.JsonClass class * extends java.lang.Enum {\n    <fields>;\n}\n\n# The name of @JsonClass types is used to look up the generated adapter.\n-keepnames @com.squareup.moshi.JsonClass class *\n\n# Retain generated target class's synthetic defaults constructor and keep DefaultConstructorMarker's\n# name. We will look this up reflectively to invoke the type's constructor.\n#\n# We can't _just_ keep the defaults constructor because Proguard/R8's spec doesn't allow wildcard\n# matching preceding parameters.\n-keepnames class kotlin.jvm.internal.DefaultConstructorMarker\n-keepclassmembers @com.squareup.moshi.JsonClass @kotlin.Metadata class * {\n    synthetic <init>(...);\n}\n\n# Retain generated JsonAdapters if annotated type is retained.\n-if @com.squareup.moshi.JsonClass class *\n-keep class <1>JsonAdapter {\n    <init>(...);\n    <fields>;\n}\n-if @com.squareup.moshi.JsonClass class **$*\n-keep class <1>_<2>JsonAdapter {\n    <init>(...);\n    <fields>;\n}\n-if @com.squareup.moshi.JsonClass class **$*$*\n-keep class <1>_<2>_<3>JsonAdapter {\n    <init>(...);\n    <fields>;\n}\n-if @com.squareup.moshi.JsonClass class **$*$*$*\n-keep class <1>_<2>_<3>_<4>JsonAdapter {\n    <init>(...);\n    <fields>;\n}\n-if @com.squareup.moshi.JsonClass class **$*$*$*$*\n-keep class <1>_<2>_<3>_<4>_<5>JsonAdapter {\n    <init>(...);\n    <fields>;\n}\n-if @com.squareup.moshi.JsonClass class **$*$*$*$*$*\n-keep class <1>_<2>_<3>_<4>_<5>_<6>JsonAdapter {\n    <init>(...);\n    <fields>;\n}\n-keep class kotlin.reflect.jvm.internal.impl.builtins.BuiltInsLoaderImpl\n\n-keepclassmembers class kotlin.Metadata {\n    public <methods>;\n}\n\n### Retrofit\n\n# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and\n# EnclosingMethod is required to use InnerClasses.\n-keepattributes Signature, InnerClasses, EnclosingMethod\n\n# Retrofit does reflection on method and parameter annotations.\n-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations\n\n# Retain service method parameters when optimizing.\n-keepclassmembers,allowshrinking,allowobfuscation interface * {\n    @retrofit2.http.* <methods>;\n}\n\n# Ignore annotation used for build tooling.\n-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement\n\n# Ignore JSR 305 annotations for embedding nullability information.\n-dontwarn javax.annotation.**\n\n# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.\n-dontwarn kotlin.Unit\n\n# Top-level functions that can only be used by Kotlin.\n-dontwarn retrofit2.KotlinExtensions\n-dontwarn retrofit2.KotlinExtensions$*\n\n# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy\n# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.\n-if interface * { @retrofit2.http.* <methods>; }\n-keep,allowobfuscation interface <1>\n\n-keep class com.michaldrabik.data_remote.tmdb.model.** { *; }\n-keep class com.michaldrabik.data_remote.omdb.model.** { *; }\n-keep class com.michaldrabik.data_remote.trakt.model.** { *; }\n-keep class com.michaldrabik.data_remote.aws.model.** { *; }\n-keep class com.michaldrabik.data_remote.reddit.model.** { *; }\n\n### OkHttp\n\n# JSR 305 annotations are for embedding nullability information.\n-dontwarn javax.annotation.**\n\n# A resource is loaded with a relative path so the package of this class must be preserved.\n-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase\n\n# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.\n-dontwarn org.codehaus.mojo.animal_sniffer.*\n\n# OkHttp platform used only on JVM and when Conscrypt dependency is available.\n-dontwarn okhttp3.internal.platform.ConscryptPlatform\n\n# A resource is loaded with a relative path so the package of this class must be preserved.\n-adaptresourcefilenames okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz\n\n# OkHttp platform used only on JVM and when Conscrypt and other security providers are available.\n-dontwarn okhttp3.internal.platform.**\n-dontwarn org.conscrypt.**\n-dontwarn org.bouncycastle.**\n-dontwarn org.openjsse.**\n\n# With R8 full mode generic signatures are stripped for classes that are not\n# kept. Suspend functions are wrapped in continuations where the type argument\n# is used.\n-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation\n-keep,allowobfuscation,allowshrinking class com.squareup.moshi.JsonAdapter\n\n# With R8 full mode generic signatures are stripped for classes that are not\n# kept. Suspend functions are wrapped in continuations where the type argument\n# is used.\n-keep,allowobfuscation,allowshrinking interface retrofit2.Call\n-keep,allowobfuscation,allowshrinking class retrofit2.Response\n\n# R8 full mode strips generic signatures from return types if not kept.\n-if interface * { @retrofit2.http.* public *** *(...); }\n-keep,allowoptimization,allowshrinking,allowobfuscation class <3>"
  },
  {
    "path": "data-remote/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "data-remote/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/Config.kt",
    "content": "package com.michaldrabik.data_remote\n\nimport java.time.Duration\n\nobject Config {\n  const val TRAKT_VERSION = \"2\"\n  const val TRAKT_BASE_URL = \"https://api.trakt.tv/\"\n  const val TRAKT_CLIENT_ID = BuildConfig.TRAKT_CLIENT_ID\n  const val TRAKT_CLIENT_SECRET = BuildConfig.TRAKT_CLIENT_SECRET\n  const val TRAKT_REDIRECT_URL = \"showlyoss://trakt\"\n  const val TRAKT_AUTHORIZE_URL = \"https://trakt.tv/oauth/authorize?response_type=code&client_id=$TRAKT_CLIENT_ID&redirect_uri=$TRAKT_REDIRECT_URL\"\n  val TRAKT_TOKEN_REFRESH_DURATION: Duration = Duration.ofDays(30)\n\n  const val TRAKT_POPULAR_SHOWS_LIMIT = 100\n  const val TRAKT_POPULAR_MOVIES_LIMIT = 50\n  const val TRAKT_TRENDING_SHOWS_LIMIT = 298\n  const val TRAKT_TRENDING_MOVIES_LIMIT = 252\n  const val TRAKT_ANTICIPATED_SHOWS_LIMIT = 40\n  const val TRAKT_ANTICIPATED_MOVIES_LIMIT = 30\n  const val TRAKT_RELATED_SHOWS_LIMIT = 20\n  const val TRAKT_RELATED_MOVIES_LIMIT = 20\n  const val TRAKT_SEARCH_LIMIT = 50\n  const val TRAKT_SYNC_PAGE_LIMIT = 100\n\n  const val TMDB_BASE_URL = \"https://api.themoviedb.org/3/\"\n  const val TMDB_API_KEY = BuildConfig.TMDB_API_KEY\n\n  const val OMDB_BASE_URL = \"https://www.omdbapi.com/\"\n  const val OMDB_API_KEY = BuildConfig.OMDB_API_KEY\n\n  const val REDDIT_BASE_URL = \"https://www.reddit.com/api/v1/\"\n  const val REDDIT_OAUTH_BASE_URL = \"https://oauth.reddit.com/\"\n  const val REDDIT_CLIENT_ID = BuildConfig.REDDIT_CLIENT_ID\n  const val REDDIT_GRANT_TYPE = \"https://oauth.reddit.com/grants/installed_client\"\n  const val REDDIT_DEVICE_ID = \"DO_NOT_TRACK_THIS_DEVICE\"\n  const val REDDIT_LIST_LIMIT = 75\n  const val REDDIT_LIST_PAGES = 2\n\n  const val AWS_BASE_URL = \"https://showly2.s3.eu-west-2.amazonaws.com/\"\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/RemoteDataSource.kt",
    "content": "package com.michaldrabik.data_remote\n\nimport com.michaldrabik.data_remote.aws.AwsRemoteDataSource\nimport com.michaldrabik.data_remote.omdb.OmdbRemoteDataSource\nimport com.michaldrabik.data_remote.reddit.RedditRemoteDataSource\nimport com.michaldrabik.data_remote.tmdb.TmdbRemoteDataSource\nimport com.michaldrabik.data_remote.trakt.TraktRemoteDataSource\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * Provides external data sources access points.\n */\ninterface RemoteDataSource {\n  val trakt: TraktRemoteDataSource\n  val aws: AwsRemoteDataSource\n  val tmdb: TmdbRemoteDataSource\n  val omdb: OmdbRemoteDataSource\n  val reddit: RedditRemoteDataSource\n}\n\n@Singleton\ninternal class MainRemoteDataSource @Inject constructor(\n  override val trakt: TraktRemoteDataSource,\n  override val tmdb: TmdbRemoteDataSource,\n  override val aws: AwsRemoteDataSource,\n  override val reddit: RedditRemoteDataSource,\n  override val omdb: OmdbRemoteDataSource,\n) : RemoteDataSource\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/aws/AwsRemoteDataSource.kt",
    "content": "package com.michaldrabik.data_remote.aws\n\nimport com.michaldrabik.data_remote.aws.model.AwsImages\n\n/**\n * Fetch/post remote resources via private AWS API\n */\ninterface AwsRemoteDataSource {\n  suspend fun fetchImagesList(): AwsImages\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/aws/api/AwsApi.kt",
    "content": "package com.michaldrabik.data_remote.aws.api\n\nimport com.michaldrabik.data_remote.aws.AwsRemoteDataSource\nimport com.michaldrabik.data_remote.aws.model.AwsImages\n\ninternal class AwsApi(private val service: AwsService) : AwsRemoteDataSource {\n\n  override suspend fun fetchImagesList() =\n    try {\n      service.fetchImagesList().shows\n    } catch (error: Throwable) {\n      AwsImages(emptyList(), emptyList())\n    }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/aws/api/AwsService.kt",
    "content": "package com.michaldrabik.data_remote.aws.api\n\nimport com.michaldrabik.data_remote.aws.model.AwsImagesList\nimport retrofit2.http.GET\n\ninterface AwsService {\n\n  @GET(\"images/images.json\")\n  suspend fun fetchImagesList(): AwsImagesList\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/aws/model/AwsImage.kt",
    "content": "package com.michaldrabik.data_remote.aws.model\n\ndata class AwsImage(\n  val idTvdb: Long,\n  val idTmdb: Long,\n  val fileType: String\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/aws/model/AwsImagesList.kt",
    "content": "package com.michaldrabik.data_remote.aws.model\n\ndata class AwsImagesList(\n  val shows: AwsImages\n)\n\ndata class AwsImages(\n  val posters: List<AwsImage>,\n  val fanarts: List<AwsImage>\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/di/module/AwsModule.kt",
    "content": "package com.michaldrabik.data_remote.di.module\n\nimport com.michaldrabik.data_remote.aws.AwsRemoteDataSource\nimport com.michaldrabik.data_remote.aws.api.AwsApi\nimport com.michaldrabik.data_remote.aws.api.AwsService\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport retrofit2.Retrofit\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject AwsModule {\n\n  @Provides\n  @Singleton\n  fun providesAwsApi(@Named(\"retrofitAws\") retrofit: Retrofit): AwsRemoteDataSource =\n    AwsApi(retrofit.create(AwsService::class.java))\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/di/module/OkHttpModule.kt",
    "content": "package com.michaldrabik.data_remote.di.module\n\nimport com.michaldrabik.data_remote.BuildConfig\nimport com.michaldrabik.data_remote.omdb.OmdbInterceptor\nimport com.michaldrabik.data_remote.tmdb.TmdbInterceptor\nimport com.michaldrabik.data_remote.trakt.interceptors.TraktAuthenticator\nimport com.michaldrabik.data_remote.trakt.interceptors.TraktAuthorizationInterceptor\nimport com.michaldrabik.data_remote.trakt.interceptors.TraktHeadersInterceptor\nimport com.michaldrabik.data_remote.trakt.interceptors.TraktRefreshTokenInterceptor\nimport com.michaldrabik.data_remote.trakt.interceptors.TraktRetryInterceptor\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport okhttp3.OkHttpClient\nimport okhttp3.logging.HttpLoggingInterceptor\nimport java.time.Duration\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject OkHttpModule {\n\n  private val TIMEOUT_DURATION = Duration.ofSeconds(60)\n\n  @Provides\n  @Singleton\n  @Named(\"okHttpBase\")\n  fun providesBaseOkHttp(): OkHttpClient {\n    return createBaseOkHttpClient().build()\n  }\n\n  @Provides\n  @Singleton\n  @Named(\"okHttpTrakt\")\n  fun providesTraktOkHttp(\n    httpLoggingInterceptor: HttpLoggingInterceptor,\n    traktAuthorizationInterceptor: TraktAuthorizationInterceptor,\n    traktHeadersInterceptor: TraktHeadersInterceptor,\n    traktRefreshTokenInterceptor: TraktRefreshTokenInterceptor,\n    traktRetryInterceptor: TraktRetryInterceptor,\n    traktAuthenticator: TraktAuthenticator,\n  ): OkHttpClient {\n    return createBaseOkHttpClient()\n      .addInterceptor(traktHeadersInterceptor)\n      .addInterceptor(traktRefreshTokenInterceptor)\n      .addInterceptor(traktAuthorizationInterceptor)\n      .addInterceptor(traktRetryInterceptor)\n      .addInterceptor(httpLoggingInterceptor)\n      .authenticator(traktAuthenticator)\n      .build()\n  }\n\n  @Provides\n  @Singleton\n  @Named(\"okHttpTmdb\")\n  fun providesTmdbOkHttp(\n    httpLoggingInterceptor: HttpLoggingInterceptor,\n    tmdbInterceptor: TmdbInterceptor,\n  ) = createBaseOkHttpClient()\n    .addInterceptor(httpLoggingInterceptor)\n    .addInterceptor(tmdbInterceptor)\n    .build()\n\n  @Provides\n  @Singleton\n  @Named(\"okHttpOmdb\")\n  fun providesOmdbOkHttp(\n    httpLoggingInterceptor: HttpLoggingInterceptor,\n    omdbInterceptor: OmdbInterceptor,\n  ) = createBaseOkHttpClient()\n    .addInterceptor(httpLoggingInterceptor)\n    .addInterceptor(omdbInterceptor)\n    .build()\n\n  @Provides\n  @Singleton\n  @Named(\"okHttpAws\")\n  fun providesAwsOkHttp(\n    httpLoggingInterceptor: HttpLoggingInterceptor,\n  ) = createBaseOkHttpClient()\n    .addInterceptor(httpLoggingInterceptor)\n    .build()\n\n  @Provides\n  @Singleton\n  @Named(\"okHttpReddit\")\n  fun providesRedditOkHttp(\n    httpLoggingInterceptor: HttpLoggingInterceptor,\n  ) = createBaseOkHttpClient()\n    .addInterceptor(httpLoggingInterceptor)\n    .build()\n\n  @Provides\n  @Singleton\n  fun providesHttpLoggingInterceptor(): HttpLoggingInterceptor =\n    HttpLoggingInterceptor().apply {\n      level = when {\n        BuildConfig.DEBUG -> HttpLoggingInterceptor.Level.BODY\n        else -> HttpLoggingInterceptor.Level.NONE\n      }\n    }\n\n  private fun createBaseOkHttpClient() = OkHttpClient.Builder()\n    .writeTimeout(TIMEOUT_DURATION)\n    .readTimeout(TIMEOUT_DURATION)\n    .callTimeout(TIMEOUT_DURATION)\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/di/module/OmdbModule.kt",
    "content": "package com.michaldrabik.data_remote.di.module\n\nimport com.michaldrabik.data_remote.omdb.OmdbInterceptor\nimport com.michaldrabik.data_remote.omdb.OmdbRemoteDataSource\nimport com.michaldrabik.data_remote.omdb.api.OmdbApi\nimport com.michaldrabik.data_remote.omdb.api.OmdbService\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport retrofit2.Retrofit\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject OmdbModule {\n\n  @Provides\n  @Singleton\n  fun providesOmdbApi(@Named(\"retrofitOmdb\") retrofit: Retrofit): OmdbRemoteDataSource =\n    OmdbApi(retrofit.create(OmdbService::class.java))\n\n  @Provides\n  @Singleton\n  fun providesOmdbInterceptor() = OmdbInterceptor()\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/di/module/PreferencesModule.kt",
    "content": "package com.michaldrabik.data_remote.di.module\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.components.SingletonComponent\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject PreferencesModule {\n\n  @Provides\n  @Singleton\n  @Named(\"networkPreferences\")\n  fun providesNetworkPreferences(@ApplicationContext context: Context): SharedPreferences =\n    context.applicationContext.getSharedPreferences(\n      \"PREFERENCES_NETWORK\",\n      Context.MODE_PRIVATE\n    )\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/di/module/RedditModule.kt",
    "content": "package com.michaldrabik.data_remote.di.module\n\nimport com.michaldrabik.data_remote.reddit.RedditRemoteDataSource\nimport com.michaldrabik.data_remote.reddit.api.RedditApi\nimport com.michaldrabik.data_remote.reddit.api.RedditAuthApi\nimport com.michaldrabik.data_remote.reddit.api.RedditListingApi\nimport com.michaldrabik.data_remote.reddit.api.RedditService\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport retrofit2.Retrofit\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject RedditModule {\n\n  @Provides\n  @Singleton\n  internal fun providesRedditApi(\n    authApi: RedditAuthApi,\n    listingApi: RedditListingApi\n  ): RedditRemoteDataSource =\n    RedditApi(authApi, listingApi)\n\n  @Provides\n  @Singleton\n  internal fun providesRedditAuthApi(@Named(\"retrofitRedditAuth\") retrofit: Retrofit): RedditAuthApi =\n    RedditAuthApi(retrofit.create(RedditService::class.java))\n\n  @Provides\n  @Singleton\n  internal fun providesRedditListingApi(@Named(\"retrofitRedditListing\") retrofit: Retrofit): RedditListingApi =\n    RedditListingApi(retrofit.create(RedditService::class.java))\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/di/module/RemoteDataModule.kt",
    "content": "package com.michaldrabik.data_remote.di.module\n\nimport com.michaldrabik.data_remote.MainRemoteDataSource\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport dagger.Binds\nimport dagger.Module\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nabstract class RemoteDataModule {\n\n  @Binds\n  @Singleton\n  internal abstract fun providesRemoteDataSource(source: MainRemoteDataSource): RemoteDataSource\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/di/module/RetrofitModule.kt",
    "content": "package com.michaldrabik.data_remote.di.module\n\nimport com.michaldrabik.data_remote.Config.AWS_BASE_URL\nimport com.michaldrabik.data_remote.Config.OMDB_BASE_URL\nimport com.michaldrabik.data_remote.Config.REDDIT_BASE_URL\nimport com.michaldrabik.data_remote.Config.REDDIT_OAUTH_BASE_URL\nimport com.michaldrabik.data_remote.Config.TMDB_BASE_URL\nimport com.michaldrabik.data_remote.Config.TRAKT_BASE_URL\nimport com.squareup.moshi.Moshi\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport okhttp3.OkHttpClient\nimport retrofit2.Retrofit\nimport retrofit2.converter.moshi.MoshiConverterFactory\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject RetrofitModule {\n\n  @Provides\n  @Singleton\n  @Named(\"retrofitTrakt\")\n  fun providesTraktRetrofit(\n    @Named(\"okHttpTrakt\") okHttpClient: OkHttpClient,\n    moshi: Moshi,\n  ): Retrofit =\n    Retrofit.Builder()\n      .client(okHttpClient)\n      .addConverterFactory(MoshiConverterFactory.create(moshi))\n      .baseUrl(TRAKT_BASE_URL)\n      .build()\n\n  @Provides\n  @Singleton\n  @Named(\"retrofitTmdb\")\n  fun providesTmdbRetrofit(\n    @Named(\"okHttpTmdb\") okHttpClient: OkHttpClient,\n    moshi: Moshi,\n  ): Retrofit =\n    Retrofit.Builder()\n      .client(okHttpClient)\n      .addConverterFactory(MoshiConverterFactory.create(moshi))\n      .baseUrl(TMDB_BASE_URL)\n      .build()\n\n  @Provides\n  @Singleton\n  @Named(\"retrofitOmdb\")\n  fun providesOmdbRetrofit(\n    @Named(\"okHttpOmdb\") okHttpClient: OkHttpClient,\n    moshi: Moshi,\n  ): Retrofit =\n    Retrofit.Builder()\n      .client(okHttpClient)\n      .addConverterFactory(MoshiConverterFactory.create(moshi))\n      .baseUrl(OMDB_BASE_URL)\n      .build()\n\n  @Provides\n  @Singleton\n  @Named(\"retrofitAws\")\n  fun providesAwsRetrofit(\n    @Named(\"okHttpAws\") okHttpClient: OkHttpClient,\n    moshi: Moshi,\n  ): Retrofit =\n    Retrofit.Builder()\n      .client(okHttpClient)\n      .addConverterFactory(MoshiConverterFactory.create(moshi))\n      .baseUrl(AWS_BASE_URL)\n      .build()\n\n  @Provides\n  @Singleton\n  @Named(\"retrofitRedditAuth\")\n  fun providesRedditRetrofit(\n    @Named(\"okHttpReddit\") okHttpClient: OkHttpClient,\n    moshi: Moshi,\n  ): Retrofit =\n    Retrofit.Builder()\n      .client(okHttpClient)\n      .addConverterFactory(MoshiConverterFactory.create(moshi))\n      .baseUrl(REDDIT_BASE_URL)\n      .build()\n\n  @Provides\n  @Singleton\n  @Named(\"retrofitRedditListing\")\n  fun providesRedditRetrofitOAuth(\n    @Named(\"okHttpReddit\") okHttpClient: OkHttpClient,\n    moshiConverter: MoshiConverterFactory,\n  ): Retrofit =\n    Retrofit.Builder()\n      .client(okHttpClient)\n      .addConverterFactory(moshiConverter)\n      .baseUrl(REDDIT_OAUTH_BASE_URL)\n      .build()\n\n  @Provides\n  @Singleton\n  fun providesMoshiFactory(moshi: Moshi): MoshiConverterFactory = MoshiConverterFactory.create(moshi)\n\n  @Provides\n  @Singleton\n  fun providesMoshi(): Moshi = Moshi.Builder().build()\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/di/module/TmdbModule.kt",
    "content": "package com.michaldrabik.data_remote.di.module\n\nimport com.michaldrabik.data_remote.tmdb.TmdbInterceptor\nimport com.michaldrabik.data_remote.tmdb.TmdbRemoteDataSource\nimport com.michaldrabik.data_remote.tmdb.api.TmdbApi\nimport com.michaldrabik.data_remote.tmdb.api.TmdbService\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport retrofit2.Retrofit\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject TmdbModule {\n\n  @Provides\n  @Singleton\n  fun providesTmdbApi(@Named(\"retrofitTmdb\") retrofit: Retrofit): TmdbRemoteDataSource =\n    TmdbApi(retrofit.create(TmdbService::class.java))\n\n  @Provides\n  @Singleton\n  fun providesTmdbInterceptor() = TmdbInterceptor()\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/di/module/TraktModule.kt",
    "content": "package com.michaldrabik.data_remote.di.module\n\nimport android.content.SharedPreferences\nimport com.michaldrabik.data_remote.token.TokenProvider\nimport com.michaldrabik.data_remote.token.TraktTokenProvider\nimport com.michaldrabik.data_remote.trakt.TraktRemoteDataSource\nimport com.michaldrabik.data_remote.trakt.api.TraktApi\nimport com.michaldrabik.data_remote.trakt.api.service.TraktAuthService\nimport com.michaldrabik.data_remote.trakt.api.service.TraktCommentsService\nimport com.michaldrabik.data_remote.trakt.api.service.TraktMoviesService\nimport com.michaldrabik.data_remote.trakt.api.service.TraktPeopleService\nimport com.michaldrabik.data_remote.trakt.api.service.TraktSearchService\nimport com.michaldrabik.data_remote.trakt.api.service.TraktShowsService\nimport com.michaldrabik.data_remote.trakt.api.service.TraktSyncService\nimport com.michaldrabik.data_remote.trakt.api.service.TraktUsersService\nimport com.squareup.moshi.Moshi\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport okhttp3.OkHttpClient\nimport retrofit2.Retrofit\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject TraktModule {\n\n  @Provides\n  @Singleton\n  fun providesTraktApi(@Named(\"retrofitTrakt\") retrofit: Retrofit): TraktRemoteDataSource =\n    TraktApi(\n      showsService = retrofit.create(TraktShowsService::class.java),\n      moviesService = retrofit.create(TraktMoviesService::class.java),\n      usersService = retrofit.create(TraktUsersService::class.java),\n      authService = retrofit.create(TraktAuthService::class.java),\n      commentsService = retrofit.create(TraktCommentsService::class.java),\n      searchService = retrofit.create(TraktSearchService::class.java),\n      peopleService = retrofit.create(TraktPeopleService::class.java),\n      syncService = retrofit.create(TraktSyncService::class.java)\n    )\n\n  @Provides\n  @Singleton\n  fun providesTraktTokenProvider(\n    @Named(\"networkPreferences\") sharedPreferences: SharedPreferences,\n    @Named(\"okHttpBase\") okHttpClient: OkHttpClient,\n    moshi: Moshi,\n  ): TokenProvider = TraktTokenProvider(\n    sharedPreferences = sharedPreferences,\n    moshi = moshi,\n    okHttpClient = okHttpClient\n  )\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/omdb/OmdbInterceptor.kt",
    "content": "package com.michaldrabik.data_remote.omdb\n\nimport com.michaldrabik.data_remote.Config\nimport okhttp3.Interceptor\nimport okhttp3.Response\n\nclass OmdbInterceptor : Interceptor {\n  override fun intercept(chain: Interceptor.Chain): Response {\n    val url = chain.request().url.newBuilder()\n      .addQueryParameter(\"apikey\", Config.OMDB_API_KEY)\n      .addQueryParameter(\"tomatoes\", \"true\")\n      .build()\n    val request = chain.request().newBuilder()\n      .url(url)\n      .build()\n    return chain.proceed(request)\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/omdb/OmdbRemoteDataSource.kt",
    "content": "package com.michaldrabik.data_remote.omdb\n\nimport com.michaldrabik.data_remote.omdb.model.OmdbResult\n\n/**\n * Fetch/post remote resources via OMDB API\n */\ninterface OmdbRemoteDataSource {\n  suspend fun fetchOmdbData(imdbId: String): OmdbResult\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/omdb/api/OmdbApi.kt",
    "content": "package com.michaldrabik.data_remote.omdb.api\n\nimport com.michaldrabik.data_remote.omdb.OmdbRemoteDataSource\n\ninternal class OmdbApi(private val service: OmdbService) : OmdbRemoteDataSource {\n\n  override suspend fun fetchOmdbData(imdbId: String) =\n    service.fetchData(imdbId)\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/omdb/api/OmdbService.kt",
    "content": "package com.michaldrabik.data_remote.omdb.api\n\nimport com.michaldrabik.data_remote.omdb.model.OmdbResult\nimport retrofit2.http.GET\nimport retrofit2.http.Query\n\ninterface OmdbService {\n\n  @GET(\"/\")\n  suspend fun fetchData(@Query(\"i\") imdbId: String): OmdbResult\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/omdb/model/OmdbResult.kt",
    "content": "package com.michaldrabik.data_remote.omdb.model\n\ndata class OmdbResult(\n  val Ratings: List<OmdbRating>?,\n  val imdbRating: String?,\n  val imdbVotes: String?,\n  val Metascore: String?,\n  val tomatoURL: String?,\n)\n\ndata class OmdbRating(\n  val Source: String?,\n  val Value: String?,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/reddit/RedditRemoteDataSource.kt",
    "content": "package com.michaldrabik.data_remote.reddit\n\nimport com.michaldrabik.data_remote.Config\nimport com.michaldrabik.data_remote.reddit.model.RedditAuthResponse\nimport com.michaldrabik.data_remote.reddit.model.RedditItem\n\n/**\n * Fetch/post remote resources via Reddit API\n */\ninterface RedditRemoteDataSource {\n\n  suspend fun fetchAuthToken(): RedditAuthResponse\n\n  suspend fun fetchTelevisionItems(\n    token: String,\n    limit: Int = Config.REDDIT_LIST_LIMIT,\n    pages: Int = Config.REDDIT_LIST_PAGES,\n  ): List<RedditItem>\n\n  suspend fun fetchMoviesItems(\n    token: String,\n    limit: Int = Config.REDDIT_LIST_LIMIT,\n    pages: Int = Config.REDDIT_LIST_PAGES,\n  ): List<RedditItem>\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/reddit/api/RedditApi.kt",
    "content": "package com.michaldrabik.data_remote.reddit.api\n\nimport com.michaldrabik.data_remote.reddit.RedditRemoteDataSource\nimport com.michaldrabik.data_remote.reddit.model.RedditItem\n\ninternal class RedditApi(\n  private val authApi: RedditAuthApi,\n  private val listingApi: RedditListingApi,\n) : RedditRemoteDataSource {\n\n  override suspend fun fetchAuthToken() = authApi.fetchAuthToken()\n\n  override suspend fun fetchTelevisionItems(\n    token: String,\n    limit: Int,\n    pages: Int,\n  ): List<RedditItem> {\n    return listingApi.fetchTelevision(token, limit, pages).filterNot { it.is_self }\n  }\n\n  override suspend fun fetchMoviesItems(\n    token: String,\n    limit: Int,\n    pages: Int,\n  ): List<RedditItem> {\n    return listingApi.fetchMovies(token, limit, pages).filterNot { it.is_self }\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/reddit/api/RedditAuthApi.kt",
    "content": "package com.michaldrabik.data_remote.reddit.api\n\nimport com.michaldrabik.data_remote.Config\nimport com.michaldrabik.data_remote.reddit.model.RedditAuthResponse\nimport okhttp3.Credentials\n\ninternal class RedditAuthApi(private val service: RedditService) {\n\n  suspend fun fetchAuthToken(): RedditAuthResponse {\n    val credentials = Credentials.basic(Config.REDDIT_CLIENT_ID, \"\")\n    return service.fetchAccessToken(\n      credentials,\n      Config.REDDIT_GRANT_TYPE,\n      Config.REDDIT_DEVICE_ID\n    )\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/reddit/api/RedditListingApi.kt",
    "content": "package com.michaldrabik.data_remote.reddit.api\n\nimport com.michaldrabik.data_remote.reddit.model.RedditItem\n\ninternal class RedditListingApi(private val service: RedditService) {\n\n  suspend fun fetchTelevision(token: String, limit: Int, pages: Int): List<RedditItem> {\n    val result = mutableListOf<RedditItem>()\n    var after: String? = null\n    (0 until pages).forEach { _ ->\n      val response = service.fetchTelevision(\"Bearer $token\", limit, after)\n      result.addAll(response.data.children.map { it.data })\n      after = response.data.after\n    }\n    return result\n  }\n\n  suspend fun fetchMovies(token: String, limit: Int, pages: Int): List<RedditItem> {\n    val result = mutableListOf<RedditItem>()\n    var after: String? = null\n    (0 until pages).forEach { _ ->\n      val response = service.fetchMovies(\"Bearer $token\", limit, after)\n      result.addAll(response.data.children.map { it.data })\n      after = response.data.after\n    }\n    return result\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/reddit/api/RedditService.kt",
    "content": "package com.michaldrabik.data_remote.reddit.api\n\nimport com.michaldrabik.data_remote.reddit.model.RedditAuthResponse\nimport com.michaldrabik.data_remote.reddit.model.RedditResponse\nimport retrofit2.http.GET\nimport retrofit2.http.Header\nimport retrofit2.http.POST\nimport retrofit2.http.Query\n\ninterface RedditService {\n  @POST(\"access_token\")\n  suspend fun fetchAccessToken(\n    @Header(\"Authorization\") credentials: String,\n    @Query(\"grant_type\") grantType: String,\n    @Query(\"device_id\") deviceId: String,\n  ): RedditAuthResponse\n\n  @GET(\"r/television/hot/.json\")\n  suspend fun fetchTelevision(\n    @Header(\"Authorization\") token: String,\n    @Query(\"limit\") limit: Int,\n    @Query(\"after\") after: String? = null,\n  ): RedditResponse\n\n  @GET(\"r/movies/hot/.json\")\n  suspend fun fetchMovies(\n    @Header(\"Authorization\") token: String,\n    @Query(\"limit\") limit: Int,\n    @Query(\"after\") after: String? = null,\n  ): RedditResponse\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/reddit/model/RedditAuthResponse.kt",
    "content": "package com.michaldrabik.data_remote.reddit.model\n\ndata class RedditAuthResponse(\n  val access_token: String,\n  val token_type: String,\n  val device_id: String,\n  val expires_in: Long,\n  val scope: String,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/reddit/model/RedditData.kt",
    "content": "package com.michaldrabik.data_remote.reddit.model\n\ndata class RedditData(\n  val children: List<RedditDataItem>,\n  val after: String?,\n  val before: String?,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/reddit/model/RedditDataItem.kt",
    "content": "package com.michaldrabik.data_remote.reddit.model\n\ndata class RedditDataItem(\n  val kind: String,\n  val data: RedditItem,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/reddit/model/RedditItem.kt",
    "content": "package com.michaldrabik.data_remote.reddit.model\n\ndata class RedditItem(\n  val id: String,\n  val is_self: Boolean,\n  val title: String,\n  val url: String,\n  val score: Long,\n  val preview: Preview?,\n  val created_utc: Long,\n) {\n\n  data class Preview(\n    val images: List<Image>?,\n  )\n\n  data class Image(\n    val resolutions: List<Resolution>?,\n  ) {\n    data class Resolution(\n      val url: String,\n      val width: Int,\n      val height: Int,\n    )\n  }\n\n  fun findImageUrl(): String? {\n    val resolutions = preview?.images?.firstOrNull()?.resolutions\n    return resolutions?.firstOrNull { it.width > 600 }?.url\n      ?: resolutions?.lastOrNull()?.url\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/reddit/model/RedditResponse.kt",
    "content": "package com.michaldrabik.data_remote.reddit.model\n\ndata class RedditResponse(\n  val kind: String,\n  val data: RedditData,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/tmdb/TmdbInterceptor.kt",
    "content": "package com.michaldrabik.data_remote.tmdb\n\nimport com.michaldrabik.data_remote.Config\nimport okhttp3.Interceptor\nimport okhttp3.Response\n\nclass TmdbInterceptor : Interceptor {\n  override fun intercept(chain: Interceptor.Chain): Response {\n    val request = chain.request().newBuilder()\n      .header(\"Content-Type\", \"application/json\")\n      .header(\"Authorization\", \"Bearer ${Config.TMDB_API_KEY}\")\n      .build()\n\n    return chain.proceed(request)\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/tmdb/TmdbRemoteDataSource.kt",
    "content": "package com.michaldrabik.data_remote.tmdb\n\nimport com.michaldrabik.data_remote.tmdb.model.TmdbImage\nimport com.michaldrabik.data_remote.tmdb.model.TmdbImages\nimport com.michaldrabik.data_remote.tmdb.model.TmdbPerson\nimport com.michaldrabik.data_remote.tmdb.model.TmdbStreamingCountry\nimport com.michaldrabik.data_remote.tmdb.model.TmdbTranslation\n\n/**\n * Fetch/post remote resources via TMDB API\n */\ninterface TmdbRemoteDataSource {\n\n  suspend fun fetchShowImages(tmdbId: Long): TmdbImages\n\n  suspend fun fetchEpisodeImage(showTmdbId: Long?, season: Int?, episode: Int?): TmdbImage?\n\n  suspend fun fetchMovieImages(tmdbId: Long): TmdbImages\n\n  suspend fun fetchMoviePeople(tmdbId: Long): Map<TmdbPerson.Type, List<TmdbPerson>>\n\n  suspend fun fetchShowPeople(tmdbId: Long): Map<TmdbPerson.Type, List<TmdbPerson>>\n\n  suspend fun fetchShowWatchProviders(tmdbId: Long, countryCode: String): TmdbStreamingCountry?\n\n  suspend fun fetchMovieWatchProviders(tmdbId: Long, countryCode: String): TmdbStreamingCountry?\n\n  suspend fun fetchPersonDetails(id: Long): TmdbPerson\n\n  suspend fun fetchPersonTranslations(id: Long): Map<String, TmdbTranslation.Data>\n\n  suspend fun fetchPersonImages(tmdbId: Long): TmdbImages\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/tmdb/api/TmdbApi.kt",
    "content": "package com.michaldrabik.data_remote.tmdb.api\n\nimport com.michaldrabik.data_remote.tmdb.TmdbRemoteDataSource\nimport com.michaldrabik.data_remote.tmdb.model.TmdbImages\nimport com.michaldrabik.data_remote.tmdb.model.TmdbPerson\nimport com.michaldrabik.data_remote.tmdb.model.TmdbStreamingCountry\nimport com.michaldrabik.data_remote.tmdb.model.TmdbTranslation\n\ninternal class TmdbApi(private val service: TmdbService) : TmdbRemoteDataSource {\n\n  override suspend fun fetchShowImages(tmdbId: Long) =\n    try {\n      if (tmdbId <= 0) TmdbImages.EMPTY\n      service.fetchShowImages(tmdbId)\n    } catch (error: Throwable) {\n      TmdbImages.EMPTY\n    }\n\n  override suspend fun fetchEpisodeImage(showTmdbId: Long?, season: Int?, episode: Int?) =\n    try {\n      if (showTmdbId == null || showTmdbId <= 0) TmdbImages.EMPTY\n      if (season == null || season <= 0) TmdbImages.EMPTY\n      if (episode == null || episode <= 0) TmdbImages.EMPTY\n      val images = service.fetchEpisodeImages(showTmdbId, season, episode)\n      images.stills?.firstOrNull()\n    } catch (error: Throwable) {\n      null\n    }\n\n  override suspend fun fetchMovieImages(tmdbId: Long) =\n    try {\n      if (tmdbId <= 0) TmdbImages.EMPTY\n      service.fetchMovieImages(tmdbId)\n    } catch (error: Throwable) {\n      TmdbImages.EMPTY\n    }\n\n  override suspend fun fetchMoviePeople(tmdbId: Long): Map<TmdbPerson.Type, List<TmdbPerson>> {\n    val result = service.fetchMoviePeople(tmdbId)\n    val cast = result.cast?.toList() ?: emptyList()\n    val crew = result.crew?.toList() ?: emptyList()\n    return mapOf(\n      TmdbPerson.Type.CAST to cast,\n      TmdbPerson.Type.CREW to crew\n    )\n  }\n\n  override suspend fun fetchShowPeople(tmdbId: Long): Map<TmdbPerson.Type, List<TmdbPerson>> {\n    val result = service.fetchShowPeople(tmdbId)\n    val cast = result.cast?.toList() ?: emptyList()\n    val crew = result.crew?.toList() ?: emptyList()\n    return mapOf(\n      TmdbPerson.Type.CAST to cast,\n      TmdbPerson.Type.CREW to crew\n    )\n  }\n\n  override suspend fun fetchShowWatchProviders(tmdbId: Long, countryCode: String): TmdbStreamingCountry? {\n    val result = service.fetchShowWatchProviders(tmdbId)\n    val code = when (countryCode.uppercase()) {\n      \"UK\" -> \"GB\"\n      else -> countryCode.uppercase()\n    }\n    return result.results[code]\n  }\n\n  override suspend fun fetchMovieWatchProviders(tmdbId: Long, countryCode: String): TmdbStreamingCountry? {\n    val result = service.fetchMovieWatchProviders(tmdbId)\n    val code = when (countryCode.uppercase()) {\n      \"UK\" -> \"GB\"\n      else -> countryCode.uppercase()\n    }\n    return result.results[code]\n  }\n\n  override suspend fun fetchPersonDetails(id: Long): TmdbPerson {\n    return service.fetchPersonDetails(id)\n  }\n\n  override suspend fun fetchPersonTranslations(id: Long): Map<String, TmdbTranslation.Data> {\n    val result = service.fetchPersonTranslation(id).translations ?: emptyList()\n    return result\n      .filter { if (it.iso_639_1.lowercase() != \"zh\") true else it.iso_3166_1.lowercase() == \"cn\" } // Chinese Simplified filter\n      .associateBy(\n        keySelector = { it.iso_639_1.lowercase() },\n        valueTransform = { it.data ?: TmdbTranslation.Data(null) }\n      )\n  }\n\n  override suspend fun fetchPersonImages(tmdbId: Long) =\n    try {\n      if (tmdbId <= 0) TmdbImages.EMPTY\n      service.fetchPersonImages(tmdbId)\n    } catch (error: Throwable) {\n      TmdbImages.EMPTY\n    }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/tmdb/api/TmdbService.kt",
    "content": "package com.michaldrabik.data_remote.tmdb.api\n\nimport com.michaldrabik.data_remote.tmdb.model.TmdbImages\nimport com.michaldrabik.data_remote.tmdb.model.TmdbPeople\nimport com.michaldrabik.data_remote.tmdb.model.TmdbPerson\nimport com.michaldrabik.data_remote.tmdb.model.TmdbStreamings\nimport com.michaldrabik.data_remote.tmdb.model.TmdbTranslationResponse\nimport retrofit2.http.GET\nimport retrofit2.http.Path\n\ninterface TmdbService {\n\n  @GET(\"tv/{tmdbId}/images\")\n  suspend fun fetchShowImages(@Path(\"tmdbId\") tmdbId: Long): TmdbImages\n\n  @GET(\"tv/{tmdbId}/season/{season}/episode/{episode}/images\")\n  suspend fun fetchEpisodeImages(\n    @Path(\"tmdbId\") tmdbId: Long?,\n    @Path(\"season\") seasonNumber: Int?,\n    @Path(\"episode\") episodeNumber: Int?\n  ): TmdbImages\n\n  @GET(\"movie/{tmdbId}/images\")\n  suspend fun fetchMovieImages(@Path(\"tmdbId\") tmdbId: Long): TmdbImages\n\n  @GET(\"person/{tmdbId}/images\")\n  suspend fun fetchPersonImages(@Path(\"tmdbId\") tmdbId: Long): TmdbImages\n\n  @GET(\"person/{tmdbId}\")\n  suspend fun fetchPersonDetails(@Path(\"tmdbId\") tmdbId: Long): TmdbPerson\n\n  @GET(\"person/{tmdbId}/translations\")\n  suspend fun fetchPersonTranslation(@Path(\"tmdbId\") tmdbId: Long): TmdbTranslationResponse\n\n  @GET(\"movie/{tmdbId}/credits\")\n  suspend fun fetchMoviePeople(@Path(\"tmdbId\") tmdbId: Long): TmdbPeople\n\n  @GET(\"tv/{tmdbId}/aggregate_credits\")\n  suspend fun fetchShowPeople(@Path(\"tmdbId\") tmdbId: Long): TmdbPeople\n\n  @GET(\"movie/{tmdbId}/watch/providers\")\n  suspend fun fetchMovieWatchProviders(@Path(\"tmdbId\") tmdbId: Long): TmdbStreamings\n\n  @GET(\"tv/{tmdbId}/watch/providers\")\n  suspend fun fetchShowWatchProviders(@Path(\"tmdbId\") tmdbId: Long): TmdbStreamings\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/tmdb/model/TmdbImage.kt",
    "content": "package com.michaldrabik.data_remote.tmdb.model\n\ndata class TmdbImage(\n  val file_path: String,\n  val vote_average: Float,\n  val vote_count: Long,\n  val iso_639_1: String?,\n) {\n\n  fun isPlain() = iso_639_1 == null\n\n  fun isEnglish() = iso_639_1 == \"en\"\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/tmdb/model/TmdbImages.kt",
    "content": "package com.michaldrabik.data_remote.tmdb.model\n\ndata class TmdbImages(\n  val backdrops: List<TmdbImage>?,\n  val posters: List<TmdbImage>?,\n  val stills: List<TmdbImage>?,\n  val profiles: List<TmdbImage>?,\n) {\n\n  companion object {\n    val EMPTY = TmdbImages(emptyList(), emptyList(), emptyList(), emptyList())\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/tmdb/model/TmdbPeople.kt",
    "content": "package com.michaldrabik.data_remote.tmdb.model\n\ndata class TmdbPeople(\n  val id: Long,\n  val cast: List<TmdbPerson>?,\n  val crew: List<TmdbPerson>?,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/tmdb/model/TmdbPerson.kt",
    "content": "package com.michaldrabik.data_remote.tmdb.model\n\ndata class TmdbPerson(\n  val id: Long,\n  val name: String?,\n  val place_of_birth: String?,\n  val homepage: String?,\n  val character: String?,\n  val department: String?,\n  val roles: List<Role>?,\n  val jobs: List<Job>?,\n  val job: String?,\n  val deathday: String?,\n  val birthday: String?,\n  val biography: String?,\n  val imdb_id: String?,\n  val known_for_department: String?,\n  val profile_path: String?,\n  val total_episode_count: Int?\n) {\n\n  data class Role(\n    val character: String?\n  )\n\n  data class Job(\n    val job: String?\n  )\n\n  enum class Type {\n    CAST,\n    CREW\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/tmdb/model/TmdbStreamingCountry.kt",
    "content": "package com.michaldrabik.data_remote.tmdb.model\n\ndata class TmdbStreamingCountry(\n  val link: String,\n  val flatrate: List<TmdbStreamingService>?,\n  val free: List<TmdbStreamingService>?,\n  val buy: List<TmdbStreamingService>?,\n  val rent: List<TmdbStreamingService>?,\n  val ads: List<TmdbStreamingService>?,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/tmdb/model/TmdbStreamingService.kt",
    "content": "package com.michaldrabik.data_remote.tmdb.model\n\ndata class TmdbStreamingService(\n  val display_priority: Long,\n  val logo_path: String,\n  val provider_id: Long,\n  val provider_name: String,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/tmdb/model/TmdbStreamings.kt",
    "content": "package com.michaldrabik.data_remote.tmdb.model\n\ndata class TmdbStreamings(\n  val id: Long,\n  val results: Map<String, TmdbStreamingCountry>,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/tmdb/model/TmdbTranslation.kt",
    "content": "package com.michaldrabik.data_remote.tmdb.model\n\ndata class TmdbTranslation(\n  val iso_639_1: String, // ex: zh\n  val iso_3166_1: String, // ex: CN\n  val data: Data?\n) {\n\n  data class Data(\n    val biography: String?\n  )\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/tmdb/model/TmdbTranslationResponse.kt",
    "content": "package com.michaldrabik.data_remote.tmdb.model\n\ndata class TmdbTranslationResponse(\n  val id: Long?,\n  val translations: List<TmdbTranslation>?\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/token/TokenProvider.kt",
    "content": "package com.michaldrabik.data_remote.token\n\nimport com.michaldrabik.data_remote.trakt.model.OAuthResponse\n\ninterface TokenProvider {\n\n  /**\n   * Returns access token if available or null.\n   */\n  fun getToken(): String?\n\n  /**\n   * Save access and refresh tokens.\n   */\n  fun saveTokens(accessToken: String, refreshToken: String)\n\n  /**\n   * Revokes and deletes access and refresh tokens.\n   */\n  fun revokeToken()\n\n  /**\n   * Tries to refresh current access token or throws if failure.\n   */\n  suspend fun refreshToken(): OAuthResponse\n\n  suspend fun shouldRefresh(): Boolean\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/token/TraktTokenProvider.kt",
    "content": "package com.michaldrabik.data_remote.token\n\nimport android.annotation.SuppressLint\nimport android.content.SharedPreferences\nimport com.michaldrabik.data_remote.Config\nimport com.michaldrabik.data_remote.trakt.model.OAuthResponse\nimport com.squareup.moshi.Moshi\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport okhttp3.Call\nimport okhttp3.Callback\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport okhttp3.Response\nimport okhttp3.internal.closeQuietly\nimport org.json.JSONObject\nimport timber.log.Timber\nimport java.io.IOException\nimport java.time.Duration\nimport javax.inject.Named\nimport javax.inject.Singleton\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException\n\n@SuppressLint(\"ApplySharedPref\")\n@Singleton\ninternal class TraktTokenProvider(\n  private val sharedPreferences: SharedPreferences,\n  private val moshi: Moshi,\n  @Named(\"okHttpBase\") private val okHttpClient: OkHttpClient,\n) : TokenProvider {\n\n  companion object {\n    private const val KEY_ACCESS_TOKEN = \"TRAKT_ACCESS_TOKEN\"\n    private const val KEY_REFRESH_TOKEN = \"TRAKT_REFRESH_TOKEN\"\n    private const val KEY_TIMESTAMP = \"TRAKT_ACCESS_TOKEN_TIMESTAMP\"\n\n    private val TRAKT_TOKEN_REFRESH_COOLDOWN: Duration = Duration.ofDays(1)\n  }\n\n  private var token: String? = null\n  private var lastRefreshCheck: Long = 0\n\n  override fun getToken(): String? {\n    if (token == null) {\n      token = sharedPreferences.getString(KEY_ACCESS_TOKEN, null)\n    }\n    return token\n  }\n\n  override fun saveTokens(accessToken: String, refreshToken: String) {\n    sharedPreferences.edit()\n      .putString(KEY_ACCESS_TOKEN, accessToken)\n      .putString(KEY_REFRESH_TOKEN, refreshToken)\n      .putLong(KEY_TIMESTAMP, System.currentTimeMillis())\n      .commit()\n    token = null\n  }\n\n  override fun revokeToken() {\n    sharedPreferences.edit()\n      .clear()\n      .remove(KEY_ACCESS_TOKEN)\n      .remove(KEY_REFRESH_TOKEN)\n      .remove(KEY_TIMESTAMP)\n      .commit()\n    token = null\n  }\n\n  override suspend fun shouldRefresh(): Boolean {\n    val now = System.currentTimeMillis()\n    if (lastRefreshCheck > 0L && now - lastRefreshCheck < TRAKT_TOKEN_REFRESH_COOLDOWN.toMillis()) {\n      return false\n    }\n    lastRefreshCheck = now\n    val timestamp = sharedPreferences.getLong(KEY_TIMESTAMP, 0L)\n    if (timestamp == 0L) {\n      return true\n    }\n    if (now - timestamp > Config.TRAKT_TOKEN_REFRESH_DURATION.toMillis()) {\n      return true\n    }\n    return false\n  }\n\n  override suspend fun refreshToken(): OAuthResponse {\n    val refreshToken = sharedPreferences.getString(KEY_REFRESH_TOKEN, null)\n      ?: throw Error(\"Refresh token is not available\")\n\n    val body = JSONObject()\n      .put(\"refresh_token\", refreshToken)\n      .put(\"client_id\", Config.TRAKT_CLIENT_ID)\n      .put(\"client_secret\", Config.TRAKT_CLIENT_SECRET)\n      .put(\"redirect_uri\", Config.TRAKT_REDIRECT_URL)\n      .put(\"grant_type\", \"refresh_token\")\n      .toString()\n\n    val request = Request.Builder()\n      .url(\"${Config.TRAKT_BASE_URL}oauth/token\")\n      .addHeader(\"Content-Type\", \"application/json\")\n      .post(body.toRequestBody(\"application/json\".toMediaType()))\n      .build()\n\n    Timber.d(\"Making refresh token call...\")\n\n    return suspendCancellableCoroutine {\n      val callback = object : Callback {\n        override fun onFailure(call: Call, e: IOException) {\n          Timber.d(\"Refresh token call failed. $e\")\n          it.resumeWithException(Error(\"Refresh token call failed. $e\"))\n        }\n\n        override fun onResponse(call: Call, response: Response) {\n          if (response.isSuccessful) {\n            Timber.d(\"Refresh token success!\")\n            val responseSource = response.body!!.source()\n            val result = moshi.adapter(OAuthResponse::class.java).fromJson(responseSource)!!\n            it.resume(result)\n          } else {\n            it.resumeWithException(Error(\"Refresh token call failed. ${response.code}\"))\n          }\n          response.closeQuietly()\n        }\n      }\n      val call = okHttpClient.newCall(request)\n      it.invokeOnCancellation { call.cancel() }\n      call.enqueue(callback)\n    }\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/TraktRemoteDataSource.kt",
    "content": "package com.michaldrabik.data_remote.trakt\n\nimport com.michaldrabik.data_remote.tmdb.model.TmdbPerson\nimport com.michaldrabik.data_remote.trakt.model.Comment\nimport com.michaldrabik.data_remote.trakt.model.CustomList\nimport com.michaldrabik.data_remote.trakt.model.Episode\nimport com.michaldrabik.data_remote.trakt.model.HiddenItem\nimport com.michaldrabik.data_remote.trakt.model.Ids\nimport com.michaldrabik.data_remote.trakt.model.Movie\nimport com.michaldrabik.data_remote.trakt.model.MovieCollection\nimport com.michaldrabik.data_remote.trakt.model.OAuthResponse\nimport com.michaldrabik.data_remote.trakt.model.PersonCredit\nimport com.michaldrabik.data_remote.trakt.model.RatingResultEpisode\nimport com.michaldrabik.data_remote.trakt.model.RatingResultMovie\nimport com.michaldrabik.data_remote.trakt.model.RatingResultSeason\nimport com.michaldrabik.data_remote.trakt.model.RatingResultShow\nimport com.michaldrabik.data_remote.trakt.model.SearchResult\nimport com.michaldrabik.data_remote.trakt.model.Season\nimport com.michaldrabik.data_remote.trakt.model.SeasonTranslation\nimport com.michaldrabik.data_remote.trakt.model.Show\nimport com.michaldrabik.data_remote.trakt.model.SyncExportItem\nimport com.michaldrabik.data_remote.trakt.model.SyncExportRequest\nimport com.michaldrabik.data_remote.trakt.model.SyncExportResult\nimport com.michaldrabik.data_remote.trakt.model.SyncItem\nimport com.michaldrabik.data_remote.trakt.model.Translation\nimport com.michaldrabik.data_remote.trakt.model.User\nimport com.michaldrabik.data_remote.trakt.model.request.CommentRequest\nimport retrofit2.Response\n\n/**\n * Fetch/post remote resources via Trakt API\n */\ninterface TraktRemoteDataSource {\n\n  suspend fun fetchShow(traktId: Long): Show\n\n  suspend fun fetchShow(traktSlug: String): Show\n\n  suspend fun fetchMovie(traktId: Long): Movie\n\n  suspend fun fetchMovie(traktSlug: String): Movie\n\n  suspend fun fetchPopularShows(genres: String, networks: String): List<Show>\n\n  suspend fun fetchPopularMovies(genres: String): List<Movie>\n\n  suspend fun fetchTrendingShows(genres: String, networks: String, limit: Int): List<Show>\n\n  suspend fun fetchTrendingMovies(genres: String, limit: Int): List<Movie>\n\n  suspend fun fetchAnticipatedShows(genres: String, networks: String): List<Show>\n\n  suspend fun fetchAnticipatedMovies(genres: String): List<Movie>\n\n  suspend fun fetchRelatedShows(traktId: Long, addToLimit: Int): List<Show>\n\n  suspend fun fetchRelatedMovies(traktId: Long, addToLimit: Int): List<Movie>\n\n  suspend fun fetchNextEpisode(traktId: Long): Episode?\n\n  suspend fun fetchSearch(query: String, withMovies: Boolean): List<SearchResult>\n\n  suspend fun fetchPersonIds(idType: String, id: String): Ids?\n\n  suspend fun fetchPersonShowsCredits(traktId: Long, type: TmdbPerson.Type): List<PersonCredit>\n\n  suspend fun fetchPersonMoviesCredits(traktId: Long, type: TmdbPerson.Type): List<PersonCredit>\n\n  suspend fun fetchSearchId(idType: String, id: String): List<SearchResult>\n\n  suspend fun fetchSeasons(traktId: Long): List<Season>\n\n  suspend fun fetchShowComments(traktId: Long, limit: Int): List<Comment>\n\n  suspend fun fetchMovieComments(traktId: Long, limit: Int): List<Comment>\n\n  suspend fun fetchCommentReplies(commentId: Long): List<Comment>\n\n  suspend fun postComment(commentRequest: CommentRequest): Comment\n\n  suspend fun postCommentReply(commentId: Long, commentRequest: CommentRequest): Comment\n\n  suspend fun deleteComment(commentId: Long): Response<Any>\n\n  suspend fun fetchShowTranslations(traktId: Long, code: String): List<Translation>\n\n  suspend fun fetchMovieTranslations(traktId: Long, code: String): List<Translation>\n\n  suspend fun fetchSeasonTranslations(showTraktId: Long, seasonNumber: Int, code: String): List<SeasonTranslation>\n\n  suspend fun fetchEpisodeComments(\n    traktId: Long,\n    seasonNumber: Int,\n    episodeNumber: Int,\n  ): List<Comment>\n\n  suspend fun fetchAuthTokens(code: String): OAuthResponse\n\n  suspend fun refreshAuthTokens(refreshToken: String): OAuthResponse\n\n  suspend fun revokeAuthTokens(token: String)\n\n  suspend fun fetchMyProfile(): User\n\n  suspend fun fetchHiddenShows(): List<HiddenItem>\n\n  suspend fun postHiddenShows(shows: List<SyncExportItem> = emptyList())\n\n  suspend fun postHiddenMovies(movies: List<SyncExportItem> = emptyList())\n\n  suspend fun fetchHiddenMovies(): List<HiddenItem>\n\n  suspend fun fetchSyncWatchedShows(extended: String? = null): List<SyncItem>\n\n  suspend fun fetchSyncWatchedMovies(extended: String? = null): List<SyncItem>\n\n  suspend fun fetchSyncShowsWatchlist(): List<SyncItem>\n\n  suspend fun fetchSyncMoviesWatchlist(): List<SyncItem>\n\n  suspend fun fetchSyncWatchlist(type: String): List<SyncItem>\n\n  suspend fun fetchSyncLists(): List<CustomList>\n\n  suspend fun fetchSyncList(listId: Long): CustomList\n\n  suspend fun fetchSyncListItems(listId: Long, withMovies: Boolean): List<SyncItem>\n\n  suspend fun postCreateList(name: String, description: String?): CustomList\n\n  suspend fun postUpdateList(customList: CustomList): CustomList\n\n  suspend fun deleteList(listId: Long)\n\n  suspend fun postAddListItems(\n    listTraktId: Long,\n    showsIds: List<Long>,\n    moviesIds: List<Long>,\n  ): SyncExportResult\n\n  suspend fun postRemoveListItems(\n    listTraktId: Long,\n    showsIds: List<Long>,\n    moviesIds: List<Long>,\n  ): SyncExportResult\n\n  suspend fun postSyncWatchlist(request: SyncExportRequest): SyncExportResult\n\n  suspend fun postSyncWatched(request: SyncExportRequest): SyncExportResult\n\n  suspend fun postDeleteProgress(request: SyncExportRequest): SyncExportResult\n\n  suspend fun postDeleteWatchlist(request: SyncExportRequest): SyncExportResult\n\n  suspend fun deleteHiddenShow(request: SyncExportRequest): SyncExportResult\n\n  suspend fun deleteHiddenMovie(request: SyncExportRequest): SyncExportResult\n\n  suspend fun deleteRating(show: Show)\n\n  suspend fun deleteRating(movie: Movie)\n\n  suspend fun deleteRating(episode: Episode)\n\n  suspend fun deleteRating(season: Season)\n\n  suspend fun postRating(movie: Movie, rating: Int)\n\n  suspend fun postRating(show: Show, rating: Int)\n\n  suspend fun postRating(episode: Episode, rating: Int)\n\n  suspend fun postRating(season: Season, rating: Int)\n\n  suspend fun fetchShowsRatings(): List<RatingResultShow>\n\n  suspend fun fetchMoviesRatings(): List<RatingResultMovie>\n\n  suspend fun fetchEpisodesRatings(): List<RatingResultEpisode>\n\n  suspend fun fetchSeasonsRatings(): List<RatingResultSeason>\n\n  suspend fun fetchMovieCollections(traktId: Long): List<MovieCollection>\n\n  suspend fun fetchMovieCollectionItems(collectionId: Long): List<Movie>\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/api/TraktApi.kt",
    "content": "package com.michaldrabik.data_remote.trakt.api\n\nimport com.michaldrabik.data_remote.Config\nimport com.michaldrabik.data_remote.Config.TRAKT_ANTICIPATED_SHOWS_LIMIT\nimport com.michaldrabik.data_remote.Config.TRAKT_CLIENT_ID\nimport com.michaldrabik.data_remote.Config.TRAKT_CLIENT_SECRET\nimport com.michaldrabik.data_remote.Config.TRAKT_REDIRECT_URL\nimport com.michaldrabik.data_remote.Config.TRAKT_SYNC_PAGE_LIMIT\nimport com.michaldrabik.data_remote.tmdb.model.TmdbPerson\nimport com.michaldrabik.data_remote.trakt.TraktRemoteDataSource\nimport com.michaldrabik.data_remote.trakt.api.service.TraktAuthService\nimport com.michaldrabik.data_remote.trakt.api.service.TraktCommentsService\nimport com.michaldrabik.data_remote.trakt.api.service.TraktMoviesService\nimport com.michaldrabik.data_remote.trakt.api.service.TraktPeopleService\nimport com.michaldrabik.data_remote.trakt.api.service.TraktSearchService\nimport com.michaldrabik.data_remote.trakt.api.service.TraktShowsService\nimport com.michaldrabik.data_remote.trakt.api.service.TraktSyncService\nimport com.michaldrabik.data_remote.trakt.api.service.TraktUsersService\nimport com.michaldrabik.data_remote.trakt.model.Comment\nimport com.michaldrabik.data_remote.trakt.model.CustomList\nimport com.michaldrabik.data_remote.trakt.model.Episode\nimport com.michaldrabik.data_remote.trakt.model.Ids\nimport com.michaldrabik.data_remote.trakt.model.Movie\nimport com.michaldrabik.data_remote.trakt.model.MovieCollection\nimport com.michaldrabik.data_remote.trakt.model.OAuthResponse\nimport com.michaldrabik.data_remote.trakt.model.PersonCredit\nimport com.michaldrabik.data_remote.trakt.model.Season\nimport com.michaldrabik.data_remote.trakt.model.Show\nimport com.michaldrabik.data_remote.trakt.model.SyncExportItem\nimport com.michaldrabik.data_remote.trakt.model.SyncExportRequest\nimport com.michaldrabik.data_remote.trakt.model.SyncExportResult\nimport com.michaldrabik.data_remote.trakt.model.SyncItem\nimport com.michaldrabik.data_remote.trakt.model.request.CommentRequest\nimport com.michaldrabik.data_remote.trakt.model.request.CreateListRequest\nimport com.michaldrabik.data_remote.trakt.model.request.OAuthRefreshRequest\nimport com.michaldrabik.data_remote.trakt.model.request.OAuthRequest\nimport com.michaldrabik.data_remote.trakt.model.request.OAuthRevokeRequest\nimport com.michaldrabik.data_remote.trakt.model.request.RatingRequest\nimport com.michaldrabik.data_remote.trakt.model.request.RatingRequestValue\nimport java.lang.System.currentTimeMillis\n\ninternal class TraktApi(\n  private val showsService: TraktShowsService,\n  private val moviesService: TraktMoviesService,\n  private val usersService: TraktUsersService,\n  private val syncService: TraktSyncService,\n  private val authService: TraktAuthService,\n  private val commentsService: TraktCommentsService,\n  private val searchService: TraktSearchService,\n  private val peopleService: TraktPeopleService,\n) : TraktRemoteDataSource {\n\n  override suspend fun fetchShow(traktId: Long) =\n    showsService.fetchShow(traktId)\n\n  override suspend fun fetchShow(traktSlug: String) =\n    showsService.fetchShow(traktSlug)\n\n  override suspend fun fetchMovie(traktId: Long) =\n    moviesService.fetchMovie(traktId)\n\n  override suspend fun fetchMovie(traktSlug: String) =\n    moviesService.fetchMovie(traktSlug)\n\n  override suspend fun fetchPopularShows(genres: String, networks: String) =\n    showsService.fetchPopularShows(genres, networks, Config.TRAKT_POPULAR_SHOWS_LIMIT)\n\n  override suspend fun fetchPopularMovies(genres: String) =\n    moviesService.fetchPopularMovies(genres)\n\n  override suspend fun fetchTrendingShows(genres: String, networks: String, limit: Int): List<Show> =\n    showsService.fetchTrendingShows(genres, networks, limit).map { it.show!! }\n\n  override suspend fun fetchTrendingMovies(genres: String, limit: Int) =\n    moviesService.fetchTrendingMovies(genres, limit).map { it.movie!! }\n\n  override suspend fun fetchAnticipatedShows(genres: String, networks: String): List<Show> =\n    showsService.fetchAnticipatedShows(genres, networks, TRAKT_ANTICIPATED_SHOWS_LIMIT).map { it.show!! }\n\n  override suspend fun fetchAnticipatedMovies(genres: String) =\n    moviesService.fetchAnticipatedMovies(genres).map { it.movie!! }\n\n  override suspend fun fetchRelatedShows(traktId: Long, addToLimit: Int) =\n    showsService.fetchRelatedShows(traktId, Config.TRAKT_RELATED_SHOWS_LIMIT + addToLimit)\n\n  override suspend fun fetchRelatedMovies(traktId: Long, addToLimit: Int) =\n    moviesService.fetchRelatedMovies(traktId, Config.TRAKT_RELATED_MOVIES_LIMIT + addToLimit)\n\n  override suspend fun fetchNextEpisode(traktId: Long): Episode? {\n    val response = showsService.fetchNextEpisode(traktId)\n    if (response.isSuccessful && response.code() == 204) return null\n    return response.body()\n  }\n\n  override suspend fun fetchSearch(query: String, withMovies: Boolean) =\n    if (withMovies) searchService.fetchSearchResultsMovies(query)\n    else searchService.fetchSearchResults(query)\n\n  override suspend fun fetchPersonIds(idType: String, id: String): Ids? {\n    val result = searchService.fetchPersonIds(idType, id)\n    if (result.isNotEmpty()) {\n      return result.first().person?.ids\n    }\n    return null\n  }\n\n  override suspend fun fetchPersonShowsCredits(traktId: Long, type: TmdbPerson.Type): List<PersonCredit> {\n    val result = peopleService.fetchPersonCredits(traktId = traktId, \"shows\")\n    val cast = result.cast ?: emptyList()\n    val crew = result.crew?.values?.flatten()?.distinctBy { it.show?.ids?.trakt } ?: emptyList()\n    return if (type == TmdbPerson.Type.CAST) cast else crew\n  }\n\n  override suspend fun fetchPersonMoviesCredits(traktId: Long, type: TmdbPerson.Type): List<PersonCredit> {\n    val result = peopleService.fetchPersonCredits(traktId = traktId, \"movies\")\n    val cast = result.cast ?: emptyList()\n    val crew = result.crew?.values?.flatten()?.distinctBy { it.movie?.ids?.trakt } ?: emptyList()\n    return if (type == TmdbPerson.Type.CAST) cast else crew\n  }\n\n  override suspend fun fetchSearchId(idType: String, id: String) =\n    searchService.fetchSearchId(idType, id)\n\n  override suspend fun fetchSeasons(traktId: Long) =\n    showsService.fetchSeasons(traktId)\n      .sortedByDescending { it.number }\n\n  override suspend fun fetchShowComments(traktId: Long, limit: Int) =\n    showsService.fetchShowComments(traktId, limit, currentTimeMillis())\n\n  override suspend fun fetchMovieComments(traktId: Long, limit: Int) =\n    moviesService.fetchMovieComments(traktId, limit, currentTimeMillis())\n\n  override suspend fun fetchCommentReplies(commentId: Long) =\n    commentsService.fetchCommentReplies(commentId, currentTimeMillis())\n\n  override suspend fun postComment(commentRequest: CommentRequest) =\n    commentsService.postComment(commentRequest)\n\n  override suspend fun postCommentReply(commentId: Long, commentRequest: CommentRequest) =\n    commentsService.postCommentReply(commentId, commentRequest)\n\n  override suspend fun deleteComment(commentId: Long) =\n    commentsService.deleteComment(commentId)\n\n  override suspend fun fetchShowTranslations(traktId: Long, code: String) =\n    showsService.fetchShowTranslations(traktId, code)\n\n  override suspend fun fetchMovieTranslations(traktId: Long, code: String) =\n    moviesService.fetchMovieTranslations(traktId, code)\n\n  override suspend fun fetchSeasonTranslations(showTraktId: Long, seasonNumber: Int, code: String) =\n    showsService.fetchSeasonTranslations(showTraktId, seasonNumber, code)\n\n  override suspend fun fetchEpisodeComments(\n    traktId: Long,\n    seasonNumber: Int,\n    episodeNumber: Int,\n  ): List<Comment> = try {\n    showsService.fetchEpisodeComments(traktId, seasonNumber, episodeNumber, currentTimeMillis())\n  } catch (t: Throwable) {\n    emptyList()\n  }\n\n  override suspend fun fetchAuthTokens(code: String): OAuthResponse {\n    val request = OAuthRequest(\n      code,\n      TRAKT_CLIENT_ID,\n      TRAKT_CLIENT_SECRET,\n      TRAKT_REDIRECT_URL\n    )\n    return authService.fetchOAuthToken(request)\n  }\n\n  override suspend fun refreshAuthTokens(refreshToken: String): OAuthResponse {\n    val request = OAuthRefreshRequest(\n      refreshToken,\n      TRAKT_CLIENT_ID,\n      TRAKT_CLIENT_SECRET,\n      TRAKT_REDIRECT_URL\n    )\n    return authService.refreshOAuthToken(request)\n  }\n\n  override suspend fun revokeAuthTokens(token: String) {\n    val request = OAuthRevokeRequest(\n      token,\n      TRAKT_CLIENT_ID,\n      TRAKT_CLIENT_SECRET\n    )\n    authService.revokeOAuthToken(request)\n  }\n\n  override suspend fun fetchMyProfile() =\n    usersService.fetchMyProfile()\n\n  override suspend fun fetchHiddenShows() =\n    usersService.fetchHiddenShows(pageLimit = 250)\n\n  override suspend fun postHiddenShows(shows: List<SyncExportItem>) {\n    usersService.postHiddenShows(SyncExportRequest(shows = shows))\n  }\n\n  override suspend fun postHiddenMovies(movies: List<SyncExportItem>) {\n    usersService.postHiddenMovies(SyncExportRequest(movies = movies))\n  }\n\n  override suspend fun fetchHiddenMovies() =\n    usersService.fetchHiddenMovies(pageLimit = 250)\n\n  override suspend fun fetchSyncWatchedShows(extended: String?) =\n    syncService.fetchSyncWatched(\"shows\", extended).filter { it.show != null }\n\n  override suspend fun fetchSyncWatchedMovies(extended: String?) =\n    syncService.fetchSyncWatched(\"movies\", extended).filter { it.movie != null }\n\n  override suspend fun fetchSyncShowsWatchlist() = fetchSyncWatchlist(\"shows\")\n\n  override suspend fun fetchSyncMoviesWatchlist() = fetchSyncWatchlist(\"movies\")\n\n  override suspend fun fetchSyncWatchlist(type: String): List<SyncItem> {\n    var page = 1\n    val results = mutableListOf<SyncItem>()\n\n    do {\n      val items = syncService.fetchSyncWatchlist(type, page, TRAKT_SYNC_PAGE_LIMIT)\n      results.addAll(items)\n      page += 1\n    } while (items.size >= TRAKT_SYNC_PAGE_LIMIT)\n\n    return results\n  }\n\n  override suspend fun fetchSyncLists() =\n    usersService.fetchSyncLists()\n\n  override suspend fun fetchSyncList(listId: Long) =\n    usersService.fetchSyncList(listId)\n\n  override suspend fun fetchSyncListItems(listId: Long, withMovies: Boolean): List<SyncItem> {\n    var page = 1\n    val results = mutableListOf<SyncItem>()\n    val types = arrayListOf(\"show\")\n      .apply { if (withMovies) add(\"movie\") }\n      .joinToString(\",\")\n\n    do {\n      val items = usersService.fetchSyncListItems(listId, types, page, TRAKT_SYNC_PAGE_LIMIT)\n      results.addAll(items)\n      page += 1\n    } while (items.size >= TRAKT_SYNC_PAGE_LIMIT)\n\n    return results\n  }\n\n  override suspend fun postCreateList(name: String, description: String?): CustomList {\n    val body = CreateListRequest(name, description)\n    return usersService.postCreateList(body)\n  }\n\n  override suspend fun postUpdateList(customList: CustomList): CustomList {\n    val body = CreateListRequest(customList.name, customList.description)\n    return usersService.postUpdateList(customList.ids.trakt, body)\n  }\n\n  override suspend fun deleteList(listId: Long) {\n    usersService.deleteList(listId)\n  }\n\n  override suspend fun postAddListItems(\n    listTraktId: Long,\n    showsIds: List<Long>,\n    moviesIds: List<Long>,\n  ): SyncExportResult {\n    val body = SyncExportRequest(\n      shows = showsIds.map { SyncExportItem.create(it, null) },\n      movies = moviesIds.map { SyncExportItem.create(it, null) }\n    )\n    return usersService.postAddListItems(listTraktId, body)\n  }\n\n  override suspend fun postRemoveListItems(\n    listTraktId: Long,\n    showsIds: List<Long>,\n    moviesIds: List<Long>,\n  ): SyncExportResult {\n    val body = SyncExportRequest(\n      shows = showsIds.map { SyncExportItem.create(it, null) },\n      movies = moviesIds.map { SyncExportItem.create(it, null) }\n    )\n    return usersService.postRemoveListItems(listTraktId, body)\n  }\n\n  override suspend fun postSyncWatchlist(request: SyncExportRequest) =\n    syncService.postSyncWatchlist(request)\n\n  override suspend fun postSyncWatched(request: SyncExportRequest) =\n    syncService.postSyncWatched(request)\n\n  override suspend fun postDeleteProgress(request: SyncExportRequest) =\n    syncService.deleteHistory(request)\n\n  override suspend fun postDeleteWatchlist(request: SyncExportRequest) =\n    syncService.deleteWatchlist(request)\n\n  override suspend fun deleteHiddenShow(request: SyncExportRequest) =\n    usersService.deleteHidden(\"progress_watched\", request)\n\n  override suspend fun deleteHiddenMovie(request: SyncExportRequest) =\n    usersService.deleteHidden(\"calendar\", request)\n\n  override suspend fun deleteRating(show: Show) {\n    val requestValue = RatingRequestValue(0, show.ids)\n    val body = RatingRequest(shows = listOf(requestValue))\n    syncService.postRemoveRating(body)\n  }\n\n  override suspend fun deleteRating(movie: Movie) {\n    val requestValue = RatingRequestValue(0, movie.ids)\n    val body = RatingRequest(movies = listOf(requestValue))\n    syncService.postRemoveRating(body)\n  }\n\n  override suspend fun deleteRating(episode: Episode) {\n    val requestValue = RatingRequestValue(0, episode.ids)\n    val body = RatingRequest(episodes = listOf(requestValue))\n    syncService.postRemoveRating(body)\n  }\n\n  override suspend fun deleteRating(season: Season) {\n    val requestValue = RatingRequestValue(0, season.ids)\n    val body = RatingRequest(seasons = listOf(requestValue))\n    syncService.postRemoveRating(body)\n  }\n\n  override suspend fun postRating(movie: Movie, rating: Int) {\n    val requestValue = RatingRequestValue(rating, movie.ids)\n    val body = RatingRequest(movies = listOf(requestValue))\n    syncService.postRating(body)\n  }\n\n  override suspend fun postRating(show: Show, rating: Int) {\n    val requestValue = RatingRequestValue(rating, show.ids)\n    val body = RatingRequest(shows = listOf(requestValue))\n    syncService.postRating(body)\n  }\n\n  override suspend fun postRating(episode: Episode, rating: Int) {\n    val requestValue = RatingRequestValue(rating, episode.ids)\n    val body = RatingRequest(episodes = listOf(requestValue))\n    syncService.postRating(body)\n  }\n\n  override suspend fun postRating(season: Season, rating: Int) {\n    val requestValue = RatingRequestValue(rating, season.ids)\n    val body = RatingRequest(seasons = listOf(requestValue))\n    syncService.postRating(body)\n  }\n\n  override suspend fun fetchShowsRatings() =\n    syncService.fetchShowsRatings()\n\n  override suspend fun fetchMoviesRatings() =\n    syncService.fetchMoviesRatings()\n\n  override suspend fun fetchEpisodesRatings() =\n    syncService.fetchEpisodesRatings()\n\n  override suspend fun fetchSeasonsRatings() =\n    syncService.fetchSeasonsRatings()\n\n  override suspend fun fetchMovieCollections(traktId: Long): List<MovieCollection> {\n    val lists = moviesService.fetchMovieCollections(traktId)\n    return lists.filter { it.privacy == \"public\" }\n  }\n\n  override suspend fun fetchMovieCollectionItems(collectionId: Long): List<Movie> {\n    return moviesService.fetchMovieCollectionItems(collectionId)\n      .sortedBy { it.rank }\n      .map { it.movie }\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/api/service/TraktAuthService.kt",
    "content": "package com.michaldrabik.data_remote.trakt.api.service\n\nimport com.michaldrabik.data_remote.trakt.model.OAuthResponse\nimport com.michaldrabik.data_remote.trakt.model.request.OAuthRefreshRequest\nimport com.michaldrabik.data_remote.trakt.model.request.OAuthRequest\nimport com.michaldrabik.data_remote.trakt.model.request.OAuthRevokeRequest\nimport retrofit2.Response\nimport retrofit2.http.Body\nimport retrofit2.http.POST\n\ninterface TraktAuthService {\n\n  @POST(\"oauth/token\")\n  suspend fun fetchOAuthToken(@Body request: OAuthRequest): OAuthResponse\n\n  @POST(\"oauth/token\")\n  suspend fun refreshOAuthToken(@Body request: OAuthRefreshRequest): OAuthResponse\n\n  @POST(\"oauth/revoke\")\n  suspend fun revokeOAuthToken(@Body request: OAuthRevokeRequest): Response<Any>\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/api/service/TraktCommentsService.kt",
    "content": "package com.michaldrabik.data_remote.trakt.api.service\n\nimport com.michaldrabik.data_remote.trakt.model.Comment\nimport com.michaldrabik.data_remote.trakt.model.request.CommentRequest\nimport retrofit2.Response\nimport retrofit2.http.Body\nimport retrofit2.http.DELETE\nimport retrofit2.http.GET\nimport retrofit2.http.POST\nimport retrofit2.http.Path\nimport retrofit2.http.Query\n\ninterface TraktCommentsService {\n\n  @GET(\"comments/{id}/replies\")\n  suspend fun fetchCommentReplies(\n    @Path(\"id\") commentId: Long,\n    @Query(\"timestamp\") timestamp: Long\n  ): List<Comment>\n\n  @POST(\"comments\")\n  suspend fun postComment(\n    @Body commentBody: CommentRequest\n  ): Comment\n\n  @POST(\"comments/{id}/replies\")\n  suspend fun postCommentReply(\n    @Path(\"id\") commentId: Long,\n    @Body commentBody: CommentRequest\n  ): Comment\n\n  @DELETE(\"comments/{id}\")\n  suspend fun deleteComment(\n    @Path(\"id\") commentIt: Long\n  ): Response<Any>\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/api/service/TraktMoviesService.kt",
    "content": "package com.michaldrabik.data_remote.trakt.api.service\n\nimport com.michaldrabik.data_remote.Config\nimport com.michaldrabik.data_remote.trakt.model.Comment\nimport com.michaldrabik.data_remote.trakt.model.Movie\nimport com.michaldrabik.data_remote.trakt.model.MovieCollection\nimport com.michaldrabik.data_remote.trakt.model.MovieCollectionItem\nimport com.michaldrabik.data_remote.trakt.model.MovieResult\nimport com.michaldrabik.data_remote.trakt.model.Translation\nimport retrofit2.http.GET\nimport retrofit2.http.Path\nimport retrofit2.http.Query\n\ninterface TraktMoviesService {\n\n  @GET(\"movies/{traktId}?extended=full\")\n  suspend fun fetchMovie(@Path(\"traktId\") traktId: Long): Movie\n\n  @GET(\"movies/{traktSlug}?extended=full\")\n  suspend fun fetchMovie(@Path(\"traktSlug\") traktSlug: String): Movie\n\n  @GET(\"movies/popular?extended=full&limit=${Config.TRAKT_POPULAR_MOVIES_LIMIT}\")\n  suspend fun fetchPopularMovies(\n    @Query(\"genres\") genres: String,\n  ): List<Movie>\n\n  @GET(\"movies/trending?extended=full\")\n  suspend fun fetchTrendingMovies(\n    @Query(\"genres\") genres: String,\n    @Query(\"limit\") limit: Int,\n  ): List<MovieResult>\n\n  @GET(\"movies/anticipated?extended=full&limit=${Config.TRAKT_ANTICIPATED_MOVIES_LIMIT}\")\n  suspend fun fetchAnticipatedMovies(\n    @Query(\"genres\") genres: String,\n  ): List<MovieResult>\n\n  @GET(\"movies/{traktId}/related?extended=full\")\n  suspend fun fetchRelatedMovies(@Path(\"traktId\") traktId: Long, @Query(\"limit\") limit: Int): List<Movie>\n\n  @GET(\"movies/{traktId}/comments/newest?extended=full\")\n  suspend fun fetchMovieComments(\n    @Path(\"traktId\") traktId: Long,\n    @Query(\"limit\") limit: Int,\n    @Query(\"timestamp\") timestamp: Long,\n  ): List<Comment>\n\n  @GET(\"movies/{traktId}/translations/{code}\")\n  suspend fun fetchMovieTranslations(\n    @Path(\"traktId\") traktId: Long,\n    @Path(\"code\") countryCode: String,\n  ): List<Translation>\n\n  @GET(\"movies/{traktId}/lists/official/popular\")\n  suspend fun fetchMovieCollections(\n    @Path(\"traktId\") traktId: Long,\n  ): List<MovieCollection>\n\n  @GET(\"lists/{collectionId}/items/movie?extended=full\")\n  suspend fun fetchMovieCollectionItems(\n    @Path(\"collectionId\") collectionId: Long,\n  ): List<MovieCollectionItem>\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/api/service/TraktPeopleService.kt",
    "content": "package com.michaldrabik.data_remote.trakt.api.service\n\nimport com.michaldrabik.data_remote.trakt.model.PersonCreditsResult\nimport retrofit2.http.GET\nimport retrofit2.http.Path\n\ninterface TraktPeopleService {\n\n  @GET(\"people/{traktId}/{type}?extended=full\")\n  suspend fun fetchPersonCredits(@Path(\"traktId\") traktId: Long, @Path(\"type\") type: String): PersonCreditsResult\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/api/service/TraktSearchService.kt",
    "content": "package com.michaldrabik.data_remote.trakt.api.service\n\nimport com.michaldrabik.data_remote.Config\nimport com.michaldrabik.data_remote.trakt.model.SearchResult\nimport retrofit2.http.GET\nimport retrofit2.http.Path\nimport retrofit2.http.Query\n\ninterface TraktSearchService {\n\n  @GET(\"search/{idType}/{id}?type=person\")\n  suspend fun fetchPersonIds(@Path(\"idType\") idType: String, @Path(\"id\") id: String): List<SearchResult>\n\n  @GET(\"search/{idType}/{id}?extended=full\")\n  suspend fun fetchSearchId(@Path(\"idType\") idType: String, @Path(\"id\") id: String): List<SearchResult>\n\n  @GET(\"search/show?extended=full&limit=${Config.TRAKT_SEARCH_LIMIT}\")\n  suspend fun fetchSearchResults(@Query(\"query\") queryText: String): List<SearchResult>\n\n  @GET(\"search/show,movie?extended=full&limit=${Config.TRAKT_SEARCH_LIMIT}\")\n  suspend fun fetchSearchResultsMovies(@Query(\"query\") queryText: String): List<SearchResult>\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/api/service/TraktShowsService.kt",
    "content": "package com.michaldrabik.data_remote.trakt.api.service\n\nimport com.michaldrabik.data_remote.trakt.model.Comment\nimport com.michaldrabik.data_remote.trakt.model.Episode\nimport com.michaldrabik.data_remote.trakt.model.Season\nimport com.michaldrabik.data_remote.trakt.model.SeasonTranslation\nimport com.michaldrabik.data_remote.trakt.model.Show\nimport com.michaldrabik.data_remote.trakt.model.ShowResult\nimport com.michaldrabik.data_remote.trakt.model.Translation\nimport retrofit2.Response\nimport retrofit2.http.GET\nimport retrofit2.http.Path\nimport retrofit2.http.Query\n\ninterface TraktShowsService {\n\n  @GET(\"shows/{traktId}?extended=full\")\n  suspend fun fetchShow(@Path(\"traktId\") traktId: Long): Show\n\n  @GET(\"shows/{traktSlug}?extended=full\")\n  suspend fun fetchShow(@Path(\"traktSlug\") traktSlug: String): Show\n\n  @GET(\"shows/popular?extended=full\")\n  suspend fun fetchPopularShows(\n    @Query(\"genres\") genres: String,\n    @Query(\"networks\") networks: String,\n    @Query(\"limit\") limit: Int\n  ): List<Show>\n\n  @GET(\"shows/trending?extended=full\")\n  suspend fun fetchTrendingShows(\n    @Query(\"genres\") genres: String,\n    @Query(\"networks\") networks: String,\n    @Query(\"limit\") limit: Int\n  ): List<ShowResult>\n\n  @GET(\"shows/anticipated?extended=full\")\n  suspend fun fetchAnticipatedShows(\n    @Query(\"genres\") genres: String,\n    @Query(\"networks\") networks: String,\n    @Query(\"limit\") limit: Int\n  ): List<ShowResult>\n\n  @GET(\"shows/{traktId}/related?extended=full\")\n  suspend fun fetchRelatedShows(@Path(\"traktId\") traktId: Long, @Query(\"limit\") limit: Int): List<Show>\n\n  @GET(\"shows/{traktId}/next_episode?extended=full\")\n  suspend fun fetchNextEpisode(@Path(\"traktId\") traktId: Long): Response<Episode>\n\n  @GET(\"shows/{traktId}/seasons?extended=full,episodes\")\n  suspend fun fetchSeasons(@Path(\"traktId\") traktId: Long): List<Season>\n\n  @GET(\"shows/{traktId}/comments/newest?extended=full\")\n  suspend fun fetchShowComments(\n    @Path(\"traktId\") traktId: Long,\n    @Query(\"limit\") limit: Int,\n    @Query(\"timestamp\") timestamp: Long\n  ): List<Comment>\n\n  @GET(\"shows/{traktId}/translations/{code}\")\n  suspend fun fetchShowTranslations(\n    @Path(\"traktId\") traktId: Long,\n    @Path(\"code\") countryCode: String\n  ): List<Translation>\n\n  @GET(\"shows/{showId}/seasons/{seasonNumber}\")\n  suspend fun fetchSeasonTranslations(\n    @Path(\"showId\") showTraktId: Long,\n    @Path(\"seasonNumber\") seasonNumber: Int,\n    @Query(\"translations\") countryCode: String\n  ): List<SeasonTranslation>\n\n  @GET(\"shows/{traktId}/seasons/{seasonNumber}/episodes/{episodeNumber}/comments/newest?limit=50&extended=full\")\n  suspend fun fetchEpisodeComments(\n    @Path(\"traktId\") traktId: Long,\n    @Path(\"seasonNumber\") seasonNumber: Int,\n    @Path(\"episodeNumber\") episodeNumber: Int,\n    @Query(\"timestamp\") timestamp: Long\n  ): List<Comment>\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/api/service/TraktSyncService.kt",
    "content": "package com.michaldrabik.data_remote.trakt.api.service\n\nimport com.michaldrabik.data_remote.trakt.model.RatingResultEpisode\nimport com.michaldrabik.data_remote.trakt.model.RatingResultMovie\nimport com.michaldrabik.data_remote.trakt.model.RatingResultSeason\nimport com.michaldrabik.data_remote.trakt.model.RatingResultShow\nimport com.michaldrabik.data_remote.trakt.model.SyncExportRequest\nimport com.michaldrabik.data_remote.trakt.model.SyncExportResult\nimport com.michaldrabik.data_remote.trakt.model.SyncItem\nimport com.michaldrabik.data_remote.trakt.model.request.RatingRequest\nimport retrofit2.http.Body\nimport retrofit2.http.GET\nimport retrofit2.http.POST\nimport retrofit2.http.Path\nimport retrofit2.http.Query\n\ninterface TraktSyncService {\n  @GET(\"sync/watched/{type}\")\n  suspend fun fetchSyncWatched(\n    @Path(\"type\") type: String,\n    @Query(\"extended\") extended: String?\n  ): List<SyncItem>\n\n  @GET(\"sync/watchlist/{type}?extended=full\")\n  suspend fun fetchSyncWatchlist(\n    @Path(\"type\") type: String,\n    @Query(\"page\") page: Int? = null,\n    @Query(\"limit\") limit: Int? = null\n  ): List<SyncItem>\n\n  @POST(\"sync/watchlist\")\n  suspend fun postSyncWatchlist(@Body request: SyncExportRequest): SyncExportResult\n\n  @POST(\"sync/history\")\n  suspend fun postSyncWatched(@Body request: SyncExportRequest): SyncExportResult\n\n  @POST(\"sync/watchlist/remove\")\n  suspend fun deleteWatchlist(@Body request: SyncExportRequest): SyncExportResult\n\n  @POST(\"sync/history/remove\")\n  suspend fun deleteHistory(@Body request: SyncExportRequest): SyncExportResult\n\n  @POST(\"sync/ratings\")\n  suspend fun postRating(@Body request: RatingRequest)\n\n  @POST(\"sync/ratings/remove\")\n  suspend fun postRemoveRating(@Body request: RatingRequest)\n\n  @GET(\"sync/ratings/shows\")\n  suspend fun fetchShowsRatings(): List<RatingResultShow>\n\n  @GET(\"sync/ratings/movies\")\n  suspend fun fetchMoviesRatings(): List<RatingResultMovie>\n\n  @GET(\"sync/ratings/episodes\")\n  suspend fun fetchEpisodesRatings(): List<RatingResultEpisode>\n\n  @GET(\"sync/ratings/seasons\")\n  suspend fun fetchSeasonsRatings(): List<RatingResultSeason>\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/api/service/TraktUsersService.kt",
    "content": "package com.michaldrabik.data_remote.trakt.api.service\n\nimport com.michaldrabik.data_remote.trakt.model.CustomList\nimport com.michaldrabik.data_remote.trakt.model.HiddenItem\nimport com.michaldrabik.data_remote.trakt.model.SyncExportRequest\nimport com.michaldrabik.data_remote.trakt.model.SyncExportResult\nimport com.michaldrabik.data_remote.trakt.model.SyncItem\nimport com.michaldrabik.data_remote.trakt.model.User\nimport com.michaldrabik.data_remote.trakt.model.request.CreateListRequest\nimport retrofit2.Response\nimport retrofit2.http.Body\nimport retrofit2.http.DELETE\nimport retrofit2.http.GET\nimport retrofit2.http.POST\nimport retrofit2.http.PUT\nimport retrofit2.http.Path\nimport retrofit2.http.Query\n\ninterface TraktUsersService {\n\n  @GET(\"users/me\")\n  suspend fun fetchMyProfile(): User\n\n  @GET(\"users/hidden/progress_watched?type=show&extended=full\")\n  suspend fun fetchHiddenShows(\n    @Query(\"limit\") pageLimit: Int\n  ): List<HiddenItem>\n\n  @POST(\"users/hidden/progress_watched\")\n  suspend fun postHiddenShows(\n    @Body request: SyncExportRequest\n  ): SyncExportResult\n\n  @POST(\"users/hidden/calendar\")\n  suspend fun postHiddenMovies(\n    @Body request: SyncExportRequest\n  ): SyncExportResult\n\n  @GET(\"users/hidden/calendar?type=movie&extended=full\")\n  suspend fun fetchHiddenMovies(\n    @Query(\"limit\") pageLimit: Int\n  ): List<HiddenItem>\n\n  @GET(\"users/me/lists\")\n  suspend fun fetchSyncLists(): List<CustomList>\n\n  @GET(\"users/me/lists/{id}\")\n  suspend fun fetchSyncList(\n    @Path(\"id\") listId: Long\n  ): CustomList\n\n  @GET(\"users/me/lists/{id}/items/{types}?extended=full\")\n  suspend fun fetchSyncListItems(\n    @Path(\"id\") listId: Long,\n    @Path(\"types\") types: String,\n    @Query(\"page\") page: Int? = null,\n    @Query(\"limit\") limit: Int? = null\n  ): List<SyncItem>\n\n  @POST(\"users/me/lists\")\n  suspend fun postCreateList(\n    @Body request: CreateListRequest\n  ): CustomList\n\n  @PUT(\"users/me/lists/{id}\")\n  suspend fun postUpdateList(\n    @Path(\"id\") listId: Long,\n    @Body request: CreateListRequest\n  ): CustomList\n\n  @DELETE(\"users/me/lists/{id}\")\n  suspend fun deleteList(\n    @Path(\"id\") listId: Long\n  ): Response<Any>\n\n  @POST(\"users/me/lists/{id}/items\")\n  suspend fun postAddListItems(\n    @Path(\"id\") listId: Long,\n    @Body request: SyncExportRequest\n  ): SyncExportResult\n\n  @POST(\"users/me/lists/{id}/items/remove\")\n  suspend fun postRemoveListItems(\n    @Path(\"id\") listId: Long,\n    @Body request: SyncExportRequest\n  ): SyncExportResult\n\n  @POST(\"users/hidden/{section}/remove\")\n  suspend fun deleteHidden(\n    @Path(\"section\") section: String,\n    @Body request: SyncExportRequest\n  ): SyncExportResult\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/interceptors/TraktAuthenticator.kt",
    "content": "package com.michaldrabik.data_remote.trakt.interceptors\n\nimport com.michaldrabik.data_remote.token.TokenProvider\nimport kotlinx.coroutines.runBlocking\nimport okhttp3.Authenticator\nimport okhttp3.Request\nimport okhttp3.Response\nimport okhttp3.Route\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass TraktAuthenticator @Inject constructor(\n  private val tokenProvider: TokenProvider,\n) : Authenticator {\n\n  override fun authenticate(route: Route?, response: Response): Request? =\n    runBlocking {\n      val accessToken = tokenProvider.getToken()\n      if (!isRequestAuthorized(response) || accessToken == null) {\n        return@runBlocking null\n      }\n\n      val newAccessToken = tokenProvider.getToken()\n      if (newAccessToken != accessToken) {\n        response.request.newBuilder()\n          .header(\"Authorization\", \"Bearer $newAccessToken\")\n          .build()\n      }\n\n      try {\n        Timber.d(\"Refreshing access token...\")\n        val refreshedTokens = tokenProvider.refreshToken()\n\n        tokenProvider.saveTokens(\n          accessToken = refreshedTokens.access_token,\n          refreshToken = refreshedTokens.refresh_token\n        )\n\n        response.request\n          .newBuilder()\n          .header(\"Authorization\", \"Bearer ${refreshedTokens.access_token}\")\n          .build()\n      } catch (error: Throwable) {\n        Timber.d(error)\n        null\n      }\n    }\n\n  private fun isRequestAuthorized(response: Response): Boolean {\n    val header = response.request.header(\"Authorization\")\n    return header != null && header.startsWith(\"Bearer\")\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/interceptors/TraktAuthorizationInterceptor.kt",
    "content": "package com.michaldrabik.data_remote.trakt.interceptors\n\nimport com.michaldrabik.data_remote.token.TokenProvider\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass TraktAuthorizationInterceptor @Inject constructor(\n  private val tokenProvider: TokenProvider,\n) : Interceptor {\n\n  override fun intercept(chain: Interceptor.Chain): Response {\n    val request = chain.request().newBuilder()\n      .also { request ->\n        tokenProvider.getToken()?.let {\n          request.header(\"Authorization\", \"Bearer $it\")\n        }\n      }\n      .build()\n    return chain.proceed(request)\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/interceptors/TraktHeadersInterceptor.kt",
    "content": "package com.michaldrabik.data_remote.trakt.interceptors\n\nimport com.michaldrabik.data_remote.Config\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass TraktHeadersInterceptor @Inject constructor() : Interceptor {\n\n  override fun intercept(chain: Interceptor.Chain): Response {\n    val request = chain.request().newBuilder()\n      .header(\"Content-Type\", \"application/json\")\n      .header(\"trakt-api-key\", Config.TRAKT_CLIENT_ID)\n      .header(\"trakt-api-version\", Config.TRAKT_VERSION)\n      .build()\n    return chain.proceed(request)\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/interceptors/TraktRefreshTokenInterceptor.kt",
    "content": "package com.michaldrabik.data_remote.trakt.interceptors\n\nimport com.michaldrabik.data_remote.token.TokenProvider\nimport kotlinx.coroutines.runBlocking\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass TraktRefreshTokenInterceptor @Inject constructor(\n  private val tokenProvider: TokenProvider,\n) : Interceptor {\n\n  override fun intercept(chain: Interceptor.Chain): Response = runBlocking {\n    val request = chain.request()\n    if (tokenProvider.shouldRefresh()) {\n      try {\n        val refreshedTokens = tokenProvider.refreshToken()\n        tokenProvider.saveTokens(\n          accessToken = refreshedTokens.access_token,\n          refreshToken = refreshedTokens.refresh_token\n        )\n      } catch (error: Throwable) {\n        Timber.e(error)\n      }\n    }\n    chain.proceed(request)\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/interceptors/TraktRetryInterceptor.kt",
    "content": "package com.michaldrabik.data_remote.trakt.interceptors\n\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.runBlocking\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass TraktRetryInterceptor @Inject constructor() : Interceptor {\n\n  private val mutex = Mutex()\n  private var tryCount = 0\n\n  override fun intercept(chain: Interceptor.Chain): Response = runBlocking {\n    mutex.withLock {\n      tryCount = 0\n      val request = chain.request()\n      var response = chain.proceed(request)\n\n      while (response.code == 429 && tryCount < 3) {\n        Timber.w(\"429 Too Many Requests. Retrying...\")\n        delay(3000)\n        tryCount += 1\n        response.close()\n        response = chain.proceed(request)\n      }\n\n      if (response.code == 429) {\n        val error = Throwable(\"429 Too Many Requests\")\n        Timber.e(error)\n      }\n\n      response\n    }\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/AirTime.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class AirTime(\n  val day: String?,\n  val time: String?,\n  val timezone: String?\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/Comment.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class Comment(\n  val id: Long?,\n  val parent_id: Long?,\n  val comment: String?,\n  val user_rating: Int?,\n  val spoiler: Boolean?,\n  val review: Boolean?,\n  val likes: Long?,\n  val replies: Long?,\n  val created_at: String?,\n  val updated_at: String?,\n  val user: TraktUser?\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/CustomList.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class CustomList(\n  val ids: Ids,\n  val name: String,\n  val description: String?,\n  val privacy: String,\n  val display_numbers: Boolean,\n  val allow_comments: Boolean,\n  val sort_by: String,\n  val sort_how: String,\n  val item_count: Long,\n  val comment_count: Long,\n  val likes: Long,\n  val created_at: String,\n  val updated_at: String\n) {\n\n  data class Ids(\n    val trakt: Long,\n    val slug: String\n  )\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/Episode.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class Episode(\n  val season: Int?,\n  val number: Int?,\n  val title: String?,\n  val ids: Ids?,\n  val overview: String?,\n  val rating: Float?,\n  val votes: Int?,\n  val comment_count: Int?,\n  val first_aired: String?,\n  val runtime: Int?,\n  val number_abs: Int?,\n  val last_watched_at: String?\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/HiddenItem.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\nimport java.time.ZoneOffset\nimport java.time.ZonedDateTime\n\ndata class HiddenItem(\n  val show: Show?,\n  val movie: Movie?,\n  val hidden_at: String?\n) {\n\n  fun hiddenAtMillis() =\n    (hidden_at?.let { ZonedDateTime.parse(hidden_at) } ?: ZonedDateTime.now(ZoneOffset.UTC)).toInstant().toEpochMilli()\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/Ids.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class Ids(\n  val trakt: Long?,\n  val slug: String?,\n  val tvdb: Long?,\n  val imdb: String?,\n  val tmdb: Long?,\n  val tvrage: Long?,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/Movie.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class Movie(\n  val ids: Ids?,\n  val title: String?,\n  val year: Int?,\n  val overview: String?,\n  val released: String?,\n  val runtime: Int?,\n  val country: String?,\n  val trailer: String?,\n  val homepage: String?,\n  val status: String?,\n  val rating: Float?,\n  val votes: Long?,\n  val comment_count: Long?,\n  val genres: List<String>?,\n  val language: String?\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/MovieCollection.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class MovieCollection(\n  val ids: Ids,\n  val name: String,\n  val description: String,\n  val privacy: String,\n  val item_count: Int,\n  val likes: Int,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/MovieCollectionItem.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class MovieCollectionItem(\n  val id: Long,\n  val rank: Int,\n  val movie: Movie,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/MovieResult.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class MovieResult(\n  val movie: Movie?\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/OAuthResponse.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class OAuthResponse(\n  val access_token: String,\n  val refresh_token: String\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/Person.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class Person(\n  val ids: Ids?,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/PersonCredit.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class PersonCredit(\n  val characters: List<String>?,\n  val episode_count: Int?,\n  val series_regular: Boolean?,\n  val show: Show?,\n  val movie: Movie?,\n) {\n\n  val isShow = show != null\n  val isMovie = movie != null\n\n  val year = if (isShow) show!!.year else movie!!.year\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/PersonCreditsResult.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class PersonCreditsResult(\n  val cast: List<PersonCredit>?,\n  val crew: Map<String, List<PersonCredit>>?,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/RatingResultMovie.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class RatingResultMovie(\n  val rated_at: String?,\n  val rating: Int,\n  val movie: RatingResultValue\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/RatingResultShow.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class RatingResultShow(\n  val rated_at: String?,\n  val rating: Int,\n  val show: RatingResultValue\n)\n\ndata class RatingResultEpisode(\n  val rating: Int,\n  val rated_at: String?,\n  val episode: RatingResultValue,\n  val show: RatingResultValue\n)\n\ndata class RatingResultSeason(\n  val rating: Int,\n  val rated_at: String?,\n  val season: RatingResultValue,\n  val show: RatingResultValue\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/RatingResultValue.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class RatingResultValue(\n  val ids: Ids,\n  val title: String,\n  val season: Int?,\n  val number: Int?,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/SearchResult.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class SearchResult(\n  val score: Float?,\n  val show: Show?,\n  val movie: Movie?,\n  val person: Person?\n) {\n\n  fun getVotes() = when {\n    show != null -> show.votes ?: 0\n    movie != null -> movie.votes ?: 0\n    else -> 0\n  }\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/Season.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class Season(\n  val ids: Ids?,\n  val number: Int?,\n  val episode_count: Int?,\n  val aired_episodes: Int?,\n  val title: String?,\n  val first_aired: String?,\n  val overview: String?,\n  val rating: Float?,\n  val episodes: List<Episode>?\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/SeasonTranslation.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class SeasonTranslation(\n  val season: Int,\n  val number: Int,\n  val ids: Ids,\n  val translations: List<Translation>?\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/Show.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class Show(\n  val ids: Ids?,\n  val title: String?,\n  val year: Int?,\n  val overview: String?,\n  val first_aired: String?,\n  val runtime: Int?,\n  val airs: AirTime?,\n  val certification: String?,\n  val network: String?,\n  val country: String?,\n  val trailer: String?,\n  val homepage: String?,\n  val status: String?,\n  val rating: Float?,\n  val votes: Long?,\n  val comment_count: Long?,\n  val genres: List<String>?,\n  val aired_episodes: Int?\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/ShowResult.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class ShowResult(\n  val show: Show?\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/SyncExportItem.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class SyncExportItem(\n  val ids: Ids,\n  val watched_at: String?,\n  val hidden_at: String?,\n) {\n\n  companion object {\n    fun create(\n      traktId: Long,\n      watchedAt: String? = \"released\",\n      hiddenAt: String? = null,\n    ) = SyncExportItem(Ids(traktId), watchedAt, hiddenAt)\n  }\n\n  data class Ids(\n    val trakt: Long\n  )\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/SyncExportRequest.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class SyncExportRequest(\n  val shows: List<SyncExportItem> = emptyList(),\n  val movies: List<SyncExportItem> = emptyList(),\n  val seasons: List<SyncExportItem> = emptyList(),\n  val episodes: List<SyncExportItem> = emptyList()\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/SyncExportResult.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class SyncExportResult(\n  val added: SyncExportResultItem,\n  val deleted: SyncExportResultItem,\n  val existing: SyncExportResultItem\n)\n\ndata class SyncExportResultItem(\n  val shows: Long,\n  val seasons: Long,\n  val episodes: Long\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/SyncItem.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\nimport java.time.ZoneOffset.UTC\nimport java.time.ZonedDateTime\n\ndata class SyncItem(\n  val show: Show?,\n  val movie: Movie?,\n  val seasons: List<Season>?,\n  val last_watched_at: String?,\n  val last_updated_at: String?,\n  val listed_at: String?\n) {\n\n  fun getTraktId(): Long? {\n    if (show != null) return show.ids?.trakt\n    if (movie != null) return movie.ids?.trakt\n    return null\n  }\n\n  fun getType(): String? {\n    if (show != null) return \"show\"\n    if (movie != null) return \"movie\"\n    return null\n  }\n\n  fun lastWatchedMillis() =\n    (last_watched_at?.let { ZonedDateTime.parse(last_watched_at) } ?: ZonedDateTime.now(UTC)).toInstant().toEpochMilli()\n\n  fun lastUpdateMillis() =\n    (last_updated_at?.let { ZonedDateTime.parse(last_updated_at) } ?: ZonedDateTime.now(UTC)).toInstant().toEpochMilli()\n\n  fun lastListedMillis() =\n    (listed_at?.let { ZonedDateTime.parse(listed_at) } ?: ZonedDateTime.now(UTC)).toInstant().toEpochMilli()\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/TraktUser.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class TraktUser(\n  val username: String,\n  val images: Image?\n) {\n\n  data class Image(\n    val avatar: ImageDetails?\n  )\n\n  data class ImageDetails(\n    val full: String?\n  )\n}\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/Translation.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class Translation(\n  val title: String?,\n  val overview: String?,\n  val language: String?,\n  val country: String?\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/User.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model\n\ndata class User(\n  val username: String\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/request/CommentRequest.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model.request\n\nimport com.michaldrabik.data_remote.trakt.model.Episode\nimport com.michaldrabik.data_remote.trakt.model.Movie\nimport com.michaldrabik.data_remote.trakt.model.Show\n\ndata class CommentRequest(\n  val show: Show? = null,\n  val movie: Movie? = null,\n  val episode: Episode? = null,\n  val comment: String,\n  val spoiler: Boolean\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/request/CreateListRequest.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model.request\n\ndata class CreateListRequest(\n  val name: String,\n  val description: String?,\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/request/OAuthRefreshRequest.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model.request\n\ndata class OAuthRefreshRequest(\n  val refresh_token: String,\n  val client_id: String,\n  val client_secret: String,\n  val redirect_uri: String,\n  val grant_type: String = \"refresh_token\"\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/request/OAuthRequest.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model.request\n\ndata class OAuthRequest(\n  val code: String,\n  val client_id: String,\n  val client_secret: String,\n  val redirect_uri: String,\n  val grant_type: String = \"authorization_code\"\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/request/OAuthRevokeRequest.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model.request\n\ndata class OAuthRevokeRequest(\n  val token: String,\n  val client_id: String,\n  val client_secret: String\n)\n"
  },
  {
    "path": "data-remote/src/main/java/com/michaldrabik/data_remote/trakt/model/request/RatingRequest.kt",
    "content": "package com.michaldrabik.data_remote.trakt.model.request\n\nimport com.michaldrabik.data_remote.trakt.model.Ids\n\ndata class RatingRequest(\n  val shows: List<RatingRequestValue>? = null,\n  val movies: List<RatingRequestValue>? = null,\n  val episodes: List<RatingRequestValue>? = null,\n  val seasons: List<RatingRequestValue>? = null,\n)\n\ndata class RatingRequestValue(\n  val rating: Int,\n  val ids: Ids?\n)\n"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\n\nandroidNavigation = \"2.6.0\"\nandroidRoom = \"2.5.2\"\nandroidLifecycle = \"2.6.1\"\nandroidTestRunner = \"1.5.2\"\nandroidTestTruth = \"1.5.0\"\nglide = \"4.15.1\"\nretrofit = \"2.9.0\"\nhilt = \"2.47\"\nhiltWork = \"1.0.0\"\n\n[libraries]\n\ngradle = { group = \"com.android.tools.build\", name = \"gradle\", version = \"8.2.0\" }\ngradle-kotlin-plugin = { group = \"org.jetbrains.kotlin\", name = \"kotlin-gradle-plugin\", version = \"1.9.0\" }\ngradle-ktlint = { group = \"org.jlleitschuh.gradle\", name = \"ktlint-gradle\", version = \"10.2.1\" }\n\nandroid-core = { group = \"androidx.core\", name = \"core-ktx\", version = \"1.10.1\" }\nandroid-appcompat = { group = \"androidx.appcompat\", name = \"appcompat\", version = \"1.6.1\" }\nandroid-gridlayout = { group = \"androidx.gridlayout\", name = \"gridlayout\", version = \"1.0.0\" }\nandroid-browser = { group = \"androidx.browser\", name = \"browser\", version = \"1.5.0\" }\nandroid-desugar = { group = \"com.android.tools\", name = \"desugar_jdk_libs\", version = \"2.0.3\" }\nandroid-material = { group = \"com.google.android.material\", name = \"material\", version = \"1.9.0\" }\nandroid-dynamicanimation = { group = \"androidx.dynamicanimation\", name = \"dynamicanimation\", version = \"1.0.0\" }\nandroid-work = { group = \"androidx.work\", name = \"work-runtime-ktx\", version = \"2.8.1\" }\nandroid-swiperefresh = { group = \"androidx.swiperefreshlayout\", name = \"swiperefreshlayout\", version = \"1.1.0\" }\nandroid-constraintlayout = { group = \"androidx.constraintlayout\", name = \"constraintlayout\", version = \"2.1.4\" }\nandroid-recycler = { group = \"androidx.recyclerview\", name = \"recyclerview\", version = \"1.3.1\" }\nandroid-fragment = { group = \"androidx.fragment\", name = \"fragment-ktx\", version = \"1.6.1\" }\n\nandroid-navigation-fragment = { group = \"androidx.navigation\", name = \"navigation-fragment-ktx\", version.ref = \"androidNavigation\" }\nandroid-navigation-ui = { group = \"androidx.navigation\", name = \"navigation-ui-ktx\", version.ref = \"androidNavigation\" }\n\nandroid-room-ktx = { group = \"androidx.room\", name = \"room-ktx\", version.ref = \"androidRoom\" }\nandroid-room-runtime = { group = \"androidx.room\", name = \"room-runtime\", version.ref = \"androidRoom\" }\nandroid-room-compiler = { group = \"androidx.room\", name = \"room-compiler\", version.ref = \"androidRoom\" }\n\nandroid-lifecycle-viewmodel = { group = \"androidx.lifecycle\", name = \"lifecycle-viewmodel-ktx\", version.ref = \"androidLifecycle\" }\nandroid-lifecycle-runtime = { group = \"androidx.lifecycle\", name = \"lifecycle-runtime-ktx\", version.ref = \"androidLifecycle\" }\nandroid-lifecycle-common = { group = \"androidx.lifecycle\", name = \"lifecycle-common-java8\", version.ref = \"androidLifecycle\" }\n\nglide = { group = \"com.github.bumptech.glide\", name = \"glide\", version.ref = \"glide\" }\nglide-compiler = { group = \"com.github.bumptech.glide\", name = \"compiler\", version.ref = \"glide\" }\n\nretrofit = { group = \"com.squareup.retrofit2\", name = \"retrofit\", version.ref = \"retrofit\" }\nretrofit-moshi = { group = \"com.squareup.retrofit2\", name = \"converter-moshi\", version.ref = \"retrofit\" }\n\nhilt-plugin = { group = \"com.google.dagger\", name = \"hilt-android-gradle-plugin\", version.ref = \"hilt\" }\nhilt-android = { group = \"com.google.dagger\", name = \"hilt-android\", version.ref = \"hilt\" }\nhilt-compiler = { group = \"com.google.dagger\", name = \"hilt-compiler\", version.ref = \"hilt\" }\n\nhilt-work = { group = \"androidx.hilt\", name = \"hilt-work\", version.ref = \"hiltWork\" }\nhilt-work-compiler = { group = \"androidx.hilt\", name = \"hilt-compiler\", version = \"1.1.0-alpha01\" }\n\ncoroutines = { group = \"org.jetbrains.kotlinx\", name = \"kotlinx-coroutines-android\", version = \"1.7.3\" }\nphoenix = { group = \"com.jakewharton\", name = \"process-phoenix\", version = \"2.1.2\" }\ntimber = { group = \"com.jakewharton.timber\", name = \"timber\", version = \"5.0.1\" }\ncircleIndicator = { group = \"me.relex\", name = \"circleindicator\", version = \"2.1.6\" }\nloggingInterceptor = { group = \"com.squareup.okhttp3\", name = \"logging-interceptor\", version = \"4.11.0\" }\noverscrollDecor = { group = \"io.github.everythingme\", name = \"overscroll-decor-android\", version = \"1.1.1\" }\n\n# Testing\njunit = { group = \"junit\", name = \"junit\", version = \"4.13.2\" }\nmockk = { group = \"io.mockk\", name = \"mockk\", version = \"1.13.7\" }\ntruth = { group = \"com.google.truth\", name = \"truth\", version = \"1.1.5\" }\ncoroutinesTest = { group = \"org.jetbrains.kotlinx\", name = \"kotlinx-coroutines-test\", version = \"1.7.3\" }\nandroid-test-core = { group = \"androidx.arch.core\", name = \"core-testing\", version = \"2.2.0\" }\n\n# Android Testing\nandroid-test-runner = { group = \"androidx.test\", name = \"runner\", version.ref = \"androidTestRunner\" }\nandroid-test-truth = { group = \"androidx.test.ext\", name = \"truth\", version.ref = \"androidTestTruth\" }\n\n[bundles]\n\nandroid-navigation = [\"android-navigation-fragment\", \"android-navigation-ui\"]\nandroid-lifecycle = [\"android-lifecycle-common\", \"android-lifecycle-runtime\", \"android-lifecycle-viewmodel\"]\ntesting = [\"junit\", \"mockk\", \"truth\", \"coroutinesTest\", \"android-test-core\"]"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Tue Apr 11 21:13:54 CEST 2023\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.2-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "## For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n#\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx1024m -XX:MaxPermSize=256m\n# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n#\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n#Tue Aug 27 20:07:15 CEST 2019\n#org.gradle.java.home\nkotlin.code.style=official\nandroid.enableJetifier=false\norg.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\\=\"-Xmx2048M\"\nandroid.useAndroidX=true\n\n# Stop generating the BuildConfig file by default on all your android modules\nandroid.defaults.buildfeatures.buildconfig = false\nandroid.nonTransitiveRClass=false\nandroid.nonFinalResIds=false"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or 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 UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\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# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\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    which java >/dev/null 2>&1 || 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.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    \n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\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=.\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%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\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 init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windows variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\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 %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"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!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "repository/.gitignore",
    "content": "/build"
  },
  {
    "path": "repository/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.repository'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-remote')\n  implementation project(':data-local')\n  implementation project(':ui-model')\n\n  implementation libs.android.core\n  implementation libs.android.appcompat\n  implementation libs.timber\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  testImplementation project(':common-test')\n  testImplementation libs.bundles.testing\n  androidTestImplementation libs.android.test.runner\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "repository/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/CommentsRepository.kt",
    "content": "package com.michaldrabik.repository\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.data_remote.trakt.model.request.CommentRequest\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.Comment\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CommentsRepository @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val mappers: Mappers\n) {\n\n  suspend fun loadComments(id: IdTrakt, mode: Mode, limit: Int = 100): List<Comment> {\n    val comments = when (mode) {\n      Mode.SHOWS -> remoteSource.trakt.fetchShowComments(id.id, limit)\n      Mode.MOVIES -> remoteSource.trakt.fetchMovieComments(id.id, limit)\n    }\n    return comments\n      .map { mappers.comment.fromNetwork(it) }\n      .filter { it.parentId <= 0 }\n  }\n\n  suspend fun loadEpisodeComments(idTrakt: IdTrakt, season: Int, episode: Int) =\n    remoteSource.trakt.fetchEpisodeComments(idTrakt.id, season, episode)\n      .map { mappers.comment.fromNetwork(it) }\n      .filter { it.parentId <= 0 }\n\n  suspend fun loadReplies(commentId: Long) =\n    remoteSource.trakt.fetchCommentReplies(commentId)\n      .map { mappers.comment.fromNetwork(it).copy(replies = 0) }\n      .sortedBy { it.createdAt?.toEpochSecond() }\n\n  suspend fun postComment(show: Show, commentText: String, isSpoiler: Boolean): Comment {\n    val showBody = mappers.show.toNetwork(Show.EMPTY.copy(ids = show.ids))\n    val request = CommentRequest(show = showBody, comment = commentText, spoiler = isSpoiler)\n\n    val comment = remoteSource.trakt.postComment(request)\n    return mappers.comment.fromNetwork(comment)\n  }\n\n  suspend fun postComment(movie: Movie, commentText: String, isSpoiler: Boolean): Comment {\n    val movieBody = mappers.movie.toNetwork(Movie.EMPTY.copy(ids = movie.ids))\n    val request = CommentRequest(movie = movieBody, comment = commentText, spoiler = isSpoiler)\n\n    val comment = remoteSource.trakt.postComment(request)\n    return mappers.comment.fromNetwork(comment)\n  }\n\n  suspend fun postComment(episode: Episode, commentText: String, isSpoiler: Boolean): Comment {\n    val episodeBody = mappers.episode.toNetwork(Episode.EMPTY.copy(ids = episode.ids))\n    val request = CommentRequest(episode = episodeBody, comment = commentText, spoiler = isSpoiler)\n\n    val comment = remoteSource.trakt.postComment(request)\n    return mappers.comment.fromNetwork(comment)\n  }\n\n  suspend fun postReply(commentId: Long, commentText: String, isSpoiler: Boolean): Comment {\n    val request = CommentRequest(comment = commentText, spoiler = isSpoiler)\n\n    val comment = remoteSource.trakt.postCommentReply(commentId, request)\n    return mappers.comment.fromNetwork(comment)\n  }\n\n  suspend fun deleteComment(commentId: Long) {\n    remoteSource.trakt.deleteComment(commentId)\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/EpisodesManager.kt",
    "content": "package com.michaldrabik.repository\n\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.data_local.database.model.EpisodesSyncLog\nimport com.michaldrabik.data_local.sources.EpisodesLocalDataSource\nimport com.michaldrabik.data_local.sources.EpisodesSyncLogLocalDataSource\nimport com.michaldrabik.data_local.sources.SeasonsLocalDataSource\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.EpisodeBundle\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.SeasonBundle\nimport com.michaldrabik.ui_model.Show\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.coroutineScope\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\nimport com.michaldrabik.data_local.database.model.Episode as EpisodeDb\nimport com.michaldrabik.data_local.database.model.Season as SeasonDb\n\n@Singleton\nclass EpisodesManager @Inject constructor(\n  private val showsRepository: ShowsRepository,\n  private val episodesLocalSource: EpisodesLocalDataSource,\n  private val seasonsLocalSource: SeasonsLocalDataSource,\n  private val syncLogLocalSource: EpisodesSyncLogLocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers,\n) {\n\n  suspend fun getWatchedSeasonsIds(show: Show) =\n    seasonsLocalSource.getAllWatchedIdsForShows(listOf(show.traktId))\n\n  suspend fun getWatchedEpisodesIds(show: Show) =\n    episodesLocalSource.getAllWatchedIdsForShows(listOf(show.traktId))\n\n  suspend fun setSeasonWatched(seasonBundle: SeasonBundle): List<Episode> {\n    val nowUtc = nowUtc()\n    val toAdd = mutableListOf<EpisodeDb>()\n    transactions.withTransaction {\n      val (season, show) = seasonBundle\n\n      val dbSeason = mappers.season.toDatabase(season, show.ids.trakt, true)\n      val localSeason = seasonsLocalSource.getById(season.ids.trakt.id)\n      if (localSeason == null) {\n        seasonsLocalSource.upsert(listOf(dbSeason))\n      }\n\n      val episodes = episodesLocalSource.getAllForSeason(season.ids.trakt.id).filter { it.isWatched }\n\n      season.episodes.forEach { ep ->\n        if (episodes.none { it.idTrakt == ep.ids.trakt.id }) {\n          val dbEpisode = mappers.episode.toDatabase(ep, season, show.ids.trakt, true, nowUtc)\n          toAdd.add(dbEpisode)\n        }\n      }\n\n      episodesLocalSource.upsert(toAdd)\n      seasonsLocalSource.update(listOf(dbSeason))\n      showsRepository.myShows.updateWatchedAt(show.traktId, nowUtc.toMillis())\n    }\n    return toAdd.map { mappers.episode.fromDatabase(it) }\n  }\n\n  suspend fun setSeasonUnwatched(seasonBundle: SeasonBundle) {\n    transactions.withTransaction {\n      val (season, show) = seasonBundle\n\n      val dbSeason = mappers.season.toDatabase(season, show.ids.trakt, false)\n      val watchedEpisodes = episodesLocalSource.getAllForSeason(season.ids.trakt.id).filter { it.isWatched }\n      val toSet = watchedEpisodes.map { it.copy(isWatched = false, lastWatchedAt = null) }\n\n      val isShowFollowed = showsRepository.myShows.load(show.ids.trakt) != null\n\n      when {\n        isShowFollowed -> {\n          episodesLocalSource.upsert(toSet)\n          seasonsLocalSource.update(listOf(dbSeason))\n        }\n        else -> {\n          episodesLocalSource.delete(toSet)\n          seasonsLocalSource.delete(listOf(dbSeason))\n        }\n      }\n    }\n  }\n\n  suspend fun setEpisodeWatched(episodeId: Long, seasonId: Long, showId: IdTrakt) {\n    val episodeDb = episodesLocalSource.getAllForSeason(seasonId).find { it.idTrakt == episodeId }!!\n    val seasonDb = seasonsLocalSource.getById(seasonId)!!\n    val show = showsRepository.myShows.load(showId)!!\n    setEpisodeWatched(\n      EpisodeBundle(\n        mappers.episode.fromDatabase(episodeDb),\n        mappers.season.fromDatabase(seasonDb),\n        show\n      )\n    )\n  }\n\n  suspend fun setEpisodeWatched(episodeBundle: EpisodeBundle) {\n    transactions.withTransaction {\n      val (episode, season, show) = episodeBundle\n      val nowUtc = nowUtc()\n\n      val dbEpisode = mappers.episode.toDatabase(episode, season, show.ids.trakt, true, nowUtc)\n      val dbSeason = mappers.season.toDatabase(season, show.ids.trakt, false)\n\n      val localSeason = seasonsLocalSource.getById(season.ids.trakt.id)\n      if (localSeason == null) {\n        seasonsLocalSource.upsert(listOf(dbSeason))\n      }\n      episodesLocalSource.upsert(listOf(dbEpisode))\n      showsRepository.myShows.updateWatchedAt(show.traktId, nowUtc.toMillis())\n      onEpisodeSet(season, show)\n    }\n  }\n\n  suspend fun setEpisodeUnwatched(episodeBundle: EpisodeBundle) {\n    transactions.withTransaction {\n      val (episode, season, show) = episodeBundle\n\n      val isShowFollowed = showsRepository.myShows.load(show.ids.trakt) != null\n      val dbEpisode = mappers.episode.toDatabase(episode, season, show.ids.trakt, true, episode.lastWatchedAt)\n\n      when {\n        isShowFollowed -> episodesLocalSource.upsert(listOf(dbEpisode.copy(isWatched = false, lastWatchedAt = null)))\n        else -> episodesLocalSource.delete(listOf(dbEpisode))\n      }\n\n      onEpisodeSet(season, show)\n    }\n  }\n\n  suspend fun setAllUnwatched(showId: IdTrakt, skipSpecials: Boolean = false) {\n    transactions.withTransaction {\n      val watchedEpisodes = episodesLocalSource.getAllByShowId(showId.id)\n      val watchedSeasons = seasonsLocalSource.getAllByShowId(showId.id)\n\n      val updateEpisodes = watchedEpisodes\n        .filter { if (skipSpecials) it.seasonNumber > 0 else true }\n        .map { it.copy(isWatched = false, lastWatchedAt = null) }\n      val updateSeasons = watchedSeasons\n        .filter { if (skipSpecials) it.seasonNumber > 0 else true }\n        .map { it.copy(isWatched = false) }\n\n      episodesLocalSource.upsert(updateEpisodes)\n      seasonsLocalSource.update(updateSeasons)\n    }\n  }\n\n  @Suppress(\"UNCHECKED_CAST\")\n  suspend fun invalidateSeasons(show: Show, newSeasons: List<Season>) {\n    if (newSeasons.isEmpty()) {\n      return\n    }\n    coroutineScope {\n      val (localSeasons, localEpisodes) = awaitAll(\n        async { seasonsLocalSource.getAllByShowId(show.traktId) },\n        async { episodesLocalSource.getAllByShowId(show.traktId) }\n      )\n      localSeasons as List<SeasonDb>\n      localEpisodes as List<EpisodeDb>\n\n      val seasonsToAdd = mutableListOf<SeasonDb>()\n      val episodesToAdd = mutableListOf<EpisodeDb>()\n\n      newSeasons.forEach { season ->\n        var isAnyEpisodeUnwatched = false\n\n        season.episodes.forEach { newEpisode ->\n          var localEpisode = localEpisodes.find {\n            it.episodeNumber == newEpisode.number &&\n              it.seasonNumber == newEpisode.season\n          }\n          if (localEpisode == null) {\n            // Double check by Trakt ID as season/episode combination might be old.\n            localEpisode = localEpisodes.find { it.idTrakt == newEpisode.ids.trakt.id }\n          }\n\n          val lastWatchedAt = localEpisode?.lastWatchedAt\n          val isWatched = localEpisode?.isWatched ?: false\n          if (!isWatched) {\n            isAnyEpisodeUnwatched = true\n          }\n\n          val episodeDb = mappers.episode.toDatabase(\n            episode = newEpisode,\n            season = season,\n            showId = show.ids.trakt,\n            isWatched = isWatched,\n            lastWatchedAt = lastWatchedAt\n          )\n          episodesToAdd.add(episodeDb)\n        }\n\n        val seasonDb = mappers.season.toDatabase(\n          season = season,\n          showId = show.ids.trakt,\n          isWatched = !isAnyEpisodeUnwatched\n        )\n        seasonsToAdd.add(seasonDb)\n      }\n\n      transactions.withTransaction {\n        episodesLocalSource.deleteAllForShow(show.traktId)\n        seasonsLocalSource.deleteAllForShow(show.traktId)\n\n        seasonsLocalSource.upsert(seasonsToAdd)\n        episodesLocalSource.upsertChunked(episodesToAdd)\n\n        syncLogLocalSource.upsert(EpisodesSyncLog(show.traktId, nowUtcMillis()))\n      }\n\n      Timber.d(\"Episodes updated: ${episodesToAdd.size} Seasons updated: ${seasonsToAdd.size}\")\n    }\n  }\n\n  private suspend fun onEpisodeSet(season: Season, show: Show) {\n    val localEpisodes = episodesLocalSource.getAllForSeason(season.ids.trakt.id)\n    val isWatched = localEpisodes.count { it.isWatched } == season.episodeCount\n    val dbSeason = mappers.season.toDatabase(season, show.ids.trakt, isWatched)\n    seasonsLocalSource.update(listOf(dbSeason))\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/ListsRepository.kt",
    "content": "package com.michaldrabik.repository\n\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.CustomListItem\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.CustomList\nimport com.michaldrabik.ui_model.IdTrakt\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass ListsRepository @Inject constructor(\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n  private val transactions: TransactionsProvider\n) {\n\n  suspend fun createList(\n    name: String,\n    description: String?,\n    idTrakt: Long?,\n    idSlug: String?\n  ): CustomList {\n    val list = CustomList.create().copy(\n      idTrakt = idTrakt,\n      idSlug = idSlug ?: \"\",\n      name = name.trim(),\n      description = description?.trim()\n    )\n    val listDb = mappers.customList.toDatabase(list)\n    localSource.customLists.insert(listOf(listDb))\n    return list\n  }\n\n  suspend fun updateList(\n    id: Long,\n    idTrakt: Long?,\n    idSlug: String?,\n    name: String,\n    description: String?\n  ): CustomList {\n    val listDb = localSource.customLists.getById(id)!!\n    val updated = listDb.copy(\n      name = name,\n      idTrakt = idTrakt ?: listDb.idTrakt,\n      idSlug = idSlug ?: listDb.idSlug,\n      description = description,\n      updatedAt = nowUtcMillis()\n    )\n    localSource.customLists.update(listOf(updated))\n    return mappers.customList.fromDatabase(updated)\n  }\n\n  suspend fun deleteList(listId: Long) = localSource.customLists.deleteById(listId)\n\n  suspend fun addToList(listId: Long, itemTraktId: IdTrakt, itemType: String) {\n    val timestamp = nowUtcMillis()\n    val itemDb = CustomListItem(\n      rank = 0,\n      idList = listId,\n      idTrakt = itemTraktId.id,\n      type = itemType,\n      listedAt = timestamp,\n      createdAt = timestamp,\n      updatedAt = timestamp\n    )\n    transactions.withTransaction {\n      localSource.customListsItems.insertItem(itemDb)\n      localSource.customLists.updateTimestamp(listId, nowUtcMillis())\n    }\n  }\n\n  suspend fun removeFromList(listId: Long, itemTraktId: IdTrakt, itemType: String) {\n    transactions.withTransaction {\n      localSource.customListsItems.deleteItem(listId, itemTraktId.id, itemType)\n      localSource.customLists.updateTimestamp(listId, nowUtcMillis())\n    }\n  }\n\n  suspend fun loadListIdsForItem(itemTraktId: IdTrakt, itemType: String) =\n    localSource.customListsItems.getListsForItem(itemTraktId.id, itemType)\n\n  suspend fun loadListItemsForId(listId: Long) =\n    localSource.customListsItems.getItemsById(listId)\n\n  suspend fun loadById(listId: Long): CustomList {\n    val listDb = localSource.customLists.getById(listId)!!\n    return mappers.customList.fromDatabase(listDb)\n  }\n\n  suspend fun loadItemsById(listId: Long) =\n    localSource.customListsItems.getItemsById(listId)\n\n  suspend fun loadAll(): List<CustomList> {\n    val listsDb = localSource.customLists.getAll()\n    return listsDb.map { mappers.customList.fromDatabase(it) }\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/NewsRepository.kt",
    "content": "package com.michaldrabik.repository\n\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.NewsItem\nimport com.michaldrabik.ui_model.NewsItem.Type.MOVIE\nimport com.michaldrabik.ui_model.NewsItem.Type.SHOW\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass NewsRepository @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n) {\n\n  companion object {\n    const val VALID_CACHE_MINUTES = 360L\n  }\n\n  suspend fun getCachedNews(type: NewsItem.Type) =\n    localSource.news.getAllByType(type.slug)\n      .map { mappers.news.fromDatabase(it) }\n\n  suspend fun loadShowsNews(token: RedditAuthToken, forceRefresh: Boolean): List<NewsItem> {\n    if (!forceRefresh) {\n      val cachedNews = getCachedNews(SHOW)\n      val cacheTimestamp = cachedNews.firstOrNull()?.createdAt?.toMillis() ?: 0\n\n      val isCacheValid = nowUtcMillis() - cacheTimestamp <= TimeUnit.MINUTES.toMillis(VALID_CACHE_MINUTES)\n      if (isCacheValid && getCachedNews(SHOW).isNotEmpty()) {\n        return cachedNews.toList()\n      }\n    }\n\n    val remoteItems = remoteSource.reddit.fetchTelevisionItems(token.token)\n      .map { mappers.news.fromNetwork(it, SHOW) }\n\n    val dbItems = remoteItems.map { mappers.news.toDatabase(it) }\n    localSource.news.replaceForType(dbItems, SHOW.slug)\n\n    return remoteItems.toList()\n  }\n\n  suspend fun loadMoviesNews(token: RedditAuthToken, forceRefresh: Boolean): List<NewsItem> {\n    if (!forceRefresh) {\n      val cachedNews = getCachedNews(MOVIE)\n      val cacheTimestamp = cachedNews.firstOrNull()?.createdAt?.toMillis() ?: 0\n\n      val isCacheValid = nowUtcMillis() - cacheTimestamp <= TimeUnit.MINUTES.toMillis(VALID_CACHE_MINUTES)\n      if (isCacheValid && getCachedNews(MOVIE).isNotEmpty()) {\n        return cachedNews.toList()\n      }\n    }\n\n    val remoteItems = remoteSource.reddit.fetchMoviesItems(token.token)\n      .map { mappers.news.fromNetwork(it, MOVIE) }\n\n    val dbItems = remoteItems.map { mappers.news.toDatabase(it) }\n    localSource.news.replaceForType(dbItems, MOVIE.slug)\n\n    return remoteItems.toList()\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/OnHoldItemsRepository.kt",
    "content": "package com.michaldrabik.repository\n\nimport android.content.SharedPreferences\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Show\nimport javax.inject.Inject\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Singleton\nclass OnHoldItemsRepository @Inject constructor(\n  @Named(\"progressOnHoldPreferences\") private val sharedPreferences: SharedPreferences\n) {\n\n  fun getAll(): List<IdTrakt> = sharedPreferences.all.keys.map { IdTrakt(it.toLong()) }\n\n  fun addItem(show: Show) =\n    sharedPreferences.edit().putLong(show.traktId.toString(), show.traktId).apply()\n\n  fun removeItem(show: Show) =\n    sharedPreferences.edit().remove(show.traktId.toString()).apply()\n\n  fun isOnHold(show: Show) =\n    sharedPreferences.contains(show.traktId.toString())\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/PeopleRepository.kt",
    "content": "package com.michaldrabik.repository\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.PersonCredits\nimport com.michaldrabik.data_local.database.model.PersonShowMovie\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.data_remote.tmdb.model.TmdbPerson\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_model.Person.Department\nimport com.michaldrabik.ui_model.PersonCredit\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.coroutineScope\nimport timber.log.Timber\nimport javax.inject.Inject\n\nclass PeopleRepository @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n  private val localSource: LocalDataSource,\n  private val remoteSource: RemoteDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers\n) {\n\n  companion object {\n    const val ACTORS_DISPLAY_LIMIT = 30\n    const val CREW_DISPLAY_LIMIT = 20\n  }\n\n  suspend fun loadDetails(person: Person): Person {\n    val local = localSource.people.getById(person.ids.tmdb.id)\n    if (local?.detailsUpdatedAt != null) {\n      return mappers.person.fromDatabase(local, person.characters)\n    }\n\n    val language = settingsRepository.language\n    val remotePerson = remoteSource.tmdb.fetchPersonDetails(person.ids.tmdb.id)\n    var bioTranslation: String? = null\n    if (language != Config.DEFAULT_LANGUAGE) {\n      val translations = remoteSource.tmdb.fetchPersonTranslations(person.ids.tmdb.id)\n      bioTranslation = translations[language]?.biography\n    }\n\n    val personUi = mappers.person.fromNetwork(remotePerson).copy(\n      bioTranslation = bioTranslation,\n      imagePath = person.imagePath ?: remotePerson.profile_path\n    )\n    val dbPerson = mappers.person.toDatabase(personUi, nowUtc())\n\n    localSource.people.upsert(listOf(dbPerson))\n\n    return personUi\n  }\n\n  suspend fun loadCredits(person: Person) = coroutineScope {\n    val idTmdb = person.ids.tmdb.id\n    var idTrakt: Long?\n\n    val localPerson = localSource.people.getById(idTmdb)\n    idTrakt = localPerson?.idTrakt\n    if (idTrakt == null) {\n      val ids = remoteSource.trakt.fetchPersonIds(\"tmdb\", idTmdb.toString())\n      ids?.trakt?.let {\n        idTrakt = it\n        localSource.people.updateTraktId(it, idTmdb)\n      }\n    }\n    if (idTrakt == null) return@coroutineScope emptyList()\n\n    // Return locally cached data if available\n    val timestamp = localSource.peopleCredits.getTimestampForPerson(idTrakt!!)\n    if (timestamp != null && nowUtcMillis() - timestamp < Config.PEOPLE_CREDITS_CACHE_DURATION) {\n      val localCredits = mutableListOf<PersonCredit>()\n\n      val showsCreditsAsync = async { localSource.peopleCredits.getAllShowsForPerson(idTrakt!!) }\n      val moviesCreditsAsync = async { localSource.peopleCredits.getAllMoviesForPerson(idTrakt!!) }\n      val shows = showsCreditsAsync.await()\n      val movies = moviesCreditsAsync.await()\n\n      shows.mapTo(localCredits) {\n        PersonCredit(\n          show = mappers.show.fromDatabase(it),\n          movie = null,\n          image = Image.createUnknown(ImageType.POSTER),\n          translation = null\n        )\n      }\n      movies.mapTo(localCredits) {\n        PersonCredit(\n          movie = mappers.movie.fromDatabase(it),\n          show = null,\n          image = Image.createUnknown(ImageType.POSTER),\n          translation = null\n        )\n      }\n\n      return@coroutineScope localCredits\n    }\n\n    // Return remote fetched data if available and cache it locally\n    val type = if (person.department == Department.ACTING) TmdbPerson.Type.CAST else TmdbPerson.Type.CREW\n    val showsCreditsAsync = async { remoteSource.trakt.fetchPersonShowsCredits(idTrakt!!, type) }\n    val moviesCreditsAsync = async { remoteSource.trakt.fetchPersonMoviesCredits(idTrakt!!, type) }\n    val remoteCredits = awaitAll(showsCreditsAsync, moviesCreditsAsync)\n      .flatten()\n      .map {\n        PersonCredit(\n          show = it.show?.let { show -> mappers.show.fromNetwork(show) },\n          movie = it.movie?.let { movie -> mappers.movie.fromNetwork(movie) },\n          image = Image.createUnknown(ImageType.POSTER),\n          translation = null\n        )\n      }\n\n    val localCredits = remoteCredits.map {\n      PersonCredits(\n        id = 0,\n        idTraktPerson = idTrakt!!,\n        idTraktShow = it.show?.traktId,\n        idTraktMovie = it.movie?.traktId,\n        type = if (it.show != null) Mode.SHOWS.type else Mode.MOVIES.type,\n        createdAt = nowUtc(),\n        updatedAt = nowUtc()\n      )\n    }\n\n    with(localSource) {\n      val remoteShows = remoteCredits.filter { it.show != null }.map { it.show!! }\n      val remoteMovies = remoteCredits.filter { it.movie != null }.map { it.movie!! }\n\n      transactions.withTransaction {\n        shows.upsert(remoteShows.map { mappers.show.toDatabase(it) })\n        movies.upsert(remoteMovies.map { mappers.movie.toDatabase(it) })\n        peopleCredits.insertSingle(idTrakt!!, localCredits)\n      }\n    }\n\n    return@coroutineScope remoteCredits\n  }\n\n  suspend fun loadAllForShow(showIds: Ids): Map<Department, List<Person>> {\n    val timestamp = nowUtc()\n\n    val localTimestamp = localSource.peopleShowsMovies.getTimestampForShow(showIds.trakt.id) ?: 0\n    val local = localSource.people.getAllForShow(showIds.trakt.id)\n    if (local.isNotEmpty() && localTimestamp + Config.ACTORS_CACHE_DURATION > timestamp.toMillis()) {\n      Timber.d(\"Returning cached result. Cache still valid for ${(localTimestamp + Config.ACTORS_CACHE_DURATION) - timestamp.toMillis()} ms\")\n      return local\n        .map { mappers.person.fromDatabase(it) }\n        .groupBy { it.department }\n        .mapValues { v -> v.value.sortedWith(compareBy { it.imagePath.isNullOrBlank() }) }\n    }\n\n    val remoteTmdbPeople = remoteSource.tmdb.fetchShowPeople(showIds.tmdb.id)\n\n    val remoteTmdbActors = remoteTmdbPeople\n      .getOrDefault(TmdbPerson.Type.CAST, emptyList())\n      .sortedWith(compareBy { it.profile_path.isNullOrBlank() })\n      .map { mappers.person.fromNetwork(it) }\n      .take(ACTORS_DISPLAY_LIMIT)\n\n    val crewFilter = arrayOf(Department.DIRECTING, Department.WRITING, Department.SOUND).map { it.slug }\n    val jobsFilter = Person.Job.values().map { it.slug }\n    val remoteTmdbCrew = remoteTmdbPeople\n      .getOrDefault(TmdbPerson.Type.CREW, emptyList())\n      .asSequence()\n      .filter { it.department in crewFilter }\n      .filter { it.jobs?.any { job -> job.job ?: \"\" in jobsFilter } == true }\n      .sortedWith(compareBy { it.profile_path.isNullOrBlank() })\n      .map { mappers.person.fromNetwork(it) }\n      .groupBy { it.department }\n\n    val directors = remoteTmdbCrew[Department.DIRECTING]?.take(CREW_DISPLAY_LIMIT)?.distinctBy { it.ids.tmdb } ?: emptyList()\n    val writers = remoteTmdbCrew[Department.WRITING]?.take(CREW_DISPLAY_LIMIT)?.distinctBy { it.ids.tmdb } ?: emptyList()\n    val sound = remoteTmdbCrew[Department.SOUND]?.take(CREW_DISPLAY_LIMIT)?.distinctBy { it.ids.tmdb } ?: emptyList()\n\n    val filteredTmdbPeople = remoteTmdbActors + directors + writers + sound\n    val dbTmdbPeople = filteredTmdbPeople.map { mappers.person.toDatabase(it, null) }\n    val dbTmdbPeopleShows = filteredTmdbPeople.map {\n      PersonShowMovie(\n        id = 0,\n        idTmdbPerson = it.ids.tmdb.id,\n        mode = Mode.SHOWS.type,\n        department = it.department.slug,\n        character = it.characters.joinToString(\",\"),\n        job = it.jobs.joinToString(\",\") { job -> job.slug },\n        episodesCount = it.episodesCount,\n        idTraktShow = showIds.trakt.id,\n        idTraktMovie = null,\n        createdAt = timestamp,\n        updatedAt = timestamp\n      )\n    }\n\n    with(localSource) {\n      transactions.withTransaction {\n        people.upsert(dbTmdbPeople)\n        peopleShowsMovies.insertForShow(dbTmdbPeopleShows, showIds.trakt.id)\n      }\n    }\n\n    Timber.d(\"Returning remote result.\")\n    return filteredTmdbPeople.groupBy { it.department }\n  }\n\n  suspend fun loadAllForMovie(movieIds: Ids): Map<Department, List<Person>> {\n    val timestamp = nowUtc()\n\n    val localTimestamp = localSource.peopleShowsMovies.getTimestampForMovie(movieIds.trakt.id) ?: 0\n    val local = localSource.people.getAllForMovie(movieIds.trakt.id)\n    if (local.isNotEmpty() && localTimestamp + Config.ACTORS_CACHE_DURATION > timestamp.toMillis()) {\n      Timber.d(\"Returning cached result. Cache still valid for ${(localTimestamp + Config.ACTORS_CACHE_DURATION) - timestamp.toMillis()} ms\")\n      return local\n        .map { mappers.person.fromDatabase(it) }\n        .groupBy { it.department }\n        .mapValues { v -> v.value.sortedWith(compareBy { it.imagePath.isNullOrBlank() }) }\n    }\n\n    val remoteTmdbPeople = remoteSource.tmdb.fetchMoviePeople(movieIds.tmdb.id)\n\n    val remoteTmdbActors = remoteTmdbPeople\n      .getOrDefault(TmdbPerson.Type.CAST, emptyList())\n      .sortedWith(compareBy { it.profile_path.isNullOrBlank() })\n      .map { mappers.person.fromNetwork(it) }\n      .take(ACTORS_DISPLAY_LIMIT)\n\n    val crewFilter = arrayOf(Department.DIRECTING, Department.WRITING, Department.SOUND).map { it.slug }\n    val jobsFilter = Person.Job.values().map { it.slug }\n    val remoteTmdbCrew = remoteTmdbPeople\n      .getOrDefault(TmdbPerson.Type.CREW, emptyList())\n      .asSequence()\n      .filter { it.department in crewFilter }\n      .filter { it.job in jobsFilter }\n      .sortedWith(compareBy { it.profile_path.isNullOrBlank() })\n      .map { mappers.person.fromNetwork(it) }\n      .groupBy { it.department }\n\n    val directors = remoteTmdbCrew[Department.DIRECTING]?.take(CREW_DISPLAY_LIMIT)?.distinctBy { it.ids.tmdb } ?: emptyList()\n    val writers = remoteTmdbCrew[Department.WRITING]?.take(CREW_DISPLAY_LIMIT)?.distinctBy { it.ids.tmdb } ?: emptyList()\n    val sound = remoteTmdbCrew[Department.SOUND]?.take(CREW_DISPLAY_LIMIT)?.distinctBy { it.ids.tmdb } ?: emptyList()\n\n    val filteredTmdbPeople = remoteTmdbActors + directors + writers + sound\n    val dbTmdbPeople = filteredTmdbPeople.map { mappers.person.toDatabase(it, null) }\n    val dbTmdbPeopleMovies = filteredTmdbPeople.map {\n      PersonShowMovie(\n        id = 0,\n        idTmdbPerson = it.ids.tmdb.id,\n        mode = Mode.MOVIES.type,\n        department = it.department.slug,\n        character = it.characters.joinToString(\",\"),\n        job = it.jobs.joinToString(\",\") { job -> job.slug },\n        episodesCount = it.episodesCount,\n        idTraktShow = null,\n        idTraktMovie = movieIds.trakt.id,\n        createdAt = timestamp,\n        updatedAt = timestamp\n      )\n    }\n\n    with(localSource) {\n      transactions.withTransaction {\n        people.upsert(dbTmdbPeople)\n        peopleShowsMovies.insertForMovie(dbTmdbPeopleMovies, movieIds.trakt.id)\n      }\n    }\n\n    Timber.d(\"Returning remote result.\")\n    return filteredTmdbPeople.groupBy { it.department }\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/PinnedItemsRepository.kt",
    "content": "package com.michaldrabik.repository\n\nimport android.content.SharedPreferences\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\nimport javax.inject.Inject\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Singleton\nclass PinnedItemsRepository @Inject constructor(\n  @Named(\"watchlistPreferences\") private val sharedPreferences: SharedPreferences,\n  @Named(\"progressMoviesPreferences\") private val sharedPreferencesMovies: SharedPreferences\n) {\n\n  fun addPinnedItem(show: Show) =\n    sharedPreferences.edit().putLong(show.traktId.toString(), show.traktId).apply()\n\n  fun addPinnedItem(movie: Movie) =\n    sharedPreferencesMovies.edit().putLong(movie.traktId.toString(), movie.traktId).apply()\n\n  fun removePinnedItem(show: Show) =\n    sharedPreferences.edit().remove(show.traktId.toString()).apply()\n\n  fun removePinnedItem(movie: Movie) =\n    sharedPreferencesMovies.edit().remove(movie.traktId.toString()).apply()\n\n  fun isItemPinned(show: Show) =\n    sharedPreferences.contains(show.traktId.toString())\n\n  fun isItemPinned(movie: Movie) =\n    sharedPreferencesMovies.contains(movie.traktId.toString())\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/RatingsRepository.kt",
    "content": "package com.michaldrabik.repository\n\nimport com.michaldrabik.repository.movies.ratings.MoviesRatingsRepository\nimport com.michaldrabik.repository.shows.ratings.ShowsRatingsRepository\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass RatingsRepository @Inject constructor(\n  val shows: ShowsRatingsRepository,\n  val movies: MoviesRatingsRepository,\n) {\n\n  suspend fun clear() {\n    shows.clear()\n    movies.clear()\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/StreamingsRepository.kt",
    "content": "package com.michaldrabik.repository\n\nimport com.michaldrabik.data_remote.tmdb.model.TmdbStreamingCountry\nimport com.michaldrabik.data_remote.tmdb.model.TmdbStreamingService\nimport com.michaldrabik.ui_model.StreamingService\nimport com.michaldrabik.ui_model.StreamingService.Option.ADS\nimport com.michaldrabik.ui_model.StreamingService.Option.BUY\nimport com.michaldrabik.ui_model.StreamingService.Option.FLATRATE\nimport com.michaldrabik.ui_model.StreamingService.Option.FREE\nimport com.michaldrabik.ui_model.StreamingService.Option.RENT\n\nabstract class StreamingsRepository {\n\n  protected fun processItems(\n    remoteItems: List<StreamingService>,\n    countryCode: String,\n  ) = remoteItems\n    .groupBy { it.name }\n    .filter { it.value.isNotEmpty() }\n    .map { entry ->\n      val entryValue = entry.value.first()\n      StreamingService(\n        name = entry.key,\n        imagePath = entryValue.imagePath,\n        options = entry.value.flatMap { it.options },\n        link = entryValue.link,\n        mediaName = entryValue.mediaName,\n        countryCode = countryCode\n      )\n    }\n\n  protected fun processItems(\n    remoteItems: TmdbStreamingCountry,\n    mediaName: String,\n    countryCode: String,\n  ): List<StreamingService> {\n    val items = mutableListOf<StreamingService>()\n    items.addAll(remoteItems.flatrate?.map { createStreamingService(mediaName, countryCode, remoteItems, it, FLATRATE) } ?: emptyList())\n    items.addAll(remoteItems.free?.map { createStreamingService(mediaName, countryCode, remoteItems, it, FREE) } ?: emptyList())\n    items.addAll(remoteItems.buy?.map { createStreamingService(mediaName, countryCode, remoteItems, it, BUY) } ?: emptyList())\n    items.addAll(remoteItems.rent?.map { createStreamingService(mediaName, countryCode, remoteItems, it, RENT) } ?: emptyList())\n    items.addAll(remoteItems.ads?.map { createStreamingService(mediaName, countryCode, remoteItems, it, ADS) } ?: emptyList())\n    return items\n      .groupBy { it.name }\n      .filter { it.value.isNotEmpty() }\n      .map { entry ->\n        val entryValue = entry.value.first()\n        StreamingService(\n          name = entry.key,\n          imagePath = entryValue.imagePath,\n          options = entry.value.flatMap { it.options },\n          link = entryValue.link,\n          mediaName = entryValue.mediaName,\n          countryCode = countryCode\n        )\n      }\n  }\n\n  private fun createStreamingService(\n    mediaName: String,\n    countryCode: String,\n    country: TmdbStreamingCountry,\n    service: TmdbStreamingService,\n    option: StreamingService.Option,\n  ) = StreamingService(\n    imagePath = service.logo_path,\n    name = service.provider_name,\n    options = listOf(option),\n    link = country.link,\n    mediaName = mediaName,\n    countryCode = countryCode\n  )\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/TranslationsRepository.kt",
    "content": "package com.michaldrabik.repository\n\nimport android.content.SharedPreferences\nimport com.michaldrabik.common.Config.DEFAULT_LANGUAGE\nimport com.michaldrabik.common.ConfigVariant\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.EpisodeTranslation\nimport com.michaldrabik.data_local.database.model.MovieTranslation\nimport com.michaldrabik.data_local.database.model.ShowTranslation\nimport com.michaldrabik.data_local.database.model.TranslationsMoviesSyncLog\nimport com.michaldrabik.data_local.database.model.TranslationsSyncLog\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsRepository.Key.LANGUAGE\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.SeasonTranslation\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.Translation\nimport javax.inject.Inject\nimport javax.inject.Named\nimport javax.inject.Singleton\nimport com.michaldrabik.data_remote.trakt.model.Translation as TranslationRemote\n\n@Singleton\nclass TranslationsRepository @Inject constructor(\n  @Named(\"miscPreferences\") private var miscPreferences: SharedPreferences,\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n) {\n\n  fun getLanguage() = miscPreferences.getString(LANGUAGE, DEFAULT_LANGUAGE) ?: DEFAULT_LANGUAGE\n\n  suspend fun loadAllShowsLocal(language: String = DEFAULT_LANGUAGE): Map<Long, Translation> {\n    val local = localSource.showTranslations.getAll(language)\n    return local.associate {\n      Pair(it.idTrakt, mappers.translation.fromDatabase(it))\n    }\n  }\n\n  suspend fun loadAllMoviesLocal(language: String = DEFAULT_LANGUAGE): Map<Long, Translation> {\n    val local = localSource.movieTranslations.getAll(language)\n    return local.associate {\n      Pair(it.idTrakt, mappers.translation.fromDatabase(it))\n    }\n  }\n\n  suspend fun loadTranslation(\n    show: Show,\n    language: String = DEFAULT_LANGUAGE,\n    onlyLocal: Boolean = false,\n  ): Translation? {\n    val local = localSource.showTranslations.getById(show.traktId, language)\n    local?.let {\n      return mappers.translation.fromDatabase(it)\n    }\n    if (onlyLocal) return null\n\n    val timestamp = localSource.translationsShowsSyncLog.getById(show.traktId)?.syncedAt ?: 0\n    if (nowUtcMillis() - timestamp < ConfigVariant.TRANSLATION_SYNC_SHOW_MOVIE_COOLDOWN) {\n      return Translation.EMPTY\n    }\n\n    val remoteTranslation = try {\n      remoteSource.trakt.fetchShowTranslations(show.traktId, language)\n        .firstOrNull { chineseLanguagePredicate(it) && frenchLanguagePredicate(it) }\n    } catch (error: Throwable) {\n      null\n    }\n\n    val translation = mappers.translation.fromNetwork(remoteTranslation)\n    val translationDb = ShowTranslation.fromTraktId(\n      show.traktId,\n      translation.title,\n      language,\n      translation.overview,\n      nowUtcMillis()\n    )\n\n    if (translationDb.overview.isNotBlank() || translationDb.title.isNotBlank()) {\n      localSource.showTranslations.insertSingle(translationDb)\n    }\n    localSource.translationsShowsSyncLog.upsert(TranslationsSyncLog(show.traktId, nowUtcMillis()))\n\n    return translation\n  }\n\n  suspend fun loadTranslation(\n    movie: Movie,\n    language: String = DEFAULT_LANGUAGE,\n    onlyLocal: Boolean = false,\n  ): Translation? {\n    val local = localSource.movieTranslations.getById(movie.traktId, language)\n    local?.let {\n      return mappers.translation.fromDatabase(it)\n    }\n    if (onlyLocal) return null\n\n    val timestamp = localSource.translationsMoviesSyncLog.getById(movie.traktId)?.syncedAt ?: 0\n    if (nowUtcMillis() - timestamp < ConfigVariant.TRANSLATION_SYNC_SHOW_MOVIE_COOLDOWN) {\n      return Translation.EMPTY\n    }\n\n    val remoteTranslation = try {\n      remoteSource.trakt.fetchMovieTranslations(movie.traktId, language)\n        .firstOrNull { chineseLanguagePredicate(it) && frenchLanguagePredicate(it) }\n    } catch (error: Throwable) {\n      null\n    }\n\n    val translation = mappers.translation.fromNetwork(remoteTranslation)\n    val translationDb = MovieTranslation.fromTraktId(\n      movie.traktId,\n      translation.title,\n      language,\n      translation.overview,\n      nowUtcMillis()\n    )\n\n    if (translationDb.overview.isNotBlank() || translationDb.title.isNotBlank()) {\n      localSource.movieTranslations.insertSingle(translationDb)\n    }\n    localSource.translationsMoviesSyncLog.upsert(TranslationsMoviesSyncLog(movie.traktId, nowUtcMillis()))\n\n    return translation\n  }\n\n  suspend fun loadTranslation(\n    episode: Episode,\n    showId: IdTrakt,\n    language: String = DEFAULT_LANGUAGE,\n    onlyLocal: Boolean = false,\n  ): Translation? {\n    val nowMillis = nowUtcMillis()\n    val local = localSource.episodesTranslations.getById(episode.ids.trakt.id, showId.id, language)\n    local?.let {\n      val isCacheValid = nowMillis - it.updatedAt < ConfigVariant.TRANSLATION_SYNC_EPISODE_COOLDOWN\n      if (it.title.isNotBlank() && it.overview.isNotBlank()) {\n        return mappers.translation.fromDatabase(it)\n      }\n      if ((it.title.isNotBlank() || it.overview.isNotBlank()) && (isCacheValid || onlyLocal)) {\n        return mappers.translation.fromDatabase(it)\n      }\n    }\n\n    if (onlyLocal) return null\n\n    val remoteTranslations = remoteSource.trakt.fetchSeasonTranslations(showId.id, episode.season, language)\n      .map { mappers.translation.fromNetwork(it) }\n\n    remoteTranslations\n      .forEach { item ->\n        val dbItem = EpisodeTranslation.fromTraktId(\n          traktEpisodeId = item.ids.trakt.id,\n          traktShowId = showId.id,\n          title = item.title,\n          overview = item.overview,\n          language = language,\n          createdAt = nowMillis\n        )\n        localSource.episodesTranslations.insertSingle(dbItem)\n      }\n\n    remoteTranslations\n      .find { it.ids.trakt == episode.ids.trakt }\n      ?.let {\n        return Translation(it.title, it.overview, it.language)\n      }\n\n    return null\n  }\n\n  suspend fun loadTranslations(\n    season: Season,\n    showId: IdTrakt,\n    language: String = DEFAULT_LANGUAGE\n  ): List<SeasonTranslation> {\n    val episodes = season.episodes.toList()\n    val episodesIds = season.episodes.map { it.ids.trakt.id }\n\n    val local = localSource.episodesTranslations.getByIds(episodesIds, showId.id, language)\n    val hasAllTranslated = local.isNotEmpty() && local.all { it.title.isNotBlank() && it.overview.isNotBlank() }\n    val isCacheValid = local.isNotEmpty() && nowUtcMillis() - local.first().updatedAt < ConfigVariant.TRANSLATION_SYNC_EPISODE_COOLDOWN\n\n    if (hasAllTranslated || (!hasAllTranslated && isCacheValid)) {\n      return episodes.map { episode ->\n        val translation = local.find { it.idTrakt == episode.ids.trakt.id }\n        SeasonTranslation(\n          ids = episode.ids.copy(),\n          title = translation?.title ?: \"\",\n          overview = translation?.overview ?: \"\",\n          seasonNumber = season.number,\n          episodeNumber = episode.number,\n          language = language,\n          isLocal = true\n        )\n      }\n    }\n\n    val remoteTranslation = remoteSource.trakt.fetchSeasonTranslations(showId.id, season.number, language)\n      .map { mappers.translation.fromNetwork(it) }\n\n    remoteTranslation\n      .forEach { item ->\n        val dbItem = EpisodeTranslation.fromTraktId(\n          item.ids.trakt.id,\n          showId.id,\n          item.title,\n          language,\n          item.overview,\n          nowUtcMillis()\n        )\n        localSource.episodesTranslations.insertSingle(dbItem)\n      }\n\n    return episodes.map { episode ->\n      val translation = remoteTranslation.find { it.ids.trakt.id == episode.ids.trakt.id }\n      SeasonTranslation(\n        ids = episode.ids.copy(),\n        title = translation?.title ?: \"\",\n        overview = translation?.overview ?: \"\",\n        seasonNumber = season.number,\n        episodeNumber = episode.number,\n        language = language,\n        isLocal = true\n      )\n    }\n  }\n\n  private fun chineseLanguagePredicate(translation: TranslationRemote) =\n    if (translation.language?.lowercase() != \"zh\") {\n      true\n    } else {\n      translation.country?.equals(\"cn\", ignoreCase = true) == true\n    }\n\n  private fun frenchLanguagePredicate(translation: TranslationRemote) =\n    if (translation.language?.lowercase() != \"fr\") {\n      true\n    } else {\n      translation.country?.equals(\"fr\", ignoreCase = true) == true\n    }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/UserRedditManager.kt",
    "content": "@file:Suppress(\"EXPERIMENTAL_FEATURE_WARNING\")\n\npackage com.michaldrabik.repository\n\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.User\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass UserRedditManager @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n) {\n\n  companion object {\n    private const val TOKEN_EXPIRE_BUFFER_SECONDS = 60\n  }\n\n  private var redditToken: RedditAuthToken? = null\n  private var redditTokenTimestamp: Long = 0\n\n  suspend fun checkAuthorization(): RedditAuthToken {\n    if (redditToken != null && nowUtcMillis() < redditTokenTimestamp) {\n      return redditToken!!\n    }\n\n    val user = localSource.user.get()\n    user?.let {\n      if (nowUtcMillis() < it.redditTokenTimestamp) {\n        val authToken = RedditAuthToken(it.redditToken)\n        redditToken = authToken\n        redditTokenTimestamp = it.redditTokenTimestamp\n        return authToken\n      }\n    }\n\n    val authResponse = remoteSource.reddit.fetchAuthToken()\n    val resultToken = RedditAuthToken(authResponse.access_token)\n    val resultTokenTimestamp = nowUtcMillis() + TimeUnit.SECONDS.toMillis(authResponse.expires_in - TOKEN_EXPIRE_BUFFER_SECONDS)\n\n    transactions.withTransaction {\n      val userDb = localSource.user.get()\n      localSource.user.upsert(\n        userDb?.copy(\n          redditToken = resultToken.token,\n          redditTokenTimestamp = resultTokenTimestamp,\n        ) ?: User(\n          redditToken = resultToken.token,\n          redditTokenTimestamp = resultTokenTimestamp,\n          traktToken = \"\",\n          traktRefreshToken = \"\",\n          traktTokenTimestamp = 0,\n          traktUsername = \"\",\n          tvdbToken = \"\",\n          tvdbTokenTimestamp = 0,\n        )\n      )\n    }\n\n    redditToken = resultToken\n    redditTokenTimestamp = resultTokenTimestamp\n\n    return resultToken\n  }\n}\n\n@JvmInline\nvalue class RedditAuthToken(val token: String = \"\")\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/UserTraktManager.kt",
    "content": "package com.michaldrabik.repository\n\nimport com.michaldrabik.common.errors.ShowlyError\nimport com.michaldrabik.data_local.database.model.User\nimport com.michaldrabik.data_local.sources.UserLocalDataSource\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.token.TokenProvider\nimport com.michaldrabik.data_remote.trakt.TraktRemoteDataSource\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\nimport com.michaldrabik.data_remote.trakt.model.User as UserModel\n\n@Singleton\nclass UserTraktManager @Inject constructor(\n  private val remoteSource: TraktRemoteDataSource,\n  private val userLocalSource: UserLocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val tokenProvider: TokenProvider\n) {\n\n  fun isAuthorized() = tokenProvider.getToken() != null\n\n  fun checkAuthorization() {\n    if (tokenProvider.getToken() == null) {\n      throw ShowlyError.UnauthorizedError(\"Authorization needed.\")\n    }\n  }\n\n  suspend fun authorize(authCode: String) {\n    val tokens = remoteSource.fetchAuthTokens(authCode)\n    tokenProvider.saveTokens(tokens.access_token, tokens.refresh_token)\n    val user = remoteSource.fetchMyProfile()\n    saveUser(user)\n  }\n\n  suspend fun revokeToken() {\n    val token = tokenProvider.getToken()\n    tokenProvider.revokeToken()\n    try {\n      if (!token.isNullOrBlank()) {\n        remoteSource.revokeAuthTokens(token)\n      }\n    } catch (error: Throwable) {\n      // Just log error as revoke token call is fully optional.\n      Timber.w(\"Error while revoking token: $error\")\n    }\n  }\n\n  suspend fun getUsername() = userLocalSource.get()?.traktUsername ?: \"\"\n\n  private suspend fun saveUser(userModel: UserModel) {\n    transactions.withTransaction {\n      val user = userLocalSource.get()\n      userLocalSource.upsert(\n        user?.copy(\n          traktUsername = userModel.username\n        ) ?: User(\n          traktToken = \"\",\n          traktRefreshToken = \"\",\n          traktTokenTimestamp = 0,\n          traktUsername = userModel.username,\n          tvdbToken = \"\",\n          tvdbTokenTimestamp = 0,\n          redditToken = \"\",\n          redditTokenTimestamp = 0\n        )\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/images/EpisodeImagesProvider.kt",
    "content": "package com.michaldrabik.repository.images\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageFamily.EPISODE\nimport com.michaldrabik.ui_model.ImageSource\nimport com.michaldrabik.ui_model.ImageStatus.AVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNAVAILABLE\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.ImageType.FANART\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass EpisodeImagesProvider @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers\n) {\n\n  suspend fun loadRemoteImage(showId: IdTmdb, episode: Episode): Image = withContext(dispatchers.IO) {\n    val tvdbId = episode.ids.tvdb\n    val tmdbId = episode.ids.tmdb\n    val cachedImage = findCachedImage(episode, FANART)\n    if (cachedImage.status == AVAILABLE) {\n      return@withContext cachedImage\n    }\n\n    var image = Image.createUnavailable(FANART)\n    try {\n      var remoteImage = remoteSource.tmdb.fetchEpisodeImage(showId.id, episode.season, episode.number)\n      if (remoteImage == null && (episode.numberAbs ?: 0) > 0) {\n        // Try absolute episode number if present (may happen with certain Anime series)\n        remoteImage = remoteSource.tmdb.fetchEpisodeImage(showId.id, episode.season, episode.numberAbs)\n      }\n      image = when (remoteImage) {\n        null -> Image.createUnavailable(FANART)\n        else -> Image(\n          id = -1,\n          idTvdb = tvdbId,\n          idTmdb = tmdbId,\n          type = FANART,\n          family = EPISODE,\n          fileUrl = remoteImage.file_path,\n          thumbnailUrl = \"\",\n          status = AVAILABLE,\n          source = ImageSource.TMDB\n        )\n      }\n    } catch (error: Throwable) {\n      Timber.w(error)\n    }\n\n    when (image.status) {\n      UNAVAILABLE -> localSource.showImages.deleteByEpisodeId(tmdbId.id, image.type.key)\n      else -> localSource.showImages.insertEpisodeImage(mappers.image.toDatabaseShow(image))\n    }\n\n    return@withContext image\n  }\n\n  private suspend fun findCachedImage(episode: Episode, type: ImageType): Image {\n    val cachedImage = localSource.showImages.getByEpisodeId(episode.ids.tmdb.id, type.key)\n    return when (cachedImage) {\n      null -> Image.createUnknown(type, EPISODE)\n      else -> mappers.image.fromDatabase(cachedImage).copy(type = type)\n    }\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/images/MovieImagesProvider.kt",
    "content": "package com.michaldrabik.repository.images\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.CustomImage\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.data_remote.tmdb.model.TmdbImage\nimport com.michaldrabik.data_remote.tmdb.model.TmdbImages\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.IdTvdb\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageFamily\nimport com.michaldrabik.ui_model.ImageFamily.MOVIE\nimport com.michaldrabik.ui_model.ImageSource.CUSTOM\nimport com.michaldrabik.ui_model.ImageSource.TMDB\nimport com.michaldrabik.ui_model.ImageStatus.AVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNAVAILABLE\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.ImageType.FANART\nimport com.michaldrabik.ui_model.ImageType.FANART_WIDE\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport com.michaldrabik.ui_model.Movie\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass MovieImagesProvider @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n  private val settingsRepository: SettingsRepository\n) {\n\n  private val unavailableCache = mutableSetOf<IdTrakt>()\n\n  suspend fun findCustomImage(traktId: Long, type: ImageType): Image? =\n    withContext(dispatchers.IO) {\n      if (!settingsRepository.isPremium) {\n        return@withContext null\n      }\n      val custom = localSource.customImages.getById(traktId, Mode.MOVIES.type, type.key)\n      custom?.let { mappers.image.fromDatabase(it, type) }\n    }\n\n  suspend fun findCachedImage(movie: Movie, type: ImageType): Image =\n    withContext(dispatchers.IO) {\n      val custom = findCustomImage(movie.traktId, type)\n      if (custom != null) {\n        return@withContext custom\n      }\n\n      val image = localSource.movieImages.getByMovieId(movie.ids.tmdb.id, type.key)\n      when (image) {\n        null ->\n          if (unavailableCache.contains(movie.ids.trakt)) {\n            Image.createUnavailable(type, MOVIE, TMDB)\n          } else {\n            Image.createUnknown(type, MOVIE, TMDB)\n          }\n        else -> mappers.image.fromDatabase(image).copy(type = type)\n      }\n    }\n\n  suspend fun loadRemoteImage(movie: Movie, type: ImageType, force: Boolean = false): Image =\n    withContext(dispatchers.IO) {\n      val tmdbId = movie.ids.tmdb\n      val tvdbId = movie.ids.tvdb\n\n      val cachedImage = findCachedImage(movie, type)\n      if (cachedImage.status in arrayOf(AVAILABLE, UNAVAILABLE)) {\n        if (!force) return@withContext cachedImage\n        if (force && cachedImage.source == CUSTOM) return@withContext cachedImage\n      }\n\n      val images = remoteSource.tmdb.fetchMovieImages(tmdbId.id)\n      val typeImages = when (type) {\n        POSTER -> images.posters ?: emptyList()\n        FANART, FANART_WIDE -> images.backdrops ?: emptyList()\n        else -> throw Error(\"Invalid type\")\n      }\n\n      val remoteImage = findBestImage(typeImages, type)\n      val image = when (remoteImage) {\n        null -> Image.createUnavailable(type, MOVIE, TMDB)\n        else -> Image.createAvailable(movie.ids, type, MOVIE, remoteImage.file_path, TMDB)\n      }\n\n      when (image.status) {\n        UNAVAILABLE -> {\n          unavailableCache.add(movie.ids.trakt)\n          localSource.movieImages.deleteByMovieId(tmdbId.id, image.type.key)\n        }\n        else -> {\n          localSource.movieImages.insertMovieImage(mappers.image.toDatabaseMovie(image))\n          storeExtraImage(tmdbId, tvdbId, images, type)\n        }\n      }\n\n      image\n    }\n\n  private suspend fun storeExtraImage(\n    tmdbId: IdTmdb,\n    tvdbId: IdTvdb,\n    images: TmdbImages,\n    targetType: ImageType\n  ) {\n    val extraType = if (targetType == POSTER) FANART else POSTER\n    val typeImages = when (extraType) {\n      POSTER -> images.posters ?: emptyList()\n      FANART, FANART_WIDE -> images.backdrops ?: emptyList()\n      else -> throw Error(\"Invalid type\")\n    }\n    findBestImage(typeImages, extraType)?.let {\n      val extraImage = Image(-1, tvdbId, tmdbId, extraType, MOVIE, it.file_path, \"\", AVAILABLE, TMDB)\n      localSource.movieImages.insertMovieImage(mappers.image.toDatabaseMovie(extraImage))\n    }\n  }\n\n  suspend fun loadRemoteImages(movie: Movie, type: ImageType): List<Image> =\n    withContext(dispatchers.IO) {\n      val tmdbId = movie.ids.tmdb\n      val remoteImages = remoteSource.tmdb.fetchMovieImages(tmdbId.id)\n      val typeImages = when (type) {\n        POSTER -> remoteImages.posters ?: emptyList()\n        FANART, FANART_WIDE -> remoteImages.backdrops ?: emptyList()\n        else -> throw Error(\"Invalid type\")\n      }\n      typeImages.map {\n        Image.createAvailable(movie.ids, type, MOVIE, it.file_path, TMDB)\n      }\n    }\n\n  private fun findBestImage(images: List<TmdbImage>, type: ImageType) =\n    images\n      .filter { if (type == POSTER) it.isEnglish() else it.isPlain() }\n      .sortedWith(compareBy({ it.vote_count }, { it.vote_average }))\n      .lastOrNull()\n      ?: images.firstOrNull { if (type == POSTER) it.isEnglish() else it.isPlain() }\n      ?: images.firstOrNull()\n\n  suspend fun saveCustomImage(traktId: IdTrakt, image: Image, imageFamily: ImageFamily, imageType: ImageType) {\n    withContext(dispatchers.IO) {\n      val imageDb = CustomImage(0, traktId.id, imageFamily.key, imageType.key, image.fullFileUrl)\n      localSource.customImages.insertImage(imageDb)\n    }\n  }\n\n  suspend fun deleteCustomImage(traktId: IdTrakt, imageFamily: ImageFamily, imageType: ImageType) {\n    withContext(dispatchers.IO) {\n      localSource.customImages.deleteById(traktId.id, imageFamily.key, imageType.key)\n    }\n  }\n\n  suspend fun deleteLocalCache() = withContext(dispatchers.IO) {\n    localSource.movieImages.deleteAll()\n  }\n\n  fun clear() = unavailableCache.clear()\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/images/PeopleImagesProvider.kt",
    "content": "package com.michaldrabik.repository.images\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.PersonImage\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageFamily\nimport com.michaldrabik.ui_model.ImageSource\nimport com.michaldrabik.ui_model.ImageType\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass PeopleImagesProvider @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val localSource: LocalDataSource,\n  private val remoteSource: RemoteDataSource,\n) {\n\n  suspend fun loadCachedImage(personTmdbId: IdTmdb): Image? = withContext(dispatchers.IO) {\n    val localPerson = localSource.people.getById(personTmdbId.id)\n    return@withContext localPerson?.image?.let {\n      Image.createAvailable(\n        ids = Ids.EMPTY,\n        type = ImageType.PROFILE,\n        family = ImageFamily.PROFILE,\n        path = it,\n        source = ImageSource.TMDB\n      )\n    }\n  }\n\n  suspend fun loadImages(personTmdbId: IdTmdb): List<Image> = withContext(dispatchers.IO) {\n    val localTimestamp = localSource.peopleImages.getTimestampForPerson(personTmdbId.id) ?: 0\n    if (localTimestamp + Config.PEOPLE_IMAGES_CACHE_DURATION > nowUtcMillis()) {\n      Timber.d(\"Returning cached result. Cache still valid for ${(localTimestamp + Config.PEOPLE_IMAGES_CACHE_DURATION) - nowUtcMillis()} ms\")\n      val local = localSource.peopleImages.getAll(personTmdbId.id)\n      return@withContext local.map {\n        Image.createAvailable(\n          ids = Ids.EMPTY,\n          type = ImageType.PROFILE,\n          family = ImageFamily.PROFILE,\n          path = it.filePath,\n          source = ImageSource.TMDB\n        )\n      }\n    }\n\n    val images = (remoteSource.tmdb.fetchPersonImages(personTmdbId.id).profiles ?: emptyList())\n      .filter { it.file_path.isNotBlank() }\n    val dbImages = images.map {\n      PersonImage(\n        id = 0,\n        idTmdb = personTmdbId.id,\n        filePath = it.file_path,\n        createdAt = nowUtc(),\n        updatedAt = nowUtc()\n      )\n    }\n\n    localSource.peopleImages.insertSingle(personTmdbId.id, dbImages)\n\n    return@withContext images.map {\n      Image.createAvailable(\n        ids = Ids.EMPTY,\n        type = ImageType.PROFILE,\n        family = ImageFamily.PROFILE,\n        path = it.file_path,\n        source = ImageSource.TMDB\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/images/ShowImagesProvider.kt",
    "content": "package com.michaldrabik.repository.images\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.CustomImage\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.data_remote.aws.model.AwsImages\nimport com.michaldrabik.data_remote.tmdb.model.TmdbImage\nimport com.michaldrabik.data_remote.tmdb.model.TmdbImages\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.IdTvdb\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageFamily\nimport com.michaldrabik.ui_model.ImageFamily.SHOW\nimport com.michaldrabik.ui_model.ImageSource.AWS\nimport com.michaldrabik.ui_model.ImageSource.CUSTOM\nimport com.michaldrabik.ui_model.ImageSource.TMDB\nimport com.michaldrabik.ui_model.ImageStatus.AVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNAVAILABLE\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.ImageType.FANART\nimport com.michaldrabik.ui_model.ImageType.FANART_WIDE\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport com.michaldrabik.ui_model.Show\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass ShowImagesProvider @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  private val unavailableCache = mutableSetOf<IdTrakt>()\n  private var awsImagesCache: AwsImages? = null\n\n  suspend fun findCustomImage(traktId: Long, type: ImageType): Image? =\n    withContext(dispatchers.IO) {\n      if (!settingsRepository.isPremium) {\n        return@withContext null\n      }\n      val custom = localSource.customImages.getById(traktId, Mode.SHOWS.type, type.key)\n      custom?.let { mappers.image.fromDatabase(it, type) }\n    }\n\n  suspend fun findCachedImage(show: Show, type: ImageType): Image =\n    withContext(dispatchers.IO) {\n      val custom = findCustomImage(show.traktId, type)\n      if (custom != null) {\n        return@withContext custom\n      }\n      val image = localSource.showImages.getByShowId(show.ids.tmdb.id, type.key)\n      when (image) {\n        null ->\n          if (unavailableCache.contains(show.ids.trakt)) {\n            Image.createUnavailable(type, SHOW)\n          } else {\n            Image.createUnknown(type, SHOW)\n          }\n        else -> mappers.image.fromDatabase(image).copy(type = type)\n      }\n    }\n\n  suspend fun loadRemoteImage(show: Show, type: ImageType, force: Boolean = false): Image =\n    withContext(dispatchers.IO) {\n      val tvdbId = show.ids.tvdb\n      val tmdbId = show.ids.tmdb\n\n      val cachedImage = findCachedImage(show, type)\n      if (cachedImage.status in arrayOf(AVAILABLE, UNAVAILABLE)) {\n        if (!force || cachedImage.source == CUSTOM) {\n          return@withContext cachedImage\n        }\n      }\n\n      var source = TMDB\n      val images = remoteSource.tmdb.fetchShowImages(tmdbId.id)\n\n      var typeImages = when (type) {\n        POSTER -> images.posters ?: emptyList()\n        FANART, FANART_WIDE -> images.backdrops ?: emptyList()\n        else -> throw Error(\"Invalid type\")\n      }\n      // If requested poster is unavailable try backing up to a fanart\n      if (typeImages.isEmpty() && type == POSTER) {\n        typeImages = images.backdrops ?: emptyList()\n        if (typeImages.isEmpty()) {\n          // Use custom uploaded S3 image as a final backup\n          loadAwsImagesCache()\n          awsImagesCache?.posters?.find { poster -> poster.idTmdb == tmdbId.id }?.let {\n            val path = \"posters/${it.idTmdb}.${it.fileType}\"\n            typeImages = listOf(TmdbImage(path, 0F, 0, \"en\"))\n            source = AWS\n          }\n        }\n      }\n\n      // Use custom uploaded S3 image as a first backup for fanart.\n      if (typeImages.isEmpty() && type in arrayOf(FANART, FANART_WIDE)) {\n        loadAwsImagesCache()\n        val awsImage = awsImagesCache?.fanarts?.find { fanart -> fanart.idTmdb == tmdbId.id }\n        if (awsImage != null) {\n          val path = \"fanarts/${awsImage.idTmdb}.${awsImage.fileType}\"\n          typeImages = listOf(TmdbImage(path, 0F, 0, \"en\"))\n          source = AWS\n        } else {\n          // If requested fanart is unavailable try backing up to an episode image\n          val seasons = remoteSource.trakt.fetchSeasons(show.traktId)\n          if (seasons.isNotEmpty()) {\n            val episode = seasons[0].episodes?.firstOrNull()\n            episode?.let { ep ->\n              runCatching {\n                val backupImage = remoteSource.tmdb.fetchEpisodeImage(tmdbId.id, ep.season, ep.number)\n                backupImage?.let {\n                  typeImages = listOf(TmdbImage(it.file_path, 0F, 0, \"en\"))\n                }\n              }\n            }\n          }\n        }\n      }\n\n      val remoteImage = findBestImage(typeImages, type)\n      val image = when (remoteImage) {\n        null -> Image.createUnavailable(type)\n        else -> Image.createAvailable(show.ids, type, SHOW, remoteImage.file_path, source)\n      }\n\n      when (image.status) {\n        UNAVAILABLE -> {\n          unavailableCache.add(show.ids.trakt)\n          localSource.showImages.deleteByShowId(tmdbId.id, image.type.key)\n        }\n        else -> {\n          localSource.showImages.insertShowImage(mappers.image.toDatabaseShow(image))\n          saveExtraImage(tmdbId, tvdbId, images, type)\n        }\n      }\n\n      image\n    }\n\n  private suspend fun saveExtraImage(\n    tmdbId: IdTmdb,\n    tvdbId: IdTvdb,\n    images: TmdbImages,\n    targetType: ImageType,\n  ) {\n    val extraType = if (targetType == POSTER) FANART else POSTER\n    val typeImages = when (extraType) {\n      POSTER -> images.posters ?: emptyList()\n      FANART, FANART_WIDE -> images.backdrops ?: emptyList()\n      else -> throw Error(\"Invalid type\")\n    }\n    findBestImage(typeImages, extraType)?.let {\n      val extraImage = Image(-1, tvdbId, tmdbId, extraType, SHOW, it.file_path, \"\", AVAILABLE, TMDB)\n      localSource.showImages.insertShowImage(mappers.image.toDatabaseShow(extraImage))\n    }\n  }\n\n  suspend fun loadRemoteImages(show: Show, type: ImageType): List<Image> =\n    withContext(dispatchers.IO) {\n      val tmdbId = show.ids.tmdb\n      val remoteImages = remoteSource.tmdb.fetchShowImages(tmdbId.id)\n      val typeImages = when (type) {\n        POSTER -> remoteImages.posters ?: emptyList()\n        FANART, FANART_WIDE -> remoteImages.backdrops ?: emptyList()\n        else -> throw Error(\"Invalid type\")\n      }\n      typeImages\n        .map {\n          Image.createAvailable(show.ids, type, SHOW, it.file_path, TMDB)\n        }\n    }\n\n  private suspend fun loadAwsImagesCache() {\n    if (awsImagesCache == null) {\n      val awsImages = remoteSource.aws.fetchImagesList()\n      awsImagesCache = awsImages.copy()\n    }\n  }\n\n  private fun findBestImage(images: List<TmdbImage>, type: ImageType) =\n    images\n      .filter { if (type == POSTER) it.isEnglish() else it.isPlain() }\n      .sortedWith(compareBy({ it.vote_count }, { it.vote_average }))\n      .lastOrNull()\n      ?: images.firstOrNull { if (type == POSTER) it.isEnglish() else it.isPlain() }\n      ?: images.firstOrNull()\n\n  suspend fun saveCustomImage(traktId: IdTrakt, image: Image, imageFamily: ImageFamily, imageType: ImageType) {\n    val imageDb = CustomImage(0, traktId.id, imageFamily.key, imageType.key, image.fullFileUrl)\n    withContext(dispatchers.IO) {\n      localSource.customImages.insertImage(imageDb)\n    }\n  }\n\n  suspend fun deleteCustomImage(traktId: IdTrakt, imageFamily: ImageFamily, imageType: ImageType) {\n    withContext(dispatchers.IO) {\n      localSource.customImages.deleteById(traktId.id, imageFamily.key, imageType.key)\n    }\n  }\n\n  suspend fun deleteLocalCache() = withContext(dispatchers.IO) {\n    localSource.showImages.deleteAll()\n  }\n\n  fun clear() = unavailableCache.clear()\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/CollectionMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.MovieCollection\nimport java.time.ZonedDateTime\nimport javax.inject.Inject\nimport com.michaldrabik.data_local.database.model.MovieCollection as MovieCollectionEntity\nimport com.michaldrabik.data_remote.trakt.model.MovieCollection as MovieCollectionNetwork\n\nclass CollectionMapper @Inject constructor() {\n\n  fun fromNetwork(input: MovieCollectionNetwork): MovieCollection {\n    return MovieCollection(\n      id = IdTrakt(input.ids.trakt!!),\n      name = input.name,\n      description = input.description,\n      itemCount = input.item_count\n    )\n  }\n\n  fun fromEntity(input: MovieCollectionEntity): MovieCollection {\n    return MovieCollection(\n      id = IdTrakt(input.idTrakt),\n      name = input.name,\n      description = input.description,\n      itemCount = input.itemCount\n    )\n  }\n\n  fun toEntity(\n    movieId: Long,\n    input: MovieCollection,\n    updatedAt: ZonedDateTime = nowUtc(),\n    createdAt: ZonedDateTime = nowUtc(),\n  ): MovieCollectionEntity {\n    return MovieCollectionEntity(\n      idTrakt = input.id.id,\n      idTraktMovie = movieId,\n      name = input.name,\n      description = input.description,\n      itemCount = input.itemCount,\n      updatedAt = updatedAt,\n      createdAt = createdAt\n    )\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/CommentMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.ui_model.Comment\nimport com.michaldrabik.ui_model.User\nimport java.time.ZonedDateTime\nimport javax.inject.Inject\nimport com.michaldrabik.data_remote.trakt.model.Comment as CommentNetwork\n\nclass CommentMapper @Inject constructor() {\n\n  fun fromNetwork(comment: CommentNetwork?) =\n    Comment(\n      id = comment?.id ?: -1,\n      parentId = comment?.parent_id ?: -1,\n      comment = comment?.comment ?: \"\",\n      userRating = comment?.user_rating ?: -1,\n      spoiler = comment?.spoiler ?: false,\n      review = comment?.review ?: false,\n      likes = comment?.likes ?: 0,\n      replies = comment?.replies ?: 0,\n      createdAt = if (comment?.created_at.isNullOrBlank()) null else ZonedDateTime.parse(comment?.created_at),\n      updatedAt = if (comment?.updated_at.isNullOrBlank()) null else ZonedDateTime.parse(comment?.updated_at),\n      user = User(\n        username = comment?.user?.username ?: \"\",\n        avatarUrl = comment?.user?.images?.avatar?.full ?: \"\"\n      ),\n      isMe = false,\n      isSignedIn = false,\n      isLoading = false,\n      hasRepliesLoaded = false\n    )\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/CustomListMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.ui_model.CustomList\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport java.time.Instant\nimport java.time.ZoneId\nimport java.time.ZonedDateTime\nimport java.time.format.DateTimeFormatter\nimport javax.inject.Inject\nimport com.michaldrabik.data_local.database.model.CustomList as CustomListDb\nimport com.michaldrabik.data_remote.trakt.model.CustomList as CustomListNetwork\nimport com.michaldrabik.data_remote.trakt.model.CustomList.Ids as IdsList\n\nclass CustomListMapper @Inject constructor() {\n\n  fun fromNetwork(list: CustomListNetwork) = CustomList(\n    id = 0,\n    idTrakt = list.ids.trakt,\n    idSlug = list.ids.slug,\n    name = list.name,\n    description = list.description,\n    privacy = list.privacy,\n    displayNumbers = list.display_numbers,\n    allowComments = list.allow_comments,\n    sortBy = SortOrder.fromSlug(list.sort_by) ?: SortOrder.RANK,\n    sortHow = SortType.fromSlug(list.sort_how),\n    sortByLocal = SortOrder.RANK,\n    sortHowLocal = SortType.ASCENDING,\n    filterTypeLocal = Mode.getAll(),\n    itemCount = list.item_count,\n    commentCount = list.comment_count,\n    likes = list.likes,\n    createdAt = ZonedDateTime.parse(list.created_at),\n    updatedAt = ZonedDateTime.parse(list.updated_at)\n  )\n\n  fun fromDatabase(list: CustomListDb) = CustomList(\n    id = list.id,\n    idTrakt = list.idTrakt,\n    idSlug = list.idSlug,\n    name = list.name,\n    description = list.description,\n    privacy = list.privacy,\n    displayNumbers = list.displayNumbers,\n    allowComments = list.allowComments,\n    sortBy = SortOrder.fromSlug(list.sortBy) ?: SortOrder.RANK,\n    sortHow = SortType.fromSlug(list.sortHow),\n    sortByLocal = SortOrder.fromSlug(list.sortByLocal) ?: SortOrder.RANK,\n    sortHowLocal = SortType.fromSlug(list.sortHowLocal),\n    filterTypeLocal = when {\n      list.filterTypeLocal.isEmpty() -> emptyList()\n      else -> list.filterTypeLocal.split(\",\").map { Mode.fromType(it) }\n    },\n    itemCount = list.itemCount,\n    commentCount = list.commentCount,\n    likes = list.likes,\n    createdAt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(list.createdAt), ZoneId.of(\"UTC\")),\n    updatedAt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(list.updatedAt), ZoneId.of(\"UTC\"))\n  )\n\n  fun toDatabase(list: CustomList) = CustomListDb(\n    id = list.id,\n    idTrakt = list.idTrakt,\n    idSlug = list.idSlug,\n    name = list.name,\n    description = list.description,\n    privacy = list.privacy,\n    displayNumbers = list.displayNumbers,\n    allowComments = list.allowComments,\n    sortBy = list.sortBy.slug,\n    sortHow = list.sortHow.slug,\n    sortByLocal = list.sortByLocal.slug,\n    sortHowLocal = list.sortHowLocal.slug,\n    filterTypeLocal = list.filterTypeLocal.joinToString(\",\") { it.type },\n    itemCount = list.itemCount,\n    commentCount = list.commentCount,\n    likes = list.likes,\n    createdAt = list.createdAt.toMillis(),\n    updatedAt = list.updatedAt.toMillis()\n  )\n\n  fun toNetwork(list: CustomList) = CustomListNetwork(\n    ids = IdsList(\n      trakt = list.idTrakt ?: -1,\n      slug = list.idSlug\n    ),\n    name = list.name,\n    description = list.description,\n    privacy = list.privacy,\n    display_numbers = list.displayNumbers,\n    allow_comments = list.allowComments,\n    sort_by = list.sortBy.slug,\n    sort_how = list.sortHow.slug,\n    item_count = list.itemCount,\n    comment_count = list.commentCount,\n    likes = list.likes,\n    created_at = list.createdAt.format(DateTimeFormatter.ISO_INSTANT),\n    updated_at = list.updatedAt.format(DateTimeFormatter.ISO_INSTANT)\n  )\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/EpisodeMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.common.extensions.toZonedDateTime\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.IdImdb\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.IdTvdb\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Season\nimport java.time.ZonedDateTime\nimport javax.inject.Inject\nimport com.michaldrabik.data_local.database.model.Episode as EpisodeDb\nimport com.michaldrabik.data_remote.trakt.model.Episode as EpisodeNetwork\n\nclass EpisodeMapper @Inject constructor(\n  private val idsMapper: IdsMapper\n) {\n\n  fun fromNetwork(episode: EpisodeNetwork) = Episode(\n    season = episode.season ?: -1,\n    number = episode.number ?: -1,\n    title = episode.title ?: \"\",\n    ids = idsMapper.fromNetwork(episode.ids),\n    overview = episode.overview ?: \"\",\n    rating = episode.rating ?: 0F,\n    votes = episode.votes ?: 0,\n    commentCount = episode.comment_count ?: 0,\n    firstAired = episode.first_aired.toZonedDateTime(),\n    runtime = episode.runtime ?: -1,\n    numberAbs = episode.number_abs,\n    lastWatchedAt = episode.last_watched_at.toZonedDateTime()\n  )\n\n  fun toNetwork(episode: Episode) = EpisodeNetwork(\n    ids = idsMapper.toNetwork(episode.ids),\n    season = episode.season,\n    number = episode.number,\n    number_abs = episode.numberAbs,\n    title = episode.title,\n    overview = episode.overview,\n    rating = episode.rating,\n    votes = episode.votes,\n    comment_count = episode.commentCount,\n    first_aired = episode.firstAired.toString(),\n    runtime = episode.runtime,\n    last_watched_at = episode.lastWatchedAt.toString()\n  )\n\n  fun toDatabase(\n    episode: Episode,\n    season: Season,\n    showId: IdTrakt,\n    isWatched: Boolean,\n    lastWatchedAt: ZonedDateTime?\n  ): EpisodeDb = EpisodeDb(\n    idTrakt = episode.ids.trakt.id,\n    idSeason = season.ids.trakt.id,\n    idShowTrakt = showId.id,\n    idShowTvdb = episode.ids.tvdb.id,\n    idShowImdb = episode.ids.imdb.id,\n    idShowTmdb = episode.ids.tmdb.id,\n    seasonNumber = season.number,\n    episodeNumber = episode.number,\n    episodeNumberAbs = episode.numberAbs,\n    episodeOverview = episode.overview,\n    title = episode.title,\n    firstAired = episode.firstAired,\n    commentsCount = episode.commentCount,\n    rating = episode.rating,\n    runtime = episode.runtime,\n    votesCount = episode.votes,\n    isWatched = isWatched,\n    lastWatchedAt = lastWatchedAt\n  )\n\n  fun fromDatabase(episodeDb: EpisodeDb) =\n    Episode(\n      ids = Ids.EMPTY.copy(\n        trakt = IdTrakt(episodeDb.idTrakt),\n        tvdb = IdTvdb(episodeDb.idShowTvdb),\n        imdb = IdImdb(episodeDb.idShowImdb),\n        tmdb = IdTmdb(episodeDb.idShowTmdb)\n      ),\n      title = episodeDb.title,\n      number = episodeDb.episodeNumber,\n      numberAbs = episodeDb.episodeNumberAbs,\n      season = episodeDb.seasonNumber,\n      overview = episodeDb.episodeOverview,\n      commentCount = episodeDb.commentsCount,\n      firstAired = episodeDb.firstAired,\n      rating = episodeDb.rating,\n      runtime = episodeDb.runtime,\n      votes = episodeDb.votesCount,\n      lastWatchedAt = episodeDb.lastWatchedAt\n    )\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/IdsMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.data_local.database.model.Movie\nimport com.michaldrabik.ui_model.IdImdb\nimport com.michaldrabik.ui_model.IdSlug\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.IdTvRage\nimport com.michaldrabik.ui_model.IdTvdb\nimport com.michaldrabik.ui_model.Ids\nimport javax.inject.Inject\nimport com.michaldrabik.data_local.database.model.Show as ShowDb\nimport com.michaldrabik.data_remote.trakt.model.Ids as IdsNetwork\n\nclass IdsMapper @Inject constructor() {\n\n  fun fromNetwork(ids: IdsNetwork?) =\n    Ids(\n      IdTrakt(ids?.trakt ?: -1),\n      IdSlug(ids?.slug ?: \"\"),\n      IdTvdb(ids?.tvdb ?: -1),\n      IdImdb(ids?.imdb ?: \"\"),\n      IdTmdb(ids?.tmdb ?: -1),\n      IdTvRage(ids?.tvrage ?: -1)\n    )\n\n  fun toNetwork(ids: Ids?) =\n    IdsNetwork(\n      trakt = ids?.trakt?.id,\n      slug = ids?.slug?.id,\n      tvdb = ids?.tvdb?.id,\n      imdb = ids?.imdb?.id,\n      tmdb = ids?.tmdb?.id,\n      tvrage = ids?.tvrage?.id\n    )\n\n  fun fromDatabase(show: ShowDb?) = Ids(\n    IdTrakt(show?.idTrakt ?: -1),\n    IdSlug(show?.idSlug ?: \"\"),\n    IdTvdb(show?.idTvdb ?: -1),\n    IdImdb(show?.idImdb ?: \"\"),\n    IdTmdb(show?.idTmdb ?: -1),\n    IdTvRage(show?.idTvrage ?: -1)\n  )\n\n  fun fromDatabase(movie: Movie?) = Ids(\n    IdTrakt(movie?.idTrakt ?: -1),\n    IdSlug(movie?.idSlug ?: \"\"),\n    IdTvdb(-1),\n    IdImdb(movie?.idImdb ?: \"\"),\n    IdTmdb(movie?.idTmdb ?: -1),\n    IdTvRage(-1)\n  )\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/ImageMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.data_local.database.model.CustomImage\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.IdTvdb\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageFamily\nimport com.michaldrabik.ui_model.ImageSource\nimport com.michaldrabik.ui_model.ImageStatus.AVAILABLE\nimport com.michaldrabik.ui_model.ImageType\nimport java.util.Locale.ROOT\nimport javax.inject.Inject\nimport com.michaldrabik.data_local.database.model.MovieImage as MovieImageDb\nimport com.michaldrabik.data_local.database.model.ShowImage as ShowImageDb\n\nclass ImageMapper @Inject constructor() {\n\n  fun fromDatabase(imageDb: ShowImageDb): Image {\n    return Image(\n      imageDb.id,\n      IdTvdb(imageDb.idTvdb),\n      IdTmdb(imageDb.idTmdb),\n      enumValueOf(imageDb.type.uppercase(ROOT)),\n      enumValueOf(imageDb.family.uppercase(ROOT)),\n      imageDb.fileUrl,\n      imageDb.thumbnailUrl,\n      AVAILABLE,\n      ImageSource.fromKey(imageDb.source)\n    )\n  }\n\n  fun fromDatabase(imageDb: MovieImageDb): Image {\n    return Image(\n      imageDb.id,\n      IdTvdb(),\n      IdTmdb(imageDb.idTmdb),\n      enumValueOf(imageDb.type.uppercase(ROOT)),\n      ImageFamily.MOVIE,\n      imageDb.fileUrl,\n      \"\",\n      AVAILABLE,\n      ImageSource.fromKey(imageDb.source)\n    )\n  }\n\n  fun fromDatabase(imageDb: CustomImage, type: ImageType?): Image {\n    return Image(\n      imageDb.id,\n      IdTvdb(),\n      IdTmdb(),\n      type ?: enumValueOf(imageDb.type.uppercase(ROOT)),\n      enumValueOf(imageDb.family.uppercase(ROOT)),\n      imageDb.fileUrl,\n      imageDb.fileUrl,\n      AVAILABLE,\n      ImageSource.CUSTOM\n    )\n  }\n\n  fun toDatabaseShow(image: Image): ShowImageDb =\n    ShowImageDb(\n      idTvdb = image.idTvdb.id,\n      idTmdb = image.idTmdb.id,\n      type = image.type.key,\n      family = image.family.key,\n      fileUrl = image.fileUrl,\n      thumbnailUrl = image.thumbnailUrl,\n      source = image.source.key\n    )\n\n  fun toDatabaseMovie(image: Image): MovieImageDb =\n    MovieImageDb(\n      idTmdb = image.idTmdb.id,\n      type = image.type.key,\n      fileUrl = image.fileUrl,\n      source = image.source.key\n    )\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/Mappers.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass Mappers @Inject constructor(\n  val ids: IdsMapper,\n  val image: ImageMapper,\n  val show: ShowMapper,\n  val movie: MovieMapper,\n  val episode: EpisodeMapper,\n  val season: SeasonMapper,\n  val person: PersonMapper,\n  val comment: CommentMapper,\n  val news: NewsMapper,\n  val settings: SettingsMapper,\n  val translation: TranslationMapper,\n  val customList: CustomListMapper,\n  val ratings: RatingsMapper,\n  val userRatings: UserRatingsMapper,\n  val streamings: StreamingsMapper,\n)\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/MovieMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.MovieStatus\nimport java.time.LocalDate\nimport javax.inject.Inject\nimport com.michaldrabik.data_local.database.model.Movie as MovieDb\nimport com.michaldrabik.data_remote.trakt.model.Movie as MovieNetwork\n\nclass MovieMapper @Inject constructor(\n  private val idsMapper: IdsMapper\n) {\n\n  fun fromNetwork(movie: MovieNetwork) = Movie(\n    idsMapper.fromNetwork(movie.ids),\n    movie.title ?: \"\",\n    movie.year ?: -1,\n    movie.overview ?: \"\",\n    movie.released?.let { if (it.isNotBlank()) LocalDate.parse(it) else null },\n    movie.runtime ?: -1,\n    movie.country ?: \"\",\n    movie.trailer ?: \"\",\n    movie.homepage ?: \"\",\n    movie.language ?: \"\",\n    MovieStatus.fromKey(movie.status),\n    movie.rating ?: -1F,\n    movie.votes ?: -1,\n    movie.comment_count ?: -1,\n    movie.genres ?: emptyList(),\n    nowUtcMillis(),\n    nowUtcMillis()\n  )\n\n  fun toNetwork(movie: Movie) = MovieNetwork(\n    idsMapper.toNetwork(movie.ids),\n    movie.title,\n    movie.year,\n    movie.overview,\n    movie.released?.toString(),\n    movie.runtime,\n    movie.country,\n    movie.trailer,\n    movie.homepage,\n    movie.status.key,\n    movie.rating,\n    movie.votes,\n    movie.commentCount,\n    movie.genres,\n    movie.language\n  )\n\n  fun fromDatabase(movie: MovieDb) = Movie(\n    idsMapper.fromDatabase(movie),\n    movie.title,\n    movie.year,\n    movie.overview,\n    if (movie.released.isBlank()) null else LocalDate.parse(movie.released),\n    movie.runtime,\n    movie.country,\n    movie.trailer,\n    movie.homepage,\n    movie.language,\n    MovieStatus.fromKey(movie.status),\n    movie.rating,\n    movie.votes,\n    movie.commentCount,\n    movie.genres.split(\",\"),\n    movie.updatedAt,\n    movie.createdAt\n  )\n\n  fun toDatabase(movie: Movie) = MovieDb(\n    movie.ids.trakt.id,\n    movie.ids.tmdb.id,\n    movie.ids.imdb.id,\n    movie.ids.slug.id,\n    movie.title,\n    movie.year,\n    movie.overview,\n    movie.released?.toString() ?: \"\",\n    movie.runtime,\n    movie.country,\n    movie.trailer,\n    movie.language,\n    movie.homepage,\n    movie.status.key,\n    movie.rating,\n    movie.votes,\n    movie.commentCount,\n    movie.genres.joinToString(\",\"),\n    nowUtcMillis(),\n    movie.createdAt\n  )\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/NewsMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.common.extensions.dateFromMillis\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.data_remote.reddit.model.RedditItem\nimport com.michaldrabik.ui_model.NewsItem\nimport javax.inject.Inject\nimport com.michaldrabik.data_local.database.model.News as NewsDb\n\nclass NewsMapper @Inject constructor() {\n\n  fun fromNetwork(input: RedditItem, type: NewsItem.Type) = NewsItem(\n    id = input.id,\n    title = input.title,\n    url = input.url,\n    type = type,\n    score = input.score,\n    image = input.findImageUrl()?.replace(\"&amp;\", \"&\"),\n    datedAt = dateFromMillis(input.created_utc * 1000),\n    createdAt = nowUtc(),\n    updatedAt = nowUtc(),\n  )\n\n  fun fromDatabase(input: NewsDb) = NewsItem(\n    id = input.idNews,\n    title = input.title,\n    url = input.url,\n    type = NewsItem.Type.fromSlug(input.type),\n    score = input.score,\n    image = input.image,\n    datedAt = dateFromMillis(input.datedAt),\n    createdAt = dateFromMillis(input.createdAt),\n    updatedAt = dateFromMillis(input.updatedAt),\n  )\n\n  fun toDatabase(input: NewsItem) = NewsDb(\n    id = 0,\n    idNews = input.id,\n    title = input.title,\n    url = input.url,\n    type = input.type.slug,\n    image = input.image,\n    score = input.score,\n    datedAt = input.datedAt.toMillis(),\n    createdAt = nowUtcMillis(),\n    updatedAt = nowUtcMillis()\n  )\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/PersonMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.data_remote.tmdb.model.TmdbPerson\nimport com.michaldrabik.ui_model.IdImdb\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Person\nimport java.time.LocalDate\nimport java.time.ZonedDateTime\nimport java.time.format.DateTimeFormatter.ISO_LOCAL_DATE\nimport javax.inject.Inject\nimport com.michaldrabik.data_local.database.model.Person as PersonDb\n\nclass PersonMapper @Inject constructor() {\n\n  fun fromNetwork(person: TmdbPerson) =\n    Person(\n      ids = Ids.EMPTY.copy(\n        tmdb = IdTmdb(person.id),\n        imdb = IdImdb(person.imdb_id ?: \"\")\n      ),\n      name = person.name ?: \"\",\n      department = typeToEnum(person.department ?: person.known_for_department),\n      bio = person.biography,\n      bioTranslation = null,\n      birthplace = person.place_of_birth,\n      imagePath = person.profile_path,\n      homepage = person.homepage,\n      characters = extractCharacters(person),\n      jobs = extractJobs(person),\n      episodesCount = person.total_episode_count ?: 0,\n      birthday = person.birthday?.let { if (it.isNotBlank()) LocalDate.parse(it) else null },\n      deathday = person.deathday?.let { if (it.isNotBlank()) LocalDate.parse(it) else null }\n    )\n\n  fun fromDatabase(personDb: PersonDb, characters: List<String> = emptyList()) =\n    Person(\n      ids = Ids.EMPTY.copy(\n        trakt = IdTrakt(personDb.idTrakt ?: -1),\n        tmdb = IdTmdb(personDb.idTmdb),\n        imdb = IdImdb(personDb.idImdb ?: \"\")\n      ),\n      name = personDb.name,\n      department = typeToEnum(personDb.department),\n      bio = personDb.biography,\n      bioTranslation = personDb.biographyTranslation,\n      characters = if (characters.isNotEmpty()) characters else personDb.character?.split(\",\") ?: emptyList(),\n      jobs = personDb.job?.split(\",\")?.map { Person.Job.fromSlug(it) } ?: emptyList(),\n      episodesCount = personDb.episodesCount ?: 0,\n      birthplace = personDb.birthplace,\n      imagePath = personDb.image,\n      homepage = personDb.homepage,\n      birthday = personDb.birthday?.let { if (it.isNotBlank()) LocalDate.parse(it) else null },\n      deathday = personDb.deathday?.let { if (it.isNotBlank()) LocalDate.parse(it) else null }\n    )\n\n  fun toDatabase(person: Person, detailsTimestamp: ZonedDateTime?): PersonDb {\n    val idTrakt = if (person.ids.trakt.id != -1L) person.ids.trakt.id else null\n    val idImdb = if (person.ids.imdb.id.isNotBlank()) person.ids.imdb.id else null\n    return PersonDb(\n      idTmdb = person.ids.tmdb.id,\n      idTrakt = idTrakt,\n      idImdb = idImdb,\n      name = person.name,\n      department = person.department.slug,\n      biography = person.bio,\n      biographyTranslation = person.bioTranslation,\n      character = person.characters.joinToString(\",\"),\n      job = person.jobs.joinToString(\",\") { it.slug },\n      episodesCount = person.episodesCount,\n      birthday = person.birthday?.format(ISO_LOCAL_DATE),\n      birthplace = person.birthplace,\n      deathday = person.deathday?.format(ISO_LOCAL_DATE),\n      image = person.imagePath,\n      homepage = person.homepage,\n      createdAt = nowUtc(),\n      updatedAt = nowUtc(),\n      detailsUpdatedAt = detailsTimestamp\n    )\n  }\n\n  private fun extractCharacters(person: TmdbPerson) = when {\n    person.roles != null -> person.roles?.mapNotNull { it.character } ?: emptyList()\n    !person.character.isNullOrBlank() -> listOf(person.character!!)\n    else -> emptyList()\n  }\n\n  private fun extractJobs(person: TmdbPerson) = when {\n    person.jobs != null -> person.jobs?.map { Person.Job.fromSlug(it.job) } ?: emptyList()\n    !person.job.isNullOrBlank() -> listOf(Person.Job.fromSlug(person.job))\n    else -> emptyList()\n  }\n\n  private fun typeToEnum(type: String?) = when (type) {\n    \"Acting\", \"Actors\" -> Person.Department.ACTING\n    \"Directing\" -> Person.Department.DIRECTING\n    \"Writing\" -> Person.Department.WRITING\n    \"Sound\" -> Person.Department.SOUND\n    else -> Person.Department.UNKNOWN\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/RatingsMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.database.model.MovieRatings\nimport com.michaldrabik.data_local.database.model.ShowRatings\nimport com.michaldrabik.data_remote.omdb.model.OmdbResult\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ratings\nimport javax.inject.Inject\n\nclass RatingsMapper @Inject constructor() {\n\n  fun fromNetwork(omdbResult: OmdbResult) =\n    Ratings(\n      imdb = if (omdbResult.imdbRating == \"N/A\") null else Ratings.Value(omdbResult.imdbRating, false),\n      metascore = if (omdbResult.Metascore == \"N/A\") null else Ratings.Value(omdbResult.Metascore, false),\n      rottenTomatoes = Ratings.Value(omdbResult.Ratings?.find { it.Source == \"Rotten Tomatoes\" }?.Value, false),\n      rottenTomatoesUrl = if (omdbResult.tomatoURL == \"N/A\") null else omdbResult.tomatoURL\n    )\n\n  fun fromDatabase(entity: MovieRatings) =\n    Ratings(\n      trakt = Ratings.Value(entity.trakt, false),\n      imdb = Ratings.Value(entity.imdb, false),\n      rottenTomatoes = Ratings.Value(entity.rottenTomatoes, false),\n      rottenTomatoesUrl = entity.rottenTomatoesUrl,\n      metascore = Ratings.Value(entity.metascore, false)\n    )\n\n  fun fromDatabase(entity: ShowRatings) =\n    Ratings(\n      trakt = Ratings.Value(entity.trakt, false),\n      imdb = Ratings.Value(entity.imdb, false),\n      rottenTomatoes = Ratings.Value(entity.rottenTomatoes, false),\n      rottenTomatoesUrl = entity.rottenTomatoesUrl,\n      metascore = Ratings.Value(entity.metascore, false)\n    )\n\n  fun toMovieDatabase(\n    idTrakt: IdTrakt,\n    ratings: Ratings,\n  ) = MovieRatings(\n    id = 0,\n    idTrakt = idTrakt.id,\n    trakt = ratings.trakt?.value,\n    imdb = ratings.imdb?.value,\n    metascore = ratings.metascore?.value,\n    rottenTomatoes = ratings.rottenTomatoes?.value,\n    rottenTomatoesUrl = ratings.rottenTomatoesUrl,\n    createdAt = nowUtcMillis(),\n    updatedAt = nowUtcMillis(),\n  )\n\n  fun toShowDatabase(\n    idTrakt: IdTrakt,\n    ratings: Ratings,\n  ) = ShowRatings(\n    id = 0,\n    idTrakt = idTrakt.id,\n    trakt = ratings.trakt?.value,\n    imdb = ratings.imdb?.value,\n    metascore = ratings.metascore?.value,\n    rottenTomatoes = ratings.rottenTomatoes?.value,\n    rottenTomatoesUrl = ratings.rottenTomatoesUrl,\n    createdAt = nowUtcMillis(),\n    updatedAt = nowUtcMillis(),\n  )\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/SeasonMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.data_local.database.model.Episode\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Season\nimport java.time.ZonedDateTime\nimport javax.inject.Inject\nimport com.michaldrabik.data_local.database.model.Season as SeasonDb\nimport com.michaldrabik.data_remote.trakt.model.Season as SeasonNetwork\n\nclass SeasonMapper @Inject constructor(\n  private val idsMapper: IdsMapper,\n  private val episodeMapper: EpisodeMapper\n) {\n\n  fun fromNetwork(season: SeasonNetwork) = Season(\n    idsMapper.fromNetwork(season.ids),\n    season.number ?: -1,\n    season.episode_count ?: -1,\n    season.aired_episodes ?: -1,\n    season.title ?: \"\",\n    if (season.first_aired.isNullOrBlank()) null else ZonedDateTime.parse(season.first_aired),\n    season.overview ?: \"\",\n    season.rating ?: -1F,\n    season.episodes?.map { episodeMapper.fromNetwork(it) } ?: emptyList()\n  )\n\n  fun toNetwork(season: Season) = SeasonNetwork(\n    ids = idsMapper.toNetwork(season.ids),\n    number = season.number,\n    episode_count = season.episodeCount,\n    aired_episodes = season.airedEpisodes,\n    title = season.title,\n    first_aired = season.firstAired.toString(),\n    overview = season.overview,\n    rating = season.rating,\n    episodes = season.episodes.map { episodeMapper.toNetwork(it) }\n  )\n\n  fun fromDatabase(seasonDb: SeasonDb, episodes: List<Episode> = emptyList()) = Season(\n    Ids.EMPTY.copy(trakt = IdTrakt(seasonDb.idTrakt)),\n    seasonDb.seasonNumber,\n    seasonDb.episodesCount,\n    seasonDb.episodesAiredCount,\n    seasonDb.seasonTitle,\n    seasonDb.seasonFirstAired,\n    seasonDb.seasonOverview,\n    seasonDb.rating ?: -1F,\n    episodes.map { episodeMapper.fromDatabase(it) }\n  )\n\n  fun toDatabase(\n    season: Season,\n    showId: IdTrakt,\n    isWatched: Boolean\n  ): SeasonDb {\n    return SeasonDb(\n      season.ids.trakt.id,\n      showId.id,\n      season.number,\n      season.title,\n      season.overview,\n      season.firstAired,\n      season.episodeCount,\n      season.airedEpisodes,\n      season.rating,\n      isWatched\n    )\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/SettingsMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.Network\nimport com.michaldrabik.ui_model.NotificationDelay\nimport com.michaldrabik.ui_model.Settings\nimport javax.inject.Inject\nimport com.michaldrabik.data_local.database.model.Settings as SettingsDb\n\nclass SettingsMapper @Inject constructor() {\n\n  fun fromDatabase(settings: SettingsDb) = Settings(\n    isInitialRun = settings.isInitialRun,\n    episodesNotificationsEnabled = settings.episodesNotificationsEnabled,\n    episodesNotificationsDelay = NotificationDelay.fromDelay(settings.episodesNotificationsDelay),\n    myShowsWatchingSortBy = enumValueOf(settings.myShowsRunningSortBy),\n    myShowsUpcomingSortBy = enumValueOf(settings.myShowsIncomingSortBy),\n    myShowsFinishedSortBy = enumValueOf(settings.myShowsEndedSortBy),\n    myShowsAllSortBy = enumValueOf(settings.myShowsAllSortBy),\n    myShowsRunningIsCollapsed = settings.myShowsRunningIsCollapsed,\n    myShowsIncomingIsCollapsed = settings.myShowsIncomingIsCollapsed,\n    myShowsEndedIsCollapsed = settings.myShowsEndedIsCollapsed,\n    myRecentsAmount = settings.myShowsRecentsAmount,\n    watchlistShowsSortBy = enumValueOf(settings.seeLaterShowsSortBy),\n    archiveShowsSortBy = enumValueOf(settings.archiveShowsSortBy),\n    showAnticipatedShows = settings.showAnticipatedShows,\n    discoverFilterFeed = enumValueOf(settings.discoverFilterFeed),\n    discoverFilterGenres = settings.discoverFilterGenres.split(\",\").filter { it.isNotBlank() }.map { Genre.valueOf(it) },\n    discoverFilterNetworks = settings.discoverFilterNetworks.split(\",\").filter { it.isNotBlank() }.map { Network.valueOf(it) },\n    traktSyncSchedule = enumValueOf(settings.traktSyncSchedule),\n    traktQuickSyncEnabled = settings.traktQuickSyncEnabled,\n    traktQuickRemoveEnabled = settings.traktQuickRemoveEnabled,\n    progressSortOrder = enumValueOf(settings.watchlistSortBy),\n    archiveIncludeStatistics = settings.archiveShowsIncludeStatistics,\n    specialSeasonsEnabled = settings.specialSeasonsEnabled,\n    showAnticipatedMovies = settings.showAnticipatedMovies,\n    discoverMoviesFilterGenres = settings.discoverMoviesFilterGenres.split(\",\").filter { it.isNotBlank() }.map { Genre.valueOf(it) },\n    discoverMoviesFilterFeed = enumValueOf(settings.discoverMoviesFilterFeed),\n    myMoviesAllSortBy = enumValueOf(settings.myMoviesAllSortBy),\n    watchlistMoviesSortBy = enumValueOf(settings.seeLaterMoviesSortBy),\n    progressMoviesSortBy = enumValueOf(settings.progressMoviesSortBy),\n    showCollectionShows = settings.showCollectionShows,\n    showCollectionMovies = settings.showCollectionMovies,\n    widgetsShowLabel = settings.widgetsShowLabel,\n    traktQuickRateEnabled = settings.quickRateEnabled,\n    listsSortBy = enumValueOf(settings.listsSortBy),\n    progressUpcomingEnabled = settings.progressUpcomingEnabled\n  )\n\n  fun toDatabase(settings: Settings) = SettingsDb(\n    isInitialRun = settings.isInitialRun,\n    pushNotificationsEnabled = false,\n    episodesNotificationsEnabled = settings.episodesNotificationsEnabled,\n    episodesNotificationsDelay = settings.episodesNotificationsDelay.delayMs,\n    myShowsRunningSortBy = settings.myShowsWatchingSortBy.name,\n    myShowsIncomingSortBy = settings.myShowsUpcomingSortBy.name,\n    myShowsEndedSortBy = settings.myShowsFinishedSortBy.name,\n    myShowsAllSortBy = settings.myShowsAllSortBy.name,\n    myShowsRunningIsCollapsed = settings.myShowsRunningIsCollapsed,\n    myShowsIncomingIsCollapsed = settings.myShowsIncomingIsCollapsed,\n    myShowsEndedIsCollapsed = settings.myShowsEndedIsCollapsed,\n    myShowsRunningIsEnabled = false,\n    myShowsIncomingIsEnabled = false,\n    myShowsEndedIsEnabled = false,\n    myShowsRecentIsEnabled = false,\n    myMoviesRecentIsEnabled = false,\n    myShowsRecentsAmount = settings.myRecentsAmount,\n    seeLaterShowsSortBy = settings.watchlistShowsSortBy.name,\n    archiveShowsSortBy = settings.archiveShowsSortBy.name,\n    showAnticipatedShows = settings.showAnticipatedShows,\n    discoverFilterFeed = settings.discoverFilterFeed.name,\n    discoverFilterGenres = settings.discoverFilterGenres.joinToString(\",\") { it.name },\n    discoverFilterNetworks = settings.discoverFilterNetworks.joinToString(\",\") { it.name },\n    traktSyncSchedule = settings.traktSyncSchedule.name,\n    traktQuickSyncEnabled = settings.traktQuickSyncEnabled,\n    traktQuickRemoveEnabled = settings.traktQuickRemoveEnabled,\n    watchlistSortBy = settings.progressSortOrder.name,\n    archiveShowsIncludeStatistics = settings.archiveIncludeStatistics,\n    specialSeasonsEnabled = settings.specialSeasonsEnabled,\n    showAnticipatedMovies = settings.showAnticipatedMovies,\n    discoverMoviesFilterFeed = settings.discoverMoviesFilterFeed.name,\n    discoverMoviesFilterGenres = settings.discoverMoviesFilterGenres.joinToString(\",\") { it.name },\n    myMoviesAllSortBy = settings.myMoviesAllSortBy.name,\n    seeLaterMoviesSortBy = settings.watchlistMoviesSortBy.name,\n    progressMoviesSortBy = settings.progressMoviesSortBy.name,\n    showCollectionShows = settings.showCollectionShows,\n    showCollectionMovies = settings.showCollectionMovies,\n    widgetsShowLabel = settings.widgetsShowLabel,\n    quickRateEnabled = settings.traktQuickRateEnabled,\n    listsSortBy = settings.listsSortBy.name,\n    progressUpcomingEnabled = settings.progressUpcomingEnabled\n  )\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/ShowMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.ui_model.AirTime\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.ShowStatus\nimport javax.inject.Inject\nimport com.michaldrabik.data_local.database.model.Show as ShowDb\nimport com.michaldrabik.data_remote.trakt.model.AirTime as AirTimeNetwork\nimport com.michaldrabik.data_remote.trakt.model.Show as ShowNetwork\n\nclass ShowMapper @Inject constructor(\n  private val idsMapper: IdsMapper\n) {\n\n  fun fromNetwork(show: ShowNetwork) = Show(\n    idsMapper.fromNetwork(show.ids),\n    show.title ?: \"\",\n    show.year ?: -1,\n    show.overview ?: \"\",\n    show.first_aired ?: \"\",\n    show.runtime ?: -1,\n    AirTime(\n      show.airs?.day ?: \"\",\n      show.airs?.time ?: \"\",\n      show.airs?.timezone ?: \"\"\n    ),\n    show.certification ?: \"\",\n    show.network ?: \"\",\n    show.country ?: \"\",\n    show.trailer ?: \"\",\n    show.homepage ?: \"\",\n    ShowStatus.fromKey(show.status),\n    show.rating ?: -1F,\n    show.votes ?: -1,\n    show.comment_count ?: -1,\n    show.genres ?: emptyList(),\n    show.aired_episodes ?: -1,\n    nowUtcMillis(),\n    nowUtcMillis()\n  )\n\n  fun toNetwork(show: Show) = ShowNetwork(\n    idsMapper.toNetwork(show.ids),\n    show.title,\n    show.year,\n    show.overview,\n    show.firstAired,\n    show.runtime,\n    AirTimeNetwork(\n      show.airTime.day,\n      show.airTime.time,\n      show.airTime.timezone\n    ),\n    show.certification,\n    show.network,\n    show.country,\n    show.trailer,\n    show.homepage,\n    show.status.key,\n    show.rating,\n    show.votes,\n    show.commentCount,\n    show.genres,\n    show.airedEpisodes\n  )\n\n  fun fromDatabase(show: ShowDb) = Show(\n    idsMapper.fromDatabase(show),\n    show.title,\n    show.year,\n    show.overview,\n    show.firstAired,\n    show.runtime,\n    AirTime(show.airtimeDay, show.airtimeTime, show.airtimeTimezone),\n    show.certification,\n    show.network,\n    show.country,\n    show.trailer,\n    show.homepage,\n    ShowStatus.fromKey(show.status),\n    show.rating,\n    show.votes,\n    show.commentCount,\n    show.genres.split(\",\"),\n    show.airedEpisodes,\n    show.createdAt,\n    show.updatedAt\n  )\n\n  fun toDatabase(show: Show) = ShowDb(\n    show.traktId,\n    show.ids.tvdb.id,\n    show.ids.tmdb.id,\n    show.ids.imdb.id,\n    show.ids.slug.id,\n    show.ids.tvrage.id,\n    show.title,\n    show.year,\n    show.overview,\n    show.firstAired,\n    show.runtime,\n    show.airTime.day,\n    show.airTime.time,\n    show.airTime.timezone,\n    show.certification,\n    show.network,\n    show.country,\n    show.trailer,\n    show.homepage,\n    show.status.key,\n    show.rating,\n    show.votes,\n    show.commentCount,\n    show.genres.joinToString(\",\"),\n    show.airedEpisodes,\n    show.createdAt,\n    nowUtcMillis()\n  )\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/StreamingsMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.data_local.database.model.MovieStreaming\nimport com.michaldrabik.data_local.database.model.ShowStreaming\nimport com.michaldrabik.data_remote.tmdb.model.TmdbStreamingCountry\nimport com.michaldrabik.data_remote.tmdb.model.TmdbStreamingService\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.StreamingService\nimport com.michaldrabik.ui_model.StreamingService.Option.ADS\nimport com.michaldrabik.ui_model.StreamingService.Option.BUY\nimport com.michaldrabik.ui_model.StreamingService.Option.FLATRATE\nimport com.michaldrabik.ui_model.StreamingService.Option.FREE\nimport com.michaldrabik.ui_model.StreamingService.Option.RENT\nimport javax.inject.Inject\n\nclass StreamingsMapper @Inject constructor() {\n\n  fun fromDatabaseShow(\n    input: List<ShowStreaming>,\n    mediaName: String,\n    countryCode: String,\n  ): List<StreamingService> {\n    return input.map {\n      StreamingService(\n        imagePath = it.logoPath ?: \"\",\n        name = it.providerName ?: \"\",\n        options = listOf(StreamingService.Option.valueOf(it.type!!)),\n        mediaName = mediaName,\n        countryCode = countryCode,\n        link = it.link ?: \"\"\n      )\n    }\n  }\n\n  fun toDatabaseShow(ids: Ids, input: TmdbStreamingCountry) =\n    mutableListOf<ShowStreaming>().apply {\n      addAll(input.flatrate?.map { createEntityShow(ids, FLATRATE, input, it) } ?: emptyList())\n      addAll(input.free?.map { createEntityShow(ids, FREE, input, it) } ?: emptyList())\n      addAll(input.buy?.map { createEntityShow(ids, BUY, input, it) } ?: emptyList())\n      addAll(input.rent?.map { createEntityShow(ids, RENT, input, it) } ?: emptyList())\n      addAll(input.ads?.map { createEntityShow(ids, ADS, input, it) } ?: emptyList())\n    }\n\n  fun fromDatabaseMovie(\n    input: List<MovieStreaming>,\n    mediaName: String,\n    countryCode: String,\n  ): List<StreamingService> {\n    return input.map {\n      StreamingService(\n        imagePath = it.logoPath ?: \"\",\n        name = it.providerName ?: \"\",\n        options = listOf(StreamingService.Option.valueOf(it.type!!)),\n        mediaName = mediaName,\n        countryCode = countryCode,\n        link = it.link ?: \"\"\n      )\n    }\n  }\n\n  fun toDatabaseMovie(ids: Ids, input: TmdbStreamingCountry) =\n    mutableListOf<MovieStreaming>().apply {\n      addAll(input.flatrate?.map { createEntityMovie(ids, FLATRATE, input, it) } ?: emptyList())\n      addAll(input.free?.map { createEntityMovie(ids, FREE, input, it) } ?: emptyList())\n      addAll(input.buy?.map { createEntityMovie(ids, BUY, input, it) } ?: emptyList())\n      addAll(input.rent?.map { createEntityMovie(ids, RENT, input, it) } ?: emptyList())\n      addAll(input.ads?.map { createEntityMovie(ids, ADS, input, it) } ?: emptyList())\n    }\n\n  private fun createEntityMovie(\n    ids: Ids,\n    option: StreamingService.Option,\n    country: TmdbStreamingCountry,\n    input: TmdbStreamingService,\n  ) = MovieStreaming(\n    idTrakt = ids.trakt.id,\n    idTmdb = ids.tmdb.id,\n    type = option.name,\n    providerId = input.provider_id,\n    providerName = input.provider_name,\n    displayPriority = input.display_priority,\n    logoPath = input.logo_path,\n    link = country.link,\n    createdAt = nowUtc(),\n    updatedAt = nowUtc()\n  )\n\n  private fun createEntityShow(\n    ids: Ids,\n    option: StreamingService.Option,\n    country: TmdbStreamingCountry,\n    input: TmdbStreamingService,\n  ) = ShowStreaming(\n    idTrakt = ids.trakt.id,\n    idTmdb = ids.tmdb.id,\n    type = option.name,\n    providerId = input.provider_id,\n    providerName = input.provider_name,\n    displayPriority = input.display_priority,\n    logoPath = input.logo_path,\n    link = country.link,\n    createdAt = nowUtc(),\n    updatedAt = nowUtc()\n  )\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/TranslationMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.data_local.database.model.EpisodeTranslation\nimport com.michaldrabik.data_local.database.model.MovieTranslation\nimport com.michaldrabik.data_local.database.model.ShowTranslation\nimport com.michaldrabik.ui_model.SeasonTranslation\nimport com.michaldrabik.ui_model.Translation\nimport javax.inject.Inject\nimport com.michaldrabik.data_remote.trakt.model.SeasonTranslation as SeasonTranslationNetwork\nimport com.michaldrabik.data_remote.trakt.model.Translation as TranslationNetwork\n\nclass TranslationMapper @Inject constructor(\n  private val idsMapper: IdsMapper\n) {\n\n  fun fromNetwork(value: TranslationNetwork?) =\n    Translation(\n      title = value?.title ?: \"\",\n      overview = value?.overview ?: \"\",\n      language = value?.language ?: \"\"\n    )\n\n  fun fromNetwork(value: SeasonTranslationNetwork?) =\n    SeasonTranslation(\n      ids = idsMapper.fromNetwork(value?.ids),\n      seasonNumber = value?.season ?: -1,\n      episodeNumber = value?.number ?: -1,\n      title = value?.translations?.firstOrNull()?.title ?: \"\",\n      overview = value?.translations?.firstOrNull()?.overview ?: \"\",\n      language = value?.translations?.firstOrNull()?.language ?: \"\"\n    )\n\n  fun fromDatabase(value: ShowTranslation?) =\n    Translation(\n      title = value?.title ?: \"\",\n      overview = value?.overview ?: \"\",\n      language = value?.language ?: \"\"\n    )\n\n  fun fromDatabase(value: MovieTranslation?) =\n    Translation(\n      title = value?.title ?: \"\",\n      overview = value?.overview ?: \"\",\n      language = value?.language ?: \"\"\n    )\n\n  fun fromDatabase(value: EpisodeTranslation?) =\n    Translation(\n      title = value?.title ?: \"\",\n      overview = value?.overview ?: \"\",\n      language = value?.language ?: \"\"\n    )\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/mappers/UserRatingsMapper.kt",
    "content": "package com.michaldrabik.repository.mappers\n\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.data_local.database.model.Rating\nimport com.michaldrabik.data_remote.trakt.model.RatingResultEpisode\nimport com.michaldrabik.data_remote.trakt.model.RatingResultMovie\nimport com.michaldrabik.data_remote.trakt.model.RatingResultSeason\nimport com.michaldrabik.data_remote.trakt.model.RatingResultShow\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.TraktRating\nimport java.time.ZonedDateTime\nimport javax.inject.Inject\n\nclass UserRatingsMapper @Inject constructor() {\n\n  fun fromDatabase(entity: Rating) = TraktRating(\n    idTrakt = IdTrakt(entity.idTrakt),\n    rating = entity.rating,\n    ratedAt = entity.ratedAt\n  )\n\n  fun toDatabaseMovie(\n    rating: RatingResultMovie\n  ) = Rating(\n    idTrakt = rating.movie.ids.trakt!!,\n    type = \"movie\",\n    rating = rating.rating,\n    seasonNumber = null,\n    episodeNumber = null,\n    ratedAt = ZonedDateTime.parse(rating.rated_at),\n    createdAt = nowUtc(),\n    updatedAt = nowUtc()\n  )\n\n  fun toDatabaseMovie(\n    movie: Movie,\n    rating: Int,\n    ratedAt: ZonedDateTime\n  ) = Rating(\n    idTrakt = movie.traktId,\n    type = \"movie\",\n    rating = rating,\n    seasonNumber = null,\n    episodeNumber = null,\n    ratedAt = ratedAt,\n    createdAt = nowUtc(),\n    updatedAt = nowUtc()\n  )\n\n  fun toDatabaseShow(\n    rating: RatingResultShow\n  ) = Rating(\n    idTrakt = rating.show.ids.trakt!!,\n    type = \"show\",\n    rating = rating.rating,\n    seasonNumber = null,\n    episodeNumber = null,\n    ratedAt = ZonedDateTime.parse(rating.rated_at),\n    createdAt = nowUtc(),\n    updatedAt = nowUtc()\n  )\n\n  fun toDatabaseShow(\n    show: Show,\n    rating: Int,\n    ratedAt: ZonedDateTime\n  ) = Rating(\n    idTrakt = show.traktId,\n    type = \"show\",\n    rating = rating,\n    seasonNumber = null,\n    episodeNumber = null,\n    ratedAt = ratedAt,\n    createdAt = nowUtc(),\n    updatedAt = nowUtc()\n  )\n\n  fun toDatabaseEpisode(\n    rating: RatingResultEpisode\n  ) = Rating(\n    idTrakt = rating.episode.ids.trakt!!,\n    type = \"episode\",\n    rating = rating.rating,\n    seasonNumber = rating.episode.season,\n    episodeNumber = rating.episode.number,\n    ratedAt = ZonedDateTime.parse(rating.rated_at),\n    createdAt = nowUtc(),\n    updatedAt = nowUtc()\n  )\n\n  fun toDatabaseEpisode(\n    episode: Episode,\n    rating: Int,\n    ratedAt: ZonedDateTime\n  ) = Rating(\n    idTrakt = episode.ids.trakt.id,\n    type = \"episode\",\n    rating = rating,\n    seasonNumber = episode.season,\n    episodeNumber = episode.number,\n    ratedAt = ratedAt,\n    createdAt = nowUtc(),\n    updatedAt = nowUtc()\n  )\n\n  fun toDatabaseSeason(\n    rating: RatingResultSeason\n  ) = Rating(\n    idTrakt = rating.season.ids.trakt!!,\n    type = \"season\",\n    rating = rating.rating,\n    seasonNumber = rating.season.season,\n    episodeNumber = rating.season.number,\n    ratedAt = ZonedDateTime.parse(rating.rated_at),\n    createdAt = nowUtc(),\n    updatedAt = nowUtc()\n  )\n\n  fun toDatabaseSeason(\n    season: Season,\n    rating: Int,\n    ratedAt: ZonedDateTime\n  ) = Rating(\n    idTrakt = season.ids.trakt.id,\n    type = \"season\",\n    rating = rating,\n    seasonNumber = season.number,\n    episodeNumber = null,\n    ratedAt = ratedAt,\n    createdAt = nowUtc(),\n    updatedAt = nowUtc()\n  )\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/movies/DiscoverMoviesRepository.kt",
    "content": "package com.michaldrabik.repository.movies\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.DiscoverMovie\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.Config.TRAKT_TRENDING_MOVIES_LIMIT\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.Movie\nimport javax.inject.Inject\n\nclass DiscoverMoviesRepository @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers\n) {\n\n  suspend fun isCacheValid(): Boolean {\n    val stamp = localSource.discoverMovies.getMostRecent()?.createdAt ?: 0\n    return nowUtcMillis() - stamp < Config.DISCOVER_MOVIES_CACHE_DURATION\n  }\n\n  suspend fun loadAllCached(): List<Movie> {\n    val cachedMovies = localSource.discoverMovies.getAll().map { it.idTrakt }\n    val movies = localSource.movies.getAll(cachedMovies)\n\n    return cachedMovies\n      .map { id -> movies.first { it.idTrakt == id } }\n      .map { mappers.movie.fromDatabase(it) }\n  }\n\n  // TODO This logic should probably sit in a case and not repository.\n  suspend fun loadAllRemote(\n    showAnticipated: Boolean,\n    showCollection: Boolean,\n    collectionSize: Int,\n    genres: List<Genre>\n  ): List<Movie> {\n    val remoteMovies = mutableListOf<Movie>()\n    val anticipatedMovies = mutableListOf<Movie>()\n    val popularMovies = mutableListOf<Movie>()\n    val genresQuery = genres.joinToString(\",\") { it.slug }\n\n    val limit =\n      if (showCollection) TRAKT_TRENDING_MOVIES_LIMIT\n      else TRAKT_TRENDING_MOVIES_LIMIT + (collectionSize / 2)\n    val trendingMovies = remoteSource.trakt.fetchTrendingMovies(genresQuery, limit)\n      .map { mappers.movie.fromNetwork(it) }\n\n    if (genres.isNotEmpty()) {\n      // Wa are adding popular results for genres filtered content to add more results.\n      val popular = remoteSource.trakt.fetchPopularMovies(genresQuery).map { mappers.movie.fromNetwork(it) }\n      popularMovies.addAll(popular)\n    }\n\n    if (showAnticipated) {\n      val movies = remoteSource.trakt.fetchAnticipatedMovies(genresQuery).map { mappers.movie.fromNetwork(it) }.toMutableList()\n      anticipatedMovies.addAll(movies)\n    }\n\n    trendingMovies.forEachIndexed { index, movie ->\n      addIfMissing(remoteMovies, movie)\n      if (index % 4 == 0 && anticipatedMovies.isNotEmpty()) {\n        val element = anticipatedMovies.removeAt(0)\n        addIfMissing(remoteMovies, element)\n      }\n    }\n    popularMovies.forEach { show -> addIfMissing(remoteMovies, show) }\n\n    if (!showAnticipated) {\n      return remoteMovies.filter { !it.status.isAnticipated() }\n    }\n\n    return remoteMovies\n  }\n\n  suspend fun cacheDiscoverMovies(movies: List<Movie>) {\n    transactions.withTransaction {\n      val timestamp = nowUtcMillis()\n      localSource.movies.upsert(movies.map { mappers.movie.toDatabase(it) })\n      localSource.discoverMovies.replace(\n        movies.map {\n          DiscoverMovie(\n            idTrakt = it.ids.trakt.id,\n            createdAt = timestamp,\n            updatedAt = timestamp\n          )\n        }\n      )\n    }\n  }\n\n  private fun addIfMissing(movies: MutableList<Movie>, movie: Movie) {\n    if (movies.any { it.ids.trakt == movie.ids.trakt }) return\n    movies.add(movie)\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/movies/HiddenMoviesRepository.kt",
    "content": "package com.michaldrabik.repository.movies\n\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.ArchiveMovie\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.IdTrakt\nimport javax.inject.Inject\n\nclass HiddenMoviesRepository @Inject constructor(\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers\n) {\n\n  suspend fun loadAll() =\n    localSource.archiveMovies.getAll()\n      .map { mappers.movie.fromDatabase(it) }\n\n  suspend fun loadAll(ids: List<IdTrakt>) =\n    localSource.archiveMovies.getAll(ids.map { it.id })\n      .map { mappers.movie.fromDatabase(it) }\n\n  suspend fun load(id: IdTrakt) =\n    localSource.archiveMovies.getById(id.id)?.let {\n      mappers.movie.fromDatabase(it)\n    }\n\n  suspend fun loadAllIds() = localSource.archiveMovies.getAllTraktIds()\n\n  suspend fun insert(id: IdTrakt) {\n    val dbMovie = ArchiveMovie.fromTraktId(id.id, nowUtcMillis())\n    transactions.withTransaction {\n      with(localSource) {\n        archiveMovies.insert(dbMovie)\n        myMovies.deleteById(id.id)\n        watchlistMovies.deleteById(id.id)\n      }\n    }\n  }\n\n  suspend fun delete(id: IdTrakt) =\n    localSource.archiveMovies.deleteById(id.id)\n\n  suspend fun exists(id: IdTrakt) =\n    localSource.archiveMovies.getById(id.id) != null\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/movies/MovieCollectionsRepository.kt",
    "content": "package com.michaldrabik.repository.movies\n\nimport com.michaldrabik.common.ConfigVariant.COLLECTIONS_CACHE_DURATION\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.data_local.database.model.MovieCollectionItem\nimport com.michaldrabik.data_local.sources.MovieCollectionsItemsLocalDataSource\nimport com.michaldrabik.data_local.sources.MovieCollectionsLocalDataSource\nimport com.michaldrabik.data_local.sources.MoviesLocalDataSource\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.trakt.TraktRemoteDataSource\nimport com.michaldrabik.repository.mappers.CollectionMapper\nimport com.michaldrabik.repository.mappers.MovieMapper\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.MovieCollection\nimport kotlinx.coroutines.withContext\nimport java.time.ZonedDateTime\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass MovieCollectionsRepository @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val remoteSource: TraktRemoteDataSource,\n  private val moviesLocalSource: MoviesLocalDataSource,\n  private val movieCollectionsLocalSource: MovieCollectionsLocalDataSource,\n  private val movieCollectionsItemsLocalSource: MovieCollectionsItemsLocalDataSource,\n  private val collectionMapper: CollectionMapper,\n  private val movieMapper: MovieMapper,\n  private val transactions: TransactionsProvider,\n) {\n\n  suspend fun loadCollection(collectionId: IdTrakt) = withContext(dispatchers.IO) {\n    movieCollectionsLocalSource.getById(collectionId.id)\n  }\n\n  suspend fun loadCollections(movieId: IdTrakt): Pair<List<MovieCollection>, Source> =\n    withContext(dispatchers.IO) {\n      val now = nowUtc()\n      val localCollections = movieCollectionsLocalSource.getByMovieId(movieId.id)\n\n      val localTimestamp = localCollections.firstOrNull()?.updatedAt\n      localTimestamp?.let { timestamp ->\n        if (now.toMillis() - timestamp.toMillis() < COLLECTIONS_CACHE_DURATION) {\n          return@withContext Pair(\n            localCollections.map { collectionMapper.fromEntity(it) },\n            Source.LOCAL\n          )\n        }\n      }\n\n      val remoteCollections = remoteSource.fetchMovieCollections(movieId.id)\n      val collections = remoteCollections.map { collectionMapper.fromNetwork(it) }\n\n      updateLocalCollections(collections, movieId, now)\n\n      return@withContext Pair(\n        collections,\n        Source.REMOTE\n      )\n    }\n\n  suspend fun loadCollectionItems(collectionId: IdTrakt): List<Movie> =\n    withContext(dispatchers.IO) {\n      val now = nowUtc()\n      val localItems = movieCollectionsItemsLocalSource.getById(collectionId.id)\n\n      val localTimestamp = localItems.firstOrNull()?.updatedAt\n      localTimestamp?.let { timestamp ->\n        if (now.toMillis() - timestamp < COLLECTIONS_CACHE_DURATION) {\n          return@withContext localItems.map { movieMapper.fromDatabase(it) }\n        }\n      }\n\n      val remoteItems = remoteSource.fetchMovieCollectionItems(collectionId.id)\n      val items = remoteItems.map { movieMapper.fromNetwork(it) }\n\n      transactions.withTransaction {\n        val entities = items.mapIndexed { index, movie ->\n          MovieCollectionItem(\n            rank = index,\n            idTrakt = movie.traktId,\n            idTraktCollection = collectionId.id,\n            createdAt = now,\n            updatedAt = now\n          )\n        }\n        moviesLocalSource.upsert(items.map { movieMapper.toDatabase(it) })\n        movieCollectionsItemsLocalSource.replace(collectionId.id, entities)\n\n        // Fill up collection with other movies that belong in it.\n        val collection = movieCollectionsLocalSource.getById(collectionId.id)\n        collection?.let { coll ->\n          val insertEntities = entities\n            .filter { it.idTrakt != coll.idTraktMovie }\n            .map { coll.copy(id = 0, idTraktMovie = it.idTrakt) }\n          movieCollectionsLocalSource.insertAll(insertEntities)\n        }\n      }\n\n      return@withContext items\n    }\n\n  private suspend fun updateLocalCollections(\n    collections: List<MovieCollection>,\n    movieId: IdTrakt,\n    now: ZonedDateTime,\n  ) {\n    var entities = collections.map {\n      collectionMapper.toEntity(\n        movieId = movieId.id,\n        input = it,\n        updatedAt = now,\n        createdAt = now\n      )\n    }\n    if (entities.isEmpty()) {\n      entities = listOf(\n        collectionMapper.toEntity(\n          movieId.id,\n          MovieCollection.EMPTY\n        )\n      )\n    }\n    movieCollectionsLocalSource.replaceByMovieId(movieId.id, entities)\n  }\n\n  enum class Source { LOCAL, REMOTE }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/movies/MovieDetailsRepository.kt",
    "content": "package com.michaldrabik.repository.movies\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.MoviesSyncLog\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.IdImdb\nimport com.michaldrabik.ui_model.IdSlug\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Movie\nimport javax.inject.Inject\n\nclass MovieDetailsRepository @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n) {\n\n  suspend fun load(idTrakt: IdTrakt, force: Boolean = false): Movie {\n    val local = localSource.movies.getById(idTrakt.id)\n    if (force || local == null || nowUtcMillis() - local.updatedAt > Config.MOVIE_DETAILS_CACHE_DURATION) {\n      val remote = remoteSource.trakt.fetchMovie(idTrakt.id)\n      val movie = mappers.movie.fromNetwork(remote)\n      localSource.movies.upsert(listOf(mappers.movie.toDatabase(movie)))\n      localSource.moviesSyncLog.upsert(MoviesSyncLog(movie.traktId, nowUtcMillis()))\n      return movie\n    }\n    return mappers.movie.fromDatabase(local)\n  }\n\n  suspend fun find(idImdb: IdImdb): Movie? {\n    val localMovie = localSource.movies.getById(idImdb.id)\n    if (localMovie != null) {\n      return mappers.movie.fromDatabase(localMovie)\n    }\n    return null\n  }\n\n  suspend fun find(idTmdb: IdTmdb): Movie? {\n    val localMovie = localSource.movies.getByTmdbId(idTmdb.id)\n    if (localMovie != null) {\n      return mappers.movie.fromDatabase(localMovie)\n    }\n    return null\n  }\n\n  suspend fun find(idSlug: IdSlug): Movie? {\n    val localMovie = localSource.movies.getBySlug(idSlug.id)\n    if (localMovie != null) {\n      return mappers.movie.fromDatabase(localMovie)\n    }\n    return null\n  }\n\n  suspend fun delete(idTrakt: IdTrakt) =\n    localSource.movies.deleteById(idTrakt.id)\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/movies/MovieStreamingsRepository.kt",
    "content": "package com.michaldrabik.repository.movies\n\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.StreamingsRepository\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.StreamingService\nimport java.time.ZonedDateTime\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass MovieStreamingsRepository @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n) : StreamingsRepository() {\n\n  suspend fun getLocalStreamings(movie: Movie, countryCode: String): Pair<List<StreamingService>, ZonedDateTime?> {\n    val localItems = localSource.movieStreamings.getById(movie.traktId)\n    val mappedItems = mappers.streamings.fromDatabaseMovie(localItems, movie.title, countryCode)\n\n    val processedItems = processItems(mappedItems, countryCode)\n    val date = localItems.firstOrNull()?.createdAt\n    return Pair(processedItems, date)\n  }\n\n  suspend fun loadRemoteStreamings(movie: Movie, countryCode: String): List<StreamingService> {\n    val remoteItems = remoteSource.tmdb.fetchMovieWatchProviders(movie.ids.tmdb.id, countryCode) ?: return emptyList()\n\n    val entities = mappers.streamings.toDatabaseMovie(movie.ids, remoteItems)\n    localSource.movieStreamings.replace(movie.traktId, entities)\n\n    return processItems(remoteItems, movie.title, countryCode)\n  }\n\n  suspend fun deleteCache() = localSource.movieStreamings.deleteAll()\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/movies/MoviesRepository.kt",
    "content": "package com.michaldrabik.repository.movies\n\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.coroutineScope\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass MoviesRepository @Inject constructor(\n  val discoverMovies: DiscoverMoviesRepository,\n  val relatedMovies: RelatedMoviesRepository,\n  val movieDetails: MovieDetailsRepository,\n  val myMovies: MyMoviesRepository,\n  val watchlistMovies: WatchlistMoviesRepository,\n  val hiddenMovies: HiddenMoviesRepository,\n) {\n\n  suspend fun loadCollection(skipHidden: Boolean = false) =\n    coroutineScope {\n      val async1 = async { myMovies.loadAll() }\n      val async2 = async { watchlistMovies.loadAll() }\n      val async3 = async { if (skipHidden) emptyList() else hiddenMovies.loadAll() }\n      val (my, watchlist, hidden) = awaitAll(async1, async2, async3)\n      (my + watchlist + hidden).distinctBy { it.traktId }\n    }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/movies/MyMoviesRepository.kt",
    "content": "package com.michaldrabik.repository.movies\n\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.MyMovie\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.IdTrakt\nimport javax.inject.Inject\n\nclass MyMoviesRepository @Inject constructor(\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers,\n) {\n\n  suspend fun load(id: IdTrakt) =\n    localSource.myMovies.getById(id.id)?.let {\n      mappers.movie.fromDatabase(it)\n    }\n\n  suspend fun loadAll() =\n    localSource.myMovies.getAll()\n      .map { mappers.movie.fromDatabase(it) }\n\n  suspend fun loadAll(ids: List<IdTrakt>) =\n    localSource.myMovies.getAll(ids.map { it.id })\n      .map { mappers.movie.fromDatabase(it) }\n\n  suspend fun loadAllRecent(amount: Int) =\n    localSource.myMovies.getAllRecent(amount)\n      .map { mappers.movie.fromDatabase(it) }\n\n  suspend fun loadAllIds() = localSource.myMovies.getAllTraktIds()\n\n  suspend fun insert(id: IdTrakt) {\n    val movie = MyMovie.fromTraktId(id.id, nowUtcMillis())\n    transactions.withTransaction {\n      with(localSource) {\n        myMovies.insert(listOf(movie))\n        watchlistMovies.deleteById(id.id)\n        archiveMovies.deleteById(id.id)\n      }\n    }\n  }\n\n  suspend fun delete(id: IdTrakt) =\n    localSource.myMovies.deleteById(id.id)\n\n  suspend fun exists(id: IdTrakt) =\n    localSource.myMovies.checkExists(id.id)\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/movies/RelatedMoviesRepository.kt",
    "content": "package com.michaldrabik.repository.movies\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.RelatedMovie\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Movie\nimport javax.inject.Inject\nimport kotlin.math.min\n\nclass RelatedMoviesRepository @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers\n) {\n\n  suspend fun loadAll(movie: Movie): List<Movie> {\n    val related = localSource.relatedMovies.getAllById(movie.ids.trakt.id)\n    val latest = related.maxByOrNull { it.updatedAt }\n\n    if (latest != null && nowUtcMillis() - latest.updatedAt < Config.RELATED_CACHE_DURATION) {\n      val relatedIds = related.map { it.idTrakt }\n      return localSource.movies.getAll(relatedIds)\n        .map { mappers.movie.fromDatabase(it) }\n    }\n\n    val remote = remoteSource.trakt.fetchRelatedMovies(movie.ids.trakt.id, min(0, 15))\n      .map { mappers.movie.fromNetwork(it) }\n\n    cacheRelated(remote, movie.ids.trakt)\n\n    return remote\n  }\n\n  private suspend fun cacheRelated(movies: List<Movie>, movieId: IdTrakt) {\n    transactions.withTransaction {\n      val timestamp = nowUtcMillis()\n      localSource.movies.upsert(movies.map { mappers.movie.toDatabase(it) })\n      localSource.relatedMovies.deleteById(movieId.id)\n      localSource.relatedMovies.insert(\n        movies.map {\n          RelatedMovie.fromTraktId(it.ids.trakt.id, movieId.id, timestamp)\n        }\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/movies/WatchlistMoviesRepository.kt",
    "content": "package com.michaldrabik.repository.movies\n\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.WatchlistMovie\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.IdTrakt\nimport javax.inject.Inject\n\nclass WatchlistMoviesRepository @Inject constructor(\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers,\n) {\n\n  suspend fun loadAll() =\n    localSource.watchlistMovies.getAll()\n      .map { mappers.movie.fromDatabase(it) }\n\n  suspend fun loadAllIds() = localSource.watchlistMovies.getAllTraktIds()\n\n  suspend fun load(id: IdTrakt) =\n    localSource.watchlistMovies.getById(id.id)?.let {\n      mappers.movie.fromDatabase(it)\n    }\n\n  suspend fun insert(id: IdTrakt) {\n    val movie = WatchlistMovie.fromTraktId(id.id, nowUtcMillis())\n    transactions.withTransaction {\n      with(localSource) {\n        watchlistMovies.insert(movie)\n        myMovies.deleteById(movie.idTrakt)\n        archiveMovies.deleteById(movie.idTrakt)\n      }\n    }\n  }\n\n  suspend fun delete(id: IdTrakt) =\n    localSource.watchlistMovies.deleteById(id.id)\n\n  suspend fun exists(id: IdTrakt) =\n    localSource.watchlistMovies.checkExists(id.id)\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/movies/ratings/MoviesExternalRatingsRepository.kt",
    "content": "package com.michaldrabik.repository.movies.ratings\n\nimport com.michaldrabik.common.ConfigVariant\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Ratings\nimport java.util.Locale\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass MoviesExternalRatingsRepository @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n) {\n\n  suspend fun loadRatings(movie: Movie): Ratings {\n    val localRatings = localSource.movieRatings.getById(movie.traktId)\n    localRatings?.let {\n      if (nowUtcMillis() - it.updatedAt < ConfigVariant.RATINGS_CACHE_DURATION) {\n        return mappers.ratings.fromDatabase(it)\n      }\n    }\n\n    val remoteRatings = remoteSource.omdb.fetchOmdbData(movie.ids.imdb.id)\n      .let { mappers.ratings.fromNetwork(it) }\n      .copy(trakt = Ratings.Value(String.format(Locale.ENGLISH, \"%.1f\", movie.rating), false))\n\n    val dbRatings = mappers.ratings.toMovieDatabase(movie.ids.trakt, remoteRatings)\n    localSource.movieRatings.upsert(dbRatings)\n\n    return remoteRatings\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/movies/ratings/MoviesRatingsRepository.kt",
    "content": "package com.michaldrabik.repository.movies.ratings\n\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.Rating\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.TraktRating\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass MoviesRatingsRepository @Inject constructor(\n  val external: MoviesExternalRatingsRepository,\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n) {\n\n  companion object {\n    private const val TYPE_MOVIE = \"movie\"\n  }\n\n  suspend fun preloadRatings() {\n    val ratings = remoteSource.trakt.fetchMoviesRatings()\n    val entities = ratings\n      .filter { it.rated_at != null && it.movie.ids.trakt != null }\n      .map { mappers.userRatings.toDatabaseMovie(it) }\n    localSource.ratings.replaceAll(entities, TYPE_MOVIE)\n  }\n\n  suspend fun loadMoviesRatings(): List<TraktRating> {\n    val ratings = localSource.ratings.getAllByType(TYPE_MOVIE)\n    return ratings.map {\n      mappers.userRatings.fromDatabase(it)\n    }\n  }\n\n  suspend fun loadRatings(movies: List<Movie>): List<TraktRating> {\n    val ratings = mutableListOf<Rating>()\n    movies.chunked(250).forEach { chunk ->\n      val items = localSource.ratings.getAllByType(chunk.map { it.traktId }, TYPE_MOVIE)\n      ratings.addAll(items)\n    }\n    return ratings.map {\n      mappers.userRatings.fromDatabase(it)\n    }\n  }\n\n  suspend fun addRating(movie: Movie, rating: Int) {\n    remoteSource.trakt.postRating(\n      mappers.movie.toNetwork(movie),\n      rating\n    )\n    val entity = mappers.userRatings.toDatabaseMovie(movie, rating, nowUtc())\n    localSource.ratings.replace(entity)\n  }\n\n  suspend fun deleteRating(movie: Movie) {\n    remoteSource.trakt.deleteRating(\n      mappers.movie.toNetwork(movie)\n    )\n    localSource.ratings.deleteByType(movie.traktId, TYPE_MOVIE)\n  }\n\n  suspend fun clear() {\n    localSource.ratings.deleteAllByType(TYPE_MOVIE)\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/settings/SettingsFiltersRepository.kt",
    "content": "package com.michaldrabik.repository.settings\n\nimport android.content.SharedPreferences\nimport androidx.core.content.edit\nimport com.michaldrabik.repository.utilities.BooleanPreference\nimport com.michaldrabik.repository.utilities.EnumPreference\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.MyShowsSection\nimport com.michaldrabik.ui_model.Network\nimport javax.inject.Inject\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Singleton\nclass SettingsFiltersRepository @Inject constructor(\n  @Named(\"miscPreferences\") private var preferences: SharedPreferences,\n) {\n\n  companion object Key {\n    private const val PROGRESS_SHOWS_UPCOMING = \"PROGRESS_SHOWS_UPCOMING\"\n    private const val PROGRESS_SHOWS_ON_HOLD = \"PROGRESS_SHOWS_ON_HOLD\"\n    private const val MY_SHOWS_TYPE = \"MY_SHOWS_TYPE\"\n    private const val MY_SHOWS_NETWORKS = \"MY_SHOWS_NETWORKS\"\n    private const val MY_SHOWS_GENRES = \"MY_SHOWS_GENRES\"\n    private const val WATCHLIST_SHOWS_UPCOMING = \"WATCHLIST_SHOWS_UPCOMING\"\n    private const val WATCHLIST_SHOWS_NETWORKS = \"WATCHLIST_SHOWS_NETWORKS\"\n    private const val WATCHLIST_SHOWS_GENRES = \"WATCHLIST_SHOWS_GENRES\"\n    private const val HIDDEN_SHOWS_NETWORKS = \"HIDDEN_SHOWS_NETWORKS\"\n    private const val HIDDEN_SHOWS_GENRES = \"HIDDEN_SHOWS_GENRES\"\n\n    private const val MY_MOVIES_GENRES = \"MY_MOVIES_GENRES\"\n    private const val WATCHLIST_MOVIES_UPCOMING = \"WATCHLIST_MOVIES_UPCOMING\"\n    private const val WATCHLIST_MOVIES_GENRES = \"WATCHLIST_MOVIES_GENRES\"\n    private const val HIDDEN_MOVIES_GENRES = \"HIDDEN_MOVIES_GENRES\"\n  }\n\n  // Shows\n\n  var progressShowsUpcoming by BooleanPreference(preferences, PROGRESS_SHOWS_UPCOMING, false)\n  var progressShowsOnHold by BooleanPreference(preferences, PROGRESS_SHOWS_ON_HOLD, false)\n\n  var myShowsType by EnumPreference(preferences, MY_SHOWS_TYPE, MyShowsSection.ALL, MyShowsSection::class.java)\n  var myShowsNetworks: List<Network>\n    get() {\n      val filters = preferences.getStringSet(MY_SHOWS_NETWORKS, emptySet()) ?: emptySet()\n      return filters.map { Network.valueOf(it) }\n    }\n    set(value) {\n      preferences.edit { putStringSet(MY_SHOWS_NETWORKS, value.map { it.name }.toSet()) }\n    }\n  var myShowsGenres: List<Genre>\n    get() {\n      val filters = preferences.getStringSet(MY_SHOWS_GENRES, emptySet()) ?: emptySet()\n      return filters.map { Genre.valueOf(it) }\n    }\n    set(value) {\n      preferences.edit { putStringSet(MY_SHOWS_GENRES, value.map { it.name }.toSet()) }\n    }\n\n  var watchlistShowsUpcoming by BooleanPreference(preferences, WATCHLIST_SHOWS_UPCOMING, false)\n  var watchlistShowsNetworks: List<Network>\n    get() {\n      val filters = preferences.getStringSet(WATCHLIST_SHOWS_NETWORKS, emptySet()) ?: emptySet()\n      return filters.map { Network.valueOf(it) }\n    }\n    set(value) {\n      preferences.edit { putStringSet(WATCHLIST_SHOWS_NETWORKS, value.map { it.name }.toSet()) }\n    }\n  var watchlistShowsGenres: List<Genre>\n    get() {\n      val filters = preferences.getStringSet(WATCHLIST_SHOWS_GENRES, emptySet()) ?: emptySet()\n      return filters.map { Genre.valueOf(it) }\n    }\n    set(value) {\n      preferences.edit { putStringSet(WATCHLIST_SHOWS_GENRES, value.map { it.name }.toSet()) }\n    }\n\n  var hiddenShowsNetworks: List<Network>\n    get() {\n      val filters = preferences.getStringSet(HIDDEN_SHOWS_NETWORKS, emptySet()) ?: emptySet()\n      return filters.map { Network.valueOf(it) }\n    }\n    set(value) {\n      preferences.edit { putStringSet(HIDDEN_SHOWS_NETWORKS, value.map { it.name }.toSet()) }\n    }\n  var hiddenShowsGenres: List<Genre>\n    get() {\n      val filters = preferences.getStringSet(HIDDEN_SHOWS_GENRES, emptySet()) ?: emptySet()\n      return filters.map { Genre.valueOf(it) }\n    }\n    set(value) {\n      preferences.edit { putStringSet(HIDDEN_SHOWS_GENRES, value.map { it.name }.toSet()) }\n    }\n\n  // Movies\n\n  var myMoviesGenres: List<Genre>\n    get() {\n      val filters = preferences.getStringSet(MY_MOVIES_GENRES, emptySet()) ?: emptySet()\n      return filters.map { Genre.valueOf(it) }\n    }\n    set(value) {\n      preferences.edit { putStringSet(MY_MOVIES_GENRES, value.map { it.name }.toSet()) }\n    }\n\n  var watchlistMoviesUpcoming by BooleanPreference(preferences, WATCHLIST_MOVIES_UPCOMING, false)\n  var watchlistMoviesGenres: List<Genre>\n    get() {\n      val filters = preferences.getStringSet(WATCHLIST_MOVIES_GENRES, emptySet()) ?: emptySet()\n      return filters.map { Genre.valueOf(it) }\n    }\n    set(value) {\n      preferences.edit { putStringSet(WATCHLIST_MOVIES_GENRES, value.map { it.name }.toSet()) }\n    }\n\n  var hiddenMoviesGenres: List<Genre>\n    get() {\n      val filters = preferences.getStringSet(HIDDEN_MOVIES_GENRES, emptySet()) ?: emptySet()\n      return filters.map { Genre.valueOf(it) }\n    }\n    set(value) {\n      preferences.edit { putStringSet(HIDDEN_MOVIES_GENRES, value.map { it.name }.toSet()) }\n    }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/settings/SettingsRepository.kt",
    "content": "package com.michaldrabik.repository.settings\n\nimport android.app.UiModeManager.MODE_NIGHT_YES\nimport android.content.SharedPreferences\nimport androidx.core.content.edit\nimport com.michaldrabik.common.Config.DEFAULT_COUNTRY\nimport com.michaldrabik.common.Config.DEFAULT_DATE_FORMAT\nimport com.michaldrabik.common.Config.DEFAULT_LANGUAGE\nimport com.michaldrabik.common.Config.DEFAULT_NEWS_VIEW_TYPE\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.utilities.BooleanPreference\nimport com.michaldrabik.repository.utilities.EnumPreference\nimport com.michaldrabik.repository.utilities.LongPreference\nimport com.michaldrabik.repository.utilities.StringPreference\nimport com.michaldrabik.ui_model.NewsItem\nimport com.michaldrabik.ui_model.ProgressNextEpisodeType\nimport com.michaldrabik.ui_model.ProgressNextEpisodeType.LAST_WATCHED\nimport com.michaldrabik.ui_model.ProgressType\nimport com.michaldrabik.ui_model.Settings\nimport kotlinx.coroutines.withContext\nimport java.util.UUID\nimport javax.inject.Inject\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Singleton\nclass SettingsRepository @Inject constructor(\n  val sorting: SettingsSortRepository,\n  val filters: SettingsFiltersRepository,\n  val widgets: SettingsWidgetsRepository,\n  val viewMode: SettingsViewModeRepository,\n  val spoilers: SettingsSpoilersRepository,\n  private val dispatchers: CoroutineDispatchers,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers,\n  @Named(\"miscPreferences\") private var preferences: SharedPreferences,\n) {\n\n  companion object Key {\n    const val LANGUAGE = \"KEY_LANGUAGE\"\n    internal const val PREMIUM = \"KEY_PREMIUM\"\n    private const val COUNTRY = \"KEY_COUNTRY\"\n    private const val DATE_FORMAT = \"KEY_DATE_FORMAT\"\n    private const val MODE = \"KEY_MOVIES_MODE\"\n    private const val MOVIES_ENABLED = \"KEY_MOVIES_ENABLED\"\n    private const val NEWS_ENABLED = \"KEY_NEWS_ENABLED\"\n    private const val TWITTER_AD_ENABLED = \"TWITTER_AD_ENABLED\"\n    private const val PROGRESS_PERCENT = \"KEY_PROGRESS_PERCENT\"\n    private const val STREAMINGS_ENABLED = \"KEY_STREAMINGS_ENABLED\"\n    private const val THEME = \"KEY_THEME\"\n    private const val USER_ID = \"KEY_USER_ID\"\n    private const val INSTALL_TIMESTAMP = \"INSTALL_TIMESTAMP\"\n    private const val PROGRESS_UPCOMING_COLLAPSED = \"PROGRESS_UPCOMING_COLLAPSED\"\n    private const val PROGRESS_UPCOMING_DAYS = \"PROGRESS_UPCOMING_DAYS\"\n    private const val PROGRESS_ON_HOLD_COLLAPSED = \"PROGRESS_ON_HOLD_COLLAPSED\"\n    private const val PROGRESS_NEXT_EPISODE_TYPE = \"PROGRESS_NEXT_EPISODE_TYPE\"\n    private const val NEWS_FILTERS = \"NEWS_FILTERS\"\n    private const val NEWS_VIEW_TYPE = \"NEWS_VIEW_TYPE\"\n    private const val LOCALE_INITIALISED = \"LOCALE_INITIALISED\"\n  }\n\n  suspend fun isInitialized() =\n    withContext(dispatchers.IO) {\n      localSource.settings.getCount() > 0\n    }\n\n  suspend fun load(): Settings {\n    val settingsDb = withContext(dispatchers.IO) {\n      localSource.settings.getAll()\n    }\n    return mappers.settings.fromDatabase(settingsDb)\n  }\n\n  suspend fun update(settings: Settings) {\n    withContext(dispatchers.IO) {\n      transactions.withTransaction {\n        val settingsDb = mappers.settings.toDatabase(settings)\n        localSource.settings.upsert(settingsDb)\n      }\n    }\n  }\n\n  var installTimestamp by LongPreference(preferences, INSTALL_TIMESTAMP, 0L)\n  var isPremium by BooleanPreference(preferences, PREMIUM)\n  var streamingsEnabled by BooleanPreference(preferences, STREAMINGS_ENABLED, true)\n  var isMoviesEnabled by BooleanPreference(preferences, MOVIES_ENABLED, true)\n  var isNewsEnabled by BooleanPreference(preferences, NEWS_ENABLED)\n  var isTwitterAdEnabled by BooleanPreference(preferences, TWITTER_AD_ENABLED, true)\n  var language by StringPreference(preferences, LANGUAGE, DEFAULT_LANGUAGE)\n  var country by StringPreference(preferences, COUNTRY, DEFAULT_COUNTRY)\n  var dateFormat by StringPreference(preferences, DATE_FORMAT, DEFAULT_DATE_FORMAT)\n\n  var progressUpcomingDays by LongPreference(preferences, PROGRESS_UPCOMING_DAYS, 90)\n  var isProgressUpcomingCollapsed by BooleanPreference(preferences, PROGRESS_UPCOMING_COLLAPSED)\n  var isProgressOnHoldCollapsed by BooleanPreference(preferences, PROGRESS_ON_HOLD_COLLAPSED)\n  var progressNextEpisodeType by EnumPreference(preferences, PROGRESS_NEXT_EPISODE_TYPE, LAST_WATCHED, ProgressNextEpisodeType::class.java)\n  var newsViewType by StringPreference(preferences, NEWS_VIEW_TYPE, DEFAULT_NEWS_VIEW_TYPE)\n  var isLocaleInitialised by BooleanPreference(preferences, LOCALE_INITIALISED, false)\n\n  var mode: Mode\n    get() {\n      val default = Mode.SHOWS.name\n      return Mode.valueOf(preferences.getString(MODE, default) ?: default)\n    }\n    set(value) = preferences.edit(true) { putString(MODE, value.name) }\n\n  var theme: Int\n    get() {\n      if (!isPremium) return MODE_NIGHT_YES\n      return preferences.getInt(THEME, MODE_NIGHT_YES)\n    }\n    set(value) = preferences.edit(true) { putInt(THEME, value) }\n\n  var progressPercentType: ProgressType\n    get() {\n      val setting = preferences.getString(PROGRESS_PERCENT, ProgressType.AIRED.name) ?: ProgressType.AIRED.name\n      return ProgressType.valueOf(setting)\n    }\n    set(value) = preferences.edit(true) { putString(PROGRESS_PERCENT, value.name) }\n\n  val userId\n    get() = when (val id = preferences.getString(USER_ID, null)) {\n      null -> {\n        val uuid = UUID.randomUUID().toString().take(13)\n        preferences.edit().putString(USER_ID, uuid).apply()\n        uuid\n      }\n      else -> id\n    }\n\n  var newsFilters: List<NewsItem.Type>\n    get() {\n      val filters = preferences.getString(NEWS_FILTERS, null)\n      return when {\n        filters.isNullOrBlank() -> emptyList()\n        else -> filters.split(\",\").map { NewsItem.Type.fromSlug(it) }\n      }\n    }\n    set(value) {\n      preferences.edit { putString(NEWS_FILTERS, value.joinToString(\",\") { it.slug }) }\n    }\n\n  suspend fun revokePremium() {\n    val settings = load()\n    update(settings.copy(traktQuickRateEnabled = false))\n    isPremium = false\n    theme = MODE_NIGHT_YES\n    isNewsEnabled = false\n    widgets.revokePremium()\n  }\n\n  suspend fun clearLanguageLogs() {\n    with(localSource) {\n      transactions.withTransaction {\n        translationsShowsSyncLog.deleteAll()\n        translationsMoviesSyncLog.deleteAll()\n      }\n    }\n  }\n\n  suspend fun clearUnusedTranslations(input: List<String>) {\n    with(localSource) {\n      transactions.withTransaction {\n        showTranslations.deleteByLanguage(input)\n        movieTranslations.deleteByLanguage(input)\n        episodesTranslations.deleteByLanguage(input)\n        people.deleteTranslations()\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/settings/SettingsSortRepository.kt",
    "content": "package com.michaldrabik.repository.settings\n\nimport android.content.SharedPreferences\nimport com.michaldrabik.repository.utilities.BooleanPreference\nimport com.michaldrabik.repository.utilities.EnumPreference\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SortType.ASCENDING\nimport javax.inject.Inject\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Singleton\nclass SettingsSortRepository @Inject constructor(\n  @Named(\"miscPreferences\") private var preferences: SharedPreferences,\n) {\n\n  companion object Key {\n    private const val PROGRESS_SHOWS_SORT_ORDER = \"PROGRESS_SHOWS_SORT_ORDER\"\n    private const val PROGRESS_SHOWS_SORT_TYPE = \"PROGRESS_SHOWS_SORT_TYPE\"\n    private const val PROGRESS_SHOWS_NEW_AT_TOP = \"PROGRESS_SHOWS_NEW_AT_TOP\"\n    private const val WATCHLIST_SHOWS_SORT_ORDER = \"WATCHLIST_SHOWS_SORT_ORDER\"\n    private const val WATCHLIST_SHOWS_SORT_TYPE = \"WATCHLIST_SHOWS_SORT_TYPE\"\n    private const val HIDDEN_SHOWS_SORT_ORDER = \"HIDDEN_SHOWS_SORT_ORDER\"\n    private const val HIDDEN_SHOWS_SORT_TYPE = \"HIDDEN_SHOWS_SORT_TYPE\"\n    private const val MY_SHOWS_ALL_SORT_ORDER = \"MY_SHOWS_ALL_SORT_ORDER\"\n    private const val MY_SHOWS_ALL_SORT_TYPE = \"MY_SHOWS_ALL_SORT_TYPE\"\n\n    private const val PROGRESS_MOVIES_SORT_ORDER = \"PROGRESS_MOVIES_SORT_ORDER\"\n    private const val PROGRESS_MOVIES_SORT_TYPE = \"PROGRESS_MOVIES_SORT_TYPE\"\n    private const val WATCHLIST_MOVIES_SORT_ORDER = \"WATCHLIST_MOVIES_SORT_ORDER\"\n    private const val WATCHLIST_MOVIES_SORT_TYPE = \"WATCHLIST_MOVIES_SORT_TYPE\"\n    private const val HIDDEN_MOVIES_SORT_ORDER = \"HIDDEN_MOVIES_SORT_ORDER\"\n    private const val HIDDEN_MOVIES_SORT_TYPE = \"HIDDEN_MOVIES_SORT_TYPE\"\n    private const val MY_MOVIES_ALL_SORT_ORDER = \"MY_MOVIES_ALL_SORT_ORDER\"\n    private const val MY_MOVIES_ALL_SORT_TYPE = \"MY_MOVIES_ALL_SORT_TYPE\"\n\n    private const val LISTS_SORT_ORDER = \"LISTS_SORT_ORDER\"\n    private const val LISTS_SORT_TYPE = \"LISTS_SORT_TYPE\"\n  }\n\n  var progressShowsNewAtTop by BooleanPreference(preferences, PROGRESS_SHOWS_NEW_AT_TOP, false)\n  var progressShowsSortOrder by EnumPreference(preferences, PROGRESS_SHOWS_SORT_ORDER, NAME, SortOrder::class.java)\n  var progressShowsSortType by EnumPreference(preferences, PROGRESS_SHOWS_SORT_TYPE, ASCENDING, SortType::class.java)\n  var watchlistShowsSortOrder by EnumPreference(preferences, WATCHLIST_SHOWS_SORT_ORDER, NAME, SortOrder::class.java)\n  var watchlistShowsSortType by EnumPreference(preferences, WATCHLIST_SHOWS_SORT_TYPE, ASCENDING, SortType::class.java)\n  var hiddenShowsSortOrder by EnumPreference(preferences, HIDDEN_SHOWS_SORT_ORDER, NAME, SortOrder::class.java)\n  var hiddenShowsSortType by EnumPreference(preferences, HIDDEN_SHOWS_SORT_TYPE, ASCENDING, SortType::class.java)\n  var myShowsAllSortOrder by EnumPreference(preferences, MY_SHOWS_ALL_SORT_ORDER, NAME, SortOrder::class.java)\n  var myShowsAllSortType by EnumPreference(preferences, MY_SHOWS_ALL_SORT_TYPE, ASCENDING, SortType::class.java)\n\n  var progressMoviesSortOrder by EnumPreference(preferences, PROGRESS_MOVIES_SORT_ORDER, NAME, SortOrder::class.java)\n  var progressMoviesSortType by EnumPreference(preferences, PROGRESS_MOVIES_SORT_TYPE, ASCENDING, SortType::class.java)\n  var watchlistMoviesSortOrder by EnumPreference(preferences, WATCHLIST_MOVIES_SORT_ORDER, NAME, SortOrder::class.java)\n  var watchlistMoviesSortType by EnumPreference(preferences, WATCHLIST_MOVIES_SORT_TYPE, ASCENDING, SortType::class.java)\n  var hiddenMoviesSortOrder by EnumPreference(preferences, HIDDEN_MOVIES_SORT_ORDER, NAME, SortOrder::class.java)\n  var hiddenMoviesSortType by EnumPreference(preferences, HIDDEN_MOVIES_SORT_TYPE, ASCENDING, SortType::class.java)\n  var myMoviesAllSortOrder by EnumPreference(preferences, MY_MOVIES_ALL_SORT_ORDER, NAME, SortOrder::class.java)\n  var myMoviesAllSortType by EnumPreference(preferences, MY_MOVIES_ALL_SORT_TYPE, ASCENDING, SortType::class.java)\n\n  var listsAllSortOrder by EnumPreference(preferences, LISTS_SORT_ORDER, NAME, SortOrder::class.java)\n  var listsAllSortType by EnumPreference(preferences, LISTS_SORT_TYPE, ASCENDING, SortType::class.java)\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/settings/SettingsSpoilersRepository.kt",
    "content": "package com.michaldrabik.repository.settings\n\nimport android.content.SharedPreferences\nimport com.michaldrabik.repository.utilities.BooleanPreference\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport javax.inject.Inject\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Singleton\nclass SettingsSpoilersRepository @Inject constructor(\n  @Named(\"spoilersPreferences\") private var preferences: SharedPreferences,\n) {\n\n  companion object Key {\n    private const val SHOWS_UNCOLLECTED_SHOWS_HIDDEN = \"SHOWS_UNCOLLECTED_SHOWS_HIDDEN\"\n    private const val SHOWS_UNCOLLECTED_SHOWS_RATINGS_HIDDEN = \"SHOWS_UNCOLLECTED_SHOWS_RATINGS_HIDDEN\"\n    private const val SHOWS_MY_SHOWS_HIDDEN = \"SHOWS_MY_SHOWS_HIDDEN\"\n    private const val SHOWS_MY_SHOWS_RATINGS_HIDDEN = \"SHOWS_MY_SHOWS_RATINGS_HIDDEN\"\n    private const val SHOWS_WATCHLIST_SHOWS_HIDDEN = \"SHOWS_WATCHLIST_SHOWS_HIDDEN\"\n    private const val SHOWS_WATCHLIST_SHOWS_RATINGS_HIDDEN = \"SHOWS_WATCHLIST_SHOWS_RATINGS_HIDDEN\"\n    private const val SHOWS_HIDDEN_SHOWS_HIDDEN = \"SHOWS_HIDDEN_SHOWS_HIDDEN\"\n    private const val SHOWS_HIDDEN_SHOWS_RATINGS_HIDDEN = \"SHOWS_HIDDEN_SHOWS_RATINGS_HIDDEN\"\n\n    private const val MOVIES_UNCOLLECTED_MOVIES_HIDDEN = \"MOVIES_UNCOLLECTED_MOVIES_HIDDEN\"\n    private const val MOVIES_UNCOLLECTED_MOVIES_RATINGS_HIDDEN = \"MOVIES_UNCOLLECTED_MOVIES_RATINGS_HIDDEN\"\n    private const val MOVIES_MY_MOVIES_HIDDEN = \"MOVIES_MY_MOVIES_HIDDEN\"\n    private const val MOVIES_MY_MOVIES_RATINGS_HIDDEN = \"MOVIES_MY_MOVIES_RATINGS_HIDDEN\"\n    private const val MOVIES_WATCHLIST_MOVIES_HIDDEN = \"MOVIES_WATCHLIST_MOVIES_HIDDEN\"\n    private const val MOVIES_WATCHLIST_MOVIES_RATINGS_HIDDEN = \"MOVIES_WATCHLIST_MOVIES_RATINGS_HIDDEN\"\n    private const val MOVIES_HIDDEN_MOVIES_HIDDEN = \"MOVIES_HIDDEN_MOVIES_HIDDEN\"\n    private const val MOVIES_HIDDEN_MOVIES_RATINGS_HIDDEN = \"MOVIES_HIDDEN_MOVIES_RATINGS_HIDDEN\"\n\n    private const val EPISODES_TITLE_HIDDEN = \"EPISODES_TITLE_HIDDEN\"\n    private const val EPISODES_DESCRIPTION_HIDDEN = \"EPISODES_DESCRIPTION_HIDDEN\"\n    private const val EPISODES_RATING_HIDDEN = \"EPISODES_RATING_HIDDEN\"\n    private const val EPISODES_IMAGE_HIDDEN = \"EPISODES_IMAGE_HIDDEN\"\n\n    private const val TAP_TO_REVEAL = \"TAP_TO_REVEAL\"\n  }\n\n  var isMyShowsHidden by BooleanPreference(preferences, SHOWS_MY_SHOWS_HIDDEN, false)\n  var isMyShowsRatingsHidden by BooleanPreference(preferences, SHOWS_MY_SHOWS_RATINGS_HIDDEN, false)\n  var isWatchlistShowsHidden by BooleanPreference(preferences, SHOWS_WATCHLIST_SHOWS_HIDDEN, false)\n  var isWatchlistShowsRatingsHidden by BooleanPreference(preferences, SHOWS_WATCHLIST_SHOWS_RATINGS_HIDDEN, false)\n  var isHiddenShowsHidden by BooleanPreference(preferences, SHOWS_HIDDEN_SHOWS_HIDDEN, false)\n  var isHiddenShowsRatingsHidden by BooleanPreference(preferences, SHOWS_HIDDEN_SHOWS_RATINGS_HIDDEN, false)\n  var isUncollectedShowsHidden by BooleanPreference(preferences, SHOWS_UNCOLLECTED_SHOWS_HIDDEN, false)\n  var isUncollectedShowsRatingsHidden by BooleanPreference(preferences, SHOWS_UNCOLLECTED_SHOWS_RATINGS_HIDDEN, false)\n\n  var isMyMoviesHidden by BooleanPreference(preferences, MOVIES_MY_MOVIES_HIDDEN, false)\n  var isMyMoviesRatingsHidden by BooleanPreference(preferences, MOVIES_MY_MOVIES_RATINGS_HIDDEN, false)\n  var isWatchlistMoviesHidden by BooleanPreference(preferences, MOVIES_WATCHLIST_MOVIES_HIDDEN, false)\n  var isWatchlistMoviesRatingsHidden by BooleanPreference(preferences, MOVIES_WATCHLIST_MOVIES_RATINGS_HIDDEN, false)\n  var isHiddenMoviesHidden by BooleanPreference(preferences, MOVIES_HIDDEN_MOVIES_HIDDEN, false)\n  var isHiddenMoviesRatingsHidden by BooleanPreference(preferences, MOVIES_HIDDEN_MOVIES_RATINGS_HIDDEN, false)\n  var isUncollectedMoviesHidden by BooleanPreference(preferences, MOVIES_UNCOLLECTED_MOVIES_HIDDEN, false)\n  var isUncollectedMoviesRatingsHidden by BooleanPreference(preferences, MOVIES_UNCOLLECTED_MOVIES_RATINGS_HIDDEN, false)\n\n  var isEpisodesTitleHidden by BooleanPreference(preferences, EPISODES_TITLE_HIDDEN, false)\n  var isEpisodesDescriptionHidden by BooleanPreference(preferences, EPISODES_DESCRIPTION_HIDDEN, false)\n  var isEpisodesRatingHidden by BooleanPreference(preferences, EPISODES_RATING_HIDDEN, false)\n  var isEpisodesImageHidden by BooleanPreference(preferences, EPISODES_IMAGE_HIDDEN, false)\n\n  var isTapToReveal by BooleanPreference(preferences, TAP_TO_REVEAL, false)\n\n  fun getAll(): SpoilersSettings = SpoilersSettings(\n    isMyShowsHidden = isMyShowsHidden,\n    isMyShowsRatingsHidden = isMyShowsRatingsHidden,\n    isMyMoviesHidden = isMyMoviesHidden,\n    isMyMoviesRatingsHidden = isMyMoviesRatingsHidden,\n    isWatchlistShowsHidden = isWatchlistShowsHidden,\n    isWatchlistShowsRatingsHidden = isWatchlistShowsRatingsHidden,\n    isWatchlistMoviesHidden = isWatchlistMoviesHidden,\n    isWatchlistMoviesRatingsHidden = isWatchlistMoviesRatingsHidden,\n    isHiddenShowsHidden = isHiddenShowsHidden,\n    isHiddenShowsRatingsHidden = isHiddenShowsRatingsHidden,\n    isHiddenMoviesHidden = isHiddenMoviesHidden,\n    isHiddenMoviesRatingsHidden = isHiddenMoviesRatingsHidden,\n    isNotCollectedShowsHidden = isUncollectedShowsHidden,\n    isNotCollectedShowsRatingsHidden = isUncollectedShowsRatingsHidden,\n    isNotCollectedMoviesHidden = isUncollectedMoviesHidden,\n    isNotCollectedMoviesRatingsHidden = isUncollectedMoviesRatingsHidden,\n    isEpisodeTitleHidden = isEpisodesTitleHidden,\n    isEpisodeDescriptionHidden = isEpisodesDescriptionHidden,\n    isEpisodeRatingHidden = isEpisodesRatingHidden,\n    isEpisodeImageHidden = isEpisodesImageHidden,\n    isTapToReveal = isTapToReveal\n  )\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/settings/SettingsViewModeRepository.kt",
    "content": "package com.michaldrabik.repository.settings\n\nimport android.content.SharedPreferences\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.utilities.IntPreference\nimport com.michaldrabik.repository.utilities.StringPreference\nimport javax.inject.Inject\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Singleton\nclass SettingsViewModeRepository @Inject constructor(\n  @Named(\"miscPreferences\") private var preferences: SharedPreferences,\n) {\n\n  companion object Key {\n    private const val MY_SHOWS_VIEW_MODE = \"MY_SHOWS_VIEW_MODE\"\n    private const val WATCHLIST_SHOWS_VIEW_MODE = \"WATCHLIST_SHOWS_VIEW_MODE\"\n    private const val HIDDEN_SHOWS_VIEW_MODE = \"HIDDEN_SHOWS_VIEW_MODE\"\n\n    private const val MY_MOVIES_VIEW_MODE = \"MY_MOVIES_VIEW_MODE\"\n    private const val WATCHLIST_MOVIES_VIEW_MODE = \"WATCHLIST_MOVIES_VIEW_MODE\"\n    private const val HIDDEN_MOVIES_VIEW_MODE = \"HIDDEN_MOVIES_VIEW_MODE\"\n\n    private const val CUSTOM_LIST_VIEW_MODE = \"CUSTOM_LIST_VIEW_MODE\"\n    private const val TABLET_GRID_SPAN_SIZE = \"TABLET_GRID_SPAN_SIZE\"\n  }\n\n  var myShowsViewMode by StringPreference(preferences, MY_SHOWS_VIEW_MODE, Config.DEFAULT_LIST_VIEW_MODE)\n  var watchlistShowsViewMode by StringPreference(preferences, WATCHLIST_SHOWS_VIEW_MODE, Config.DEFAULT_LIST_VIEW_MODE)\n  var hiddenShowsViewMode by StringPreference(preferences, HIDDEN_SHOWS_VIEW_MODE, Config.DEFAULT_LIST_VIEW_MODE)\n\n  var myMoviesViewMode by StringPreference(preferences, MY_MOVIES_VIEW_MODE, Config.DEFAULT_LIST_VIEW_MODE)\n  var watchlistMoviesViewMode by StringPreference(preferences, WATCHLIST_MOVIES_VIEW_MODE, Config.DEFAULT_LIST_VIEW_MODE)\n  var hiddenMoviesViewMode by StringPreference(preferences, HIDDEN_MOVIES_VIEW_MODE, Config.DEFAULT_LIST_VIEW_MODE)\n\n  var customListsViewMode by StringPreference(preferences, CUSTOM_LIST_VIEW_MODE, Config.DEFAULT_LIST_VIEW_MODE)\n  var tabletGridSpanSize by IntPreference(preferences, TABLET_GRID_SPAN_SIZE, Config.DEFAULT_LISTS_GRID_SPAN)\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/settings/SettingsWidgetsRepository.kt",
    "content": "package com.michaldrabik.repository.settings\n\nimport android.app.UiModeManager\nimport android.content.SharedPreferences\nimport androidx.core.content.edit\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.repository.utilities.BooleanPreference\nimport com.michaldrabik.ui_model.CalendarMode\nimport javax.inject.Inject\nimport javax.inject.Named\nimport javax.inject.Singleton\n\n@Singleton\nclass SettingsWidgetsRepository @Inject constructor(\n  @Named(\"miscPreferences\") private var preferences: SharedPreferences\n) {\n\n  companion object Key {\n    private const val THEME_WIDGET = \"KEY_THEME_WIDGET\"\n    private const val THEME_WIDGET_TRANSPARENT = \"KEY_THEME_WIDGET_TRANSPARENT\"\n\n    private const val WIDGET_CALENDAR_MODE = \"WIDGET_CALENDAR_MODE\"\n    private const val WIDGET_CALENDAR_MOVIES_MODE = \"WIDGET_CALENDAR_MOVIES_MODE\"\n  }\n\n  val isPremium by BooleanPreference(preferences, SettingsRepository.PREMIUM)\n\n  var widgetsTheme: Int\n    get() {\n      if (!isPremium) return UiModeManager.MODE_NIGHT_YES\n      return preferences.getInt(THEME_WIDGET, UiModeManager.MODE_NIGHT_YES)\n    }\n    set(value) = preferences.edit(true) { putInt(THEME_WIDGET, value) }\n\n  var widgetsTransparency: Int\n    get() {\n      if (!isPremium) return 100\n      return preferences.getInt(THEME_WIDGET_TRANSPARENT, 100)\n    }\n    set(value) = preferences.edit(true) { putInt(THEME_WIDGET_TRANSPARENT, value) }\n\n  fun getWidgetCalendarMode(mode: Mode, widgetId: Int): CalendarMode {\n    val default = CalendarMode.PRESENT_FUTURE.name\n    val key = when (mode) {\n      Mode.SHOWS -> WIDGET_CALENDAR_MODE\n      Mode.MOVIES -> WIDGET_CALENDAR_MOVIES_MODE\n    }\n    val value = preferences.getString(\"$key$widgetId\", default) ?: default\n    return CalendarMode.valueOf(value)\n  }\n\n  fun setWidgetCalendarMode(mode: Mode, widgetId: Int, calendarMode: CalendarMode) {\n    val key = when (mode) {\n      Mode.SHOWS -> WIDGET_CALENDAR_MODE\n      Mode.MOVIES -> WIDGET_CALENDAR_MOVIES_MODE\n    }\n    preferences.edit(true) { putString(\"$key$widgetId\", calendarMode.name) }\n  }\n\n  fun revokePremium() {\n    widgetsTheme = UiModeManager.MODE_NIGHT_YES\n    widgetsTransparency = 100\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/shows/DiscoverShowsRepository.kt",
    "content": "package com.michaldrabik.repository.shows\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.DiscoverShow\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.Config.TRAKT_TRENDING_SHOWS_LIMIT\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.Network\nimport com.michaldrabik.ui_model.Show\nimport javax.inject.Inject\n\nclass DiscoverShowsRepository @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers\n) {\n\n  suspend fun isCacheValid(): Boolean {\n    val stamp = localSource.discoverShows.getMostRecent()?.createdAt ?: 0\n    return nowUtcMillis() - stamp < Config.DISCOVER_SHOWS_CACHE_DURATION\n  }\n\n  suspend fun loadAllCached(): List<Show> {\n    val cachedShows = localSource.discoverShows.getAll().map { it.idTrakt }\n    val shows = localSource.shows.getAll(cachedShows)\n\n    return cachedShows\n      .map { id -> shows.first { it.idTrakt == id } }\n      .map { mappers.show.fromDatabase(it) }\n  }\n\n  // TODO This logic should probably sit in a case and not repository.\n  suspend fun loadAllRemote(\n    showAnticipated: Boolean,\n    showCollection: Boolean,\n    collectionSize: Int,\n    genres: List<Genre>,\n    networks: List<Network>\n  ): List<Show> {\n    val remoteShows = mutableListOf<Show>()\n    val anticipatedShows = mutableListOf<Show>()\n    val popularShows = mutableListOf<Show>()\n\n    val genresQuery = genres.joinToString(\",\") { it.slug }\n    val networksQuery = networks.joinToString(\",\") { it.channels.joinToString(\",\") }\n\n    val limit =\n      if (showCollection) TRAKT_TRENDING_SHOWS_LIMIT\n      else TRAKT_TRENDING_SHOWS_LIMIT + (collectionSize / 2)\n    val trendingShows = remoteSource.trakt.fetchTrendingShows(genresQuery, networksQuery, limit)\n      .map { mappers.show.fromNetwork(it) }\n\n    if (genres.isNotEmpty() || networks.isNotEmpty()) {\n      // Wa are adding popular results for genres/networks filtered content to add more results.\n      val popular = remoteSource.trakt.fetchPopularShows(genresQuery, networksQuery)\n        .map { mappers.show.fromNetwork(it) }\n      popularShows.addAll(popular)\n    }\n\n    if (showAnticipated) {\n      val shows = remoteSource.trakt.fetchAnticipatedShows(genresQuery, networksQuery).map { mappers.show.fromNetwork(it) }.toMutableList()\n      anticipatedShows.addAll(shows)\n    }\n\n    trendingShows.forEachIndexed { index, show ->\n      addIfMissing(remoteShows, show)\n      if (index % 4 == 0 && anticipatedShows.isNotEmpty()) {\n        val element = anticipatedShows.removeAt(0)\n        addIfMissing(remoteShows, element)\n      }\n    }\n    popularShows.forEach { show -> addIfMissing(remoteShows, show) }\n\n    if (!showAnticipated) {\n      return remoteShows.filter { !it.status.isAnticipated() }\n    }\n\n    return remoteShows\n  }\n\n  suspend fun cacheDiscoverShows(shows: List<Show>) {\n    transactions.withTransaction {\n      val timestamp = nowUtcMillis()\n      localSource.shows.upsert(shows.map { mappers.show.toDatabase(it) })\n      localSource.discoverShows.replace(\n        shows.map {\n          DiscoverShow(\n            idTrakt = it.ids.trakt.id,\n            createdAt = timestamp,\n            updatedAt = timestamp\n          )\n        }\n      )\n    }\n  }\n\n  private fun addIfMissing(shows: MutableList<Show>, show: Show) {\n    if (shows.any { it.ids.trakt == show.ids.trakt }) return\n    shows.add(show)\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/shows/HiddenShowsRepository.kt",
    "content": "package com.michaldrabik.repository.shows\n\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.ArchiveShow\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.IdTrakt\nimport javax.inject.Inject\n\nclass HiddenShowsRepository @Inject constructor(\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers\n) {\n\n  suspend fun loadAll() =\n    localSource.archiveShows.getAll()\n      .map { mappers.show.fromDatabase(it) }\n\n  suspend fun loadAll(ids: List<IdTrakt>) =\n    localSource.archiveShows.getAll(ids.map { it.id })\n      .map { mappers.show.fromDatabase(it) }\n\n  suspend fun load(id: IdTrakt) =\n    localSource.archiveShows.getById(id.id)?.let {\n      mappers.show.fromDatabase(it)\n    }\n\n  suspend fun loadAllIds() = localSource.archiveShows.getAllTraktIds()\n\n  suspend fun insert(id: IdTrakt) {\n    val dbShow = ArchiveShow.fromTraktId(id.id, nowUtcMillis())\n    with(localSource) {\n      transactions.withTransaction {\n        archiveShows.insert(dbShow)\n        myShows.deleteById(id.id)\n        watchlistShows.deleteById(id.id)\n      }\n    }\n  }\n\n  suspend fun delete(id: IdTrakt) =\n    localSource.archiveShows.deleteById(id.id)\n\n  suspend fun exists(id: IdTrakt) =\n    localSource.archiveShows.getById(id.id) != null\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/shows/MyShowsRepository.kt",
    "content": "package com.michaldrabik.repository.shows\n\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.database.model.MyShow\nimport com.michaldrabik.data_local.sources.ArchiveShowsLocalDataSource\nimport com.michaldrabik.data_local.sources.MyShowsLocalDataSource\nimport com.michaldrabik.data_local.sources.WatchlistShowsLocalDataSource\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.IdTrakt\nimport javax.inject.Inject\n\nclass MyShowsRepository @Inject constructor(\n  private val myShowsLocalSource: MyShowsLocalDataSource,\n  private val watchlistShowsLocalSource: WatchlistShowsLocalDataSource,\n  private val hiddenShowsLocalDataSource: ArchiveShowsLocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers,\n) {\n\n  suspend fun load(id: IdTrakt) =\n    myShowsLocalSource.getById(id.id)?.let {\n      mappers.show.fromDatabase(it)\n    }\n\n  suspend fun loadAll() =\n    myShowsLocalSource.getAll()\n      .map { mappers.show.fromDatabase(it) }\n\n  suspend fun loadAll(ids: List<IdTrakt>) =\n    myShowsLocalSource.getAll(ids.map { it.id })\n      .map { mappers.show.fromDatabase(it) }\n\n  suspend fun loadAllRecent(amount: Int) =\n    myShowsLocalSource.getAllRecent(amount)\n      .map { mappers.show.fromDatabase(it) }\n\n  suspend fun loadAllIds() = myShowsLocalSource.getAllTraktIds()\n\n  suspend fun insert(id: IdTrakt, lastWatchedAt: Long) {\n    val nowUtc = nowUtcMillis()\n    val dbShow = MyShow.fromTraktId(\n      traktId = id.id,\n      createdAt = nowUtc,\n      updatedAt = nowUtc,\n      watchedAt = lastWatchedAt\n    )\n    transactions.withTransaction {\n      myShowsLocalSource.insert(listOf(dbShow))\n      watchlistShowsLocalSource.deleteById(id.id)\n      hiddenShowsLocalDataSource.deleteById(id.id)\n    }\n  }\n\n  suspend fun delete(id: IdTrakt) {\n    myShowsLocalSource.deleteById(id.id)\n  }\n\n  suspend fun exists(id: IdTrakt) =\n    myShowsLocalSource.checkExists(id.id)\n\n  suspend fun updateWatchedAt(idTrakt: Long, watchedAt: Long) {\n    myShowsLocalSource.updateWatchedAt(idTrakt, watchedAt)\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/shows/RelatedShowsRepository.kt",
    "content": "package com.michaldrabik.repository.shows\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.RelatedShow\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Show\nimport javax.inject.Inject\nimport kotlin.math.min\n\nclass RelatedShowsRepository @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers\n) {\n\n  suspend fun loadAll(show: Show, hiddenCount: Int): List<Show> {\n    val relatedShows = localSource.relatedShows.getAllById(show.traktId)\n    val latest = relatedShows.maxByOrNull { it.updatedAt }\n\n    if (latest != null && nowUtcMillis() - latest.updatedAt < Config.RELATED_CACHE_DURATION) {\n      val relatedShowsIds = relatedShows.map { it.idTrakt }\n      return localSource.shows.getAll(relatedShowsIds)\n        .map { mappers.show.fromDatabase(it) }\n    }\n\n    val remoteShows = remoteSource.trakt.fetchRelatedShows(show.traktId, min(hiddenCount, 10))\n      .map { mappers.show.fromNetwork(it) }\n\n    cacheRelatedShows(remoteShows, show.ids.trakt)\n\n    return remoteShows\n  }\n\n  private suspend fun cacheRelatedShows(shows: List<Show>, showId: IdTrakt) {\n    transactions.withTransaction {\n      val timestamp = nowUtcMillis()\n      localSource.shows.upsert(shows.map { mappers.show.toDatabase(it) })\n      localSource.relatedShows.deleteById(showId.id)\n      localSource.relatedShows.insert(\n        shows.map {\n          RelatedShow.fromTraktId(it.ids.trakt.id, showId.id, timestamp)\n        }\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/shows/ShowDetailsRepository.kt",
    "content": "package com.michaldrabik.repository.shows\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.IdImdb\nimport com.michaldrabik.ui_model.IdSlug\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Show\nimport javax.inject.Inject\n\nclass ShowDetailsRepository @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers,\n) {\n\n  suspend fun load(idTrakt: IdTrakt, force: Boolean = false): Show {\n    val localShow = localSource.shows.getById(idTrakt.id)\n    if (force || localShow == null || nowUtcMillis() - localShow.updatedAt > Config.SHOW_DETAILS_CACHE_DURATION) {\n      val remoteShow = remoteSource.trakt.fetchShow(idTrakt.id)\n      val show = mappers.show.fromNetwork(remoteShow)\n      localSource.shows.upsert(listOf(mappers.show.toDatabase(show)))\n      return show\n    }\n    return mappers.show.fromDatabase(localShow)\n  }\n\n  suspend fun find(idImdb: IdImdb): Show? {\n    val localShow = localSource.shows.getById(idImdb.id)\n    if (localShow != null) {\n      return mappers.show.fromDatabase(localShow)\n    }\n    return null\n  }\n\n  suspend fun find(idTmdb: IdTmdb): Show? {\n    val localShow = localSource.shows.getByTmdbId(idTmdb.id)\n    if (localShow != null) {\n      return mappers.show.fromDatabase(localShow)\n    }\n    return null\n  }\n\n  suspend fun find(idSlug: IdSlug): Show? {\n    val localShow = localSource.shows.getBySlug(idSlug.id)\n    if (localShow != null) {\n      return mappers.show.fromDatabase(localShow)\n    }\n    return null\n  }\n\n  suspend fun delete(idTrakt: IdTrakt) {\n    with(localSource) {\n      transactions.withTransaction {\n        shows.deleteById(idTrakt.id)\n        seasons.deleteAllForShow(idTrakt.id)\n        episodes.deleteAllForShow(idTrakt.id)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/shows/ShowStreamingsRepository.kt",
    "content": "package com.michaldrabik.repository.shows\n\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.StreamingsRepository\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.StreamingService\nimport java.time.ZonedDateTime\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass ShowStreamingsRepository @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n) : StreamingsRepository() {\n\n  suspend fun getLocalStreamings(show: Show, countryCode: String): Pair<List<StreamingService>, ZonedDateTime?> {\n    val localItems = localSource.showStreamings.getById(show.traktId)\n    val mappedItems = mappers.streamings.fromDatabaseShow(localItems, show.title, countryCode)\n\n    val processedItems = processItems(mappedItems, countryCode)\n    val date = localItems.firstOrNull()?.createdAt\n    return Pair(processedItems, date)\n  }\n\n  suspend fun loadRemoteStreamings(show: Show, countryCode: String): List<StreamingService> {\n    val remoteItems = remoteSource.tmdb.fetchShowWatchProviders(show.ids.tmdb.id, countryCode) ?: return emptyList()\n\n    val entities = mappers.streamings.toDatabaseShow(show.ids, remoteItems)\n    localSource.showStreamings.replace(show.traktId, entities)\n\n    return processItems(remoteItems, show.title, countryCode)\n  }\n\n  suspend fun deleteCache() = localSource.showStreamings.deleteAll()\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/shows/ShowsRepository.kt",
    "content": "package com.michaldrabik.repository.shows\n\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass ShowsRepository @Inject constructor(\n  val discoverShows: DiscoverShowsRepository,\n  val myShows: MyShowsRepository,\n  val watchlistShows: WatchlistShowsRepository,\n  val hiddenShows: HiddenShowsRepository,\n  val relatedShows: RelatedShowsRepository,\n  val detailsShow: ShowDetailsRepository\n)\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/shows/WatchlistShowsRepository.kt",
    "content": "package com.michaldrabik.repository.shows\n\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.WatchlistShow\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.IdTrakt\nimport javax.inject.Inject\n\nclass WatchlistShowsRepository @Inject constructor(\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers,\n) {\n\n  suspend fun loadAll() =\n    localSource.watchlistShows.getAll()\n      .map { mappers.show.fromDatabase(it) }\n\n  suspend fun loadAllIds() = localSource.watchlistShows.getAllTraktIds()\n\n  suspend fun load(id: IdTrakt) =\n    localSource.watchlistShows.getById(id.id)?.let {\n      mappers.show.fromDatabase(it)\n    }\n\n  suspend fun insert(id: IdTrakt) {\n    val dbShow = WatchlistShow.fromTraktId(id.id, nowUtcMillis())\n    with(localSource) {\n      transactions.withTransaction {\n        watchlistShows.insert(dbShow)\n        myShows.deleteById(id.id)\n        archiveShows.deleteById(id.id)\n      }\n    }\n  }\n\n  suspend fun delete(id: IdTrakt) =\n    localSource.watchlistShows.deleteById(id.id)\n\n  suspend fun exists(id: IdTrakt) =\n    localSource.watchlistShows.checkExists(id.id)\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/shows/ratings/ShowsExternalRatingsRepository.kt",
    "content": "package com.michaldrabik.repository.shows.ratings\n\nimport com.michaldrabik.common.ConfigVariant\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.Ratings\nimport com.michaldrabik.ui_model.Show\nimport java.util.Locale\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass ShowsExternalRatingsRepository @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n) {\n\n  suspend fun loadRatings(show: Show): Ratings {\n    val localRatings = localSource.showRatings.getById(show.traktId)\n    localRatings?.let {\n      if (nowUtcMillis() - it.updatedAt < ConfigVariant.RATINGS_CACHE_DURATION) {\n        return mappers.ratings.fromDatabase(it)\n      }\n    }\n\n    val remoteRatings = remoteSource.omdb.fetchOmdbData(show.ids.imdb.id)\n      .let { mappers.ratings.fromNetwork(it) }\n      .copy(trakt = Ratings.Value(String.format(Locale.ENGLISH, \"%.1f\", show.rating), false))\n\n    val dbRatings = mappers.ratings.toShowDatabase(show.ids.trakt, remoteRatings)\n    localSource.showRatings.upsert(dbRatings)\n\n    return remoteRatings\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/shows/ratings/ShowsRatingsRepository.kt",
    "content": "package com.michaldrabik.repository.shows.ratings\n\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.Rating\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.TraktRating\nimport kotlinx.coroutines.CoroutineExceptionHandler\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.supervisorScope\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass ShowsRatingsRepository @Inject constructor(\n  val external: ShowsExternalRatingsRepository,\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers\n) {\n\n  companion object {\n    private const val TYPE_SHOW = \"show\"\n    private const val TYPE_EPISODE = \"episode\"\n    private const val TYPE_SEASON = \"season\"\n    private const val CHUNK_SIZE = 250\n  }\n\n  suspend fun preloadRatings() = supervisorScope {\n\n    suspend fun preloadShowsRatings() {\n      val ratings = remoteSource.trakt.fetchShowsRatings()\n      val entities = ratings\n        .filter { it.rated_at != null && it.show.ids.trakt != null }\n        .map { mappers.userRatings.toDatabaseShow(it) }\n      localSource.ratings.replaceAll(entities, TYPE_SHOW)\n    }\n\n    suspend fun preloadEpisodesRatings() {\n      val ratings = remoteSource.trakt.fetchEpisodesRatings()\n      val entities = ratings\n        .filter { it.rated_at != null && it.episode.ids.trakt != null }\n        .map { mappers.userRatings.toDatabaseEpisode(it) }\n      localSource.ratings.replaceAll(entities, TYPE_EPISODE)\n    }\n\n    suspend fun preloadSeasonsRatings() {\n      val ratings = remoteSource.trakt.fetchSeasonsRatings()\n      val entities = ratings\n        .filter { it.rated_at != null && it.season.ids.trakt != null }\n        .map { mappers.userRatings.toDatabaseSeason(it) }\n      localSource.ratings.replaceAll(entities, TYPE_SEASON)\n    }\n\n    val errorHandler = CoroutineExceptionHandler { _, _ -> Timber.e(\"Failed to preload some of ratings.\") }\n    launch(errorHandler) { preloadShowsRatings() }\n    launch(errorHandler) { preloadEpisodesRatings() }\n    launch(errorHandler) { preloadSeasonsRatings() }\n  }\n\n  suspend fun loadShowsRatings(): List<TraktRating> {\n    val ratings = localSource.ratings.getAllByType(TYPE_SHOW)\n    return ratings.map {\n      mappers.userRatings.fromDatabase(it)\n    }\n  }\n\n  suspend fun loadRatings(shows: List<Show>): List<TraktRating> {\n    val ratings = mutableListOf<Rating>()\n    shows.chunked(CHUNK_SIZE).forEach { chunk ->\n      val items = localSource.ratings.getAllByType(chunk.map { it.traktId }, TYPE_SHOW)\n      ratings.addAll(items)\n    }\n    return ratings.map {\n      mappers.userRatings.fromDatabase(it)\n    }\n  }\n\n  suspend fun loadRatingsSeasons(seasons: List<Season>): List<TraktRating> {\n    val ratings = mutableListOf<Rating>()\n    seasons.chunked(CHUNK_SIZE).forEach { chunk ->\n      val items = localSource.ratings.getAllByType(chunk.map { it.ids.trakt.id }, TYPE_SEASON)\n      ratings.addAll(items)\n    }\n    return ratings.map {\n      mappers.userRatings.fromDatabase(it)\n    }\n  }\n\n  suspend fun loadRating(episode: Episode): TraktRating? {\n    val rating = localSource.ratings.getAllByType(listOf(episode.ids.trakt.id), TYPE_EPISODE)\n    return rating.firstOrNull()?.let {\n      mappers.userRatings.fromDatabase(it)\n    }\n  }\n\n  suspend fun loadRating(season: Season): TraktRating? {\n    val rating = localSource.ratings.getAllByType(listOf(season.ids.trakt.id), TYPE_SEASON)\n    return rating.firstOrNull()?.let {\n      mappers.userRatings.fromDatabase(it)\n    }\n  }\n\n  suspend fun addRating(show: Show, rating: Int) {\n    remoteSource.trakt.postRating(\n      mappers.show.toNetwork(show),\n      rating\n    )\n    val entity = mappers.userRatings.toDatabaseShow(show, rating, nowUtc())\n    localSource.ratings.replace(entity)\n  }\n\n  suspend fun addRating(episode: Episode, rating: Int) {\n    remoteSource.trakt.postRating(\n      mappers.episode.toNetwork(episode),\n      rating\n    )\n    val entity = mappers.userRatings.toDatabaseEpisode(episode, rating, nowUtc())\n    localSource.ratings.replace(entity)\n  }\n\n  suspend fun addRating(season: Season, rating: Int) {\n    remoteSource.trakt.postRating(\n      mappers.season.toNetwork(season),\n      rating\n    )\n    val entity = mappers.userRatings.toDatabaseSeason(season, rating, nowUtc())\n    localSource.ratings.replace(entity)\n  }\n\n  suspend fun deleteRating(show: Show) {\n    remoteSource.trakt.deleteRating(\n      mappers.show.toNetwork(show)\n    )\n    localSource.ratings.deleteByType(show.traktId, TYPE_SHOW)\n  }\n\n  suspend fun deleteRating(episode: Episode) {\n    remoteSource.trakt.deleteRating(\n      mappers.episode.toNetwork(episode)\n    )\n    localSource.ratings.deleteByType(episode.ids.trakt.id, TYPE_EPISODE)\n  }\n\n  suspend fun deleteRating(season: Season) {\n    remoteSource.trakt.deleteRating(\n      mappers.season.toNetwork(season)\n    )\n    localSource.ratings.deleteByType(season.ids.trakt.id, TYPE_SEASON)\n  }\n\n  suspend fun clear() {\n    with(localSource) {\n      transactions.withTransaction {\n        ratings.deleteAllByType(TYPE_EPISODE)\n        ratings.deleteAllByType(TYPE_SEASON)\n        ratings.deleteAllByType(TYPE_SHOW)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "repository/src/main/java/com/michaldrabik/repository/utilities/PreferencesDelegates.kt",
    "content": "package com.michaldrabik.repository.utilities\n\nimport android.content.SharedPreferences\nimport kotlin.properties.ReadWriteProperty\nimport kotlin.reflect.KProperty\n\nclass StringPreference(\n  private val sharedPreferences: SharedPreferences,\n  private val key: String,\n  private val defaultValue: String,\n) : ReadWriteProperty<Any, String> {\n\n  override fun getValue(thisRef: Any, property: KProperty<*>): String =\n    sharedPreferences.getString(key, defaultValue) ?: defaultValue\n\n  override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {\n    sharedPreferences.edit()\n      .putString(key, value)\n      .apply()\n  }\n}\n\nclass BooleanPreference(\n  private val sharedPreferences: SharedPreferences,\n  private val key: String,\n  private val defaultValue: Boolean = false,\n) : ReadWriteProperty<Any, Boolean> {\n\n  override fun getValue(thisRef: Any, property: KProperty<*>): Boolean =\n    sharedPreferences.getBoolean(key, defaultValue)\n\n  override fun setValue(thisRef: Any, property: KProperty<*>, value: Boolean) =\n    sharedPreferences.edit()\n      .putBoolean(key, value)\n      .apply()\n}\n\nclass IntPreference(\n  private val sharedPreferences: SharedPreferences,\n  private val key: String,\n  private val defaultValue: Int = 0,\n) : ReadWriteProperty<Any, Int> {\n\n  override fun getValue(thisRef: Any, property: KProperty<*>): Int =\n    sharedPreferences.getInt(key, defaultValue)\n\n  override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) =\n    sharedPreferences.edit()\n      .putInt(key, value)\n      .apply()\n}\n\nclass LongPreference(\n  private val sharedPreferences: SharedPreferences,\n  private val key: String,\n  private val defaultValue: Long = 0,\n) : ReadWriteProperty<Any, Long> {\n\n  override fun getValue(thisRef: Any, property: KProperty<*>): Long =\n    sharedPreferences.getLong(key, defaultValue)\n\n  override fun setValue(thisRef: Any, property: KProperty<*>, value: Long) =\n    sharedPreferences.edit()\n      .putLong(key, value)\n      .apply()\n}\n\nclass EnumPreference<T : Enum<T>>(\n  private val sharedPreferences: SharedPreferences,\n  private val key: String,\n  private val defaultValue: T,\n  private val clazz: Class<T>,\n) : ReadWriteProperty<Any, T> {\n\n  @Suppress(\"RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS\")\n  override fun getValue(thisRef: Any, property: KProperty<*>): T {\n    val enumName = sharedPreferences.getString(key, \"\")\n    return clazz.enumConstants.find { it.name == enumName } ?: defaultValue\n  }\n\n  override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {\n    sharedPreferences.edit()\n      .putString(key, value.name)\n      .apply()\n  }\n}\n"
  },
  {
    "path": "repository/src/test/java/com/michaldrabik/repository/DiscoverShowsRepositoryTest.kt",
    "content": "package com.michaldrabik.repository\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.database.dao.DiscoverShowsDao\nimport com.michaldrabik.data_local.database.dao.ShowsDao\nimport com.michaldrabik.data_local.database.model.DiscoverShow\nimport com.michaldrabik.repository.common.BaseMockTest\nimport com.michaldrabik.repository.shows.DiscoverShowsRepository\nimport com.michaldrabik.ui_model.Show\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.confirmVerified\nimport io.mockk.every\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.mockk\nimport kotlinx.coroutines.runBlocking\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\nimport java.util.concurrent.TimeUnit\nimport com.michaldrabik.data_local.database.model.Show as ShowDb\n\nclass DiscoverShowsRepositoryTest : BaseMockTest() {\n\n  @MockK lateinit var showsDao: ShowsDao\n  @MockK lateinit var discoverShowsDao: DiscoverShowsDao\n\n  private lateinit var SUT: DiscoverShowsRepository\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    SUT = DiscoverShowsRepository(cloud, database, transactions, mappers)\n    coEvery { database.shows } returns showsDao\n    coEvery { database.discoverShows } returns discoverShowsDao\n  }\n\n  @After\n  fun confirmSutVerified() {\n    confirmVerified(showsDao)\n    confirmVerified(discoverShowsDao)\n  }\n\n  @Test\n  fun `Should return true if cache is valid`() {\n    runBlocking {\n      val discoverShow = mockk<DiscoverShow> {\n        every { createdAt } returns nowUtcMillis() - TimeUnit.HOURS.toMillis(6)\n      }\n      coEvery { discoverShowsDao.getMostRecent() } returns discoverShow\n\n      assertThat(SUT.isCacheValid()).isTrue()\n      coVerify(exactly = 1) { discoverShowsDao.getMostRecent() }\n    }\n  }\n\n  @Test\n  fun `Should return false if cache is not valid`() {\n    runBlocking {\n      val discoverShow = mockk<DiscoverShow> {\n        every { createdAt } returns nowUtcMillis() - TimeUnit.HOURS.toMillis(13)\n      }\n      coEvery { discoverShowsDao.getMostRecent() } returns discoverShow\n\n      assertThat(SUT.isCacheValid()).isFalse()\n      coVerify(exactly = 1) { discoverShowsDao.getMostRecent() }\n    }\n  }\n\n  @Test\n  fun `Should load cached shows`() {\n    runBlocking {\n      val discoverShow = mockk<DiscoverShow> {\n        every { idTrakt } returns 10\n      }\n      val showDb = mockk<ShowDb>() {\n        every { idTrakt } returns 10\n      }\n\n      coEvery { discoverShowsDao.getAll() } returns listOf(discoverShow)\n      coEvery { showsDao.getAll(any()) } returns listOf(showDb)\n      coEvery { mappers.show.fromDatabase(any()) } returns Show.EMPTY\n\n      val shows = SUT.loadAllCached()\n\n      assertThat(shows).hasSize(1)\n      coVerify(exactly = 1) { discoverShowsDao.getAll() }\n      coVerify(exactly = 1) { showsDao.getAll(any()) }\n    }\n  }\n}\n"
  },
  {
    "path": "repository/src/test/java/com/michaldrabik/repository/MyShowsRepositoryTest.kt",
    "content": "package com.michaldrabik.repository\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.database.model.MyShow\nimport com.michaldrabik.data_local.sources.ArchiveShowsLocalDataSource\nimport com.michaldrabik.data_local.sources.MyShowsLocalDataSource\nimport com.michaldrabik.data_local.sources.WatchlistShowsLocalDataSource\nimport com.michaldrabik.repository.common.BaseMockTest\nimport com.michaldrabik.repository.shows.MyShowsRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Show\nimport io.mockk.coEvery\nimport io.mockk.coJustRun\nimport io.mockk.coVerify\nimport io.mockk.confirmVerified\nimport io.mockk.impl.annotations.RelaxedMockK\nimport io.mockk.slot\nimport kotlinx.coroutines.runBlocking\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\nimport com.michaldrabik.data_local.database.model.Show as ShowDb\n\nclass MyShowsRepositoryTest : BaseMockTest() {\n\n  @RelaxedMockK lateinit var myShowsLocalSource: MyShowsLocalDataSource\n  @RelaxedMockK lateinit var watchlistShowsLocalSource: WatchlistShowsLocalDataSource\n  @RelaxedMockK lateinit var hiddenShowsLocalDataSource: ArchiveShowsLocalDataSource\n  @RelaxedMockK lateinit var showDb: ShowDb\n\n  private lateinit var SUT: MyShowsRepository\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    SUT = MyShowsRepository(\n      myShowsLocalSource,\n      watchlistShowsLocalSource,\n      hiddenShowsLocalDataSource,\n      transactions,\n      mappers\n    )\n    coEvery { database.myShows } returns myShowsLocalSource\n    coEvery { database.watchlistShows } returns watchlistShowsLocalSource\n    coEvery { database.archiveShows } returns hiddenShowsLocalDataSource\n  }\n\n  @After\n  fun confirmSutVerified() {\n    confirmVerified(myShowsLocalSource)\n    confirmVerified(watchlistShowsLocalSource)\n    confirmVerified(hiddenShowsLocalDataSource)\n  }\n\n  @Test\n  fun `Should load and map single show by Trakt ID`() {\n    runBlocking {\n      val show = Show.EMPTY.copy(title = \"Test\")\n\n      coEvery { myShowsLocalSource.getById(any()) } returns showDb\n      coEvery { mappers.show.fromDatabase(any()) } returns show\n\n      val testShow = SUT.load(IdTrakt(1L))\n\n      assertThat(testShow?.title).isEqualTo(show.title)\n      coVerify(exactly = 1) { myShowsLocalSource.getById(any()) }\n      coVerify(exactly = 1) { mappers.show.fromDatabase(showDb) }\n    }\n  }\n\n  @Test\n  fun `Should load and map all shows`() {\n    runBlocking {\n      coEvery { myShowsLocalSource.getAll() } returns listOf(showDb)\n      coEvery { mappers.show.fromDatabase(any()) } returns Show.EMPTY\n\n      SUT.loadAll()\n\n      coVerify(exactly = 1) { myShowsLocalSource.getAll() }\n      coVerify(exactly = 1) { mappers.show.fromDatabase(showDb) }\n    }\n  }\n\n  @Test\n  fun `Should load all shows ids`() {\n    runBlocking {\n      coEvery { myShowsLocalSource.getAllTraktIds() } returns listOf(1L, 2L)\n\n      val ids = SUT.loadAllIds()\n\n      assertThat(ids).containsExactly(1L, 2L)\n      coVerify(exactly = 1) { myShowsLocalSource.getAllTraktIds() }\n    }\n  }\n\n  @Test\n  fun `Should load and map all shows by Trakt Ids`() {\n    runBlocking {\n      coEvery { myShowsLocalSource.getAll(any()) } returns listOf(showDb, showDb)\n      coEvery { mappers.show.fromDatabase(any()) } returns Show.EMPTY\n\n      val shows = SUT.loadAll(listOf(IdTrakt(1), IdTrakt(2)))\n\n      assertThat(shows).hasSize(2)\n      coVerify(exactly = 1) { myShowsLocalSource.getAll(listOf(1, 2)) }\n      coVerify(exactly = 2) { mappers.show.fromDatabase(showDb) }\n    }\n  }\n\n  @Test\n  fun `Should load and map all recents shows using amount`() {\n    runBlocking {\n      coEvery { myShowsLocalSource.getAllRecent(any()) } returns listOf(showDb, showDb)\n      coEvery { mappers.show.fromDatabase(any()) } returns Show.EMPTY\n\n      val shows = SUT.loadAllRecent(2)\n\n      assertThat(shows).hasSize(2)\n      coVerify(exactly = 1) { myShowsLocalSource.getAllRecent(2) }\n      coVerify(exactly = 2) { mappers.show.fromDatabase(showDb) }\n    }\n  }\n\n  @Test\n  fun `Should insert show into database using Trakt ID`() {\n    runBlocking {\n      val slot = slot<List<MyShow>>()\n      coJustRun { myShowsLocalSource.insert(capture(slot)) }\n\n      SUT.insert(IdTrakt(10L), 666)\n\n      slot.captured[0].run {\n        assertThat(id).isEqualTo(0)\n        assertThat(idTrakt).isEqualTo(10)\n        assertThat(createdAt).isGreaterThan(0L)\n        assertThat(updatedAt).isGreaterThan(0L)\n        assertThat(lastWatchedAt).isEqualTo(666)\n      }\n      coVerify(exactly = 1) { myShowsLocalSource.insert(any()) }\n      coVerify(exactly = 1) { watchlistShowsLocalSource.deleteById(any()) }\n      coVerify(exactly = 1) { hiddenShowsLocalDataSource.deleteById(any()) }\n    }\n  }\n\n  @Test\n  fun `Should delete show from database using Trakt ID`() {\n    runBlocking {\n      val slot = slot<Long>()\n      coJustRun { myShowsLocalSource.deleteById(capture(slot)) }\n\n      SUT.delete(IdTrakt(10L))\n\n      assertThat(slot.captured).isEqualTo(10L)\n      coVerify(exactly = 1) { myShowsLocalSource.deleteById(10L) }\n    }\n  }\n}\n"
  },
  {
    "path": "repository/src/test/java/com/michaldrabik/repository/PeopleRepositoryTest.kt",
    "content": "package com.michaldrabik.repository\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.data_local.database.dao.MoviesDao\nimport com.michaldrabik.data_local.database.dao.PeopleCreditsDao\nimport com.michaldrabik.data_local.database.dao.PeopleDao\nimport com.michaldrabik.data_local.database.dao.PeopleShowsMoviesDao\nimport com.michaldrabik.data_local.database.dao.ShowsDao\nimport com.michaldrabik.data_local.database.model.Movie\nimport com.michaldrabik.data_local.database.model.Show\nimport com.michaldrabik.data_remote.tmdb.TmdbRemoteDataSource\nimport com.michaldrabik.data_remote.trakt.TraktRemoteDataSource\nimport com.michaldrabik.data_remote.trakt.model.PersonCredit\nimport com.michaldrabik.repository.common.BaseMockTest\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_model.Person.Department\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.coVerifyOrder\nimport io.mockk.confirmVerified\nimport io.mockk.impl.annotations.RelaxedMockK\nimport io.mockk.mockk\nimport kotlinx.coroutines.runBlocking\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\nimport com.michaldrabik.data_local.database.model.Person as PersonDb\nimport com.michaldrabik.data_remote.trakt.model.Ids as IdsRemote\n\nclass PeopleRepositoryTest : BaseMockTest() {\n\n  @RelaxedMockK lateinit var peopleDao: PeopleDao\n  @RelaxedMockK lateinit var showsDao: ShowsDao\n  @RelaxedMockK lateinit var moviesDao: MoviesDao\n  @RelaxedMockK lateinit var peopleShowsMoviesDao: PeopleShowsMoviesDao\n  @RelaxedMockK lateinit var peopleCreditsDao: PeopleCreditsDao\n  @RelaxedMockK lateinit var person: PersonDb\n  @RelaxedMockK lateinit var tmdbApi: TmdbRemoteDataSource\n  @RelaxedMockK lateinit var traktApi: TraktRemoteDataSource\n  @RelaxedMockK lateinit var settingsRepository: SettingsRepository\n\n  private lateinit var SUT: PeopleRepository\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    SUT = PeopleRepository(settingsRepository, database, cloud, transactions, mappers)\n    coEvery { database.people } returns peopleDao\n    coEvery { database.shows } returns showsDao\n    coEvery { database.movies } returns moviesDao\n    coEvery { database.peopleCredits } returns peopleCreditsDao\n    coEvery { database.peopleShowsMovies } returns peopleShowsMoviesDao\n    coEvery { cloud.tmdb } returns tmdbApi\n    coEvery { cloud.trakt } returns traktApi\n  }\n\n  @After\n  fun confirmSutVerified() {\n    confirmVerified(peopleDao)\n  }\n\n  @Test\n  fun `Should return local data for shows properly`() = runBlocking {\n    coEvery { peopleShowsMoviesDao.getTimestampForShow(any()) } returns nowUtc().minusHours(10).toMillis()\n    coEvery { peopleDao.getAllForShow(any()) } returns listOf(person)\n\n    SUT.loadAllForShow(Ids.EMPTY.copy(trakt = IdTrakt(11)))\n\n    coVerifyOrder {\n      peopleShowsMoviesDao.getTimestampForShow(11)\n      peopleDao.getAllForShow(11)\n    }\n    coVerify(exactly = 0) { tmdbApi.fetchShowPeople(any()) }\n  }\n\n  @Test\n  fun `Should return remote data for shows properly`() = runBlocking {\n    coEvery { peopleShowsMoviesDao.getTimestampForShow(any()) } returns nowUtc().minusDays(10).toMillis()\n    coEvery { peopleDao.getAllForShow(any()) } returns listOf(person)\n\n    SUT.loadAllForShow(Ids.EMPTY.copy(trakt = IdTrakt(11), tmdb = IdTmdb(12)))\n\n    coVerifyOrder {\n      peopleShowsMoviesDao.getTimestampForShow(11)\n      peopleDao.getAllForShow(11)\n      tmdbApi.fetchShowPeople(12)\n      peopleDao.upsert(any())\n      peopleShowsMoviesDao.insertForShow(any(), 11)\n    }\n  }\n\n  @Test\n  fun `Should return local data for movies properly`() = runBlocking {\n    coEvery { peopleShowsMoviesDao.getTimestampForMovie(any()) } returns nowUtc().minusHours(10).toMillis()\n    coEvery { peopleDao.getAllForMovie(any()) } returns listOf(person)\n\n    SUT.loadAllForMovie(Ids.EMPTY.copy(trakt = IdTrakt(11)))\n\n    coVerifyOrder {\n      peopleShowsMoviesDao.getTimestampForMovie(11)\n      peopleDao.getAllForMovie(11)\n    }\n    coVerify(exactly = 0) { tmdbApi.fetchMoviePeople(any()) }\n  }\n\n  @Test\n  fun `Should return remote data for movies properly`() = runBlocking {\n    coEvery { peopleShowsMoviesDao.getTimestampForMovie(any()) } returns nowUtc().minusDays(10).toMillis()\n    coEvery { peopleDao.getAllForMovie(any()) } returns listOf(person)\n\n    SUT.loadAllForMovie(Ids.EMPTY.copy(trakt = IdTrakt(11), tmdb = IdTmdb(12)))\n\n    coVerifyOrder {\n      peopleShowsMoviesDao.getTimestampForMovie(11)\n      peopleDao.getAllForMovie(11)\n      tmdbApi.fetchMoviePeople(12)\n      peopleDao.upsert(any())\n      peopleShowsMoviesDao.insertForMovie(any(), 11)\n    }\n  }\n\n  @Test\n  fun `Should return shows items with image in the first place`() = runBlocking {\n    coEvery { peopleShowsMoviesDao.getTimestampForShow(any()) } returns nowUtc().minusHours(10).toMillis()\n\n    val person1 = mockk<PersonDb>(relaxed = true) {\n      coEvery { image } returns null\n      coEvery { department } returns \"Acting\"\n    }\n    val person2 = mockk<PersonDb>(relaxed = true) {\n      coEvery { image } returns \"test\"\n      coEvery { department } returns \"Acting\"\n    }\n    val person3 = mockk<PersonDb>(relaxed = true) {\n      coEvery { image } returns \"test\"\n      coEvery { department } returns \"Acting\"\n    }\n    coEvery { peopleDao.getAllForShow(any()) } returns listOf(person1, person2, person3)\n\n    val result = SUT.loadAllForShow(Ids.EMPTY.copy(trakt = IdTrakt(11)))\n    assertThat(result[Department.ACTING]!!.first().imagePath).isNotNull()\n\n    coVerify { peopleDao.getAllForShow(any()) }\n  }\n\n  @Test\n  fun `Should return movies items with image in the first place`() = runBlocking {\n    coEvery { peopleShowsMoviesDao.getTimestampForMovie(any()) } returns nowUtc().minusHours(10).toMillis()\n\n    val person1 = mockk<PersonDb>(relaxed = true) {\n      coEvery { image } returns null\n      coEvery { department } returns \"Acting\"\n    }\n    val person2 = mockk<PersonDb>(relaxed = true) {\n      coEvery { image } returns \"test\"\n      coEvery { department } returns \"Acting\"\n    }\n    val person3 = mockk<PersonDb>(relaxed = true) {\n      coEvery { image } returns \"test\"\n      coEvery { department } returns \"Acting\"\n    }\n    coEvery { peopleDao.getAllForMovie(any()) } returns listOf(person1, person2, person3)\n\n    val result = SUT.loadAllForMovie(Ids.EMPTY.copy(trakt = IdTrakt(11)))\n    assertThat(result[Department.ACTING]!!.first().imagePath).isNotNull()\n\n    coVerify { peopleDao.getAllForMovie(any()) }\n  }\n\n  @Test\n  fun `Should return empty credits if Trakt ID is not found for given TMDB ID`() = runBlocking {\n    val person = mockk<Person>(relaxed = true)\n    val personDb = mockk<PersonDb>(relaxed = true) {\n      coEvery { idTrakt } returns null\n    }\n    val ids = mockk<IdsRemote>(relaxed = true) {\n      coEvery { trakt } returns null\n    }\n    coEvery { peopleDao.getById(any()) } returns personDb\n    coEvery { traktApi.fetchPersonIds(any(), any()) } returns ids\n\n    val result = SUT.loadCredits(person)\n\n    assertThat(result).isEmpty()\n    coVerify { peopleDao.getById(any()) }\n    coVerify(exactly = 0) { peopleDao.updateTraktId(any(), any()) }\n  }\n\n  @Test\n  fun `Should return locally cached credits if Trakt ID is found and cache is valid`() = runBlocking {\n    val person = mockk<Person>(relaxed = true)\n    val personDb = mockk<PersonDb>(relaxed = true) {\n      coEvery { idTrakt } returns 1\n    }\n    val ids = mockk<IdsRemote>(relaxed = true) {\n      coEvery { trakt } returns 1\n    }\n    val show = mockk<Show>(relaxed = true)\n    val movie = mockk<Movie>(relaxed = true)\n    coEvery { peopleDao.getById(any()) } returns personDb\n    coEvery { traktApi.fetchPersonIds(any(), any()) } returns ids\n    coEvery { peopleCreditsDao.getTimestampForPerson(any()) } returns nowUtcMillis() - 100\n    coEvery { peopleCreditsDao.getAllShowsForPerson(any()) } returns listOf(show)\n    coEvery { peopleCreditsDao.getAllMoviesForPerson(any()) } returns listOf(movie)\n\n    val result = SUT.loadCredits(person)\n\n    assertThat(result).hasSize(2)\n    assertThat(result[0].show).isNotNull()\n    assertThat(result[1].movie).isNotNull()\n    coVerify { peopleDao.getById(any()) }\n    coVerify(exactly = 0) { peopleDao.updateTraktId(any(), any()) }\n    coVerify(exactly = 0) { traktApi.fetchPersonShowsCredits(any(), any()) }\n    coVerify(exactly = 0) { traktApi.fetchPersonMoviesCredits(any(), any()) }\n  }\n\n  @Test\n  fun `Should return remote credits if Trakt ID is found and cache is invalid`() = runBlocking {\n    val person = mockk<Person>(relaxed = true)\n    val personDb = mockk<PersonDb>(relaxed = true) {\n      coEvery { idTrakt } returns 1\n    }\n    val ids = mockk<IdsRemote>(relaxed = true) {\n      coEvery { trakt } returns 1\n    }\n    val creditsShows = mockk<PersonCredit>(relaxed = true)\n    val creditsMovies = mockk<PersonCredit>(relaxed = true)\n    coEvery { peopleDao.getById(any()) } returns personDb\n    coEvery { traktApi.fetchPersonIds(any(), any()) } returns ids\n    coEvery { traktApi.fetchPersonShowsCredits(any(), any()) } returns listOf(creditsShows)\n    coEvery { traktApi.fetchPersonMoviesCredits(any(), any()) } returns listOf(creditsMovies)\n\n    val result = SUT.loadCredits(person)\n\n    assertThat(result).hasSize(2)\n    assertThat(result[0].show).isNotNull()\n    assertThat(result[1].movie).isNotNull()\n    coVerify { peopleDao.getById(any()) }\n    coVerify(exactly = 1) { showsDao.upsert(any()) }\n    coVerify(exactly = 1) { moviesDao.upsert(any()) }\n    coVerify(exactly = 1) { peopleCreditsDao.insertSingle(any(), any()) }\n    coVerify(exactly = 0) { peopleDao.updateTraktId(any(), any()) }\n  }\n}\n"
  },
  {
    "path": "repository/src/test/java/com/michaldrabik/repository/RelatedShowsRepositoryTest.kt",
    "content": "package com.michaldrabik.repository\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.database.dao.RelatedShowsDao\nimport com.michaldrabik.data_local.database.dao.ShowsDao\nimport com.michaldrabik.data_local.database.model.RelatedShow\nimport com.michaldrabik.data_remote.trakt.TraktRemoteDataSource\nimport com.michaldrabik.repository.common.BaseMockTest\nimport com.michaldrabik.repository.shows.RelatedShowsRepository\nimport io.mockk.Runs\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.coVerifyOrder\nimport io.mockk.coVerifySequence\nimport io.mockk.every\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.impl.annotations.RelaxedMockK\nimport io.mockk.just\nimport io.mockk.mockk\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Before\nimport org.junit.Test\nimport java.util.concurrent.TimeUnit.HOURS\n\nclass RelatedShowsRepositoryTest : BaseMockTest() {\n\n  @MockK\n  lateinit var traktApi: TraktRemoteDataSource\n\n  @RelaxedMockK\n  lateinit var relatedShowsDao: RelatedShowsDao\n\n  @MockK\n  lateinit var showsDao: ShowsDao\n\n  private lateinit var SUT: RelatedShowsRepository\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    every { database.shows } returns showsDao\n    every { database.relatedShows } returns relatedShowsDao\n    every { cloud.trakt } returns traktApi\n\n    SUT = RelatedShowsRepository(cloud, database, transactions, mappers)\n  }\n\n  @Test\n  fun `Should return cached shows properly`() {\n    runBlocking {\n      val showDb = mockk<RelatedShow>(relaxed = true) {\n        every { updatedAt } returns nowUtcMillis() - HOURS.toMillis(1)\n      }\n      coEvery { showsDao.getAll(any()) } returns emptyList()\n      coEvery { relatedShowsDao.getAllById(any()) } returns listOf(showDb)\n\n      SUT.loadAll(mockk(relaxed = true), 0)\n\n      coVerifySequence {\n        relatedShowsDao.getAllById(any())\n        showsDao.getAll(any())\n      }\n      coVerify(exactly = 0) { traktApi.fetchRelatedShows(any(), 0) }\n    }\n  }\n\n  @Test\n  fun `Should return remote shows if nothing is cached`() {\n    runBlocking {\n      coEvery { showsDao.getAll(any()) } returns emptyList()\n      coEvery { showsDao.upsert(any()) } just Runs\n      coEvery { traktApi.fetchRelatedShows(any(), 0) } returns listOf(mockk(relaxed = true))\n      coEvery { relatedShowsDao.getAllById(any()) } returns listOf()\n\n      SUT.loadAll(mockk(relaxed = true), 0)\n\n      coVerifyOrder {\n        relatedShowsDao.getAllById(any())\n        traktApi.fetchRelatedShows(any(), 0)\n      }\n      coVerify(exactly = 0) { showsDao.getAll(any()) }\n    }\n  }\n\n  @Test\n  fun `Should return remote shows if cached values expired`() {\n    runBlocking {\n      val showDb = mockk<RelatedShow>(relaxed = true) {\n        every { updatedAt } returns nowUtcMillis() - (Config.RELATED_CACHE_DURATION + 1000)\n      }\n      coEvery { showsDao.getAll(any()) } returns emptyList()\n      coEvery { showsDao.upsert(any()) } just Runs\n      coEvery { traktApi.fetchRelatedShows(any(), 0) } returns listOf(mockk(relaxed = true))\n      coEvery { relatedShowsDao.getAllById(any()) } returns listOf(showDb)\n\n      SUT.loadAll(mockk(relaxed = true), 0)\n\n      coVerifyOrder {\n        relatedShowsDao.getAllById(any())\n        traktApi.fetchRelatedShows(any(), 0)\n      }\n      coVerify(exactly = 0) { showsDao.getAll(any()) }\n    }\n  }\n}\n"
  },
  {
    "path": "repository/src/test/java/com/michaldrabik/repository/SettingsRepositoryTest.kt",
    "content": "package com.michaldrabik.repository\n\nimport android.content.SharedPreferences\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.database.dao.SettingsDao\nimport com.michaldrabik.repository.common.BaseMockTest\nimport com.michaldrabik.repository.mappers.SettingsMapper\nimport com.michaldrabik.repository.settings.SettingsFiltersRepository\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.settings.SettingsSortRepository\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.repository.settings.SettingsWidgetsRepository\nimport com.michaldrabik.ui_model.Settings\nimport io.mockk.Runs\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.confirmVerified\nimport io.mockk.every\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.just\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Before\nimport org.junit.Test\n\nclass SettingsRepositoryTest : BaseMockTest() {\n\n  @MockK lateinit var settingsDao: SettingsDao\n  @MockK lateinit var sharedPreferences: SharedPreferences\n  @MockK lateinit var settingsSortRepository: SettingsSortRepository\n  @MockK lateinit var settingsFilterRepository: SettingsFiltersRepository\n  @MockK lateinit var settingsWidgetsRepository: SettingsWidgetsRepository\n  @MockK lateinit var settingsViewModeRepository: SettingsViewModeRepository\n  @MockK lateinit var settingsSpoilerRepositoryTest: SettingsSpoilersRepository\n\n  private lateinit var SUT: SettingsRepository\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    every { database.settings } returns settingsDao\n    SUT = SettingsRepository(\n      sorting = settingsSortRepository,\n      filters = settingsFilterRepository,\n      widgets = settingsWidgetsRepository,\n      viewMode = settingsViewModeRepository,\n      spoilers = settingsSpoilerRepositoryTest,\n      dispatchers = testDispatchers,\n      localSource = database,\n      transactions = transactions,\n      mappers = mappers,\n      preferences = sharedPreferences\n    )\n  }\n\n  @Test\n  fun `Should be initialized if there are settings in database`() {\n    runBlocking {\n      coEvery { settingsDao.getCount() } returns 1\n      assertThat(SUT.isInitialized()).isTrue()\n    }\n  }\n\n  @Test\n  fun `Should not be initialized if there are no settings in database`() {\n    runBlocking {\n      coEvery { settingsDao.getCount() } returns 0\n      assertThat(SUT.isInitialized()).isFalse()\n    }\n  }\n\n  @Test\n  fun `Should load settings properly`() {\n    runBlocking {\n      val mapper = SettingsMapper()\n      val settings = Settings.createInitial()\n      val settingsDb = mapper.toDatabase(settings)\n\n      coEvery { mappers.settings } returns mapper\n      coEvery { settingsDao.getAll() } returns settingsDb\n\n      val loaded = SUT.load()\n\n      assertThat(loaded).isEqualTo(settings)\n      coVerify { settingsDao.getAll() }\n      confirmVerified(settingsDao)\n    }\n  }\n\n  @Test\n  fun `Should update settings properly`() {\n    runBlocking {\n      val mapper = SettingsMapper()\n      val settings = Settings.createInitial()\n      val settingsDb = mapper.toDatabase(settings)\n\n      coEvery { mappers.settings } returns mapper\n      coEvery { settingsDao.upsert(settingsDb) } just Runs\n\n      SUT.update(settings)\n\n      coVerify { settingsDao.upsert(settingsDb) }\n      confirmVerified(settingsDao)\n    }\n  }\n}\n"
  },
  {
    "path": "repository/src/test/java/com/michaldrabik/repository/ShowDetailsRepositoryTest.kt",
    "content": "package com.michaldrabik.repository\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.database.dao.ShowsDao\nimport com.michaldrabik.data_local.database.model.Show\nimport com.michaldrabik.data_remote.trakt.TraktRemoteDataSource\nimport com.michaldrabik.repository.common.BaseMockTest\nimport com.michaldrabik.repository.shows.ShowDetailsRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport io.mockk.Called\nimport io.mockk.Runs\nimport io.mockk.coEvery\nimport io.mockk.coVerifySequence\nimport io.mockk.every\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.just\nimport io.mockk.mockk\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Before\nimport org.junit.Test\nimport java.util.concurrent.TimeUnit\nimport com.michaldrabik.data_remote.trakt.model.Show as ShowRemote\n\nclass ShowDetailsRepositoryTest : BaseMockTest() {\n\n  @MockK lateinit var traktApi: TraktRemoteDataSource\n  @MockK lateinit var showsDao: ShowsDao\n\n  private lateinit var SUT: ShowDetailsRepository\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    every { database.shows } returns showsDao\n    every { cloud.trakt } returns traktApi\n\n    SUT = ShowDetailsRepository(cloud, database, transactions, mappers)\n  }\n\n  @Test\n  fun `Should load cached show details on given conditions`() {\n    runBlocking {\n      val showDb = mockk<Show>(relaxed = true) {\n        every { idTrakt } returns 1\n        every { updatedAt } returns nowUtcMillis() - 100\n      }\n      coEvery { showsDao.getById(any<Long>()) } returns showDb\n\n      val show = SUT.load(IdTrakt(1), false)\n\n      assertThat(show.ids.trakt).isEqualTo(IdTrakt(1))\n      coVerifySequence {\n        showsDao.getById(any<Long>())\n        traktApi.fetchShow(any<Long>()) wasNot Called\n      }\n    }\n  }\n\n  @Test\n  fun `Should load remote show details if force flag is set`() {\n    runBlocking {\n      val showRemote = mockk<ShowRemote>(relaxed = true) {\n        every { ids?.trakt } returns 1\n      }\n      coEvery { showsDao.getById(any<Long>()) } returns null\n      coEvery { showsDao.upsert(any()) } just Runs\n      coEvery { traktApi.fetchShow(any<Long>()) } returns showRemote\n\n      val show = SUT.load(IdTrakt(1), true)\n\n      assertThat(show.ids.trakt).isEqualTo(IdTrakt(1))\n\n      coVerifySequence {\n        showsDao.getById(any<Long>())\n        traktApi.fetchShow(any<Long>())\n        showsDao.upsert(any())\n      }\n    }\n  }\n\n  @Test\n  fun `Should load remote show details if nothing is cached`() {\n    runBlocking {\n      val showRemote = mockk<ShowRemote>(relaxed = true) {\n        every { ids?.trakt } returns 1\n      }\n      coEvery { showsDao.getById(any<Long>()) } returns null\n      coEvery { showsDao.upsert(any()) } just Runs\n      coEvery { traktApi.fetchShow(any<Long>()) } returns showRemote\n\n      val show = SUT.load(IdTrakt(1), false)\n\n      assertThat(show.ids.trakt).isEqualTo(IdTrakt(1))\n\n      coVerifySequence {\n        showsDao.getById(any<Long>())\n        traktApi.fetchShow(any<Long>())\n        showsDao.upsert(any())\n      }\n    }\n  }\n\n  @Test\n  fun `Should load remote show details if cached show expired`() {\n    runBlocking {\n      val showDb = mockk<Show>(relaxed = true) {\n        every { idTrakt } returns 1\n        every { updatedAt } returns nowUtcMillis() - TimeUnit.DAYS.toMillis(10)\n      }\n      val showRemote = mockk<ShowRemote>(relaxed = true) {\n        every { ids?.trakt } returns 1\n      }\n      coEvery { showsDao.getById(any<Long>()) } returns showDb\n      coEvery { showsDao.upsert(any()) } just Runs\n      coEvery { traktApi.fetchShow(any<Long>()) } returns showRemote\n\n      val show = SUT.load(IdTrakt(1), false)\n\n      assertThat(show.ids.trakt).isEqualTo(IdTrakt(1))\n\n      coVerifySequence {\n        showsDao.getById(any<Long>())\n        traktApi.fetchShow(any<Long>())\n        showsDao.upsert(any())\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "repository/src/test/java/com/michaldrabik/repository/WatchlistShowsRepositoryTest.kt",
    "content": "package com.michaldrabik.repository\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.database.dao.ArchiveShowsDao\nimport com.michaldrabik.data_local.database.dao.MyShowsDao\nimport com.michaldrabik.data_local.database.dao.WatchlistShowsDao\nimport com.michaldrabik.data_local.database.model.WatchlistShow\nimport com.michaldrabik.repository.common.BaseMockTest\nimport com.michaldrabik.repository.shows.WatchlistShowsRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Show\nimport io.mockk.coEvery\nimport io.mockk.coJustRun\nimport io.mockk.coVerify\nimport io.mockk.confirmVerified\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.impl.annotations.RelaxedMockK\nimport io.mockk.slot\nimport kotlinx.coroutines.runBlocking\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\nimport com.michaldrabik.data_local.database.model.Show as ShowDb\n\nclass WatchlistShowsRepositoryTest : BaseMockTest() {\n\n  @MockK lateinit var seeLaterShowsDao: WatchlistShowsDao\n  @MockK lateinit var myShowsDao: MyShowsDao\n  @MockK lateinit var archivedShowsDao: ArchiveShowsDao\n\n  @RelaxedMockK lateinit var showDb: ShowDb\n\n  private lateinit var SUT: WatchlistShowsRepository\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    SUT = WatchlistShowsRepository(database, transactions, mappers)\n\n    coEvery { database.watchlistShows } returns seeLaterShowsDao\n    coEvery { database.myShows } returns myShowsDao\n    coEvery { database.archiveShows } returns archivedShowsDao\n  }\n\n  @After\n  fun confirmSutVerified() {\n    confirmVerified(seeLaterShowsDao)\n  }\n\n  @Test\n  fun `Should load and map all SeeLater shows`() {\n    runBlocking {\n      coEvery { seeLaterShowsDao.getAll() } returns listOf(showDb)\n      coEvery { mappers.show.fromDatabase(any()) } returns Show.EMPTY\n\n      SUT.loadAll()\n\n      coVerify(exactly = 1) { seeLaterShowsDao.getAll() }\n      coVerify(exactly = 1) { mappers.show.fromDatabase(showDb) }\n    }\n  }\n\n  @Test\n  fun `Should load and map single SeeLater show by Trakt ID`() {\n    runBlocking {\n      val show = Show.EMPTY.copy(title = \"Test\")\n\n      coEvery { seeLaterShowsDao.getById(any()) } returns showDb\n      coEvery { mappers.show.fromDatabase(any()) } returns show\n\n      val testShow = SUT.load(IdTrakt(1L))\n\n      assertThat(testShow?.title).isEqualTo(show.title)\n      coVerify(exactly = 1) { seeLaterShowsDao.getById(any()) }\n      coVerify(exactly = 1) { mappers.show.fromDatabase(showDb) }\n    }\n  }\n\n  @Test\n  fun `Should insert show into database using Trakt ID`() {\n    runBlocking {\n      coJustRun { myShowsDao.deleteById(any()) }\n      coJustRun { archivedShowsDao.deleteById(any()) }\n\n      val slot = slot<WatchlistShow>()\n      coJustRun { seeLaterShowsDao.insert(capture(slot)) }\n\n      SUT.insert(IdTrakt(1L))\n\n      assertThat(slot.captured.id).isEqualTo(0)\n      assertThat(slot.captured.idTrakt).isEqualTo(1)\n\n      coVerify(exactly = 1) { seeLaterShowsDao.insert(any()) }\n    }\n  }\n\n  @Test\n  fun `Should delete show from archived and my shows when inserting into see later`() {\n    runBlocking {\n      coJustRun { myShowsDao.deleteById(any()) }\n      coJustRun { archivedShowsDao.deleteById(any()) }\n\n      val slot = slot<WatchlistShow>()\n      coJustRun { seeLaterShowsDao.insert(capture(slot)) }\n\n      SUT.insert(IdTrakt(1L))\n\n      assertThat(slot.captured.id).isEqualTo(0)\n      assertThat(slot.captured.idTrakt).isEqualTo(1)\n\n      coVerify(exactly = 1) { seeLaterShowsDao.insert(any()) }\n      coVerify(exactly = 1) { myShowsDao.deleteById(1L) }\n      coVerify(exactly = 1) { archivedShowsDao.deleteById(1L) }\n    }\n  }\n\n  @Test\n  fun `Should delete show from database using Trakt ID`() {\n    runBlocking {\n      val slot = slot<Long>()\n      coJustRun { seeLaterShowsDao.deleteById(capture(slot)) }\n\n      SUT.delete(IdTrakt(10L))\n\n      assertThat(slot.captured).isEqualTo(10L)\n      coVerify(exactly = 1) { seeLaterShowsDao.deleteById(10L) }\n    }\n  }\n\n  @Test\n  fun `Should load all SeeLater shows ids`() {\n    runBlocking {\n      coEvery { seeLaterShowsDao.getAllTraktIds() } returns listOf(1L, 2L)\n\n      val ids = SUT.loadAllIds()\n\n      assertThat(ids).containsExactly(1L, 2L)\n      coVerify(exactly = 1) { seeLaterShowsDao.getAllTraktIds() }\n    }\n  }\n}\n"
  },
  {
    "path": "repository/src/test/java/com/michaldrabik/repository/common/BaseMockTest.kt",
    "content": "package com.michaldrabik.repository.common\n\nimport com.michaldrabik.common_test.UnconfinedCoroutineDispatchers\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.R\nimport com.michaldrabik.repository.mappers.CommentMapper\nimport com.michaldrabik.repository.mappers.CustomListMapper\nimport com.michaldrabik.repository.mappers.EpisodeMapper\nimport com.michaldrabik.repository.mappers.IdsMapper\nimport com.michaldrabik.repository.mappers.ImageMapper\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.mappers.MovieMapper\nimport com.michaldrabik.repository.mappers.NewsMapper\nimport com.michaldrabik.repository.mappers.PersonMapper\nimport com.michaldrabik.repository.mappers.RatingsMapper\nimport com.michaldrabik.repository.mappers.SeasonMapper\nimport com.michaldrabik.repository.mappers.SettingsMapper\nimport com.michaldrabik.repository.mappers.ShowMapper\nimport com.michaldrabik.repository.mappers.StreamingsMapper\nimport com.michaldrabik.repository.mappers.TranslationMapper\nimport com.michaldrabik.repository.mappers.UserRatingsMapper\nimport io.mockk.MockKAnnotations\nimport io.mockk.clearAllMocks\nimport io.mockk.coEvery\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.impl.annotations.SpyK\nimport io.mockk.mockkStatic\nimport io.mockk.slot\nimport org.junit.Before\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nabstract class BaseMockTest {\n\n  @MockK lateinit var database: LocalDataSource\n  @MockK lateinit var transactions: TransactionsProvider\n  @MockK lateinit var cloud: RemoteDataSource\n\n  protected val testDispatchers = UnconfinedCoroutineDispatchers()\n  private val idsMapper = IdsMapper()\n  private val episodeMappers = EpisodeMapper(idsMapper)\n\n  @SpyK var mappers = Mappers(\n    idsMapper,\n    ImageMapper(),\n    ShowMapper(idsMapper),\n    MovieMapper(idsMapper),\n    episodeMappers,\n    SeasonMapper(idsMapper, episodeMappers),\n    PersonMapper(),\n    CommentMapper(),\n    NewsMapper(),\n    SettingsMapper(),\n    TranslationMapper(idsMapper),\n    CustomListMapper(),\n    RatingsMapper(),\n    UserRatingsMapper(),\n    StreamingsMapper()\n  )\n\n  @Before\n  open fun setUp() {\n    MockKAnnotations.init(this)\n    clearAllMocks()\n    mockkStatic(\"androidx.room.RoomDatabaseKt\")\n    val lambda = slot<suspend () -> R>()\n    coEvery { transactions.withTransaction(capture(lambda)) } coAnswers { lambda.captured.invoke() }\n  }\n}\n"
  },
  {
    "path": "settings.gradle",
    "content": "rootProject.name = 'Showly OSS'\ninclude ':app'\ninclude ':data-remote'\ninclude ':data-local'\ninclude ':common'\ninclude ':common-test'\ninclude ':repository'\ninclude ':ui-news'\ninclude ':ui-lists'\ninclude ':ui-premium'\ninclude ':ui-statistics-movies'\ninclude ':ui-progress-movies'\ninclude ':ui-my-movies'\ninclude ':ui-comments'\ninclude ':ui-gallery'\ninclude ':ui-movie'\ninclude ':ui-discover-movies'\ninclude ':ui-episodes'\ninclude ':ui-widgets'\ninclude ':ui-progress'\ninclude ':ui-discover'\ninclude ':ui-my-shows'\ninclude ':ui-show'\ninclude ':ui-search'\ninclude ':ui-statistics'\ninclude ':ui-trakt-sync'\ninclude ':ui-navigation'\ninclude ':ui-settings'\ninclude ':ui-base'\ninclude ':ui-model'\ninclude ':ui-streamings'\ninclude ':ui-people'\n"
  },
  {
    "path": "ui-base/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-base/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-parcelize'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_base'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-remote')\n  implementation project(':data-local')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n  implementation project(':repository')\n\n  api libs.android.appcompat\n  api libs.android.core\n  api libs.bundles.android.lifecycle\n  api libs.bundles.android.navigation\n  api libs.android.fragment\n  api libs.android.recycler\n  api libs.android.constraintlayout\n  api libs.android.swiperefresh\n  api libs.android.work\n  api libs.android.material\n  api libs.android.dynamicanimation\n  api libs.overscrollDecor\n  api libs.timber\n\n  api libs.glide\n  ksp libs.glide.compiler\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  implementation libs.hilt.work\n  ksp libs.hilt.work.compiler\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-base/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/Analytics.kt",
    "content": "package com.michaldrabik.ui_base\n\nimport com.michaldrabik.ui_model.DiscoverFilters\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\n\n// Analytics removed, do nothing with log events\nobject Analytics {\n  fun logShowDetailsDisplay(show: Show) {}\n\n  fun logMovieDetailsDisplay(movie: Movie) {}\n\n  fun logShowAddToMyShows(show: Show) {}\n\n  fun logMovieAddToMyMovies(movie: Movie) {}\n\n  fun logShowAddToWatchlistShows(show: Show) {}\n\n  fun logMovieAddToWatchlistMovies(movie: Movie) {}\n\n  fun logShowAddToArchive(show: Show) {}\n\n  fun logMovieAddToArchive(movie: Movie) {}\n\n  fun logShowTrailerClick(show: Show) {}\n\n  fun logMovieTrailerClick(movie: Movie) {}\n\n  fun logShowCommentsClick(show: Show) {}\n\n  fun logMovieCommentsClick(movie: Movie) {}\n\n  fun logShowShareClick(show: Show) {}\n\n  fun logMovieShareClick(movie: Movie) {}\n\n  fun logShowGalleryClick(idTrakt: Long) {}\n\n  fun logMovieGalleryClick(idTrakt: Long) {}\n\n  fun logShowQuickProgress(show: Show) {}\n\n  fun logMovieRated(movie: Movie, rating: Int) {}\n\n  fun logEpisodeRated(idTrakt: Long, episode: Episode, rating: Int) {}\n\n  fun logDiscoverFiltersApply(filters: DiscoverFilters) {}\n\n  fun logDiscoverMoviesFiltersApply(filters: DiscoverFilters) {}\n\n  // In App Rate\n\n  fun logInAppRateDisplayed() {}\n\n  fun logInAppRateDecision(isYes: Boolean) {}\n\n  // Trakt\n\n  fun logTraktLogin() {}\n\n  fun logTraktLogout() {}\n\n  fun logTraktFullSyncSuccess(import: Boolean, export: Boolean) {}\n\n  fun logTraktQuickSyncSuccess(count: Int) {}\n\n  // Settings\n\n  fun logSettingsTraktQuickSync(enabled: Boolean) {}\n\n  fun logSettingsTraktQuickRemove(enabled: Boolean) {}\n\n  fun logSettingsTraktQuickRate(enabled: Boolean) {}\n\n  fun logSettingsRecentlyAddedAmount(amount: Long) {}\n\n  fun logSettingsAnnouncements(enabled: Boolean) {}\n\n  fun logSettingsSpecialSeasons(enabled: Boolean) {}\n\n  fun logSettingsProgressUpcoming(enabled: Boolean) {}\n\n  fun logSettingsMoviesEnabled(enabled: Boolean) {}\n\n  fun logSettingsNewsEnabled(enabled: Boolean) {}\n\n  fun logSettingsStreamingsEnabled(enabled: Boolean) {}\n\n  fun logSettingsWidgetsTitlesEnabled(enabled: Boolean) {}\n\n  fun logSettingsWhenToNotify(value: String) {}\n\n  fun logSettingsLanguage(value: String) {}\n\n  fun logSettingsTheme(value: Int) {}\n\n  fun logSettingsPremium(value: Boolean) {}\n\n  fun logSettingsWidgetsTheme(value: Int) {}\n\n  fun logSettingsCountry(value: String) {}\n\n  fun logSettingsProgressType(value: String) {}\n\n  fun logInAppUpdate(versionName: String, versionCode: Long) {}\n\n  fun logUnsupportedSubscriptions() {}\n\n  fun logExportHistory(episodesCount: Int, moviesCount: Int, retryCount: Int) {}\n\n  fun logQuickExportHistory(episodesCount: Int, moviesCount: Int, retryCount: Int) {}\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/BaseAdapter.kt",
    "content": "package com.michaldrabik.ui_base\n\nimport android.view.View\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.common.ListItem\n\nabstract class BaseAdapter<Item : ListItem>(\n  val listChangeListener: (() -> Unit)? = null\n) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), AsyncListDiffer.ListListener<Item> {\n\n  abstract val asyncDiffer: AsyncListDiffer<Item>\n\n  private var notifyChange = false\n\n  open fun setItems(newItems: List<Item>, notifyChange: Boolean = false) {\n    this.notifyChange = notifyChange\n    with(asyncDiffer) {\n      removeListListener(this@BaseAdapter)\n      addListListener(this@BaseAdapter)\n      submitList(newItems)\n    }\n  }\n\n  override fun getItemCount() = asyncDiffer.currentList.size\n\n  fun getItems(): List<Item> = asyncDiffer.currentList\n\n  fun indexOf(item: Item) = asyncDiffer.currentList.indexOfFirst { it.isSameAs(item) }\n\n  override fun onCurrentListChanged(\n    previousList: MutableList<Item>,\n    currentList: MutableList<Item>,\n  ) {\n    if (notifyChange) {\n      listChangeListener?.invoke()\n    }\n  }\n\n  class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/BaseBottomSheetFragment.kt",
    "content": "package com.michaldrabik.ui_base\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.IdRes\nimport androidx.annotation.LayoutRes\nimport androidx.appcompat.view.ContextThemeWrapper\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetDialog\nimport com.google.android.material.bottomsheet.BottomSheetDialogFragment\nimport com.michaldrabik.ui_base.utilities.NavigationHost\n\nabstract class BaseBottomSheetFragment(@LayoutRes val layoutResId: Int) : BottomSheetDialogFragment() {\n\n  override fun onCreateView(\n    inflater: LayoutInflater,\n    container: ViewGroup?,\n    savedInstanceState: Bundle?,\n  ): View {\n    val contextThemeWrapper = ContextThemeWrapper(requireActivity(), R.style.AppTheme)\n    return inflater.cloneInContext(contextThemeWrapper).inflate(layoutResId, container, false)\n  }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    expandSheet()\n  }\n\n  protected fun navigateTo(@IdRes destination: Int, bundle: Bundle? = null) =\n    (requireActivity() as NavigationHost).findNavControl()?.navigate(destination, bundle)\n\n  protected fun isSheetExpanded(): Boolean {\n    val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior\n    return behavior.state == BottomSheetBehavior.STATE_EXPANDED\n  }\n\n  protected fun expandSheet() {\n    val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior\n    behavior.state = BottomSheetBehavior.STATE_EXPANDED\n    behavior.skipCollapsed = true\n  }\n\n  protected fun closeSheet() = (requireActivity() as NavigationHost).findNavControl()?.navigateUp()\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/BaseFragment.kt",
    "content": "package com.michaldrabik.ui_base\n\nimport android.animation.Animator\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.ViewPropertyAnimator\nimport androidx.activity.addCallback\nimport androidx.annotation.IdRes\nimport androidx.annotation.LayoutRes\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.ViewModel\nimport com.google.android.material.snackbar.Snackbar\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_base.utilities.ModeHost\nimport com.michaldrabik.ui_base.utilities.MoviesStatusHost\nimport com.michaldrabik.ui_base.utilities.NavigationHost\nimport com.michaldrabik.ui_base.utilities.SnackbarHost\nimport com.michaldrabik.ui_base.utilities.TipsHost\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_base.utilities.extensions.showErrorSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_model.Tip\n\nabstract class BaseFragment<T : ViewModel>(@LayoutRes contentLayoutId: Int) :\n  Fragment(contentLayoutId),\n  TipsHost {\n\n  protected abstract val viewModel: T\n  open val navigationId: Int = 0\n\n  protected var isInitialized = false\n\n  protected val animations = mutableListOf<ViewPropertyAnimator?>()\n  protected val animators = mutableListOf<Animator?>()\n  protected val snackbars = mutableListOf<Snackbar?>()\n\n  protected var mode: Mode\n    get() = (requireActivity() as ModeHost).getMode()\n    set(value) = (requireActivity() as ModeHost).setMode(value)\n\n  protected val moviesEnabled: Boolean\n    get() = (requireActivity() as MoviesStatusHost).hasMoviesEnabled()\n\n  protected val isTablet by lazy { requireContext().isTablet() }\n\n  override fun onResume() {\n    super.onResume()\n    setupBackPressed()\n  }\n\n  protected fun findNavControl() =\n    (requireActivity() as NavigationHost).findNavControl()\n\n  protected fun hideNavigation(animate: Boolean = true) =\n    (requireActivity() as NavigationHost).hideNavigation(animate)\n\n  protected fun showNavigation(animate: Boolean = true) =\n    (requireActivity() as NavigationHost).showNavigation(animate)\n\n  protected fun showSnack(message: MessageEvent) {\n    val host = (requireActivity() as SnackbarHost).provideSnackbarLayout()\n    when (message) {\n      is MessageEvent.Info -> {\n        val length = if (message.isIndefinite) Snackbar.LENGTH_INDEFINITE else Snackbar.LENGTH_SHORT\n        val action = if (message.isIndefinite) ({}) else null\n        host.showInfoSnackbar(getString(message.textRestId), length = length, action = action)\n      }\n      is MessageEvent.Error -> host.showErrorSnackbar(getString(message.textRestId))\n    }\n  }\n\n  protected open fun setupBackPressed() {\n    val dispatcher = requireActivity().onBackPressedDispatcher\n    dispatcher.addCallback(viewLifecycleOwner) {\n      isEnabled = false\n      findNavControl()?.popBackStack()\n    }\n  }\n\n  protected fun navigateTo(@IdRes destination: Int, bundle: Bundle? = null) {\n    findNavControl()?.navigate(destination, bundle)\n  }\n\n  override fun isTipShown(tip: Tip) = (requireActivity() as TipsHost).isTipShown(tip)\n\n  override fun showTip(tip: Tip) = (requireActivity() as TipsHost).showTip(tip)\n\n  override fun setTipShow(tip: Tip) = (requireActivity() as TipsHost).showTip(tip)\n\n  private fun clearAnimations() {\n    animations.forEach { it?.cancel() }\n    animators.forEach { it?.cancel() }\n    animations.clear()\n    animators.clear()\n  }\n\n  override fun onDestroyView() {\n    snackbars.forEach { it?.dismiss() }\n    clearAnimations()\n    super.onDestroyView()\n  }\n\n  fun Fragment.requireAppContext(): Context = requireContext().applicationContext\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/BaseMovieAdapter.kt",
    "content": "package com.michaldrabik.ui_base\n\nimport android.view.View\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.common.MovieListItem\n\nabstract class BaseMovieAdapter<Item : MovieListItem>(\n  val listChangeListener: (() -> Unit)? = null\n) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), AsyncListDiffer.ListListener<Item> {\n\n  abstract val asyncDiffer: AsyncListDiffer<Item>\n\n  private var notifyChange = false\n\n  open fun setItems(newItems: List<Item>, notifyChange: Boolean = false) {\n    this.notifyChange = notifyChange\n    asyncDiffer.removeListListener(this)\n    asyncDiffer.addListListener(this)\n    asyncDiffer.submitList(newItems)\n  }\n\n  override fun getItemCount() = asyncDiffer.currentList.size\n\n  fun getItems(): List<Item> = asyncDiffer.currentList\n\n  fun indexOf(item: Item) = asyncDiffer.currentList.indexOfFirst { it isSameAs item }\n\n  override fun onCurrentListChanged(\n    previousList: MutableList<Item>,\n    currentList: MutableList<Item>\n  ) {\n    if (notifyChange) {\n      listChangeListener?.invoke()\n    }\n  }\n\n  class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/Logger.kt",
    "content": "package com.michaldrabik.ui_base\n\nimport okhttp3.RequestBody\nimport okhttp3.internal.closeQuietly\nimport okio.Buffer\nimport retrofit2.HttpException\nimport java.io.IOException\nimport kotlin.coroutines.cancellation.CancellationException\n\nobject Logger {\n\n  fun record(error: Throwable, source: String) {\n    if (error is CancellationException) {\n      return\n    }\n    if (error is IOException && error.message == \"Canceled\") {\n      return\n    }\n    if (error is HttpException) {\n      return\n    }\n  }\n\n  private fun RequestBody.asString(): String? {\n    val buffer = Buffer()\n    return try {\n      this.writeTo(buffer)\n      buffer.readUtf8()\n    } catch (error: Throwable) {\n      null\n    } finally {\n      buffer.closeQuietly()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/AppCountry.kt",
    "content": "package com.michaldrabik.ui_base.common\n\nenum class AppCountry(\n  val code: String,\n  val displayName: String,\n  val justWatchQuery: String = \"search\"\n) {\n  ARGENTINA(\"ar\", \"Argentina\", \"buscar\"),\n  AUSTRALIA(\"au\", \"Australia\"),\n  AUSTRIA(\"at\", \"Austria\", \"Suche\"),\n  BELGIUM(\"be\", \"Belgium\", \"recherche\"),\n  BRAZIL(\"br\", \"Brazil\", \"busca\"),\n  BULGARIA(\"bg\", \"Bulgaria\"),\n  CANADA(\"ca\", \"Canada\"),\n  CHILE(\"cl\", \"Chile\", \"buscar\"),\n  COLOMBIA(\"co\", \"Colombia\", \"buscar\"),\n  CZECH_REP(\"cz\", \"Czech Republic\", \"vyhledání\"),\n  DENMARK(\"dk\", \"Denmark\"),\n  ECUADOR(\"ec\", \"Ecuador\", \"buscar\"),\n  ESTONIA(\"ee\", \"Estonia\", \"otsing\"),\n  FINLAND(\"fi\", \"Finland\", \"etsi\"),\n  FRANCE(\"fr\", \"France\", \"recherche\"),\n  GERMANY(\"de\", \"Germany\", \"Suche\"),\n  GREECE(\"gr\", \"Greece\"),\n  HUNGARY(\"hu\", \"Hungary\"),\n  INDIA(\"in\", \"India\"),\n  INDONESIA(\"id\", \"Indonesia\"),\n  IRELAND(\"ie\", \"Ireland\"),\n  ITALY(\"it\", \"Italy\", \"cerca\"),\n  JAPAN(\"jp\", \"Japan\", \"検索\"),\n  LATVIA(\"lv\", \"Latvia\"),\n  LITHUANIA(\"lt\", \"Lithuania\"),\n  MALAYSIA(\"my\", \"Malaysia\"),\n  MEXICO(\"mx\", \"Mexico\", \"buscar\"),\n  NETHERLANDS(\"nl\", \"Netherlands\"),\n  NEW_ZEALAND(\"nz\", \"New Zealand\"),\n  NORWAY(\"no\", \"Norway\"),\n  PERU(\"pe\", \"Peru\", \"buscar\"),\n  PHILIPPINES(\"ph\", \"Philippines\"),\n  POLAND(\"pl\", \"Poland\"),\n  PORTUGAL(\"pt\", \"Portugal\", \"busca\"),\n  ROMANIA(\"ro\", \"Romania\"),\n  RUSSIA(\"ru\", \"Russia\", \"поиск\"),\n  SINGAPORE(\"sg\", \"Singapore\"),\n  SOUTH_AFRICA(\"za\", \"South Africa\"),\n  SOUTH_KOREA(\"kr\", \"South Korea\", \"검색\"),\n  SPAIN(\"es\", \"Spain\", \"buscar\"),\n  SWEDEN(\"se\", \"Sweden\"),\n  SWITZERLAND(\"ch\", \"Switzerland\", \"Suche\"),\n  THAILAND(\"th\", \"Thailand\"),\n  TURKEY(\"tr\", \"Turkey\", \"arama\"),\n  UNITED_KINGDOM(\"uk\", \"United Kingdom\"),\n  UNITED_STATES(\"us\", \"United States\"),\n  VENEZUELA(\"ve\", \"Venezuela\", \"buscar\");\n\n  companion object {\n    fun fromCode(code: String) = values().first { it.code == code }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/AppScopeProvider.kt",
    "content": "package com.michaldrabik.ui_base.common\n\nimport kotlinx.coroutines.CoroutineScope\n\ninterface AppScopeProvider {\n  val appScope: CoroutineScope\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/FastLinearLayoutManager.kt",
    "content": "package com.michaldrabik.ui_base.common\n\nimport android.content.Context\nimport android.util.DisplayMetrics\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearSmoothScroller\nimport androidx.recyclerview.widget.RecyclerView\n\nclass FastLinearLayoutManager(context: Context?, orientation: Int, reverseLayout: Boolean) :\n  LinearLayoutManager(context, orientation, reverseLayout) {\n\n  companion object {\n    const val SPEED_RATIO = 8F\n  }\n\n  override fun smoothScrollToPosition(recyclerView: RecyclerView?, state: RecyclerView.State?, position: Int) {\n    val scroller = object : LinearSmoothScroller(recyclerView?.context) {\n      override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics) =\n        SPEED_RATIO / displayMetrics.densityDpi\n    }\n    scroller.targetPosition = position\n    startSmoothScroll(scroller)\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/ListItem.kt",
    "content": "package com.michaldrabik.ui_base.common\n\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Show\n\ninterface ListItem {\n  val show: Show\n  val image: Image\n  val isLoading: Boolean\n\n  infix fun isSameAs(other: ListItem) = show.ids.trakt == other.show.ids.trakt\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/ListViewMode.kt",
    "content": "package com.michaldrabik.ui_base.common\n\nenum class ListViewMode {\n  LIST_NORMAL,\n  LIST_COMPACT,\n  GRID,\n  GRID_TITLE\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/MovieListItem.kt",
    "content": "package com.michaldrabik.ui_base.common\n\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Movie\n\ninterface MovieListItem {\n  val movie: Movie\n  val image: Image\n  val isLoading: Boolean\n\n  infix fun isSameAs(other: MovieListItem) = movie.ids.trakt == other.movie.ids.trakt\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/OnScrollResetListener.kt",
    "content": "package com.michaldrabik.ui_base.common\n\ninterface OnScrollResetListener {\n  fun onScrollReset()\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/OnSearchClickListener.kt",
    "content": "package com.michaldrabik.ui_base.common\n\ninterface OnSearchClickListener {\n  fun onEnterSearch()\n  fun onExitSearch()\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/OnShowsMoviesSyncedListener.kt",
    "content": "package com.michaldrabik.ui_base.common\n\ninterface OnShowsMoviesSyncedListener {\n  fun onShowsMoviesSyncFinished()\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/OnTabReselectedListener.kt",
    "content": "package com.michaldrabik.ui_base.common\n\ninterface OnTabReselectedListener {\n  fun onTabReselected()\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/OnTraktAuthorizeListener.kt",
    "content": "package com.michaldrabik.ui_base.common\n\nimport android.net.Uri\n\ninterface OnTraktAuthorizeListener {\n  fun onAuthorizationResult(authData: Uri?)\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/SafeOnClickListener.kt",
    "content": "package com.michaldrabik.ui_base.common\n\nimport android.view.View\nimport com.michaldrabik.common.extensions.nowUtcMillis\n\nprivate const val SAFE_INTERVAL = 650\n\nclass SafeOnClickListener(\n  private val isSafe: Boolean,\n  private val action: (view: View) -> Unit\n) : View.OnClickListener {\n  private var lastClickTimestamp = 0L\n\n  override fun onClick(clickedView: View) {\n    if (!isSafe) {\n      action(clickedView)\n      return\n    }\n    val currentTimestamp = nowUtcMillis()\n    if (lastClickTimestamp == 0L || currentTimestamp - lastClickTimestamp > SAFE_INTERVAL) {\n      action(clickedView)\n      lastClickTimestamp = currentTimestamp\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/WidgetsProvider.kt",
    "content": "package com.michaldrabik.ui_base.common\n\ninterface WidgetsProvider {\n  fun requestShowsWidgetsUpdate()\n  fun requestMoviesWidgetsUpdate()\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/behaviour/ScrollableViewBehaviour.kt",
    "content": "package com.michaldrabik.ui_base.common.behaviour\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.view.ViewCompat\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\n\n/**\n * Note: some extra work is added because of an issue:\n * https://gist.github.com/erikhuizinga/edf408167b46eb5b1568424563ca4e59?ts=2\n */\nclass ScrollableViewBehaviour : CoordinatorLayout.Behavior<View> {\n\n  constructor() : super()\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n\n  override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View) =\n    dependency is RecyclerView\n\n  override fun onNestedPreScroll(\n    coordinatorLayout: CoordinatorLayout,\n    child: View,\n    target: View,\n    dx: Int,\n    dy: Int,\n    consumed: IntArray,\n    type: Int,\n  ) {\n    super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)\n    stopNestedScrollIfNeeded(dy, target, type)\n  }\n\n  override fun onStartNestedScroll(\n    coordinatorLayout: CoordinatorLayout,\n    child: View,\n    directTargetChild: View,\n    target: View,\n    axes: Int,\n    type: Int,\n  ) = when (axes) {\n    ViewCompat.SCROLL_AXIS_VERTICAL -> true\n    else -> super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type)\n  }\n\n  override fun onNestedScroll(\n    coordinatorLayout: CoordinatorLayout,\n    child: View,\n    target: View,\n    dxConsumed: Int,\n    dyConsumed: Int,\n    dxUnconsumed: Int,\n    dyUnconsumed: Int,\n    type: Int,\n    consumed: IntArray,\n  ) {\n    super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed)\n    child.translationY = (child.translationY - dyConsumed.toFloat()).coerceAtMost(0F)\n    stopNestedScrollIfNeeded(dyConsumed, target, type)\n    resetAtTop(target, child)\n  }\n\n  private fun resetAtTop(target: View, child: View) {\n    val lm = (target as? RecyclerView)?.layoutManager as? LinearLayoutManager\n    lm?.let {\n      val isScrolled = lm.findFirstCompletelyVisibleItemPosition() != 0\n      if (!isScrolled) {\n        child.animate().translationY(0F).setDuration(50).start()\n      }\n    }\n  }\n\n  private fun stopNestedScrollIfNeeded(dy: Int, target: View, type: Int) {\n    if (type == ViewCompat.TYPE_NON_TOUCH) {\n      if (dy == 0) {\n        ViewCompat.stopNestedScroll(target, ViewCompat.TYPE_NON_TOUCH)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/behaviour/SearchViewBehaviour.kt",
    "content": "package com.michaldrabik.ui_base.common.behaviour\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.view.ViewCompat\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\n\n/**\n * Note: some extra work is added because of an issue:\n * https://gist.github.com/erikhuizinga/edf408167b46eb5b1568424563ca4e59?ts=2\n */\nclass SearchViewBehaviour(private val padding: Int) : CoordinatorLayout.Behavior<ViewGroup>() {\n\n  override fun layoutDependsOn(parent: CoordinatorLayout, child: ViewGroup, dependency: View) =\n    dependency is RecyclerView\n\n  override fun onNestedPreScroll(\n    coordinatorLayout: CoordinatorLayout,\n    child: ViewGroup,\n    target: View,\n    dx: Int,\n    dy: Int,\n    consumed: IntArray,\n    type: Int,\n  ) {\n    super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)\n    stopNestedScrollIfNeeded(dy, target, type)\n  }\n\n  override fun onStartNestedScroll(\n    coordinatorLayout: CoordinatorLayout,\n    child: ViewGroup,\n    directTargetChild: View,\n    target: View,\n    axes: Int,\n    type: Int,\n  ) = when (axes) {\n    ViewCompat.SCROLL_AXIS_VERTICAL -> true\n    else -> super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type)\n  }\n\n  override fun onNestedScroll(\n    coordinatorLayout: CoordinatorLayout,\n    child: ViewGroup,\n    target: View,\n    dxConsumed: Int,\n    dyConsumed: Int,\n    dxUnconsumed: Int,\n    dyUnconsumed: Int,\n    type: Int,\n    consumed: IntArray,\n  ) {\n    super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed)\n    if (dyConsumed > 0) {\n      val limit = -(child.height + 1.5F * padding)\n      child.translationY = (child.translationY - dyConsumed.toFloat()).coerceAtLeast(limit)\n    } else if (dyConsumed <= 0) {\n      child.translationY = (child.translationY - dyConsumed.toFloat()).coerceAtMost(0F)\n    }\n    stopNestedScrollIfNeeded(dyConsumed, target, type)\n    resetAtTop(target, child)\n  }\n\n  private fun resetAtTop(target: View, child: View) {\n    val lm = (target as? RecyclerView)?.layoutManager as? LinearLayoutManager\n    lm?.let {\n      val isScrolled = lm.findFirstCompletelyVisibleItemPosition() != 0\n      if (!isScrolled) {\n        child.animate().translationY(0F).setDuration(50).start()\n      }\n    }\n  }\n\n  private fun stopNestedScrollIfNeeded(dy: Int, target: View, type: Int) {\n    if (type == ViewCompat.TYPE_NON_TOUCH) {\n      if (dy == 0) {\n        ViewCompat.stopNestedScroll(target, ViewCompat.TYPE_NON_TOUCH)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/ContextMenuBottomSheet.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu\n\nimport android.os.Bundle\nimport androidx.annotation.IdRes\nimport androidx.core.content.ContextCompat\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.setFragmentResultListener\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet.Mode\nimport com.michaldrabik.ui_base.databinding.ViewContextMenuBinding\nimport com.michaldrabik.ui_base.utilities.SnackbarHost\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.requireParcelable\nimport com.michaldrabik.ui_base.utilities.extensions.showErrorSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.IdTvdb\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageStatus\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_OPTIONS\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_ITEM_MENU\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_REMOVE_TRAKT\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.RESULT\n\nabstract class ContextMenuBottomSheet : BaseBottomSheetFragment(R.layout.view_context_menu) {\n\n  companion object {\n    private const val ARG_SHOW_PIN_BUTTONS = \"ARG_SHOW_PIN_BUTTONS\"\n    private const val ARG_DETAILS_ENABLED = \"ARG_DETAILS_ENABLED\"\n\n    fun createBundle(\n      idTrakt: IdTrakt,\n      showPinButtons: Boolean = false,\n      detailsEnabled: Boolean = true\n    ) = bundleOf(\n      ARG_ID to idTrakt,\n      ARG_OPTIONS to bundleOf(\n        ARG_SHOW_PIN_BUTTONS to showPinButtons,\n        ARG_DETAILS_ENABLED to detailsEnabled\n      )\n    )\n  }\n\n  protected val binding by viewBinding(ViewContextMenuBinding::bind)\n\n  protected val itemId by lazy { requireParcelable<IdTrakt>(ARG_ID) }\n  private val showPinButtons by lazy { requireParcelable<Bundle>(ARG_OPTIONS).getBoolean(ARG_SHOW_PIN_BUTTONS) }\n  private val detailsEnabled by lazy { requireParcelable<Bundle>(ARG_OPTIONS).getBoolean(ARG_DETAILS_ENABLED) }\n\n  private val cornerRadius by lazy { dimenToPx(R.dimen.mediaTileCorner).toFloat() }\n  private val cornerBigRadius by lazy { dimenToPx(R.dimen.collectionItemCorner).toFloat() }\n  private val centerCropTransformation by lazy { CenterCrop() }\n  private val cornersTransformation by lazy { GranularRoundedCorners(cornerBigRadius, cornerRadius, cornerRadius, cornerRadius) }\n\n  protected val colorAccent by lazy { ContextCompat.getColor(requireContext(), R.color.colorAccent) }\n  protected val colorGray by lazy { ContextCompat.getColor(requireContext(), R.color.colorGrayLight) }\n\n  protected abstract fun openDetails()\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  protected open fun setupView() {\n    with(binding) {\n      contextMenuItemDescription.setInitialLines(5)\n      contextMenuItemPinButtonsLayout.visibleIf(showPinButtons)\n      contextMenuItemSeparator2.visibleIf(showPinButtons)\n      contextMenuItemImage.onClick { if (detailsEnabled) openDetails() }\n      contextMenuItemPlaceholder.onClick { if (detailsEnabled) openDetails() }\n    }\n  }\n\n  protected fun renderImage(image: Image, tvdbId: IdTvdb) {\n    Glide.with(this).clear(binding.contextMenuItemImage)\n    var imageUrl = image.fullFileUrl\n\n    if (image.status == ImageStatus.UNAVAILABLE) {\n      binding.contextMenuItemPlaceholder.visible()\n      binding.contextMenuItemImage.gone()\n      return\n    }\n\n    if (image.status == ImageStatus.UNKNOWN) {\n      imageUrl = \"${Config.TVDB_IMAGE_BASE_POSTER_URL}${tvdbId.id}-1.jpg\"\n    }\n\n    Glide.with(this)\n      .load(imageUrl)\n      .transform(centerCropTransformation, cornersTransformation)\n      .transition(DrawableTransitionOptions.withCrossFade(Config.IMAGE_FADE_DURATION_MS))\n      .withSuccessListener {\n        binding.contextMenuItemPlaceholder.gone()\n        binding.contextMenuItemImage.visible()\n      }\n      .withFailListener {\n        binding.contextMenuItemPlaceholder.visible()\n        binding.contextMenuItemImage.gone()\n      }\n      .into(binding.contextMenuItemImage)\n  }\n\n  protected fun renderSnackbar(message: MessageEvent) {\n    when (message) {\n      is MessageEvent.Info -> binding.contextMenuItemSnackbarHost.showInfoSnackbar(getString(message.textRestId))\n      is MessageEvent.Error -> binding.contextMenuItemSnackbarHost.showErrorSnackbar(getString(message.textRestId))\n    }\n  }\n\n  protected fun openRemoveTraktSheet(@IdRes action: Int, mode: Mode) {\n    setFragmentResultListener(REQUEST_REMOVE_TRAKT) { _, bundle ->\n      if (bundle.getBoolean(RESULT, false)) {\n        val text = resources.getQuantityString(R.plurals.textTraktQuickSyncComplete, 1)\n        (requireActivity() as SnackbarHost).provideSnackbarLayout().showInfoSnackbar(text)\n      }\n      close()\n    }\n    val args = RemoveTraktBottomSheet.createBundle(itemId, mode)\n    navigateTo(action, args)\n  }\n\n  protected fun close() {\n    setFragmentResult(REQUEST_ITEM_MENU, Bundle.EMPTY)\n    closeSheet()\n    dismiss()\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/events/FinishUiEvent.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.events\n\ndata class FinishUiEvent(val isSuccess: Boolean)\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/events/RemoveTraktUiEvent.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.events\n\ndata class RemoveTraktUiEvent(\n  val removeProgress: Boolean = false,\n  val removeWatchlist: Boolean = false,\n  val removeHidden: Boolean = false\n)\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/movie/MovieContextMenuBottomSheet.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.movie\n\nimport android.content.res.ColorStateList\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.core.widget.ImageViewCompat\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_RATINGS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.sheets.context_menu.ContextMenuBottomSheet\nimport com.michaldrabik.ui_base.common.sheets.context_menu.events.FinishUiEvent\nimport com.michaldrabik.ui_base.common.sheets.context_menu.events.RemoveTraktUiEvent\nimport com.michaldrabik.ui_base.common.sheets.context_menu.movie.helpers.MovieContextItem\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet.Mode\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport dagger.hilt.android.AndroidEntryPoint\nimport java.util.Locale\n\n@AndroidEntryPoint\nclass MovieContextMenuBottomSheet : ContextMenuBottomSheet() {\n\n  private val viewModel by viewModels<MovieContextMenuViewModel>()\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.messageFlow.collect { renderSnackbar(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      { viewModel.uiState.collect { render(it) } },\n      doAfterLaunch = { viewModel.loadMovie(itemId) }\n    )\n  }\n\n  override fun setupView() {\n    super.setupView()\n    with(binding) {\n      contextMenuItemMoveToMyButton.text = getString(R.string.textMoveToMyMovies)\n      contextMenuItemRemoveFromMyButton.text = getString(R.string.textRemoveFromMyMovies)\n\n      contextMenuItemMoveToMyButton.onClick { viewModel.moveToMyMovies() }\n      contextMenuItemRemoveFromMyButton.onClick { viewModel.removeFromMyMovies() }\n      contextMenuItemMoveToWatchlistButton.onClick { viewModel.moveToWatchlist() }\n      contextMenuItemRemoveFromWatchlistButton.onClick { viewModel.removeFromWatchlist() }\n      contextMenuItemMoveToHiddenButton.onClick { viewModel.moveToHidden() }\n      contextMenuItemRemoveFromHiddenButton.onClick { viewModel.removeFromHidden() }\n      contextMenuItemPinButton.onClick { viewModel.addToTopPinned() }\n      contextMenuItemUnpinButton.onClick { viewModel.removeFromTopPinned() }\n    }\n  }\n\n  private fun render(uiState: MovieContextMenuUiState) {\n    uiState.run {\n      isLoading?.let { isLoading ->\n        when {\n          isLoading -> binding.contextMenuItemProgress.show()\n          else -> binding.contextMenuItemProgress.hide()\n        }\n        binding.contextMenuItemButtonsLayout.visibleIf(!isLoading, gone = false)\n      }\n      item?.let {\n        renderItem(it)\n        renderImage(it.image, it.movie.ids.tvdb)\n      }\n    }\n  }\n\n  private fun renderItem(item: MovieContextItem) {\n    with(binding) {\n      contextMenuItemTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.movie.title\n        else item.translation?.title\n\n      renderItemDescription(item)\n      renderItemRating(item)\n\n      contextMenuItemNetwork.text = when {\n        item.movie.released != null -> item.dateFormat?.format(item.movie.released)?.capitalizeWords()\n        else -> String.format(Locale.ENGLISH, \"%d\", item.movie.year)\n      }\n\n      contextMenuRatingStar.visibleIf(item.movie.rating > 0)\n\n      contextMenuUserRating.text = String.format(Locale.ENGLISH, \"%d\", item.userRating)\n      contextMenuUserRating.visibleIf(item.userRating != null)\n      contextMenuUserRatingStar.visibleIf(item.userRating != null)\n\n      contextMenuItemDescription.visibleIf(item.movie.overview.isNotBlank())\n      contextMenuItemNetwork.visibleIf(item.movie.released != null || item.movie.year > 0)\n\n      contextMenuItemPinButton.visibleIf(!item.isPinnedTop)\n      contextMenuItemUnpinButton.visibleIf(item.isPinnedTop)\n\n      contextMenuItemMoveToMyButton.visibleIf(!item.isMyMovie)\n      contextMenuItemMoveToWatchlistButton.visibleIf(!item.isWatchlist)\n      contextMenuItemMoveToHiddenButton.visibleIf(!item.isHidden)\n\n      contextMenuItemRemoveFromMyButton.visibleIf(item.isMyMovie)\n      contextMenuItemRemoveFromWatchlistButton.visibleIf(item.isWatchlist)\n      contextMenuItemRemoveFromHiddenButton.visibleIf(item.isHidden)\n\n      contextMenuItemBadge.visibleIf(item.isMyMovie || item.isWatchlist)\n      val color = if (item.isMyMovie) colorAccent else colorGray\n      ImageViewCompat.setImageTintList(contextMenuItemBadge, ColorStateList.valueOf(color))\n\n      if (!item.isInCollection()) {\n        contextMenuItemMoveToMyButton.text = getString(R.string.textAddToMyMovies)\n        contextMenuItemMoveToWatchlistButton.text = getString(R.string.textAddToWatchlist)\n        contextMenuItemMoveToHiddenButton.text = getString(R.string.textHide)\n      }\n    }\n  }\n\n  private fun renderItemDescription(item: MovieContextItem) {\n    with(binding) {\n      contextMenuItemDescription.text =\n        if (item.translation?.overview.isNullOrBlank()) item.movie.overview\n        else item.translation?.overview\n\n      val isMyMovieHidden = item.spoilers.isMyMoviesHidden && item.isMyMovie\n      val isWatchlistHidden = item.spoilers.isWatchlistMoviesHidden && item.isWatchlist\n      val isHiddenMovieHidden = item.spoilers.isHiddenMoviesHidden && item.isHidden\n      val isNotCollectedHidden = item.spoilers.isNotCollectedMoviesHidden && (!item.isInCollection())\n\n      if (isMyMovieHidden || isWatchlistHidden || isHiddenMovieHidden || isNotCollectedHidden) {\n        val spoilerDescription = contextMenuItemDescription.text.toString()\n        val hiddenDescription = SPOILERS_REGEX.replace(spoilerDescription, SPOILERS_HIDE_SYMBOL)\n        contextMenuItemDescription.tag = spoilerDescription\n        contextMenuItemDescription.text = hiddenDescription\n      }\n\n      if (item.spoilers.isTapToReveal) {\n        with(contextMenuItemDescription) {\n          onClick {\n            tag?.let { text = it.toString() }\n            enableFoldOnClick()\n          }\n        }\n      }\n    }\n  }\n\n  private fun renderItemRating(item: MovieContextItem) {\n    with(binding) {\n      var rating = String.format(Locale.ENGLISH, \"%.1f\", item.movie.rating)\n\n      val isMyHidden = item.spoilers.isMyMoviesRatingsHidden && item.isMyMovie\n      val isWatchlistHidden = item.spoilers.isWatchlistMoviesRatingsHidden && item.isWatchlist\n      val isHiddenShowHidden = item.spoilers.isHiddenMoviesRatingsHidden && item.isHidden\n      val isNotCollectedHidden = item.spoilers.isNotCollectedMoviesRatingsHidden && (!item.isInCollection())\n\n      if (isMyHidden || isWatchlistHidden || isHiddenShowHidden || isNotCollectedHidden) {\n        contextMenuRating.tag = rating\n        rating = SPOILERS_RATINGS_HIDE_SYMBOL\n      }\n\n      contextMenuRating.visibleIf(item.movie.rating > 0)\n      contextMenuRatingStar.visibleIf(item.movie.rating > 0)\n      contextMenuRating.text = rating\n\n      if (item.spoilers.isTapToReveal) {\n        with(contextMenuRating) {\n          onClick {\n            tag?.let { text = it.toString() }\n          }\n        }\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (val result = event.peek()) {\n      is RemoveTraktUiEvent -> when {\n        result.removeProgress -> openRemoveTraktSheet(R.id.actionMovieItemContextDialogToRemoveTraktProgress, Mode.MOVIE)\n        result.removeWatchlist -> openRemoveTraktSheet(R.id.actionMovieItemContextDialogToRemoveTraktWatchlist, Mode.MOVIE)\n        result.removeHidden -> openRemoveTraktSheet(R.id.actionMovieItemContextDialogToRemoveTraktHidden, Mode.MOVIE)\n        else -> close()\n      }\n      is FinishUiEvent -> if (result.isSuccess) close()\n      else -> throw IllegalStateException()\n    }\n  }\n\n  override fun openDetails() {\n    val bundle = bundleOf(NavigationArgs.ARG_MOVIE_ID to itemId.id)\n    navigateTo(R.id.actionMovieItemContextDialogToMovieDetails, bundle)\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/movie/MovieContextMenuUiState.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.movie\n\nimport com.michaldrabik.ui_base.common.sheets.context_menu.movie.helpers.MovieContextItem\n\ndata class MovieContextMenuUiState(\n  val isLoading: Boolean? = null,\n  val item: MovieContextItem? = null,\n)\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/movie/MovieContextMenuViewModel.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.movie\n\nimport android.annotation.SuppressLint\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.sheets.context_menu.events.FinishUiEvent\nimport com.michaldrabik.ui_base.common.sheets.context_menu.events.RemoveTraktUiEvent\nimport com.michaldrabik.ui_base.common.sheets.context_menu.movie.cases.MovieContextMenuHiddenCase\nimport com.michaldrabik.ui_base.common.sheets.context_menu.movie.cases.MovieContextMenuLoadItemCase\nimport com.michaldrabik.ui_base.common.sheets.context_menu.movie.cases.MovieContextMenuMyMoviesCase\nimport com.michaldrabik.ui_base.common.sheets.context_menu.movie.cases.MovieContextMenuPinnedCase\nimport com.michaldrabik.ui_base.common.sheets.context_menu.movie.cases.MovieContextMenuWatchlistCase\nimport com.michaldrabik.ui_base.common.sheets.context_menu.movie.helpers.MovieContextItem\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\nimport kotlin.properties.Delegates.notNull\n\n@SuppressLint(\"StaticFieldLeak\")\n@HiltViewModel\nclass MovieContextMenuViewModel @Inject constructor(\n  private val loadItemCase: MovieContextMenuLoadItemCase,\n  private val myMoviesCase: MovieContextMenuMyMoviesCase,\n  private val watchlistCase: MovieContextMenuWatchlistCase,\n  private val hiddenCase: MovieContextMenuHiddenCase,\n  private val pinnedCase: MovieContextMenuPinnedCase,\n  private val settingsRepository: SettingsRepository\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private var movieId by notNull<IdTrakt>()\n  private var isQuickRemoveEnabled by notNull<Boolean>()\n\n  private val loadingState = MutableStateFlow(false)\n  private val itemState = MutableStateFlow<MovieContextItem?>(null)\n\n  fun loadMovie(idTrakt: IdTrakt) {\n    viewModelScope.launch {\n      movieId = idTrakt\n      isQuickRemoveEnabled = settingsRepository.load().traktQuickRemoveEnabled\n\n      try {\n        loadingState.value = true\n        val item = loadItemCase.loadItem(idTrakt)\n        itemState.value = item\n      } catch (error: Throwable) {\n        messageChannel.send(MessageEvent.Error(R.string.errorGeneral))\n      } finally {\n        loadingState.value = false\n      }\n    }\n  }\n\n  fun moveToMyMovies() {\n    viewModelScope.launch {\n      try {\n        val result = myMoviesCase.moveToMyMovies(movieId)\n        checkQuickRemove(result)\n      } catch (error: Throwable) {\n        onError(error)\n      }\n    }\n  }\n\n  fun removeFromMyMovies() {\n    viewModelScope.launch {\n      try {\n        myMoviesCase.removeFromMyMovies(movieId)\n        checkQuickRemove(RemoveTraktUiEvent(removeProgress = true))\n      } catch (error: Throwable) {\n        onError(error)\n      }\n    }\n  }\n\n  fun moveToWatchlist() {\n    viewModelScope.launch {\n      try {\n        val result = watchlistCase.moveToWatchlist(movieId)\n        checkQuickRemove(result)\n      } catch (error: Throwable) {\n        onError(error)\n      }\n    }\n  }\n\n  fun removeFromWatchlist() {\n    viewModelScope.launch {\n      try {\n        watchlistCase.removeFromWatchlist(movieId)\n        checkQuickRemove(RemoveTraktUiEvent(removeWatchlist = true))\n      } catch (error: Throwable) {\n        onError(error)\n      }\n    }\n  }\n\n  fun moveToHidden() {\n    viewModelScope.launch {\n      try {\n        val result = hiddenCase.moveToHidden(movieId)\n        checkQuickRemove(result)\n      } catch (error: Throwable) {\n        onError(error)\n      }\n    }\n  }\n\n  fun removeFromHidden() {\n    viewModelScope.launch {\n      try {\n        hiddenCase.removeFromHidden(movieId)\n        checkQuickRemove(RemoveTraktUiEvent(removeHidden = true))\n      } catch (error: Throwable) {\n        onError(error)\n      }\n    }\n  }\n\n  fun addToTopPinned() {\n    viewModelScope.launch {\n      pinnedCase.addToTopPinned(movieId)\n      eventChannel.send(Event(FinishUiEvent(true)))\n    }\n  }\n\n  fun removeFromTopPinned() {\n    viewModelScope.launch {\n      pinnedCase.removeFromTopPinned(movieId)\n      eventChannel.send(Event(FinishUiEvent(true)))\n    }\n  }\n\n  private suspend fun checkQuickRemove(event: RemoveTraktUiEvent) {\n    if (isQuickRemoveEnabled) {\n      loadingState.value = false\n      eventChannel.send(Event(event))\n    } else {\n      eventChannel.send(Event(FinishUiEvent(true)))\n    }\n  }\n\n  private suspend fun onError(error: Throwable) {\n    loadingState.value = false\n    messageChannel.send(MessageEvent.Error(R.string.errorGeneral))\n    rethrowCancellation(error)\n  }\n\n  val uiState = combine(\n    loadingState,\n    itemState\n  ) { s1, s2 ->\n    MovieContextMenuUiState(\n      isLoading = s1,\n      item = s2\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = MovieContextMenuUiState()\n  )\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/movie/cases/MovieContextMenuHiddenCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.movie.cases\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.ui_base.common.sheets.context_menu.events.RemoveTraktUiEvent\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Movie\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieContextMenuHiddenCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val moviesRepository: MoviesRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val quickSyncManager: QuickSyncManager,\n) {\n\n  suspend fun moveToHidden(traktId: IdTrakt) = withContext(dispatchers.IO) {\n    val movie = Movie.EMPTY.copy(ids = Ids.EMPTY.copy(traktId))\n\n    val (isMyMovie, isWatchlist) = awaitAll(\n      async { moviesRepository.myMovies.exists(traktId) },\n      async { moviesRepository.watchlistMovies.exists(traktId) }\n    )\n\n    moviesRepository.hiddenMovies.insert(movie.ids.trakt)\n    pinnedItemsRepository.removePinnedItem(movie)\n    with(quickSyncManager) {\n      clearMovies(listOf(traktId.id))\n      clearWatchlistMovies(listOf(traktId.id))\n      scheduleHidden(traktId.id, Mode.MOVIES, TraktSyncQueue.Operation.ADD)\n    }\n\n    RemoveTraktUiEvent(removeProgress = isMyMovie, removeWatchlist = isWatchlist)\n  }\n\n  suspend fun removeFromHidden(traktId: IdTrakt) = withContext(dispatchers.IO) {\n    moviesRepository.hiddenMovies.delete(traktId)\n    quickSyncManager.clearHiddenMovies(listOf(traktId.id))\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/movie/cases/MovieContextMenuLoadItemCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.movie.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.ui_base.common.sheets.context_menu.movie.helpers.MovieContextItem\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.ImageType\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieContextMenuLoadItemCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val moviesRepository: MoviesRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val imagesProvider: MovieImagesProvider,\n  private val translationsRepository: TranslationsRepository,\n  private val ratingsRepository: RatingsRepository,\n  private val settingsSpoilersRepository: SettingsSpoilersRepository,\n  private val dateFormatProvider: DateFormatProvider,\n) {\n\n  suspend fun loadItem(traktId: IdTrakt) = withContext(dispatchers.IO) {\n    val movie = moviesRepository.movieDetails.load(traktId)\n    val dateFormat = dateFormatProvider.loadShortDayFormat()\n    val language = translationsRepository.getLanguage()\n    val spoilers = settingsSpoilersRepository.getAll()\n\n    val imageAsync = async { imagesProvider.findCachedImage(movie, ImageType.POSTER) }\n    val translationAsync = async { translationsRepository.loadTranslation(movie, language = language, onlyLocal = true) }\n    val ratingAsync = async { ratingsRepository.movies.loadRatings(listOf(movie)) }\n\n    val isMyMovieAsync = async { moviesRepository.myMovies.exists(traktId) }\n    val isWatchlistAsync = async { moviesRepository.watchlistMovies.exists(traktId) }\n    val isHiddenAsync = async { moviesRepository.hiddenMovies.exists(traktId) }\n\n    val isPinnedAsync = async { pinnedItemsRepository.isItemPinned(movie) }\n\n    MovieContextItem(\n      movie = movie,\n      image = imageAsync.await(),\n      translation = translationAsync.await(),\n      userRating = ratingAsync.await().firstOrNull()?.rating,\n      isMyMovie = isMyMovieAsync.await(),\n      isWatchlist = isWatchlistAsync.await(),\n      isHidden = isHiddenAsync.await(),\n      isPinnedTop = isPinnedAsync.await(),\n      dateFormat = dateFormat,\n      spoilers = spoilers\n    )\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/movie/cases/MovieContextMenuMyMoviesCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.movie.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.ui_base.common.sheets.context_menu.events.RemoveTraktUiEvent\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Movie\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieContextMenuMyMoviesCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val moviesRepository: MoviesRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val announcementManager: AnnouncementManager,\n  private val quickSyncManager: QuickSyncManager,\n) {\n\n  suspend fun moveToMyMovies(traktId: IdTrakt) = withContext(dispatchers.IO) {\n    val movie = Movie.EMPTY.copy(ids = Ids.EMPTY.copy(traktId))\n\n    val (isWatchlist, isHidden) = awaitAll(\n      async { moviesRepository.watchlistMovies.exists(traktId) },\n      async { moviesRepository.hiddenMovies.exists(traktId) }\n    )\n\n    moviesRepository.myMovies.insert(traktId)\n    pinnedItemsRepository.removePinnedItem(movie)\n    announcementManager.refreshMoviesAnnouncements()\n    with(quickSyncManager) {\n      clearWatchlistMovies(listOf(traktId.id))\n      clearHiddenMovies(listOf(traktId.id))\n      scheduleMovies(listOf(traktId.id))\n    }\n\n    RemoveTraktUiEvent(removeWatchlist = isWatchlist, removeHidden = isHidden)\n  }\n\n  suspend fun removeFromMyMovies(traktId: IdTrakt) = withContext(dispatchers.IO) {\n    val movie = Movie.EMPTY.copy(ids = Ids.EMPTY.copy(traktId))\n    moviesRepository.myMovies.delete(traktId)\n    pinnedItemsRepository.removePinnedItem(movie)\n    quickSyncManager.clearMovies(listOf(traktId.id))\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/movie/cases/MovieContextMenuPinnedCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.movie.cases\n\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Movie\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieContextMenuPinnedCase @Inject constructor(\n  private val pinnedItemsRepository: PinnedItemsRepository,\n) {\n\n  fun addToTopPinned(traktId: IdTrakt) {\n    val movie = Movie.EMPTY.copy(ids = Ids.EMPTY.copy(traktId))\n    pinnedItemsRepository.addPinnedItem(movie)\n  }\n\n  fun removeFromTopPinned(traktId: IdTrakt) {\n    val movie = Movie.EMPTY.copy(ids = Ids.EMPTY.copy(traktId))\n    pinnedItemsRepository.removePinnedItem(movie)\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/movie/cases/MovieContextMenuWatchlistCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.movie.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.ui_base.common.sheets.context_menu.events.RemoveTraktUiEvent\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Movie\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieContextMenuWatchlistCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val moviesRepository: MoviesRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val announcementManager: AnnouncementManager,\n  private val quickSyncManager: QuickSyncManager,\n) {\n\n  suspend fun moveToWatchlist(traktId: IdTrakt) = withContext(dispatchers.IO) {\n    val movie = Movie.EMPTY.copy(ids = Ids.EMPTY.copy(traktId))\n\n    val (isMyMovie, isHidden) = awaitAll(\n      async { moviesRepository.myMovies.exists(traktId) },\n      async { moviesRepository.hiddenMovies.exists(traktId) }\n    )\n\n    moviesRepository.watchlistMovies.insert(movie.ids.trakt)\n    pinnedItemsRepository.removePinnedItem(movie)\n    announcementManager.refreshMoviesAnnouncements()\n\n    with(quickSyncManager) {\n      clearMovies(listOf(traktId.id))\n      clearHiddenMovies(listOf(traktId.id))\n      scheduleMoviesWatchlist(listOf(traktId.id))\n    }\n\n    RemoveTraktUiEvent(removeProgress = isMyMovie, removeHidden = isHidden)\n  }\n\n  suspend fun removeFromWatchlist(traktId: IdTrakt) = withContext(dispatchers.IO) {\n    moviesRepository.watchlistMovies.delete(traktId)\n    quickSyncManager.clearWatchlistMovies(listOf(traktId.id))\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/movie/helpers/MovieContextItem.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.movie.helpers\n\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.Translation\nimport java.time.format.DateTimeFormatter\n\ndata class MovieContextItem(\n  val movie: Movie,\n  val image: Image,\n  val translation: Translation?,\n  val dateFormat: DateTimeFormatter?,\n  val userRating: Int?,\n  val isMyMovie: Boolean,\n  val isWatchlist: Boolean,\n  val isHidden: Boolean,\n  val isPinnedTop: Boolean,\n  val spoilers: SpoilersSettings\n) {\n\n  fun isInCollection() = isHidden || isWatchlist || isMyMovie\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/show/ShowContextMenuBottomSheet.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.show\n\nimport android.content.res.ColorStateList\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.core.widget.ImageViewCompat\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_RATINGS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.sheets.context_menu.ContextMenuBottomSheet\nimport com.michaldrabik.ui_base.common.sheets.context_menu.events.FinishUiEvent\nimport com.michaldrabik.ui_base.common.sheets.context_menu.events.RemoveTraktUiEvent\nimport com.michaldrabik.ui_base.common.sheets.context_menu.show.helpers.ShowContextItem\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet.Mode\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SHOW_ID\nimport dagger.hilt.android.AndroidEntryPoint\nimport java.util.Locale\n\n@AndroidEntryPoint\nclass ShowContextMenuBottomSheet : ContextMenuBottomSheet() {\n\n  private val viewModel by viewModels<ShowContextMenuViewModel>()\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.messageFlow.collect { renderSnackbar(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      { viewModel.uiState.collect { render(it) } },\n      doAfterLaunch = { viewModel.loadShow(itemId) }\n    )\n  }\n\n  override fun setupView() {\n    super.setupView()\n    with(binding) {\n      contextMenuItemMoveToMyButton.text = getString(R.string.textMoveToMyShows)\n      contextMenuItemRemoveFromMyButton.text = getString(R.string.textRemoveFromMyShows)\n\n      contextMenuItemMoveToMyButton.onClick { viewModel.moveToMyShows() }\n      contextMenuItemRemoveFromMyButton.onClick { viewModel.removeFromMyShows() }\n      contextMenuItemMoveToWatchlistButton.onClick { viewModel.moveToWatchlist() }\n      contextMenuItemRemoveFromWatchlistButton.onClick { viewModel.removeFromWatchlist() }\n      contextMenuItemMoveToHiddenButton.onClick { viewModel.moveToHidden() }\n      contextMenuItemRemoveFromHiddenButton.onClick { viewModel.removeFromHidden() }\n      contextMenuItemPinButton.onClick { viewModel.addToTopPinned() }\n      contextMenuItemUnpinButton.onClick { viewModel.removeFromTopPinned() }\n      contextMenuItemAddOnHoldButton.onClick { viewModel.addToOnHoldPinned() }\n      contextMenuItemRemoveOnHoldButton.onClick { viewModel.removeFromOnHoldPinned() }\n    }\n  }\n\n  private fun render(uiState: ShowContextMenuUiState) {\n    uiState.run {\n      isLoading?.let {\n        when {\n          isLoading -> binding.contextMenuItemProgress.show()\n          else -> binding.contextMenuItemProgress.hide()\n        }\n        binding.contextMenuItemButtonsLayout.visibleIf(!isLoading, gone = false)\n      }\n      isLoadingSecondary?.let {\n        when {\n          isLoadingSecondary -> binding.contextMenuItemProgressSecondary.visible()\n          else -> binding.contextMenuItemProgressSecondary.gone()\n        }\n        binding.contextMenuItemButtonsLayout.visibleIf(!isLoadingSecondary, gone = false)\n      }\n      item?.let {\n        renderItem(it)\n        renderImage(it.image, it.show.ids.tvdb)\n      }\n    }\n  }\n\n  private fun renderItem(item: ShowContextItem) {\n    with(binding) {\n      contextMenuItemTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.show.title\n        else item.translation?.title\n\n      renderItemDescription(item)\n      renderItemRating(item)\n\n      contextMenuItemNetwork.text =\n        if (item.show.year > 0) getString(R.string.textNetwork, item.show.network, item.show.year.toString())\n        else String.format(\"%s\", item.show.network)\n\n      contextMenuUserRating.text = String.format(Locale.ENGLISH, \"%d\", item.userRating)\n      contextMenuUserRating.visibleIf(item.userRating != null)\n      contextMenuUserRatingStar.visibleIf(item.userRating != null)\n\n      contextMenuItemDescription.visibleIf(item.show.overview.isNotBlank())\n      contextMenuItemNetwork.visibleIf(item.show.network.isNotBlank())\n\n      contextMenuItemPinButton.visibleIf(!item.isPinnedTop)\n      contextMenuItemUnpinButton.visibleIf(item.isPinnedTop)\n      contextMenuItemAddOnHoldButton.visibleIf(!item.isOnHold)\n      contextMenuItemRemoveOnHoldButton.visibleIf(item.isOnHold)\n\n      contextMenuItemMoveToMyButton.visibleIf(!item.isMyShow)\n      contextMenuItemMoveToWatchlistButton.visibleIf(!item.isWatchlist)\n      contextMenuItemMoveToHiddenButton.visibleIf(!item.isHidden)\n\n      contextMenuItemRemoveFromMyButton.visibleIf(item.isMyShow)\n      contextMenuItemRemoveFromWatchlistButton.visibleIf(item.isWatchlist)\n      contextMenuItemRemoveFromHiddenButton.visibleIf(item.isHidden)\n\n      contextMenuItemBadge.visibleIf(item.isMyShow || item.isWatchlist)\n      val color = if (item.isMyShow) colorAccent else colorGray\n      ImageViewCompat.setImageTintList(contextMenuItemBadge, ColorStateList.valueOf(color))\n\n      if (!item.isInCollection()) {\n        contextMenuItemMoveToMyButton.text = getString(R.string.textAddToMyShows)\n        contextMenuItemMoveToWatchlistButton.text = getString(R.string.textAddToWatchlist)\n        contextMenuItemMoveToHiddenButton.text = getString(R.string.textHide)\n      }\n    }\n  }\n\n  private fun renderItemDescription(item: ShowContextItem) {\n    with(binding) {\n      contextMenuItemDescription.text =\n        if (item.translation?.overview.isNullOrBlank()) item.show.overview\n        else item.translation?.overview\n\n      val isMyShowHidden = item.spoilers.isMyShowsHidden && item.isMyShow\n      val isWatchlistHidden = item.spoilers.isWatchlistShowsHidden && item.isWatchlist\n      val isHiddenShowHidden = item.spoilers.isHiddenShowsHidden && item.isHidden\n      val isNotCollectedHidden = item.spoilers.isNotCollectedShowsHidden && (!item.isInCollection())\n\n      if (isMyShowHidden || isWatchlistHidden || isHiddenShowHidden || isNotCollectedHidden) {\n        val spoilerDescription = contextMenuItemDescription.text.toString()\n        val hiddenDescription = SPOILERS_REGEX.replace(contextMenuItemDescription.text.toString(), SPOILERS_HIDE_SYMBOL)\n        contextMenuItemDescription.tag = spoilerDescription\n        contextMenuItemDescription.text = hiddenDescription\n      }\n\n      if (item.spoilers.isTapToReveal) {\n        with(contextMenuItemDescription) {\n          onClick {\n            tag?.let { text = it.toString() }\n            enableFoldOnClick()\n          }\n        }\n      }\n    }\n  }\n\n  private fun renderItemRating(item: ShowContextItem) {\n    with(binding) {\n      var rating = String.format(Locale.ENGLISH, \"%.1f\", item.show.rating)\n\n      val isMyShowHidden = item.spoilers.isMyShowsRatingsHidden && item.isMyShow\n      val isWatchlistHidden = item.spoilers.isWatchlistShowsRatingsHidden && item.isWatchlist\n      val isHiddenShowHidden = item.spoilers.isHiddenShowsRatingsHidden && item.isHidden\n      val isNotCollectedHidden = item.spoilers.isNotCollectedShowsRatingsHidden && (!item.isInCollection())\n\n      if (isMyShowHidden || isWatchlistHidden || isHiddenShowHidden || isNotCollectedHidden) {\n        contextMenuRating.tag = rating\n        rating = SPOILERS_RATINGS_HIDE_SYMBOL\n      }\n\n      contextMenuRating.visibleIf(item.show.rating > 0)\n      contextMenuRatingStar.visibleIf(item.show.rating > 0)\n      contextMenuRating.text = rating\n\n      if (item.spoilers.isTapToReveal) {\n        with(contextMenuRating) {\n          onClick {\n            tag?.let { text = it.toString() }\n          }\n        }\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (val result = event.peek()) {\n      is RemoveTraktUiEvent -> when {\n        result.removeProgress -> openRemoveTraktSheet(R.id.actionShowItemContextDialogToRemoveTraktProgress, Mode.SHOW)\n        result.removeWatchlist -> openRemoveTraktSheet(R.id.actionShowItemContextDialogToRemoveTraktWatchlist, Mode.SHOW)\n        result.removeHidden -> openRemoveTraktSheet(R.id.actionShowItemContextDialogToRemoveTraktHidden, Mode.SHOW)\n        else -> close()\n      }\n      is FinishUiEvent -> if (result.isSuccess) close()\n      else -> throw IllegalStateException()\n    }\n  }\n\n  override fun openDetails() {\n    val bundle = bundleOf(ARG_SHOW_ID to itemId.id)\n    navigateTo(R.id.actionShowItemContextDialogToShowDetails, bundle)\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/show/ShowContextMenuUiState.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.show\n\nimport com.michaldrabik.ui_base.common.sheets.context_menu.show.helpers.ShowContextItem\n\ndata class ShowContextMenuUiState(\n  val isLoading: Boolean? = null,\n  val isLoadingSecondary: Boolean? = null,\n  val item: ShowContextItem? = null,\n)\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/show/ShowContextMenuViewModel.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.show\n\nimport android.annotation.SuppressLint\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.sheets.context_menu.events.FinishUiEvent\nimport com.michaldrabik.ui_base.common.sheets.context_menu.events.RemoveTraktUiEvent\nimport com.michaldrabik.ui_base.common.sheets.context_menu.show.cases.ShowContextMenuHiddenCase\nimport com.michaldrabik.ui_base.common.sheets.context_menu.show.cases.ShowContextMenuLoadItemCase\nimport com.michaldrabik.ui_base.common.sheets.context_menu.show.cases.ShowContextMenuMyShowsCase\nimport com.michaldrabik.ui_base.common.sheets.context_menu.show.cases.ShowContextMenuOnHoldCase\nimport com.michaldrabik.ui_base.common.sheets.context_menu.show.cases.ShowContextMenuPinnedCase\nimport com.michaldrabik.ui_base.common.sheets.context_menu.show.cases.ShowContextMenuWatchlistCase\nimport com.michaldrabik.ui_base.common.sheets.context_menu.show.helpers.ShowContextItem\nimport com.michaldrabik.ui_base.network.NetworkStatusProvider\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.launchDelayed\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.ImageType\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\nimport kotlin.properties.Delegates.notNull\n\n@SuppressLint(\"StaticFieldLeak\")\n@HiltViewModel\nclass ShowContextMenuViewModel @Inject constructor(\n  private val loadItemCase: ShowContextMenuLoadItemCase,\n  private val myShowsCase: ShowContextMenuMyShowsCase,\n  private val watchlistCase: ShowContextMenuWatchlistCase,\n  private val hiddenCase: ShowContextMenuHiddenCase,\n  private val pinnedCase: ShowContextMenuPinnedCase,\n  private val onHoldCase: ShowContextMenuOnHoldCase,\n  private val imagesProvider: ShowImagesProvider,\n  private val networkProvider: NetworkStatusProvider,\n  private val settingsRepository: SettingsRepository\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private var showId by notNull<IdTrakt>()\n  private var isQuickRemoveEnabled by notNull<Boolean>()\n\n  private val loadingState = MutableStateFlow(false)\n  private val loadingSecondaryState = MutableStateFlow(false)\n  private val itemState = MutableStateFlow<ShowContextItem?>(null)\n\n  fun loadShow(idTrakt: IdTrakt) {\n    viewModelScope.launch {\n      showId = idTrakt\n      isQuickRemoveEnabled = settingsRepository.load().traktQuickRemoveEnabled\n\n      try {\n        loadingState.value = true\n        val item = loadItemCase.loadItem(idTrakt)\n        itemState.value = item\n      } catch (error: Throwable) {\n        messageChannel.send(MessageEvent.Error(R.string.errorGeneral))\n      } finally {\n        loadingState.value = false\n      }\n    }\n  }\n\n  fun moveToMyShows() {\n    viewModelScope.launch {\n      if (!networkProvider.isOnline()) {\n        messageChannel.send(MessageEvent.Error(R.string.errorNoInternetConnection))\n        return@launch\n      }\n      val progressJob = launchDelayed(250) {\n        loadingSecondaryState.value = true\n      }\n      try {\n        val result = myShowsCase.moveToMyShows(showId)\n        preloadImage()\n        checkQuickRemove(result)\n      } catch (error: Throwable) {\n        onError(error)\n      } finally {\n        progressJob.cancel()\n      }\n    }\n  }\n\n  fun removeFromMyShows() {\n    viewModelScope.launch {\n      try {\n        myShowsCase.removeFromMyShows(\n          traktId = showId,\n          removeLocalData = networkProvider.isOnline()\n        )\n        checkQuickRemove(RemoveTraktUiEvent(removeProgress = true))\n      } catch (error: Throwable) {\n        onError(error)\n      }\n    }\n  }\n\n  fun moveToWatchlist() {\n    viewModelScope.launch {\n      try {\n        val result = watchlistCase.moveToWatchlist(\n          traktId = showId,\n          removeLocalData = networkProvider.isOnline()\n        )\n        checkQuickRemove(result)\n      } catch (error: Throwable) {\n        onError(error)\n      }\n    }\n  }\n\n  fun removeFromWatchlist() {\n    viewModelScope.launch {\n      try {\n        watchlistCase.removeFromWatchlist(showId)\n        checkQuickRemove(RemoveTraktUiEvent(removeWatchlist = true))\n      } catch (error: Throwable) {\n        onError(error)\n      }\n    }\n  }\n\n  fun moveToHidden() {\n    viewModelScope.launch {\n      try {\n        val result = hiddenCase.moveToHidden(\n          traktId = showId,\n          removeLocalData = networkProvider.isOnline()\n        )\n        checkQuickRemove(result)\n      } catch (error: Throwable) {\n        onError(error)\n      }\n    }\n  }\n\n  fun removeFromHidden() {\n    viewModelScope.launch {\n      try {\n        hiddenCase.removeFromHidden(showId)\n        checkQuickRemove(RemoveTraktUiEvent(removeHidden = true))\n      } catch (error: Throwable) {\n        onError(error)\n      }\n    }\n  }\n\n  fun addToTopPinned() {\n    viewModelScope.launch {\n      pinnedCase.addToTopPinned(showId)\n      eventChannel.send(Event(FinishUiEvent(true)))\n    }\n  }\n\n  fun removeFromTopPinned() {\n    viewModelScope.launch {\n      pinnedCase.removeFromTopPinned(showId)\n      eventChannel.send(Event(FinishUiEvent(true)))\n    }\n  }\n\n  fun addToOnHoldPinned() {\n    viewModelScope.launch {\n      onHoldCase.addToOnHold(showId)\n      eventChannel.send(Event(FinishUiEvent(true)))\n    }\n  }\n\n  fun removeFromOnHoldPinned() {\n    viewModelScope.launch {\n      onHoldCase.removeFromOnHold(showId)\n      eventChannel.send(Event(FinishUiEvent(true)))\n    }\n  }\n\n  private suspend fun preloadImage() {\n    try {\n      val show = itemState.value?.show\n      show?.let {\n        imagesProvider.loadRemoteImage(it, ImageType.FANART)\n      }\n    } catch (error: Throwable) {\n      Timber.e(error)\n      rethrowCancellation(error)\n    }\n  }\n\n  private suspend fun checkQuickRemove(event: RemoveTraktUiEvent) {\n    if (isQuickRemoveEnabled) {\n      loadingState.value = false\n      loadingSecondaryState.value = false\n      eventChannel.send(Event(event))\n    } else {\n      eventChannel.send(Event(FinishUiEvent(true)))\n    }\n  }\n\n  private suspend fun onError(error: Throwable) {\n    loadingState.value = false\n    loadingSecondaryState.value = false\n    messageChannel.send(MessageEvent.Error(R.string.errorGeneral))\n    rethrowCancellation(error)\n  }\n\n  val uiState = combine(\n    loadingState,\n    loadingSecondaryState,\n    itemState\n  ) { s1, s2, s3 ->\n    ShowContextMenuUiState(\n      isLoading = s1,\n      isLoadingSecondary = s2,\n      item = s3\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ShowContextMenuUiState()\n  )\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/show/cases/ShowContextMenuHiddenCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.show.cases\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.Season\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.common.sheets.context_menu.events.RemoveTraktUiEvent\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowContextMenuHiddenCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val showsRepository: ShowsRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val quickSyncManager: QuickSyncManager,\n  private val announcementManager: AnnouncementManager,\n) {\n\n  suspend fun moveToHidden(traktId: IdTrakt, removeLocalData: Boolean) =\n    withContext(dispatchers.IO) {\n      val show = Show.EMPTY.copy(ids = Ids.EMPTY.copy(traktId))\n\n      val (isMyShow, isWatchlist) = awaitAll(\n        async { showsRepository.myShows.exists(traktId) },\n        async { showsRepository.watchlistShows.exists(traktId) }\n      )\n\n      transactions.withTransaction {\n        showsRepository.hiddenShows.insert(show.ids.trakt)\n\n        if (removeLocalData && isMyShow) {\n          localSource.episodes.deleteAllUnwatchedForShow(traktId.id)\n          val seasons = localSource.seasons.getAllByShowId(traktId.id)\n          val episodes = localSource.episodes.getAllByShowId(traktId.id)\n          val toDelete = mutableListOf<Season>()\n          seasons.forEach { season ->\n            if (episodes.none { it.idSeason == season.idTrakt }) {\n              toDelete.add(season)\n            }\n          }\n          localSource.seasons.delete(toDelete)\n        }\n      }\n\n      pinnedItemsRepository.removePinnedItem(show)\n      announcementManager.refreshShowsAnnouncements()\n      with(quickSyncManager) {\n        clearWatchlistShows(listOf(traktId.id))\n        scheduleHidden(traktId.id, Mode.SHOWS, TraktSyncQueue.Operation.ADD)\n      }\n\n      RemoveTraktUiEvent(removeProgress = isMyShow, removeWatchlist = isWatchlist)\n    }\n\n  suspend fun removeFromHidden(traktId: IdTrakt) = withContext(dispatchers.IO) {\n    showsRepository.hiddenShows.delete(traktId)\n    announcementManager.refreshShowsAnnouncements()\n    quickSyncManager.clearHiddenShows(listOf(traktId.id))\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/show/cases/ShowContextMenuLoadItemCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.show.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.OnHoldItemsRepository\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.common.sheets.context_menu.show.helpers.ShowContextItem\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.ImageType\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowContextMenuLoadItemCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val showsRepository: ShowsRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val onHoldItemsRepository: OnHoldItemsRepository,\n  private val imagesProvider: ShowImagesProvider,\n  private val translationsRepository: TranslationsRepository,\n  private val ratingsRepository: RatingsRepository,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun loadItem(traktId: IdTrakt) = withContext(dispatchers.IO) {\n    val show = showsRepository.detailsShow.load(traktId)\n    val language = translationsRepository.getLanguage()\n    val spoilers = settingsRepository.spoilers.getAll()\n\n    val imageAsync = async { imagesProvider.findCachedImage(show, ImageType.POSTER) }\n    val translationAsync = async { translationsRepository.loadTranslation(show, language = language, onlyLocal = true) }\n    val ratingAsync = async { ratingsRepository.shows.loadRatings(listOf(show)) }\n\n    val isMyShowAsync = async { showsRepository.myShows.exists(traktId) }\n    val isWatchlistAsync = async { showsRepository.watchlistShows.exists(traktId) }\n    val isHiddenAsync = async { showsRepository.hiddenShows.exists(traktId) }\n\n    val isPinnedAsync = async { pinnedItemsRepository.isItemPinned(show) }\n    val isOnHoldAsync = async { onHoldItemsRepository.isOnHold(show) }\n\n    ShowContextItem(\n      show = show,\n      image = imageAsync.await(),\n      translation = translationAsync.await(),\n      userRating = ratingAsync.await().firstOrNull()?.rating,\n      isMyShow = isMyShowAsync.await(),\n      isWatchlist = isWatchlistAsync.await(),\n      isHidden = isHiddenAsync.await(),\n      isPinnedTop = isPinnedAsync.await(),\n      isOnHold = isOnHoldAsync.await(),\n      spoilers = spoilers\n    )\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/show/cases/ShowContextMenuMyShowsCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.show.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.common.sheets.context_menu.events.RemoveTraktUiEvent\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\nimport com.michaldrabik.data_local.database.model.Episode as EpisodeDb\nimport com.michaldrabik.data_local.database.model.Season as SeasonDb\n\n@ViewModelScoped\nclass ShowContextMenuMyShowsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val remoteSource: RemoteDataSource,\n  private val mappers: Mappers,\n  private val showsRepository: ShowsRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val settingsRepository: SettingsRepository,\n  private val announcementManager: AnnouncementManager,\n) {\n\n  suspend fun moveToMyShows(traktId: IdTrakt) =\n    withContext(dispatchers.IO) {\n      val show = Show.EMPTY.copy(ids = Ids.EMPTY.copy(traktId))\n\n      val (isWatchlist, isHidden) = awaitAll(\n        async { showsRepository.watchlistShows.exists(traktId) },\n        async { showsRepository.hiddenShows.exists(traktId) }\n      )\n\n      val seasons = remoteSource.trakt.fetchSeasons(traktId.id)\n        .map { mappers.season.fromNetwork(it) }\n        .filter { it.episodes.isNotEmpty() }\n        .filter { if (!showSpecials()) !it.isSpecial() else true }\n\n      val episodes = seasons.flatMap { it.episodes }\n\n      transactions.withTransaction {\n        val localSeasons = localSource.seasons.getAllByShowId(traktId.id)\n        val localEpisodes = localSource.episodes.getAllByShowId(traktId.id)\n        val lastWatchedAt = localEpisodes.maxByOrNull { it.lastWatchedAt != null }?.lastWatchedAt?.toMillis() ?: 0L\n\n        showsRepository.myShows.insert(traktId, lastWatchedAt)\n\n        val seasonsToAdd = mutableListOf<SeasonDb>()\n        val episodesToAdd = mutableListOf<EpisodeDb>()\n\n        seasons.forEach { season ->\n          if (localSeasons.none { it.idTrakt == season.ids.trakt.id }) {\n            seasonsToAdd.add(mappers.season.toDatabase(season, traktId, false))\n          }\n        }\n        episodes.forEach { episode ->\n          if (localEpisodes.none { it.idTrakt == episode.ids.trakt.id }) {\n            val season = seasons.find { it.number == episode.season }!!\n            episodesToAdd.add(mappers.episode.toDatabase(episode, season, traktId, false, null))\n          }\n        }\n\n        localSource.seasons.upsert(seasonsToAdd)\n        localSource.episodes.upsert(episodesToAdd)\n      }\n\n      pinnedItemsRepository.removePinnedItem(show)\n      announcementManager.refreshShowsAnnouncements()\n\n      RemoveTraktUiEvent(removeWatchlist = isWatchlist, removeHidden = isHidden)\n    }\n\n  suspend fun removeFromMyShows(traktId: IdTrakt, removeLocalData: Boolean) =\n    withContext(dispatchers.IO) {\n      val show = Show.EMPTY.copy(ids = Ids.EMPTY.copy(traktId))\n      transactions.withTransaction {\n        showsRepository.myShows.delete(show.ids.trakt)\n\n        if (removeLocalData) {\n          localSource.episodes.deleteAllUnwatchedForShow(show.traktId)\n          val seasons = localSource.seasons.getAllByShowId(show.traktId)\n          val episodes = localSource.episodes.getAllByShowId(show.traktId)\n          val toDelete = mutableListOf<SeasonDb>()\n          seasons.forEach { season ->\n            if (episodes.none { it.idSeason == season.idTrakt }) {\n              toDelete.add(season)\n            }\n          }\n          localSource.seasons.delete(toDelete)\n        }\n\n        pinnedItemsRepository.removePinnedItem(show)\n        announcementManager.refreshShowsAnnouncements()\n      }\n    }\n\n  private suspend fun showSpecials() = withContext(dispatchers.IO) {\n    settingsRepository.load().specialSeasonsEnabled\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/show/cases/ShowContextMenuOnHoldCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.show.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.OnHoldItemsRepository\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowContextMenuOnHoldCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val onHoldItemsRepository: OnHoldItemsRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val announcementManager: AnnouncementManager,\n) {\n\n  suspend fun addToOnHold(traktId: IdTrakt) {\n    val show = Show.EMPTY.copy(ids = Ids.EMPTY.copy(traktId))\n    pinnedItemsRepository.removePinnedItem(show)\n    onHoldItemsRepository.addItem(show)\n    withContext(dispatchers.IO) {\n      announcementManager.refreshShowsAnnouncements()\n    }\n  }\n\n  suspend fun removeFromOnHold(traktId: IdTrakt) {\n    val show = Show.EMPTY.copy(ids = Ids.EMPTY.copy(traktId))\n    onHoldItemsRepository.removeItem(show)\n    withContext(dispatchers.IO) {\n      announcementManager.refreshShowsAnnouncements()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/show/cases/ShowContextMenuPinnedCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.show.cases\n\nimport com.michaldrabik.repository.OnHoldItemsRepository\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowContextMenuPinnedCase @Inject constructor(\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val onHoldItemsRepository: OnHoldItemsRepository,\n) {\n\n  fun addToTopPinned(traktId: IdTrakt) {\n    val show = Show.EMPTY.copy(ids = Ids.EMPTY.copy(traktId))\n    onHoldItemsRepository.removeItem(show)\n    pinnedItemsRepository.addPinnedItem(show)\n  }\n\n  fun removeFromTopPinned(traktId: IdTrakt) {\n    val show = Show.EMPTY.copy(ids = Ids.EMPTY.copy(traktId))\n    pinnedItemsRepository.removePinnedItem(show)\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/show/cases/ShowContextMenuWatchlistCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.show.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.Season\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.common.sheets.context_menu.events.RemoveTraktUiEvent\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowContextMenuWatchlistCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val showsRepository: ShowsRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val quickSyncManager: QuickSyncManager,\n  private val announcementManager: AnnouncementManager,\n) {\n\n  suspend fun moveToWatchlist(\n    traktId: IdTrakt,\n    removeLocalData: Boolean,\n  ) = withContext(dispatchers.IO) {\n    val show = Show.EMPTY.copy(ids = Ids.EMPTY.copy(traktId))\n\n    val (isMyShow, isHidden) = awaitAll(\n      async { showsRepository.myShows.exists(traktId) },\n      async { showsRepository.hiddenShows.exists(traktId) }\n    )\n\n    transactions.withTransaction {\n      showsRepository.watchlistShows.insert(show.ids.trakt)\n\n      if (removeLocalData && isMyShow) {\n        localSource.episodes.deleteAllUnwatchedForShow(traktId.id)\n        val seasons = localSource.seasons.getAllByShowId(traktId.id)\n        val episodes = localSource.episodes.getAllByShowId(traktId.id)\n        val toDelete = mutableListOf<Season>()\n        seasons.forEach { season ->\n          if (episodes.none { it.idSeason == season.idTrakt }) {\n            toDelete.add(season)\n          }\n        }\n        localSource.seasons.delete(toDelete)\n      }\n    }\n\n    pinnedItemsRepository.removePinnedItem(show)\n    announcementManager.refreshShowsAnnouncements()\n    with(quickSyncManager) {\n      clearHiddenShows(listOf(traktId.id))\n      scheduleShowsWatchlist(listOf(traktId.id))\n    }\n\n    RemoveTraktUiEvent(removeProgress = isMyShow, removeHidden = isHidden)\n  }\n\n  suspend fun removeFromWatchlist(traktId: IdTrakt) = withContext(dispatchers.IO) {\n    showsRepository.watchlistShows.delete(traktId)\n    announcementManager.refreshShowsAnnouncements()\n    quickSyncManager.clearWatchlistShows(listOf(traktId.id))\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/context_menu/show/helpers/ShowContextItem.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.context_menu.show.helpers\n\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.Translation\n\ndata class ShowContextItem(\n  val show: Show,\n  val image: Image,\n  val translation: Translation?,\n  val userRating: Int?,\n  val isMyShow: Boolean,\n  val isWatchlist: Boolean,\n  val isHidden: Boolean,\n  val isPinnedTop: Boolean,\n  val isOnHold: Boolean,\n  val spoilers: SpoilersSettings\n) {\n\n  fun isInCollection() = isHidden || isWatchlist || isMyShow\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/links/LinksBottomSheet.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.links\n\nimport android.content.ActivityNotFoundException\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport android.os.Parcelable\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.databinding.ViewLinksBinding\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.openWebUrl\nimport com.michaldrabik.ui_base.utilities.extensions.requireParcelable\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.parcelize.Parcelize\n\n@AndroidEntryPoint\nclass LinksBottomSheet : BaseBottomSheetFragment(R.layout.view_links) {\n\n  @Parcelize\n  data class Options(\n    val ids: Ids,\n    val title: String,\n    val website: String,\n    val type: Mode,\n  ) : Parcelable\n\n  companion object {\n    fun createBundle(movie: Movie): Bundle {\n      val options = Options(movie.ids, movie.title, movie.homepage, Mode.MOVIES)\n      return bundleOf(NavigationArgs.ARG_OPTIONS to options)\n    }\n\n    fun createBundle(show: Show): Bundle {\n      val options = Options(show.ids, show.title, show.homepage, Mode.SHOWS)\n      return bundleOf(NavigationArgs.ARG_OPTIONS to options)\n    }\n  }\n\n  private val viewModel by viewModels<LinksViewModel>()\n  private val binding by viewBinding(ViewLinksBinding::bind)\n\n  private val options by lazy { requireParcelable<Options>(NavigationArgs.ARG_OPTIONS) }\n  private val ids by lazy { options.ids }\n  private val title by lazy { options.title }\n  private val website by lazy { options.website }\n  private val type by lazy { options.type }\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n  }\n\n  private fun setupView() {\n    with(binding) {\n      viewLinksJustWatch.onClick {\n        val country = viewModel.loadCountry()\n        openWebUrl(\"https://www.justwatch.com/${country.code}/${country.justWatchQuery}?content_type=${type.type}&q=${Uri.encode(title)}\")\n      }\n      viewLinksYouTube.onClick {\n        openWebUrl(\"https://www.youtube.com/results?search_query=${getQuery()}\")\n      }\n      viewLinksWiki.onClick {\n        openWebUrl(\"https://en.wikipedia.org/w/index.php?search=${getQuery()}\")\n      }\n      viewLinksGoogle.onClick {\n        openWebUrl(\"https://www.google.com/search?q=${getQuery()}\")\n      }\n      viewLinksDuckDuck.onClick {\n        openWebUrl(\"https://duckduckgo.com/?q=${getQuery()}\")\n      }\n      viewLinksGif.onClick {\n        openWebUrl(\"https://giphy.com/search/${getQuery()}\")\n      }\n      viewLinksTwitter.onClick {\n        openWebUrl(\"https://twitter.com/search?q=${getQuery()}&src=typed_query\")\n      }\n      viewLinksButtonClose.onClick { closeSheet() }\n    }\n    setWebLink()\n    setTraktLink()\n    setTvdbLink()\n    setTmdbLink()\n    setImdbLink()\n  }\n\n  private fun setWebLink() {\n    binding.viewLinksWebsite.run {\n      if (website.isBlank()) {\n        alpha = 0.5F\n        isEnabled = false\n      } else {\n        onClick { openWebUrl(website) }\n      }\n    }\n  }\n\n  private fun setTraktLink() {\n    binding.viewLinksTrakt.run {\n      if (ids.trakt.id == -1L) {\n        alpha = 0.5F\n        isEnabled = false\n      } else {\n        onClick { openWebUrl(\"https://trakt.tv/search/trakt/${ids.trakt.id}?id_type=${type.type}\") }\n      }\n    }\n  }\n\n  private fun setTvdbLink() {\n    binding.viewLinksTvdb.run {\n      if (ids.tvdb.id == -1L) {\n        alpha = 0.5F\n        isEnabled = false\n      } else {\n        onClick {\n          when (type) {\n            Mode.SHOWS -> openWebUrl(\"https://www.thetvdb.com/?id=${ids.tvdb.id}&tab=series\")\n            Mode.MOVIES -> openWebUrl(\"https://www.thetvdb.com/?id=${ids.tvdb.id}&tab=movies\")\n          }\n        }\n      }\n    }\n  }\n\n  private fun setTmdbLink() {\n    binding.viewLinksTmdb.run {\n      if (ids.tmdb.id == -1L) {\n        alpha = 0.5F\n        isEnabled = false\n      } else {\n        onClick {\n          when (type) {\n            Mode.SHOWS -> openWebUrl(\"https://www.themoviedb.org/tv/${ids.tmdb.id}\")\n            Mode.MOVIES -> openWebUrl(\"https://www.themoviedb.org/movie/${ids.tmdb.id}\")\n          }\n        }\n      }\n    }\n  }\n\n  private fun setImdbLink() {\n    binding.viewLinksImdb.run {\n      if (ids.imdb.id.isBlank()) {\n        alpha = 0.5F\n        isEnabled = false\n      } else {\n        onClick {\n          val i = Intent(Intent.ACTION_VIEW)\n          i.data = Uri.parse(\"imdb:///title/${ids.imdb.id}\")\n          try {\n            startActivity(i)\n          } catch (e: ActivityNotFoundException) {\n            // IMDb App not installed. Start in web browser\n            openWebUrl(\"https://www.imdb.com/title/${ids.imdb.id}\")\n          }\n        }\n      }\n    }\n  }\n\n  private fun getQuery() = when (type) {\n    Mode.SHOWS -> \"$title TV Series\"\n    Mode.MOVIES -> \"$title Movie\"\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/links/LinksViewModel.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.links\n\nimport androidx.lifecycle.ViewModel\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.AppCountry\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport javax.inject.Inject\n\n@HiltViewModel\nclass LinksViewModel @Inject constructor(\n  private val settingsRepository: SettingsRepository\n) : ViewModel() {\n\n  fun loadCountry() = AppCountry.fromCode(settingsRepository.country)\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/links/views/LinkItemView.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.links.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.databinding.ViewLinksItemBinding\n\nclass LinkItemView : FrameLayout {\n\n  private val binding = ViewLinksItemBinding.inflate(LayoutInflater.from(context), this)\n\n  constructor(context: Context) : this(context, null)\n  constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n\n    clipChildren = false\n    clipToPadding = false\n\n    context.theme.obtainStyledAttributes(attrs, R.styleable.LinkItem, 0, 0).apply {\n      try {\n        with(binding) {\n          viewLinkItemName.text = getString(R.styleable.LinkItem_text)\n          viewLinkItemImage.setImageResource(getResourceId(R.styleable.LinkItem_icon, -1))\n        }\n      } finally {\n        recycle()\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/ratings/RatingsBottomSheet.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.ratings\n\nimport android.os.Bundle\nimport android.os.Parcelable\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.views.RateValueView.Direction\nimport com.michaldrabik.ui_base.databinding.ViewRateSheetBinding\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.requireParcelable\nimport com.michaldrabik.ui_base.utilities.extensions.showErrorSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.TraktRating\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.parcelize.Parcelize\n\n@AndroidEntryPoint\nclass RatingsBottomSheet : BaseBottomSheetFragment(R.layout.view_rate_sheet) {\n\n  companion object {\n    fun createBundle(id: IdTrakt, type: Options.Type): Bundle {\n      val options = Options(id, type)\n      return bundleOf(NavigationArgs.ARG_OPTIONS to options)\n    }\n\n    private const val INITIAL_RATING = 5\n  }\n\n  private val viewModel by viewModels<RatingsSheetViewModel>()\n  private val binding by viewBinding(ViewRateSheetBinding::bind)\n\n  private val options by lazy { requireParcelable<Options>(NavigationArgs.ARG_OPTIONS) }\n  private val id by lazy { options.id }\n  private val type by lazy { options.type }\n\n  private val starsViews by lazy {\n    with(binding) { listOf(star1, star2, star3, star4, star5, star6, star7, star8, star9, star10) }\n  }\n  private var selectedRating = INITIAL_RATING\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.messageFlow.collect { renderSnackbar(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = { viewModel.loadRating(id, type) }\n    )\n  }\n\n  private fun setupView() {\n    renderRating(INITIAL_RATING)\n    starsViews.forEach { star -> star.onClick { renderRating(it.tag.toString().toInt(), animate = true) } }\n    binding.viewRateSheetSaveButton.onClick { viewModel.saveRating(selectedRating, id, type) }\n    binding.viewRateSheetRemoveButton.onClick { viewModel.removeRating(id, type) }\n  }\n\n  private fun render(uiState: RatingsUiState) {\n    with(uiState) {\n      with(binding) {\n        isLoading?.let {\n          viewRateSheetProgress.visibleIf(it)\n          viewRateSheetSaveButton.visibleIf(!it, gone = false)\n          viewRateSheetRemoveButton.visibleIf(!it, gone = false)\n          starsViews.forEach { view -> view.isEnabled = !it }\n        }\n        rating?.let {\n          viewRateSheetSaveButton.isEnabled = true\n          if (isLoading != true) {\n            viewRateSheetRemoveButton.visibleIf(it != TraktRating.EMPTY)\n          }\n          viewRateSheetStarsLayout.visible()\n          if (it != TraktRating.EMPTY && isLoading != true) {\n            renderRating(it.rating)\n          }\n        }\n      }\n    }\n  }\n\n  private fun renderRating(rate: Int, animate: Boolean = false) {\n    val currentRating = selectedRating\n    selectedRating = rate.coerceIn(1..10)\n    starsViews.forEach { it.setImageResource(R.drawable.ic_star_empty) }\n    (1..selectedRating).forEachIndexed { index, _ ->\n      starsViews[index].setImageResource(R.drawable.ic_star)\n    }\n    if (animate && currentRating != selectedRating) {\n      val direction = if (currentRating > selectedRating) Direction.RIGHT else Direction.LEFT\n      binding.viewRateSheetRating.setValueAnimated(selectedRating.toString(), direction)\n    } else {\n      binding.viewRateSheetRating.setValue(selectedRating.toString())\n    }\n  }\n\n  private fun renderSnackbar(message: MessageEvent) {\n    when (message) {\n      is MessageEvent.Info -> binding.viewRateSheetSnackHost.showInfoSnackbar(getString(message.textRestId))\n      is MessageEvent.Error -> binding.viewRateSheetSnackHost.showErrorSnackbar(getString(message.textRestId))\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is FinishUiEvent -> closeWithSuccess(event.operation)\n    }\n  }\n\n  private fun closeWithSuccess(operation: Options.Operation) {\n    val result = bundleOf(NavigationArgs.RESULT to operation)\n    setFragmentResult(NavigationArgs.REQUEST_RATING, result)\n    closeSheet()\n  }\n\n  @Parcelize\n  data class Options(\n    val id: IdTrakt,\n    val type: Type,\n  ) : Parcelable {\n\n    enum class Type {\n      SHOW,\n      MOVIE,\n      EPISODE,\n      SEASON\n    }\n\n    @Parcelize\n    enum class Operation : Parcelable {\n      SAVE,\n      REMOVE\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/ratings/RatingsSheetViewModel.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.ratings\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError.CoroutineCancellation\nimport com.michaldrabik.common.errors.ShowlyError.UnauthorizedError\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Operation\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Type\nimport com.michaldrabik.ui_base.common.sheets.ratings.cases.RatingsEpisodeCase\nimport com.michaldrabik.ui_base.common.sheets.ratings.cases.RatingsMovieCase\nimport com.michaldrabik.ui_base.common.sheets.ratings.cases.RatingsSeasonCase\nimport com.michaldrabik.ui_base.common.sheets.ratings.cases.RatingsShowCase\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.TraktRating\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass RatingsSheetViewModel @Inject constructor(\n  private val showRatingsCase: RatingsShowCase,\n  private val movieRatingsCase: RatingsMovieCase,\n  private val episodeRatingsCase: RatingsEpisodeCase,\n  private val seasonRatingsCase: RatingsSeasonCase,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val loadingState = MutableStateFlow(false)\n  private val ratingState = MutableStateFlow<TraktRating?>(null)\n\n  fun loadRating(idTrakt: IdTrakt, type: Type) {\n    viewModelScope.launch {\n      try {\n        val rating = when (type) {\n          Type.SHOW -> showRatingsCase.loadRating(idTrakt)\n          Type.MOVIE -> movieRatingsCase.loadRating(idTrakt)\n          Type.EPISODE -> episodeRatingsCase.loadRating(idTrakt)\n          Type.SEASON -> seasonRatingsCase.loadRating(idTrakt)\n        }\n        ratingState.value = rating\n      } catch (error: Throwable) {\n        handleError(error)\n      }\n    }\n  }\n\n  fun saveRating(rating: Int, id: IdTrakt, type: Type) {\n    viewModelScope.launch {\n      try {\n        loadingState.value = true\n        when (type) {\n          Type.SHOW -> showRatingsCase.saveRating(id, rating)\n          Type.MOVIE -> movieRatingsCase.saveRating(id, rating)\n          Type.EPISODE -> episodeRatingsCase.saveRating(id, rating)\n          Type.SEASON -> seasonRatingsCase.saveRating(id, rating)\n        }\n        eventChannel.send(FinishUiEvent(operation = Operation.SAVE))\n      } catch (error: Throwable) {\n        loadingState.value = false\n        handleError(error)\n      }\n    }\n  }\n\n  fun removeRating(id: IdTrakt, type: Type) {\n    viewModelScope.launch {\n      try {\n        loadingState.value = true\n        when (type) {\n          Type.SHOW -> showRatingsCase.deleteRating(id)\n          Type.MOVIE -> movieRatingsCase.deleteRating(id)\n          Type.EPISODE -> episodeRatingsCase.deleteRating(id)\n          Type.SEASON -> seasonRatingsCase.deleteRating(id)\n        }\n        eventChannel.send(FinishUiEvent(operation = Operation.REMOVE))\n      } catch (error: Throwable) {\n        loadingState.value = false\n        handleError(error)\n      }\n    }\n  }\n\n  private suspend fun handleError(error: Throwable) {\n    when (ErrorHelper.parse(error)) {\n      is CoroutineCancellation -> throw error\n      is UnauthorizedError -> messageChannel.send(MessageEvent.Error(R.string.errorTraktAuthorization))\n      else -> messageChannel.send(MessageEvent.Error(R.string.errorGeneral))\n    }\n  }\n\n  val uiState = combine(\n    loadingState,\n    ratingState\n  ) { s1, s2 ->\n    RatingsUiState(\n      isLoading = s1,\n      rating = s2\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = RatingsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/ratings/RatingsUiEvents.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_base.common.sheets.ratings\n\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Operation\nimport com.michaldrabik.ui_base.utilities.events.Event\n\ndata class FinishUiEvent(val operation: Operation) : Event<Operation>(operation)\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/ratings/RatingsUiState.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.ratings\n\nimport com.michaldrabik.ui_model.TraktRating\n\ndata class RatingsUiState(\n  val isLoading: Boolean? = null,\n  val rating: TraktRating? = null,\n)\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/ratings/cases/RatingsEpisodeCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.ratings.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.TraktRating\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass RatingsEpisodeCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val userTraktManager: UserTraktManager,\n  private val ratingsRepository: RatingsRepository,\n) {\n\n  companion object {\n    private val RATING_VALID_RANGE = 1..10\n  }\n\n  suspend fun loadRating(idTrakt: IdTrakt): TraktRating = withContext(dispatchers.IO) {\n    val episode = Episode.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = idTrakt))\n    try {\n      val rating = ratingsRepository.shows.loadRating(episode)\n      rating ?: TraktRating.EMPTY\n    } catch (error: Throwable) {\n      handleError(error)\n      TraktRating.EMPTY\n    }\n  }\n\n  suspend fun saveRating(idTrakt: IdTrakt, rating: Int) = withContext(dispatchers.IO) {\n    check(rating in RATING_VALID_RANGE)\n    userTraktManager.checkAuthorization()\n\n    val episode = Episode.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = idTrakt))\n    try {\n      ratingsRepository.shows.addRating(episode, rating)\n    } catch (error: Throwable) {\n      handleError(error)\n    }\n  }\n\n  suspend fun deleteRating(idTrakt: IdTrakt) = withContext(dispatchers.IO) {\n    userTraktManager.checkAuthorization()\n\n    val episode = Episode.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = idTrakt))\n    try {\n      ratingsRepository.shows.deleteRating(episode)\n    } catch (error: Throwable) {\n      handleError(error)\n    }\n  }\n\n  private suspend fun handleError(error: Throwable) {\n    val showlyError = ErrorHelper.parse(error)\n    if (showlyError is ShowlyError.UnauthorizedError) {\n      userTraktManager.revokeToken()\n    }\n    throw error\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/ratings/cases/RatingsMovieCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.ratings.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.TraktRating\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass RatingsMovieCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val userTraktManager: UserTraktManager,\n  private val ratingsRepository: RatingsRepository,\n) {\n\n  companion object {\n    private val RATING_VALID_RANGE = 1..10\n  }\n\n  suspend fun loadRating(idTrakt: IdTrakt): TraktRating =\n    withContext(dispatchers.IO) {\n      val movie = Movie.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = idTrakt))\n      try {\n        val rating = ratingsRepository.movies.loadRatings(listOf(movie))\n        rating.firstOrNull() ?: TraktRating.EMPTY\n      } catch (error: Throwable) {\n        handleError(error)\n        TraktRating.EMPTY\n      }\n    }\n\n  suspend fun saveRating(idTrakt: IdTrakt, rating: Int) =\n    withContext(dispatchers.IO) {\n      check(rating in RATING_VALID_RANGE)\n      userTraktManager.checkAuthorization()\n\n      val movie = Movie.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = idTrakt))\n      try {\n        ratingsRepository.movies.addRating(movie, rating)\n      } catch (error: Throwable) {\n        handleError(error)\n      }\n    }\n\n  suspend fun deleteRating(idTrakt: IdTrakt) = withContext(dispatchers.IO) {\n    userTraktManager.checkAuthorization()\n\n    val movie = Movie.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = idTrakt))\n    try {\n      ratingsRepository.movies.deleteRating(movie)\n    } catch (error: Throwable) {\n      handleError(error)\n    }\n  }\n\n  private suspend fun handleError(error: Throwable) {\n    val showlyError = ErrorHelper.parse(error)\n    if (showlyError is ShowlyError.UnauthorizedError) {\n      userTraktManager.revokeToken()\n    }\n    throw error\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/ratings/cases/RatingsSeasonCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.ratings.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.TraktRating\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass RatingsSeasonCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val userTraktManager: UserTraktManager,\n  private val ratingsRepository: RatingsRepository,\n) {\n\n  companion object {\n    private val RATING_VALID_RANGE = 1..10\n  }\n\n  suspend fun loadRating(idTrakt: IdTrakt): TraktRating = withContext(dispatchers.IO) {\n    val season = Season.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = idTrakt))\n    try {\n      val rating = ratingsRepository.shows.loadRatingsSeasons(listOf(season))\n      rating.firstOrNull() ?: TraktRating.EMPTY\n    } catch (error: Throwable) {\n      handleError(error)\n      TraktRating.EMPTY\n    }\n  }\n\n  suspend fun saveRating(idTrakt: IdTrakt, rating: Int) = withContext(dispatchers.IO) {\n    check(rating in RATING_VALID_RANGE)\n    userTraktManager.checkAuthorization()\n\n    val season = Season.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = idTrakt))\n    try {\n      ratingsRepository.shows.addRating(season, rating)\n    } catch (error: Throwable) {\n      handleError(error)\n    }\n  }\n\n  suspend fun deleteRating(idTrakt: IdTrakt) = withContext(dispatchers.IO) {\n    userTraktManager.checkAuthorization()\n\n    val season = Season.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = idTrakt))\n    try {\n      ratingsRepository.shows.deleteRating(season)\n    } catch (error: Throwable) {\n      handleError(error)\n    }\n  }\n\n  private suspend fun handleError(error: Throwable) {\n    val showlyError = ErrorHelper.parse(error)\n    if (showlyError is ShowlyError.UnauthorizedError) {\n      userTraktManager.revokeToken()\n    }\n    throw error\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/ratings/cases/RatingsShowCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.ratings.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.TraktRating\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass RatingsShowCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val userTraktManager: UserTraktManager,\n  private val ratingsRepository: RatingsRepository,\n) {\n\n  companion object {\n    private val RATING_VALID_RANGE = 1..10\n  }\n\n  suspend fun loadRating(idTrakt: IdTrakt): TraktRating =\n    withContext(dispatchers.IO) {\n      val show = Show.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = idTrakt))\n      try {\n        val rating = ratingsRepository.shows.loadRatings(listOf(show))\n        rating.firstOrNull() ?: TraktRating.EMPTY\n      } catch (error: Throwable) {\n        handleError(error)\n        TraktRating.EMPTY\n      }\n    }\n\n  suspend fun saveRating(idTrakt: IdTrakt, rating: Int) = withContext(dispatchers.IO) {\n    check(rating in RATING_VALID_RANGE)\n    userTraktManager.checkAuthorization()\n\n    val show = Show.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = idTrakt))\n    try {\n      ratingsRepository.shows.addRating(show, rating)\n    } catch (error: Throwable) {\n      handleError(error)\n    }\n  }\n\n  suspend fun deleteRating(idTrakt: IdTrakt) = withContext(dispatchers.IO) {\n    userTraktManager.checkAuthorization()\n\n    val show = Show.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = idTrakt))\n    try {\n      ratingsRepository.shows.deleteRating(show)\n    } catch (error: Throwable) {\n      handleError(error)\n    }\n  }\n\n  private suspend fun handleError(error: Throwable) {\n    val showlyError = ErrorHelper.parse(error)\n    if (showlyError is ShowlyError.UnauthorizedError) {\n      userTraktManager.revokeToken()\n    }\n    throw error\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/remove_trakt/RemoveTraktBottomSheet.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.remove_trakt\n\nimport android.content.DialogInterface\nimport android.os.Parcelable\nimport androidx.annotation.LayoutRes\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.setFragmentResult\nimport androidx.lifecycle.ViewModel\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.utilities.extensions.requireLongArray\nimport com.michaldrabik.ui_base.utilities.extensions.requireParcelable\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_TYPE\nimport kotlinx.parcelize.Parcelize\n\nabstract class RemoveTraktBottomSheet<T : ViewModel>(@LayoutRes layoutResId: Int) : BaseBottomSheetFragment(layoutResId) {\n\n  companion object {\n    fun createBundle(itemIds: List<IdTrakt>, mode: Mode) = bundleOf(\n      ARG_ID to itemIds.map { it.id }.toLongArray(),\n      ARG_TYPE to mode\n    )\n\n    fun createBundle(itemId: IdTrakt, mode: Mode) = createBundle(listOf(itemId), mode)\n  }\n\n  protected val itemIds: List<IdTrakt> by lazy { requireLongArray(ARG_ID).map { IdTrakt(it) } }\n  protected val itemType by lazy { requireParcelable<Mode>(ARG_TYPE) }\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onCancel(dialog: DialogInterface) {\n    setFragmentResult(NavigationArgs.REQUEST_REMOVE_TRAKT, bundleOf(NavigationArgs.RESULT to false))\n    super.onCancel(dialog)\n  }\n\n  @Parcelize\n  enum class Mode : Parcelable {\n    SHOW,\n    EPISODE,\n    MOVIE\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/remove_trakt/remove_trakt_hidden/RemoveTraktHiddenBottomSheet.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_hidden\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet\nimport com.michaldrabik.ui_base.databinding.ViewRemoveTraktHiddenBinding\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.showErrorSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_REMOVE_TRAKT\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.RESULT\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass RemoveTraktHiddenBottomSheet : RemoveTraktBottomSheet<RemoveTraktHiddenViewModel>(R.layout.view_remove_trakt_hidden) {\n\n  private val viewModel by viewModels<RemoveTraktHiddenViewModel>()\n  private val binding by viewBinding(ViewRemoveTraktHiddenBinding::bind)\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.messageFlow.collect { renderSnackbar(it) } },\n      { viewModel.uiState.collect { render(it) } }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      viewRemoveTraktHiddenButtonNo.onClick {\n        setFragmentResult(REQUEST_REMOVE_TRAKT, bundleOf(RESULT to false))\n        closeSheet()\n      }\n      viewRemoveTraktHiddenButtonYes.onClick {\n        viewModel.removeFromTrakt(itemIds, itemType)\n      }\n    }\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun render(uiState: RemoveTraktHiddenUiState) {\n    uiState.run {\n      isLoading?.let {\n        with(binding) {\n          viewRemoveTraktHiddenProgress.visibleIf(it)\n          viewRemoveTraktHiddenButtonNo.visibleIf(!it, gone = false)\n          viewRemoveTraktHiddenButtonNo.isClickable = !it\n          viewRemoveTraktHiddenButtonYes.visibleIf(!it, gone = false)\n          viewRemoveTraktHiddenButtonYes.isClickable = !it\n        }\n      }\n      isFinished?.let {\n        if (it) {\n          setFragmentResult(REQUEST_REMOVE_TRAKT, bundleOf(RESULT to true))\n          closeSheet()\n        }\n      }\n    }\n  }\n\n  private fun renderSnackbar(message: MessageEvent) {\n    when (message) {\n      is MessageEvent.Info -> binding.viewRemoveTraktHiddenSnackHost.showInfoSnackbar(getString(message.textRestId))\n      is MessageEvent.Error -> binding.viewRemoveTraktHiddenSnackHost.showErrorSnackbar(getString(message.textRestId))\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/remove_trakt/remove_trakt_hidden/RemoveTraktHiddenUiState.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_hidden\n\ndata class RemoveTraktHiddenUiState(\n  val isLoading: Boolean? = null,\n  val isFinished: Boolean? = null,\n)\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/remove_trakt/remove_trakt_hidden/RemoveTraktHiddenViewModel.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_hidden\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet.Mode\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_hidden.cases.RemoveTraktHiddenCase\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass RemoveTraktHiddenViewModel @Inject constructor(\n  private val removeTraktHiddenCase: RemoveTraktHiddenCase\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val loadingState = MutableStateFlow(false)\n  private val finishedState = MutableStateFlow(false)\n\n  fun removeFromTrakt(traktIds: List<IdTrakt>, mode: Mode) {\n    viewModelScope.launch {\n      try {\n        loadingState.value = true\n        removeTraktHiddenCase.removeTraktHidden(traktIds, mode)\n        finishedState.value = true\n      } catch (error: Throwable) {\n        messageChannel.send(MessageEvent.Error(R.string.errorTraktSyncGeneral))\n        loadingState.value = false\n      }\n    }\n  }\n\n  val uiState = combine(\n    loadingState,\n    finishedState\n  ) { s1, s2 ->\n    RemoveTraktHiddenUiState(\n      isLoading = s1,\n      isFinished = s2\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = RemoveTraktHiddenUiState()\n  )\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/remove_trakt/remove_trakt_hidden/cases/RemoveTraktHiddenCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_hidden.cases\n\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.data_remote.trakt.model.SyncExportItem\nimport com.michaldrabik.data_remote.trakt.model.SyncExportRequest\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet.Mode\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass RemoveTraktHiddenCase @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val userManager: UserTraktManager,\n) {\n\n  suspend fun removeTraktHidden(traktIds: List<IdTrakt>, mode: Mode) {\n    userManager.checkAuthorization()\n    val items = traktIds.map { SyncExportItem.create(it.id) }\n\n    when (mode) {\n      Mode.SHOW -> {\n        val request = SyncExportRequest(shows = items)\n        remoteSource.trakt.deleteHiddenShow(request)\n      }\n      Mode.MOVIE -> {\n        val request = SyncExportRequest(movies = items)\n        remoteSource.trakt.deleteHiddenMovie(request)\n      }\n      else -> throw IllegalStateException()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/remove_trakt/remove_trakt_progress/RemoveTraktProgressBottomSheet.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_progress\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet\nimport com.michaldrabik.ui_base.databinding.ViewRemoveTraktProgressBinding\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.showErrorSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_REMOVE_TRAKT\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.RESULT\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass RemoveTraktProgressBottomSheet : RemoveTraktBottomSheet<RemoveTraktProgressViewModel>(R.layout.view_remove_trakt_progress) {\n\n  private val viewModel by viewModels<RemoveTraktProgressViewModel>()\n  private val binding by viewBinding(ViewRemoveTraktProgressBinding::bind)\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.messageFlow.collect { renderSnackbar(it) } },\n      { viewModel.uiState.collect { render(it) } }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      viewRemoveTraktProgressButtonNo.onClick {\n        setFragmentResult(REQUEST_REMOVE_TRAKT, bundleOf(RESULT to false))\n        closeSheet()\n      }\n      viewRemoveTraktProgressButtonYes.onClick {\n        viewModel.removeFromTrakt(itemIds, itemType)\n      }\n    }\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun render(uiState: RemoveTraktProgressUiState) {\n    uiState.run {\n      isLoading?.let {\n        with(binding) {\n          viewRemoveTraktProgressProgress.visibleIf(it)\n          viewRemoveTraktProgressButtonNo.visibleIf(!it, gone = false)\n          viewRemoveTraktProgressButtonNo.isClickable = !it\n          viewRemoveTraktProgressButtonYes.visibleIf(!it, gone = false)\n          viewRemoveTraktProgressButtonYes.isClickable = !it\n        }\n      }\n      isFinished?.let {\n        if (it) {\n          setFragmentResult(REQUEST_REMOVE_TRAKT, bundleOf(RESULT to true))\n          closeSheet()\n        }\n      }\n    }\n  }\n\n  private fun renderSnackbar(message: MessageEvent) {\n    when (message) {\n      is MessageEvent.Info -> binding.viewRemoveTraktProgressSnackHost.showInfoSnackbar(getString(message.textRestId))\n      is MessageEvent.Error -> binding.viewRemoveTraktProgressSnackHost.showErrorSnackbar(getString(message.textRestId))\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/remove_trakt/remove_trakt_progress/RemoveTraktProgressUiState.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_progress\n\ndata class RemoveTraktProgressUiState(\n  val isLoading: Boolean? = null,\n  val isFinished: Boolean? = null,\n)\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/remove_trakt/remove_trakt_progress/RemoveTraktProgressViewModel.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_progress\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet.Mode\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_progress.cases.RemoveTraktProgressCase\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass RemoveTraktProgressViewModel @Inject constructor(\n  private val removeTraktProgressCase: RemoveTraktProgressCase\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val loadingState = MutableStateFlow(false)\n  private val finishedState = MutableStateFlow(false)\n\n  fun removeFromTrakt(traktIds: List<IdTrakt>, mode: Mode) {\n    viewModelScope.launch {\n      try {\n        loadingState.value = true\n        removeTraktProgressCase.removeTraktProgress(traktIds, mode)\n        finishedState.value = true\n      } catch (error: Throwable) {\n        messageChannel.send(MessageEvent.Error(R.string.errorTraktSyncGeneral))\n        loadingState.value = false\n      }\n    }\n  }\n\n  val uiState = combine(\n    loadingState,\n    finishedState\n  ) { s1, s2 ->\n    RemoveTraktProgressUiState(\n      isLoading = s1,\n      isFinished = s2\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = RemoveTraktProgressUiState()\n  )\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/remove_trakt/remove_trakt_progress/cases/RemoveTraktProgressCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_progress.cases\n\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.data_remote.trakt.model.SyncExportItem\nimport com.michaldrabik.data_remote.trakt.model.SyncExportRequest\nimport com.michaldrabik.repository.EpisodesManager\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet.Mode\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass RemoveTraktProgressCase @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val userManager: UserTraktManager,\n  private val episodesManager: EpisodesManager\n) {\n\n  suspend fun removeTraktProgress(traktIds: List<IdTrakt>, mode: Mode) {\n    userManager.checkAuthorization()\n    val items = traktIds.map { SyncExportItem.create(it.id) }\n\n    val request = when (mode) {\n      Mode.SHOW -> SyncExportRequest(shows = items)\n      Mode.MOVIE -> SyncExportRequest(movies = items)\n      Mode.EPISODE -> SyncExportRequest(episodes = items)\n    }\n\n    remoteSource.trakt.postDeleteProgress(request)\n    if (mode == Mode.SHOW && traktIds.isNotEmpty()) {\n      episodesManager.setAllUnwatched(traktIds.first())\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/remove_trakt/remove_trakt_watchlist/RemoveTraktWatchlistBottomSheet.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_watchlist\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet\nimport com.michaldrabik.ui_base.databinding.ViewRemoveTraktWatchlistBinding\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.showErrorSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_REMOVE_TRAKT\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.RESULT\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass RemoveTraktWatchlistBottomSheet : RemoveTraktBottomSheet<RemoveTraktWatchlistViewModel>(R.layout.view_remove_trakt_watchlist) {\n\n  private val viewModel by viewModels<RemoveTraktWatchlistViewModel>()\n  private val binding by viewBinding(ViewRemoveTraktWatchlistBinding::bind)\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.messageFlow.collect { renderSnackbar(it) } },\n      { viewModel.uiState.collect { render(it) } }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      viewRemoveTraktWatchlistButtonNo.onClick {\n        setFragmentResult(REQUEST_REMOVE_TRAKT, bundleOf(RESULT to false))\n        closeSheet()\n      }\n      viewRemoveTraktWatchlistButtonYes.onClick {\n        viewModel.removeFromTrakt(itemIds, itemType)\n      }\n    }\n  }\n\n  private fun render(uiState: RemoveTraktWatchlistUiState) {\n    uiState.run {\n      isLoading?.let {\n        with(binding) {\n          viewRemoveTraktWatchlistProgress.visibleIf(it)\n          viewRemoveTraktWatchlistButtonNo.visibleIf(!it, gone = false)\n          viewRemoveTraktWatchlistButtonNo.isClickable = !it\n          viewRemoveTraktWatchlistButtonYes.visibleIf(!it, gone = false)\n          viewRemoveTraktWatchlistButtonYes.isClickable = !it\n        }\n      }\n      isFinished?.let {\n        if (it) {\n          setFragmentResult(REQUEST_REMOVE_TRAKT, bundleOf(RESULT to true))\n          closeSheet()\n        }\n      }\n    }\n  }\n\n  private fun renderSnackbar(message: MessageEvent) {\n    when (message) {\n      is MessageEvent.Info -> binding.viewRemoveTraktWatchlistSnackHost.showInfoSnackbar(getString(message.textRestId))\n      is MessageEvent.Error -> binding.viewRemoveTraktWatchlistSnackHost.showErrorSnackbar(getString(message.textRestId))\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/remove_trakt/remove_trakt_watchlist/RemoveTraktWatchlistUiState.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_watchlist\n\ndata class RemoveTraktWatchlistUiState(\n  val isLoading: Boolean? = null,\n  val isFinished: Boolean? = null,\n)\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/remove_trakt/remove_trakt_watchlist/RemoveTraktWatchlistViewModel.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_watchlist\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet.Mode\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_watchlist.cases.RemoveTraktWatchlistCase\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass RemoveTraktWatchlistViewModel @Inject constructor(\n  private val removeTraktWatchlistCase: RemoveTraktWatchlistCase\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val loadingState = MutableStateFlow(false)\n  private val finishedState = MutableStateFlow(false)\n\n  fun removeFromTrakt(traktIds: List<IdTrakt>, mode: Mode) {\n    viewModelScope.launch {\n      try {\n        loadingState.value = true\n        removeTraktWatchlistCase.removeTraktWatchlist(traktIds, mode)\n        finishedState.value = true\n      } catch (error: Throwable) {\n        messageChannel.send(MessageEvent.Error(R.string.errorTraktSyncGeneral))\n        loadingState.value = false\n      }\n    }\n  }\n\n  val uiState = combine(\n    loadingState,\n    finishedState\n  ) { s1, s2 ->\n    RemoveTraktWatchlistUiState(\n      isLoading = s1,\n      isFinished = s2\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = RemoveTraktWatchlistUiState()\n  )\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/remove_trakt/remove_trakt_watchlist/cases/RemoveTraktWatchlistCase.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_watchlist.cases\n\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.data_remote.trakt.model.SyncExportItem\nimport com.michaldrabik.data_remote.trakt.model.SyncExportRequest\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet.Mode\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass RemoveTraktWatchlistCase @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val userManager: UserTraktManager,\n) {\n\n  suspend fun removeTraktWatchlist(traktIds: List<IdTrakt>, mode: Mode) {\n    userManager.checkAuthorization()\n    val items = traktIds.map { SyncExportItem.create(it.id) }\n\n    val request = when (mode) {\n      Mode.SHOW -> SyncExportRequest(shows = items)\n      Mode.MOVIE -> SyncExportRequest(movies = items)\n      else -> throw IllegalStateException()\n    }\n\n    remoteSource.trakt.postDeleteWatchlist(request)\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/sort_order/SortOrderBottomSheet.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.sort_order\n\nimport android.annotation.SuppressLint\nimport android.graphics.Typeface\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.core.view.children\nimport androidx.fragment.app.setFragmentResult\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.sheets.sort_order.views.SortOrderItemView\nimport com.michaldrabik.ui_base.databinding.ViewSortOrderBinding\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.requireSerializable\nimport com.michaldrabik.ui_base.utilities.extensions.requireString\nimport com.michaldrabik.ui_base.utilities.extensions.requireStringArray\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_REQUEST_KEY\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_NEW_AT_TOP\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_SORT_ORDER\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_SORT_TYPE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SORT_ORDERS\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_SORT_ORDER\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass SortOrderBottomSheet : BaseBottomSheetFragment(R.layout.view_sort_order) {\n\n  companion object {\n    fun createBundle(\n      options: List<SortOrder>,\n      selectedOrder: SortOrder,\n      selectedType: SortType,\n      requestKey: String = REQUEST_SORT_ORDER,\n      newAtTop: Pair<Boolean, Boolean> = Pair(false, false),\n    ) = bundleOf(\n      ARG_SORT_ORDERS to options.map { it.name },\n      ARG_SELECTED_SORT_ORDER to selectedOrder,\n      ARG_SELECTED_SORT_TYPE to selectedType,\n      ARG_SELECTED_NEW_AT_TOP to newAtTop,\n      ARG_REQUEST_KEY to requestKey\n    )\n  }\n\n  private val binding by viewBinding(ViewSortOrderBinding::bind)\n\n  private val requestKey by lazy { requireString(ARG_REQUEST_KEY, default = REQUEST_SORT_ORDER) }\n  private val initialSortOrder by lazy { requireSerializable<SortOrder>(ARG_SELECTED_SORT_ORDER) }\n  private val initialSortType by lazy { requireSerializable<SortType>(ARG_SELECTED_SORT_TYPE) }\n  private val initialNewAtTop by lazy { requireSerializable<Pair<Boolean, Boolean>>(ARG_SELECTED_NEW_AT_TOP) }\n  private val initialOptions by lazy { requireStringArray(ARG_SORT_ORDERS).map { SortOrder.valueOf(it) } }\n\n  private lateinit var selectedSortOrder: SortOrder\n  private lateinit var selectedSortType: SortType\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    selectedSortOrder = initialSortOrder\n    selectedSortType = initialSortType\n    setupView()\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun setupView() {\n    with(binding) {\n      viewSortOrderItemsLayout.removeAllViews()\n      initialOptions.forEach { item ->\n        val itemView = SortOrderItemView(requireContext()).apply {\n          onItemClickListener = itemClickListener\n          bind(item, initialSortType, item == initialSortOrder)\n        }\n        viewSortOrderItemsLayout.addView(itemView)\n      }\n\n      with(viewSortOrderNewCheckbox) {\n        visibleIf(initialNewAtTop.first)\n        setOnCheckedChangeListener { _, isChecked ->\n          val color = if (isChecked) android.R.attr.textColorPrimary else android.R.attr.textColorSecondary\n          val typeface = if (isChecked) Typeface.DEFAULT_BOLD else Typeface.DEFAULT\n          setTextColor(context.colorFromAttr(color))\n          setTypeface(typeface)\n        }\n        isChecked = initialNewAtTop.second\n      }\n\n      viewSortOrderButtonApply.onClick { onApplySortOrder() }\n    }\n  }\n\n  private fun onItemClicked(\n    sortOrder: SortOrder,\n    sortType: SortType,\n  ) {\n    binding.viewSortOrderItemsLayout.children.forEach { child ->\n      with(child as SortOrderItemView) {\n        if (sortOrder == child.sortOrder) {\n          if (sortOrder == selectedSortOrder) {\n            val newSortType = if (sortType == SortType.ASCENDING) SortType.DESCENDING else SortType.ASCENDING\n            selectedSortType = newSortType\n            bind(sortOrder, newSortType, true, animate = true)\n          } else {\n            bind(sortOrder, selectedSortType, true)\n          }\n          selectedSortOrder = sortOrder\n        } else {\n          bind(child.sortOrder, child.sortType, false)\n        }\n      }\n    }\n  }\n\n  private fun onApplySortOrder() {\n    val selectedNewAtTop = binding.viewSortOrderNewCheckbox.isChecked\n\n    if (selectedSortOrder != initialSortOrder ||\n      initialSortType != selectedSortType ||\n      initialNewAtTop.second != selectedNewAtTop\n    ) {\n      val result = bundleOf(\n        ARG_SELECTED_SORT_ORDER to selectedSortOrder,\n        ARG_SELECTED_SORT_TYPE to selectedSortType,\n        ARG_SELECTED_NEW_AT_TOP to selectedNewAtTop,\n      )\n      setFragmentResult(requestKey, result)\n    }\n\n    closeSheet()\n  }\n\n  private val itemClickListener: (SortOrder, SortType) -> Unit =\n    { sortOrder, sortType -> onItemClicked(sortOrder, sortType) }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/sheets/sort_order/views/SortOrderItemView.kt",
    "content": "package com.michaldrabik.ui_base.common.sheets.sort_order.views\n\nimport android.content.Context\nimport android.graphics.Typeface\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.databinding.ViewSortOrderItemBinding\nimport com.michaldrabik.ui_base.utilities.extensions.addRipple\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\n\nclass SortOrderItemView : ConstraintLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewSortOrderItemBinding.inflate(LayoutInflater.from(context), this)\n\n  var onItemClickListener: ((SortOrder, SortType) -> Unit)? = null\n\n  lateinit var sortOrder: SortOrder\n  lateinit var sortType: SortType\n  var isChecked: Boolean = false\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    val paddingHorizontal = context.dimenToPx(R.dimen.spaceNormal)\n    setPadding(paddingHorizontal, 0, paddingHorizontal, 0)\n    addRipple()\n    onClick(safe = false) { onItemClickListener?.invoke(sortOrder, sortType) }\n  }\n\n  fun bind(\n    sortOrder: SortOrder,\n    sortType: SortType,\n    isChecked: Boolean,\n    animate: Boolean = false\n  ) {\n    this.sortOrder = sortOrder\n    this.sortType = sortType\n    this.isChecked = isChecked\n\n    with(binding) {\n      viewSortOrderItemBadge.visibleIf(isChecked)\n\n      with(viewSortOrderItemTitle) {\n        val color = if (isChecked) android.R.attr.textColorPrimary else android.R.attr.textColorSecondary\n        val typeface = if (isChecked) Typeface.DEFAULT_BOLD else Typeface.DEFAULT\n        setTextColor(context.colorFromAttr(color))\n        setTypeface(typeface)\n        text = context.getString(sortOrder.displayString)\n      }\n\n      with(viewSortOrderItemAscDesc) {\n        visibleIf(isChecked)\n        val rotation = if (sortType == SortType.ASCENDING) -90F else 90F\n        val duration = if (animate) 200L else 0\n        animate().rotation(rotation).setDuration(duration).start()\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/views/EmptySearchView.kt",
    "content": "package com.michaldrabik.ui_base.common.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.Gravity.CENTER\nimport android.widget.LinearLayout\nimport com.michaldrabik.ui_base.R\n\nclass EmptySearchView : LinearLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  init {\n    inflate(context, R.layout.view_search_empty, this)\n    orientation = VERTICAL\n    gravity = CENTER\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/views/FoldableTextView.kt",
    "content": "package com.michaldrabik.ui_base.common.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatTextView\n\nclass FoldableTextView : AppCompatTextView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  companion object {\n    private const val MIN_LINES = 3\n    private const val MAX_LINES = 100\n  }\n\n  private var initialLines = MIN_LINES\n\n  init {\n    maxLines = initialLines\n    enableFoldOnClick()\n  }\n\n  fun setInitialLines(lines: Int) {\n    initialLines = lines\n    maxLines = lines\n  }\n\n  fun enableFoldOnClick() {\n    setOnClickListener {\n      maxLines = if (maxLines == MAX_LINES) initialLines else MAX_LINES\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/views/ModeTabsView.kt",
    "content": "package com.michaldrabik.ui_base.common.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.LinearLayout\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.Mode.MOVIES\nimport com.michaldrabik.common.Mode.SHOWS\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.databinding.ViewModeTabsBinding\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\n\nclass ModeTabsView : LinearLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewModeTabsBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    orientation = HORIZONTAL\n\n    with(binding) {\n      viewMovies.onClick { onModeSelected?.invoke(MOVIES) }\n      viewShows.onClick { onModeSelected?.invoke(SHOWS) }\n      viewLists.onClick { onListsSelected?.invoke() }\n    }\n  }\n\n  var onModeSelected: ((Mode) -> Unit)? = null\n  var onListsSelected: (() -> Unit)? = null\n\n  fun selectShows() {\n    with(binding) {\n      viewShows.setTextColor(context.colorFromAttr(R.attr.textColorTabSelected))\n      viewMovies.setTextColor(context.colorFromAttr(R.attr.textColorTab))\n      viewLists.setTextColor(context.colorFromAttr(R.attr.textColorTab))\n    }\n  }\n\n  fun selectMovies() {\n    with(binding) {\n      viewShows.setTextColor(context.colorFromAttr(R.attr.textColorTab))\n      viewMovies.setTextColor(context.colorFromAttr(R.attr.textColorTabSelected))\n      viewLists.setTextColor(context.colorFromAttr(R.attr.textColorTab))\n    }\n  }\n\n  fun selectLists() {\n    with(binding) {\n      viewShows.setTextColor(context.colorFromAttr(R.attr.textColorTab))\n      viewMovies.setTextColor(context.colorFromAttr(R.attr.textColorTab))\n      viewLists.setTextColor(context.colorFromAttr(R.attr.textColorTabSelected))\n    }\n  }\n\n  fun showMovies(show: Boolean) = binding.viewMovies.visibleIf(show)\n\n  fun showLists(show: Boolean, anchorEnd: Boolean = true) {\n    with(binding) {\n      viewLists.visibleIf(show)\n      viewSpacer.visibleIf(anchorEnd)\n    }\n  }\n\n  override fun setEnabled(enabled: Boolean) {\n    with(binding) {\n      viewShows.isEnabled = enabled\n      viewMovies.isEnabled = enabled\n      viewLists.isEnabled = enabled\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/views/MovieView.kt",
    "content": "package com.michaldrabik.ui_base.common.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.widget.FrameLayout\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade\nimport com.michaldrabik.common.Config.IMAGE_FADE_DURATION_MS\nimport com.michaldrabik.common.Config.MAIN_GRID_SPAN\nimport com.michaldrabik.common.Config.MAIN_GRID_SPAN_TABLET\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.MovieListItem\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_model.ImageStatus.AVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNAVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNKNOWN\n\nabstract class MovieView<Item : MovieListItem> : FrameLayout {\n\n  companion object {\n    const val ASPECT_RATIO = 1.4705\n  }\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.mediaTileCorner) }\n  private val gridPadding by lazy { context.dimenToPx(R.dimen.gridPadding) }\n  private val centerCropTransformation by lazy { CenterCrop() }\n  private val cornersTransformation by lazy { RoundedCorners(cornerRadius) }\n\n  private val isTablet by lazy { context.isTablet() }\n  private val span by lazy { if (isTablet) MAIN_GRID_SPAN_TABLET else MAIN_GRID_SPAN }\n  private val width by lazy { (screenWidth().toFloat() - (2.0 * gridPadding)) / span }\n  private val height by lazy { width * ASPECT_RATIO }\n\n  protected abstract val imageView: ImageView\n  protected abstract val placeholderView: ImageView\n\n  var itemClickListener: ((Item) -> Unit)? = null\n  var itemLongClickListener: ((Item) -> Unit)? = null\n  var imageLoadCompleteListener: (() -> Unit)? = null\n  var missingImageListener: ((Item, Boolean) -> Unit)? = null\n  var missingTranslationListener: ((Item) -> Unit)? = null\n\n  open fun bind(item: Item) {\n    layoutParams = LayoutParams(\n      (width * item.image.type.getSpan(isTablet).toFloat()).toInt(),\n      height.toInt()\n    )\n  }\n\n  protected open fun loadImage(item: Item) {\n    if (item.isLoading) return\n\n    if (item.image.status == UNAVAILABLE) {\n      placeholderView.visible()\n      return\n    }\n\n    if (item.image.status == UNKNOWN) {\n      onImageLoadFail(item)\n      return\n    }\n\n    Glide.with(this)\n      .load(item.image.fullFileUrl)\n      .transform(centerCropTransformation, cornersTransformation)\n      .transition(withCrossFade(IMAGE_FADE_DURATION_MS))\n      .withSuccessListener { onImageLoadSuccess() }\n      .withFailListener { onImageLoadFail(item) }\n      .into(imageView)\n  }\n\n  protected open fun onImageLoadSuccess() = imageLoadCompleteListener?.invoke()\n\n  protected open fun onImageLoadFail(item: Item) {\n    if (item.image.status == AVAILABLE) {\n      placeholderView.visible()\n      imageLoadCompleteListener?.invoke()\n      return\n    }\n    val force = (item.image.status == UNKNOWN)\n    missingImageListener?.invoke(item, force)\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/views/PremiumAdView.kt",
    "content": "package com.michaldrabik.ui_base.common.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_base.R\n\nclass PremiumAdView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  init {\n    inflate(context, R.layout.view_premium_ad, this)\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/views/RateValueView.kt",
    "content": "package com.michaldrabik.ui_base.common.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.databinding.ViewRateValueBinding\n\nclass RateValueView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewRateValueBinding.inflate(LayoutInflater.from(context), this)\n\n  private val translation by lazy { resources.getDimensionPixelSize(R.dimen.rateValueTranslation).toFloat() }\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n  }\n\n  fun setValue(value: String) {\n    with(binding) {\n      viewRateValueText1.text = value\n      viewRateValueText1.alpha = 1F\n      viewRateValueText2.text = \"\"\n      viewRateValueText2.alpha = 0F\n    }\n  }\n\n  fun setValueAnimated(value: String, direction: Direction = Direction.LEFT) {\n    val translation = when (direction) {\n      Direction.LEFT -> -translation\n      Direction.RIGHT -> translation\n    }\n    with(binding) {\n      viewRateValueText2.alpha = 0F\n      viewRateValueText2.text = value\n      viewRateValueText2.translationX = -translation\n      viewRateValueText2.animate().translationX(0F).alpha(1F).setDuration(175L).start()\n\n      viewRateValueText1.animate().translationX(translation).setDuration(175L).alpha(0F)\n        .withEndAction {\n          viewRateValueText2.alpha = 0F\n          viewRateValueText1.translationX = 0F\n          viewRateValueText1.alpha = 1F\n          viewRateValueText1.text = value\n        }\n        .start()\n    }\n  }\n\n  enum class Direction {\n    LEFT,\n    RIGHT\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/views/RatingsStripView.kt",
    "content": "package com.michaldrabik.ui_base.common.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.LinearLayout\nimport android.widget.TextView\nimport com.michaldrabik.common.Config.SPOILERS_RATINGS_HIDE_SYMBOL\nimport com.michaldrabik.ui_base.databinding.ViewRatingsStripBinding\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.Ratings\n\nclass RatingsStripView : LinearLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewRatingsStripBinding.inflate(LayoutInflater.from(context), this)\n\n  var onTraktClick: ((Ratings) -> Unit)? = null\n  var onImdbClick: ((Ratings) -> Unit)? = null\n  var onMetaClick: ((Ratings) -> Unit)? = null\n  var onRottenClick: ((Ratings) -> Unit)? = null\n\n  private val colorPrimary by lazy { context.colorFromAttr(android.R.attr.textColorPrimary) }\n  private val colorSecondary by lazy { context.colorFromAttr(android.R.attr.textColorSecondary) }\n\n  private lateinit var ratings: Ratings\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    orientation = HORIZONTAL\n    gravity = Gravity.TOP\n  }\n\n  fun bind(ratings: Ratings) {\n    this.ratings = ratings\n    with(binding) {\n      bindValue(\n        ratingsValue = ratings.trakt,\n        layoutView = viewRatingsStripTrakt,\n        valueView = viewRatingsStripTraktValue,\n        progressView = viewRatingsStripTraktProgress,\n        linkView = viewRatingsStripTraktLinkIcon,\n        isHidden = ratings.isHidden,\n        isTapToReveal = ratings.isTapToReveal,\n        callback = onTraktClick\n      )\n      bindValue(\n        ratingsValue = ratings.imdb,\n        layoutView = viewRatingsStripImdb,\n        valueView = viewRatingsStripImdbValue,\n        progressView = viewRatingsStripImdbProgress,\n        linkView = viewRatingsStripImdbLinkIcon,\n        isHidden = ratings.isHidden,\n        isTapToReveal = ratings.isTapToReveal,\n        callback = onImdbClick\n      )\n      bindValue(\n        ratingsValue = ratings.metascore,\n        layoutView = viewRatingsStripMeta,\n        valueView = viewRatingsStripMetaValue,\n        progressView = viewRatingsStripMetaProgress,\n        linkView = viewRatingsStripMetaLinkIcon,\n        isHidden = ratings.isHidden,\n        isTapToReveal = ratings.isTapToReveal,\n        callback = onMetaClick\n      )\n      bindValue(\n        ratingsValue = ratings.rottenTomatoes,\n        layoutView = viewRatingsStripRotten,\n        valueView = viewRatingsStripRottenValue,\n        progressView = viewRatingsStripRottenProgress,\n        linkView = viewRatingsStripRottenLinkIcon,\n        isHidden = ratings.isHidden,\n        isTapToReveal = ratings.isTapToReveal,\n        callback = onRottenClick\n      )\n    }\n  }\n\n  private fun bindValue(\n    ratingsValue: Ratings.Value?,\n    layoutView: View,\n    valueView: TextView,\n    progressView: View,\n    linkView: View,\n    isHidden: Boolean,\n    isTapToReveal: Boolean,\n    callback: ((Ratings) -> Unit)?,\n  ) {\n    val rating = ratingsValue?.value\n    val isLoading = ratingsValue?.isLoading == true\n    with(valueView) {\n      visibleIf(!isLoading && !rating.isNullOrBlank(), gone = false)\n      text = if (isHidden) {\n        tag = rating\n        SPOILERS_RATINGS_HIDE_SYMBOL\n      } else {\n        rating\n      }\n      setTextColor(if (rating != null) colorPrimary else colorSecondary)\n    }\n\n    with(layoutView) {\n      if (isHidden && isTapToReveal && !rating.isNullOrBlank()) {\n        onClick {\n          valueView.tag?.let { valueView.text = it.toString() }\n          onClick {\n            if (!ratings.isAnyLoading()) {\n              callback?.invoke(ratings)\n            }\n          }\n        }\n      } else {\n        onClick {\n          if (!ratings.isAnyLoading()) {\n            callback?.invoke(ratings)\n          }\n        }\n      }\n    }\n\n    progressView.visibleIf(isLoading)\n    linkView.visibleIf(!isLoading && rating.isNullOrBlank())\n  }\n\n  fun isBound() = this::ratings.isInitialized && !this.ratings.isAnyLoading()\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/views/ScrollableImageView.kt",
    "content": "package com.michaldrabik.ui_base.common.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatImageView\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\n\nclass ScrollableImageView : AppCompatImageView, CoordinatorLayout.AttachedBehavior {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  override fun getBehavior() = ScrollableViewBehaviour()\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/views/ScrollableTabLayout.kt",
    "content": "package com.michaldrabik.ui_base.common.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport com.google.android.material.tabs.TabLayout\nimport com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\n\nclass ScrollableTabLayout : TabLayout, CoordinatorLayout.AttachedBehavior {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  override fun getBehavior() = ScrollableViewBehaviour()\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/views/SearchLocalView.kt",
    "content": "package com.michaldrabik.ui_base.common.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\nimport com.michaldrabik.ui_base.databinding.ViewSearchLocalBinding\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\n\nclass SearchLocalView : FrameLayout, CoordinatorLayout.AttachedBehavior {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  val binding = ViewSearchLocalBinding.inflate(LayoutInflater.from(context), this, true)\n\n  var onCloseClickListener: (() -> Unit)? = null\n\n  init {\n    binding.searchViewLocalIcon.onClick { onCloseClickListener?.invoke() }\n  }\n\n  override fun getBehavior() = ScrollableViewBehaviour()\n\n  override fun setEnabled(enabled: Boolean) {\n    binding.searchViewLocalInput.isEnabled = enabled\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/views/SearchView.kt",
    "content": "package com.michaldrabik.ui_base.common.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.behaviour.SearchViewBehaviour\nimport com.michaldrabik.ui_base.databinding.ViewSearchBinding\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.expandTouch\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\n\nclass SearchView : FrameLayout, CoordinatorLayout.AttachedBehavior {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  val binding = ViewSearchBinding.inflate(LayoutInflater.from(context), this, true)\n\n  var onSettingsClickListener: (() -> Unit)? = null\n  var onStatsClickListener: (() -> Unit)? = null\n  var onTraktClickListener: (() -> Unit)? = null\n\n  init {\n    with(binding) {\n      searchSettingsIcon.expandTouch()\n      searchSettingsIcon.onClick { onSettingsClickListener?.invoke() }\n      searchStatsIcon.onClick { onStatsClickListener?.invoke() }\n      searchTraktIcon.onClick { onTraktClickListener?.invoke() }\n    }\n  }\n\n  var hint: String\n    get() = binding.searchViewInput.hint.toString()\n    set(value) {\n      with(binding) {\n        searchViewInput.hint = value\n        searchViewText.text = value\n      }\n    }\n\n  var settingsIconVisible\n    get() = binding.searchSettingsIcon.isVisible\n    set(value) {\n      binding.searchSettingsIcon.visibleIf(value)\n    }\n\n  var statsIconVisible\n    get() = binding.searchStatsIcon.isVisible\n    set(value) {\n      binding.searchStatsIcon.visibleIf(value)\n    }\n\n  var traktIconVisible\n    get() = binding.searchTraktIcon.isVisible\n    set(value) {\n      binding.searchTraktIcon.visibleIf(value)\n    }\n\n  var isSearching = false\n\n  override fun onAttachedToWindow() {\n    doOnApplyWindowInsets { _, insets, _, _ ->\n      val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top\n      applyWindowInsetBehaviour(context.dimenToPx(R.dimen.spaceNormal) + inset)\n    }\n    super.onAttachedToWindow()\n  }\n\n  fun applyWindowInsetBehaviour(newInset: Int) {\n    updateLayoutParams {\n      (layoutParams as? CoordinatorLayout.LayoutParams)?.behavior = SearchViewBehaviour(newInset)\n    }\n  }\n\n  override fun getBehavior() = SearchViewBehaviour(context.dimenToPx(R.dimen.spaceNormal))\n\n  override fun setEnabled(enabled: Boolean) {\n    binding.searchViewInput.isEnabled = enabled\n    super.setEnabled(enabled)\n  }\n\n  fun setTraktProgress(isProgress: Boolean, withIcon: Boolean = false) {\n    with(binding) {\n      searchViewIcon.visibleIf(!isProgress)\n      searchViewText.visibleIf(!isProgress)\n      searchTraktIcon.visibleIf(!isProgress && withIcon)\n      searchViewTraktSync.visibleIf(isProgress)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/views/SecretTextView.kt",
    "content": "package com.michaldrabik.ui_base.common.views\n\nimport android.content.Context\nimport android.text.TextUtils\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatTextView\n\nclass SecretTextView : AppCompatTextView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  var isRevealable = true\n    set(value) {\n      field = value\n      if (value) {\n        setOnClickListener { toggle() }\n      } else {\n        setOnClickListener(null)\n      }\n    }\n\n  private var originalText: String? = null\n  private var isSecret = true\n\n  init {\n    isRevealable = true\n  }\n\n  fun setSecretText(text: String?, isSecret: Boolean = true) {\n    this.isSecret = isSecret\n    this.originalText = text\n    if (isSecret) {\n      this.text = originalText?.map { '*' }?.joinToString(\"\")\n      ellipsize = null\n    } else {\n      this.text = originalText\n      ellipsize = TextUtils.TruncateAt.END\n    }\n  }\n\n  private fun toggle() {\n    if (!isRevealable) return\n    if (!isSecret) return\n    text = if (isSecret) {\n      setOnClickListener(null)\n      ellipsize = TextUtils.TruncateAt.END\n      originalText\n    } else {\n      ellipsize = null\n      originalText?.map { '*' }?.joinToString(\"\")\n    }\n    isSecret = !isSecret\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/views/ShowView.kt",
    "content": "package com.michaldrabik.ui_base.common.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.widget.FrameLayout\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade\nimport com.michaldrabik.common.Config.IMAGE_FADE_DURATION_MS\nimport com.michaldrabik.common.Config.MAIN_GRID_SPAN\nimport com.michaldrabik.common.Config.MAIN_GRID_SPAN_TABLET\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.ListItem\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_model.ImageStatus.AVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNAVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNKNOWN\n\nabstract class ShowView<Item : ListItem> : FrameLayout {\n\n  companion object {\n    const val ASPECT_RATIO = 1.4705\n  }\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.mediaTileCorner) }\n  private val gridPadding by lazy { context.dimenToPx(R.dimen.gridPadding) }\n  private val centerCropTransformation by lazy { CenterCrop() }\n  private val cornersTransformation by lazy { RoundedCorners(cornerRadius) }\n\n  private val isTablet by lazy { context.isTablet() }\n  private val span by lazy { if (isTablet) MAIN_GRID_SPAN_TABLET else MAIN_GRID_SPAN }\n  private val width by lazy {\n    (screenWidth().toFloat() - (2.0 * gridPadding)) / span\n  }\n  private val height by lazy { width * ASPECT_RATIO }\n\n  protected abstract val imageView: ImageView\n  protected abstract val placeholderView: ImageView\n\n  var itemClickListener: ((Item) -> Unit)? = null\n  var itemLongClickListener: ((Item) -> Unit)? = null\n  var imageLoadCompleteListener: (() -> Unit)? = null\n  var missingImageListener: ((Item, Boolean) -> Unit)? = null\n  var missingTranslationListener: ((Item) -> Unit)? = null\n\n  open fun bind(item: Item) {\n    layoutParams = LayoutParams(\n      (width * item.image.type.getSpan(isTablet).toFloat()).toInt(),\n      height.toInt()\n    )\n  }\n\n  protected open fun loadImage(item: Item) {\n    if (item.isLoading) return\n\n    if (item.image.status == UNAVAILABLE) {\n      placeholderView.visible()\n      return\n    }\n\n    if (item.image.status == UNKNOWN) {\n      onImageLoadFail(item)\n      return\n    }\n\n    Glide.with(this)\n      .load(item.image.fullFileUrl)\n      .transform(centerCropTransformation, cornersTransformation)\n      .transition(withCrossFade(IMAGE_FADE_DURATION_MS))\n      .withSuccessListener { onImageLoadSuccess() }\n      .withFailListener { onImageLoadFail(item) }\n      .into(imageView)\n  }\n\n  protected open fun onImageLoadSuccess() = imageLoadCompleteListener?.invoke()\n\n  protected open fun onImageLoadFail(item: Item) {\n    if (item.image.status == AVAILABLE) {\n      placeholderView.visible()\n      imageLoadCompleteListener?.invoke()\n      return\n    }\n    val force = (item.image.status == UNKNOWN)\n    missingImageListener?.invoke(item, force)\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/views/tips/TipOverlayView.kt",
    "content": "package com.michaldrabik.ui_base.common.views.tips\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.widget.FrameLayout\nimport androidx.dynamicanimation.animation.DynamicAnimation\nimport androidx.dynamicanimation.animation.SpringAnimation\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.databinding.ViewTipOverlayBinding\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenHeight\nimport com.michaldrabik.ui_model.Tip\n\nclass TipOverlayView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewTipOverlayBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n    setBackgroundResource(R.color.colorBlackTranslucent)\n    setupView()\n  }\n\n  private val springStartValue by lazy { (screenHeight().toFloat()) / 3F }\n  private val springAnimation by lazy {\n    SpringAnimation(binding.tutorialTipView, DynamicAnimation.TRANSLATION_Y, 0F).apply {\n      spring.stiffness = 300F\n      spring.dampingRatio = 0.65F\n      setStartValue(springStartValue)\n    }\n  }\n\n  private fun setupView() {\n    onClick { /* Block background clicks */ }\n    binding.tutorialViewButton.onClick { fadeOut() }\n  }\n\n  fun showTip(tip: Tip) {\n    binding.tutorialViewText.setText(tip.textResId)\n    springAnimation.setStartValue(springStartValue)\n    springAnimation.start()\n    fadeIn()\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/common/views/tips/TipView.kt",
    "content": "package com.michaldrabik.ui_base.common.views.tips\n\nimport android.animation.AnimatorSet\nimport android.animation.ObjectAnimator\nimport android.animation.ValueAnimator.INFINITE\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_base.R\n\nclass TipView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  companion object {\n    private const val ANIMATION_DURATION = 2500L\n  }\n\n  init {\n    inflate(context, R.layout.view_tip, this)\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n    clipChildren = false\n  }\n\n  private val animatorX by lazy {\n    ObjectAnimator.ofFloat(this, \"scaleX\", 1F, 1.2F, 1F, 0.8F, 1F).apply {\n      repeatCount = INFINITE\n    }\n  }\n\n  private val animatorY by lazy {\n    ObjectAnimator.ofFloat(this, \"scaleY\", 1F, 1.2F, 1F, 0.8F, 1F).apply {\n      repeatCount = INFINITE\n    }\n  }\n\n  private val animatorSet by lazy {\n    AnimatorSet().apply {\n      playTogether(animatorX, animatorY)\n      duration = ANIMATION_DURATION\n    }\n  }\n\n  override fun onAttachedToWindow() {\n    super.onAttachedToWindow()\n    animatorSet.start()\n  }\n\n  override fun onDetachedFromWindow() {\n    animatorSet.cancel()\n    super.onDetachedFromWindow()\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/dates/AppDateFormat.kt",
    "content": "package com.michaldrabik.ui_base.dates\n\nenum class AppDateFormat {\n  DEFAULT_24,\n  DEFAULT_12,\n  TRAKT_24,\n  TRAKT_12,\n  MISC_24,\n  MISC_12\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/dates/DateFormatProvider.kt",
    "content": "package com.michaldrabik.ui_base.dates\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.dates.AppDateFormat.DEFAULT_12\nimport com.michaldrabik.ui_base.dates.AppDateFormat.DEFAULT_24\nimport com.michaldrabik.ui_base.dates.AppDateFormat.MISC_12\nimport com.michaldrabik.ui_base.dates.AppDateFormat.MISC_24\nimport com.michaldrabik.ui_base.dates.AppDateFormat.TRAKT_12\nimport com.michaldrabik.ui_base.dates.AppDateFormat.TRAKT_24\nimport com.michaldrabik.ui_base.dates.AppDateFormat.valueOf\nimport java.time.format.DateTimeFormatter\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass DateFormatProvider @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  companion object {\n    const val DAY_1 = \"dd MMM yyyy\"\n    const val DAY_2 = \"MMM dd, yyyy\"\n    const val DAY_3 = \"EEEE, dd MMMM yyyy\"\n    const val DAY_4 = \"MMMM dd, yyyy (EEEE)\"\n    const val DAY_5 = \"dd MMMM yyyy (EEEE)\"\n\n    const val DAY_HOUR_1 = \"EEEE, dd MMM yyyy, h:mm a\"\n    const val DAY_HOUR_2 = \"EEEE, dd MMM yyyy, HH:mm\"\n    const val DAY_HOUR_3 = \"MMM dd, yyyy h:mm a (EEEE)\"\n    const val DAY_HOUR_4 = \"MMM dd, yyyy HH:mm (EEEE)\"\n    const val DAY_HOUR_5 = \"dd MMM yyyy, h:mm a (EEEE)\"\n    const val DAY_HOUR_6 = \"dd MMM yyyy, HH:mm (EEEE)\"\n\n    fun loadSettingsFormat(\n      format: AppDateFormat,\n      language: String,\n    ): DateTimeFormatter {\n      val pattern = when (format) {\n        DEFAULT_12 -> DAY_HOUR_1\n        DEFAULT_24 -> DAY_HOUR_2\n        TRAKT_12 -> DAY_HOUR_3\n        TRAKT_24 -> DAY_HOUR_4\n        MISC_12 -> DAY_HOUR_5\n        MISC_24 -> DAY_HOUR_6\n      }\n      if (language == \"zh\") {\n        return DateTimeFormatter.ofPattern(pattern.appendChineseDay())\n      }\n      return DateTimeFormatter.ofPattern(pattern)\n    }\n  }\n\n  fun loadShortDayFormat(): DateTimeFormatter {\n    val pattern = when (valueOf(settingsRepository.dateFormat)) {\n      DEFAULT_12 -> DAY_1\n      DEFAULT_24 -> DAY_1\n      TRAKT_12 -> DAY_2\n      TRAKT_24 -> DAY_2\n      MISC_12 -> DAY_1\n      MISC_24 -> DAY_1\n    }\n    return createDateFormat(pattern)\n  }\n\n  fun loadFullDayFormat(): DateTimeFormatter {\n    val pattern = when (valueOf(settingsRepository.dateFormat)) {\n      DEFAULT_12 -> DAY_3\n      DEFAULT_24 -> DAY_3\n      TRAKT_12 -> DAY_4\n      TRAKT_24 -> DAY_4\n      MISC_12 -> DAY_5\n      MISC_24 -> DAY_5\n    }\n    return createDateFormat(pattern)\n  }\n\n  fun loadFullHourFormat(): DateTimeFormatter {\n    val pattern = when (valueOf(settingsRepository.dateFormat)) {\n      DEFAULT_12 -> DAY_HOUR_1\n      DEFAULT_24 -> DAY_HOUR_2\n      TRAKT_12 -> DAY_HOUR_3\n      TRAKT_24 -> DAY_HOUR_4\n      MISC_12 -> DAY_HOUR_5\n      MISC_24 -> DAY_HOUR_6\n    }\n    return createDateFormat(pattern)\n  }\n\n  private fun createDateFormat(pattern: String): DateTimeFormatter {\n    val language = settingsRepository.language\n    if (language == \"zh\") {\n      return DateTimeFormatter.ofPattern(pattern.appendChineseDay())\n    }\n    return DateTimeFormatter.ofPattern(pattern)\n  }\n}\n\n// Adding special symbol in case of Chinese language as it is missing from DateTimeFormatter implementation.\nprivate fun String.appendChineseDay(): String {\n  return this.replace(\"dd\", \"dd\\'日\\'\")\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/events/Event.kt",
    "content": "package com.michaldrabik.ui_base.events\n\nsealed class Event\n\nobject ReloadData : Event()\n\n// Trakt Sync\n\nobject TraktSyncStart : Event()\n\nobject TraktSyncSuccess : Event()\n\nobject TraktSyncError : Event()\n\nobject TraktSyncAuthError : Event()\n\ndata class TraktSyncProgress(val status: String = \"\") : Event()\n\n// Trakt Instant Sync\n\ndata class TraktQuickSyncSuccess(val count: Int) : Event()\n\nobject TraktListQuickSyncSuccess : Event()\n\n// Shows, Movies Sync\n\ndata class ShowsMoviesSyncComplete(val count: Int) : Event()\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/events/EventsManager.kt",
    "content": "package com.michaldrabik.ui_base.events\n\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.asSharedFlow\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass EventsManager @Inject constructor() {\n\n  private val _events = MutableSharedFlow<Event>(extraBufferCapacity = 10)\n  val events = _events.asSharedFlow()\n\n  suspend fun sendEvent(event: Event) {\n    _events.emit(event)\n    Timber.d(\"Event emitted: $event\")\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/fcm/NotificationChannel.kt",
    "content": "package com.michaldrabik.ui_base.fcm\n\nimport androidx.core.app.NotificationManagerCompat\n\nenum class NotificationChannel(\n  val displayName: String,\n  val description: String,\n  val importance: Int,\n  val topicName: String\n) {\n  GENERAL_INFO(\n    \"General Info\",\n    \"General information and announcements\",\n    NotificationManagerCompat.IMPORTANCE_HIGH,\n    \"general\"\n  ),\n  SHOWS_INFO(\n    \"Shows Info\",\n    \"Shows related information\",\n    NotificationManagerCompat.IMPORTANCE_DEFAULT,\n    \"shows\"\n  ),\n  EPISODES_ANNOUNCEMENTS(\n    \"Episodes Announcements\",\n    \"Episodes and seasons announcements\",\n    NotificationManagerCompat.IMPORTANCE_DEFAULT,\n    \"shows_announcements\"\n  ),\n  MOVIES_ANNOUNCEMENTS(\n    \"Movies Announcements\",\n    \"Movies announcements\",\n    NotificationManagerCompat.IMPORTANCE_DEFAULT,\n    \"movies_announcements\"\n  )\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/network/NetworkCallbackAdapter.kt",
    "content": "package com.michaldrabik.ui_base.network\n\nimport android.net.ConnectivityManager\n\nabstract class NetworkCallbackAdapter : ConnectivityManager.NetworkCallback()\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/network/NetworkStatusProvider.kt",
    "content": "package com.michaldrabik.ui_base.network\n\nimport android.net.ConnectivityManager\nimport android.net.Network\nimport android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET\nimport android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN\nimport android.net.NetworkCapabilities.TRANSPORT_CELLULAR\nimport android.net.NetworkCapabilities.TRANSPORT_ETHERNET\nimport android.net.NetworkCapabilities.TRANSPORT_VPN\nimport android.net.NetworkCapabilities.TRANSPORT_WIFI\nimport android.net.NetworkRequest\nimport android.os.Build\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycle.LifecycleOwner\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.update\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass NetworkStatusProvider @Inject constructor(\n  private val connectivityManager: ConnectivityManager\n) : DefaultLifecycleObserver {\n\n  private val _status = MutableStateFlow(false)\n  val status = _status.asStateFlow()\n\n  private val availableNetworksIds = mutableListOf<String>()\n\n  fun isOnline() = status.value\n\n  override fun onStart(owner: LifecycleOwner) {\n    super.onStart(owner)\n    val networkRequest = NetworkRequest.Builder()\n      .addTransportType(TRANSPORT_WIFI)\n      .addTransportType(TRANSPORT_CELLULAR)\n      .addTransportType(TRANSPORT_ETHERNET)\n      .addTransportType(TRANSPORT_VPN)\n      .addCapability(NET_CAPABILITY_INTERNET)\n      .removeCapability(NET_CAPABILITY_NOT_VPN)\n      .build()\n\n    connectivityManager.registerNetworkCallback(networkRequest, networkCallback)\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n      connectivityManager.requestNetwork(networkRequest, networkCallback, 1000)\n    }\n\n    Timber.d(\"Registering network observer.\")\n  }\n\n  override fun onStop(owner: LifecycleOwner) {\n    connectivityManager.unregisterNetworkCallback(networkCallback)\n    availableNetworksIds.clear()\n    Timber.d(\"Unregistered network observer.\")\n    super.onStop(owner)\n  }\n\n  private val networkCallback = object : NetworkCallbackAdapter() {\n    override fun onAvailable(network: Network) {\n      availableNetworksIds.add(network.toString())\n      _status.update { true }\n      Timber.d(\"Network available: $network\")\n    }\n\n    override fun onLost(network: Network) {\n      availableNetworksIds.remove(network.toString())\n      if (availableNetworksIds.isEmpty()) {\n        _status.update { false }\n      }\n      Timber.d(\"Network lost: $network. Available networks: ${availableNetworksIds.size}\")\n    }\n\n    override fun onUnavailable() {\n      _status.update { false }\n      Timber.d(\"Network unavailable\")\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/notifications/AnnouncementManager.kt",
    "content": "package com.michaldrabik.ui_base.notifications\n\nimport android.content.Context\nimport androidx.work.WorkManager\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.nowUtcDay\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.common.extensions.toZonedDateTime\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.repository.OnHoldItemsRepository\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.notifications.schedulers.MovieAnnouncementScheduler\nimport com.michaldrabik.ui_base.notifications.schedulers.MovieAnnouncementScheduler.Companion.ANNOUNCEMENT_MOVIE_WORK_TAG\nimport com.michaldrabik.ui_base.notifications.schedulers.ShowAnnouncementScheduler\nimport com.michaldrabik.ui_base.notifications.schedulers.ShowAnnouncementScheduler.Companion.ANNOUNCEMENT_WORK_TAG\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport timber.log.Timber\nimport java.time.ZonedDateTime\nimport java.time.format.DateTimeFormatter\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass AnnouncementManager @Inject constructor(\n  @ApplicationContext private val context: Context,\n  private val mappers: Mappers,\n  private val localSource: LocalDataSource,\n  private val settingsRepository: SettingsRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val onHoldItemsRepository: OnHoldItemsRepository,\n  private val showAnnouncementScheduler: ShowAnnouncementScheduler,\n  private val movieAnnouncementScheduler: MovieAnnouncementScheduler,\n) {\n\n  companion object {\n    private const val MOVIE_MIN_THRESHOLD_DAYS = 30\n    private const val MOVIE_THRESHOLD_HOUR = 12\n  }\n\n  private val logFormatter by lazy { DateTimeFormatter.ofPattern(\"EEEE, dd MMM yyyy, HH:mm\") }\n\n  suspend fun refreshShowsAnnouncements() {\n    Timber.d(\"Refreshing shows announcements\")\n\n    val now = nowUtc()\n    val nowMillis = now.toMillis()\n    val limit = now.plusMonths(3)\n    WorkManager.getInstance(context).cancelAllWorkByTag(ANNOUNCEMENT_WORK_TAG)\n    Timber.d(\"Current time: ${logFormatter.format(now)} UTC\")\n\n    val settings = settingsRepository.load()\n    if (!settings.episodesNotificationsEnabled) {\n      Timber.d(\"Episodes announcements are disabled. Exiting...\")\n      return\n    }\n\n    val myShows = localSource.myShows.getAll()\n    val watchlistShows = localSource.watchlistShows.getAll()\n    if (myShows.isEmpty() && watchlistShows.isEmpty()) {\n      Timber.d(\"Nothing to process. Exiting...\")\n      return\n    }\n\n    val language = translationsRepository.getLanguage()\n    val delay = settings.episodesNotificationsDelay\n    val onHoldIds = onHoldItemsRepository.getAll().map { it.id }\n\n    myShows\n      .forEach { show ->\n        Timber.d(\"Processing ${show.title} (${show.idTrakt})\")\n\n        if (onHoldIds.contains(show.idTrakt)) {\n          Timber.d(\"${show.title} (${show.idTrakt}) is on hold. Skipping...\")\n          return@forEach\n        }\n\n        val fromTime = if (delay.isBefore()) nowMillis else nowMillis - delay.delayMs\n        val episode = localSource.episodes.getFirstUnwatched(show.idTrakt, fromTime, limit.toMillis())\n        episode?.firstAired?.let { airDate ->\n          when {\n            delay.isBefore() -> {\n              if (airDate.toMillis() + delay.delayMs >= nowMillis) {\n                showAnnouncementScheduler.scheduleAnnouncement(\n                  showDb = show,\n                  episodeNumber = episode.episodeNumber,\n                  episodeSeasonNumber = episode.seasonNumber,\n                  episodeDate = episode.firstAired!!,\n                  delay = delay,\n                  language = language\n                )\n              } else {\n                Timber.d(\"Time with delay included has already passed.\")\n              }\n            }\n            else -> {\n              showAnnouncementScheduler.scheduleAnnouncement(\n                showDb = show,\n                episodeNumber = episode.episodeNumber,\n                episodeSeasonNumber = episode.seasonNumber,\n                episodeDate = episode.firstAired!!,\n                delay = delay,\n                language = language\n              )\n            }\n          }\n        }\n      }\n\n    for (show in watchlistShows) {\n      Timber.d(\"Processing Watchlist ${show.title} (${show.idTrakt})\")\n\n      val fromTime = if (delay.isBefore()) nowMillis else nowMillis - delay.delayMs\n      val airDate = show.firstAired.toZonedDateTime() ?: ZonedDateTime.now().minusYears(1)\n\n      if (airDate.toMillis() <= fromTime) {\n        continue\n      }\n\n      if (delay.isBefore()) {\n        if (airDate.toMillis() + delay.delayMs >= nowMillis) {\n          showAnnouncementScheduler.scheduleAnnouncement(\n            showDb = show,\n            episodeNumber = 1,\n            episodeSeasonNumber = 1,\n            episodeDate = airDate,\n            delay = delay,\n            language = language\n          )\n        } else {\n          Timber.d(\"Time with delay included has already passed.\")\n        }\n      } else {\n        showAnnouncementScheduler.scheduleAnnouncement(\n          showDb = show,\n          episodeNumber = 1,\n          episodeSeasonNumber = 1,\n          episodeDate = airDate,\n          delay = delay,\n          language = language\n        )\n      }\n    }\n  }\n\n  suspend fun refreshMoviesAnnouncements() {\n    Timber.d(\"Refreshing movies announcements\")\n\n    val now = nowUtc()\n    Timber.d(\"Current time: ${logFormatter.format(now)} UTC\")\n\n    WorkManager.getInstance(context).cancelAllWorkByTag(ANNOUNCEMENT_MOVIE_WORK_TAG)\n\n    if (!settingsRepository.isMoviesEnabled) {\n      Timber.d(\"Movies disabled. Skipping...\")\n      return\n    }\n\n    val movies = localSource.watchlistMovies.getAll()\n      .map { mappers.movie.fromDatabase(it) }\n\n    if (movies.isEmpty()) {\n      Timber.d(\"Nothing to process. Exiting...\")\n      return\n    }\n\n    val language = translationsRepository.getLanguage()\n    movies\n      .filter {\n        Timber.d(\"Processing ${it.title} (${it.traktId})\")\n        it.released != null &&\n          (!it.hasAired() || it.isToday()) &&\n          it.released!!.toEpochDay() - nowUtcDay().toEpochDay() < MOVIE_MIN_THRESHOLD_DAYS &&\n          ZonedDateTime.now().hour < MOVIE_THRESHOLD_HOUR // We want movies notifications to come out the release day at 12:00 local time\n      }\n      .forEach {\n        movieAnnouncementScheduler.scheduleAnnouncement(context, it, language)\n      }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/notifications/AnnouncementWorker.kt",
    "content": "package com.michaldrabik.ui_base.notifications\n\nimport android.annotation.SuppressLint\nimport android.app.PendingIntent\nimport android.app.PendingIntent.FLAG_IMMUTABLE\nimport android.app.PendingIntent.FLAG_UPDATE_CURRENT\nimport android.app.UiModeManager.MODE_NIGHT_NO\nimport android.app.UiModeManager.MODE_NIGHT_YES\nimport android.content.Context\nimport android.content.Intent\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.content.ContextCompat\nimport androidx.work.Worker\nimport androidx.work.WorkerParameters\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.R\nimport kotlin.random.Random\n\nclass AnnouncementWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {\n\n  companion object {\n    const val DATA_SHOW_ID = \"DATA_SHOW_ID\"\n    const val DATA_MOVIE_ID = \"DATA_MOVIE_ID\"\n    const val DATA_TITLE = \"DATA_TITLE\"\n    const val DATA_CONTENT = \"DATA_CONTENT\"\n    const val DATA_CHANNEL = \"DATA_CHANNEL\"\n    const val DATA_IMAGE_URL = \"DATA_IMAGE_URL\"\n    const val DATA_THEME = \"DATA_THEME\"\n  }\n\n  @SuppressLint(\"MissingPermission\")\n  override fun doWork(): Result {\n    val color = when (inputData.getInt(DATA_THEME, MODE_NIGHT_YES)) {\n      MODE_NIGHT_YES -> R.color.colorNotificationDark\n      MODE_NIGHT_NO -> R.color.colorNotificationLight\n      else -> R.color.colorNotificationDark\n    }\n\n    val notification = NotificationCompat.Builder(applicationContext, inputData.getString(DATA_CHANNEL)!!)\n      .setContentIntent(createIntent())\n      .setSmallIcon(R.drawable.ic_notification)\n      .setContentTitle(inputData.getString(DATA_TITLE))\n      .setContentText(inputData.getString(DATA_CONTENT))\n      .setPriority(NotificationCompat.PRIORITY_DEFAULT)\n      .setAutoCancel(true)\n      .setColor(ContextCompat.getColor(applicationContext, color))\n\n    val imageUrl = inputData.getString(DATA_IMAGE_URL)\n    if ((imageUrl ?: \"\").isNotBlank()) {\n      val target = Glide.with(applicationContext).asBitmap().load(imageUrl).submit()\n      try {\n        val bitmap = target.get()\n        notification.setLargeIcon(bitmap)\n      } catch (e: Exception) {\n        // NOOP\n      } finally {\n        Glide.with(applicationContext).clear(target)\n      }\n    }\n\n    NotificationManagerCompat.from(applicationContext)\n      .notify(Random.nextInt(), notification.build())\n\n    return Result.success()\n  }\n\n  private fun createIntent(): PendingIntent {\n    var requestCode = 0L\n    val targetClass = Class.forName(Config.HOST_ACTIVITY_NAME)\n    val notifyIntent = Intent(applicationContext, targetClass).apply {\n      val showId = inputData.getLong(DATA_SHOW_ID, -1)\n      val movieId = inputData.getLong(DATA_MOVIE_ID, -1)\n      when {\n        showId != -1L -> {\n          putExtra(\"EXTRA_SHOW_ID\", showId.toString())\n          requestCode = showId\n        }\n        movieId != -1L -> {\n          putExtra(\"EXTRA_MOVIE_ID\", movieId.toString())\n          requestCode = movieId\n        }\n      }\n      flags = Intent.FLAG_ACTIVITY_SINGLE_TOP\n    }\n    return PendingIntent.getActivity(\n      applicationContext, requestCode.toInt(), notifyIntent, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT\n    )\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/notifications/schedulers/MovieAnnouncementScheduler.kt",
    "content": "package com.michaldrabik.ui_base.notifications.schedulers\n\nimport android.content.Context\nimport androidx.work.Data\nimport androidx.work.OneTimeWorkRequestBuilder\nimport androidx.work.WorkManager\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.dateFromMillis\nimport com.michaldrabik.common.extensions.nowUtcDay\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.fcm.NotificationChannel\nimport com.michaldrabik.ui_base.notifications.AnnouncementWorker\nimport com.michaldrabik.ui_model.ImageStatus\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Translation\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport timber.log.Timber\nimport java.time.ZonedDateTime\nimport java.time.format.DateTimeFormatter\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\n\nclass MovieAnnouncementScheduler @Inject constructor(\n  @ApplicationContext private val context: Context,\n  private val settingsRepository: SettingsRepository,\n  private val moviesImagesProvider: MovieImagesProvider,\n  private val translationsRepository: TranslationsRepository,\n) {\n\n  companion object {\n    const val ANNOUNCEMENT_MOVIE_WORK_TAG = \"ANNOUNCEMENT_MOVIE_WORK_TAG\"\n    private const val MOVIE_THRESHOLD_HOUR = 12\n  }\n\n  private val logFormatter by lazy { DateTimeFormatter.ofPattern(\"EEEE, dd MMM yyyy, HH:mm\") }\n\n  suspend fun scheduleAnnouncement(\n    context: Context,\n    movie: Movie,\n    language: String,\n  ) {\n    var translation: Translation? = null\n    if (language != Config.DEFAULT_LANGUAGE) {\n      translation = translationsRepository.loadTranslation(movie, language, onlyLocal = true)\n    }\n\n    val data = Data.Builder().apply {\n      putLong(AnnouncementWorker.DATA_MOVIE_ID, movie.traktId)\n      putString(AnnouncementWorker.DATA_CHANNEL, NotificationChannel.MOVIES_ANNOUNCEMENTS.name)\n      putString(AnnouncementWorker.DATA_TITLE, if (translation?.hasTitle == true) translation.title else movie.title)\n      putString(AnnouncementWorker.DATA_CONTENT, context.getString(R.string.textNewMovieAvailable))\n      putInt(AnnouncementWorker.DATA_THEME, settingsRepository.theme)\n\n      val posterImage = moviesImagesProvider.findCachedImage(movie, ImageType.POSTER)\n      if (posterImage.status == ImageStatus.AVAILABLE) {\n        putString(AnnouncementWorker.DATA_IMAGE_URL, posterImage.fullFileUrl)\n      } else {\n        val fanartImage = moviesImagesProvider.findCachedImage(movie, ImageType.FANART)\n        if (fanartImage.status == ImageStatus.AVAILABLE) {\n          putString(AnnouncementWorker.DATA_IMAGE_URL, fanartImage.fullFileUrl)\n        }\n      }\n    }\n\n    val now = ZonedDateTime.now()\n    val days = movie.released!!.toEpochDay() - nowUtcDay().toEpochDay()\n    val offset = now.withHour(MOVIE_THRESHOLD_HOUR).withMinute(0).toMillis() - now.toMillis()\n    val delayed = (days * TimeUnit.DAYS.toMillis(1)) + offset\n    val request = OneTimeWorkRequestBuilder<AnnouncementWorker>()\n      .setInputData(data.build())\n      .setInitialDelay(delayed, TimeUnit.MILLISECONDS)\n      .addTag(ANNOUNCEMENT_MOVIE_WORK_TAG)\n      .build()\n\n    WorkManager.getInstance(context).enqueue(request)\n\n    val logTime = logFormatter.format(dateFromMillis(nowUtcMillis() + delayed))\n    Timber.d(\"Notification set for ${movie.title}: $logTime UTC\")\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/notifications/schedulers/ShowAnnouncementScheduler.kt",
    "content": "package com.michaldrabik.ui_base.notifications.schedulers\n\nimport android.content.Context\nimport androidx.work.Data\nimport androidx.work.OneTimeWorkRequestBuilder\nimport androidx.work.WorkManager\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.dateFromMillis\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.data_local.database.model.Show\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.fcm.NotificationChannel\nimport com.michaldrabik.ui_base.notifications.AnnouncementWorker\nimport com.michaldrabik.ui_model.ImageStatus\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.NotificationDelay\nimport com.michaldrabik.ui_model.Translation\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport timber.log.Timber\nimport java.time.ZonedDateTime\nimport java.time.format.DateTimeFormatter\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\n\nclass ShowAnnouncementScheduler @Inject constructor(\n  @ApplicationContext private val context: Context,\n  private val settingsRepository: SettingsRepository,\n  private val showsImagesProvider: ShowImagesProvider,\n  private val translationsRepository: TranslationsRepository,\n  private val mappers: Mappers,\n) {\n\n  companion object {\n    const val ANNOUNCEMENT_WORK_TAG = \"ANNOUNCEMENT_WORK_TAG\"\n    private const val ANNOUNCEMENT_STATIC_DELAY_MS = 60000 // 1 min\n  }\n\n  private val logFormatter by lazy { DateTimeFormatter.ofPattern(\"EEEE, dd MMM yyyy, HH:mm\") }\n\n  suspend fun scheduleAnnouncement(\n    showDb: Show,\n    episodeNumber: Int,\n    episodeSeasonNumber: Int,\n    episodeDate: ZonedDateTime,\n    delay: NotificationDelay,\n    language: String,\n  ) {\n    val show = mappers.show.fromDatabase(showDb)\n\n    var translation: Translation? = null\n    if (language != Config.DEFAULT_LANGUAGE) {\n      translation = translationsRepository.loadTranslation(show, language, onlyLocal = true)\n    }\n\n    val data = Data.Builder().apply {\n      val title = if (translation?.hasTitle == true) translation.title else show.title\n      val episode = context.getString(R.string.textSeasonEpisode, episodeSeasonNumber, episodeNumber)\n\n      putLong(AnnouncementWorker.DATA_SHOW_ID, showDb.idTrakt)\n      putString(AnnouncementWorker.DATA_TITLE, \"$title - $episode\")\n      putInt(AnnouncementWorker.DATA_THEME, settingsRepository.theme)\n      putString(AnnouncementWorker.DATA_CHANNEL, NotificationChannel.EPISODES_ANNOUNCEMENTS.name)\n\n      val stringResId = when (episodeNumber) {\n        1 -> if (delay.isBefore()) R.string.textNewSeasonAvailableSoon else R.string.textNewSeasonAvailable\n        else -> if (delay.isBefore()) R.string.textNewEpisodeAvailableSoon else R.string.textNewEpisodeAvailable\n      }\n      putString(AnnouncementWorker.DATA_CONTENT, context.getString(stringResId))\n\n      val posterImage = showsImagesProvider.findCachedImage(show, ImageType.POSTER)\n      if (posterImage.status == ImageStatus.AVAILABLE) {\n        putString(AnnouncementWorker.DATA_IMAGE_URL, posterImage.fullFileUrl)\n      } else {\n        val fanartImage = showsImagesProvider.findCachedImage(show, ImageType.FANART)\n        if (fanartImage.status == ImageStatus.AVAILABLE) {\n          putString(AnnouncementWorker.DATA_IMAGE_URL, fanartImage.fullFileUrl)\n        }\n      }\n    }\n\n    val delayed = (episodeDate.toMillis() - nowUtcMillis()) + delay.delayMs + ANNOUNCEMENT_STATIC_DELAY_MS\n    val request = OneTimeWorkRequestBuilder<AnnouncementWorker>()\n      .setInputData(data.build())\n      .setInitialDelay(delayed, TimeUnit.MILLISECONDS)\n      .addTag(ANNOUNCEMENT_WORK_TAG)\n      .build()\n\n    WorkManager.getInstance(context).enqueue(request)\n\n    val logTime = logFormatter.format(dateFromMillis(nowUtcMillis() + delayed))\n    Timber.d(\"Notification set for ${show.title}: $logTime UTC\")\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/sync/ShowsMoviesSyncWorker.kt",
    "content": "package com.michaldrabik.ui_base.sync\n\nimport android.content.Context\nimport androidx.hilt.work.HiltWorker\nimport androidx.work.Constraints\nimport androidx.work.CoroutineWorker\nimport androidx.work.ExistingWorkPolicy.KEEP\nimport androidx.work.NetworkType\nimport androidx.work.OneTimeWorkRequestBuilder\nimport androidx.work.WorkManager\nimport androidx.work.WorkerParameters\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.ShowsMoviesSyncComplete\nimport com.michaldrabik.ui_base.sync.runners.MoviesSyncRunner\nimport com.michaldrabik.ui_base.sync.runners.ShowsSyncRunner\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedInject\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\n\n@HiltWorker\nclass ShowsMoviesSyncWorker @AssistedInject constructor(\n  @Assisted val context: Context,\n  @Assisted workerParams: WorkerParameters,\n  private val showsSyncRunner: ShowsSyncRunner,\n  private val moviesSyncRunner: MoviesSyncRunner,\n  private val eventsManager: EventsManager,\n  private val dispatchers: CoroutineDispatchers\n) : CoroutineWorker(context, workerParams) {\n\n  companion object {\n    private const val TAG = \"ShowsMoviesSyncWorker\"\n\n    fun schedule(workManager: WorkManager) {\n      val request = OneTimeWorkRequestBuilder<ShowsMoviesSyncWorker>()\n        .setConstraints(\n          Constraints.Builder()\n            .setRequiredNetworkType(NetworkType.CONNECTED)\n            .build()\n        )\n        .addTag(TAG)\n        .build()\n\n      workManager.enqueueUniqueWork(TAG, KEEP, request)\n      Timber.i(\"ShowsMoviesSyncWorker scheduled.\")\n    }\n  }\n\n  override suspend fun doWork() = withContext(dispatchers.IO) {\n    Timber.d(\"Doing work...\")\n\n    val showsAsync = async {\n      try {\n        Timber.d(\"Starting shows runner...\")\n        showsSyncRunner.run()\n      } catch (error: Throwable) {\n        Timber.e(error)\n        0\n      }\n    }\n\n    val moviesAsync = async {\n      try {\n        Timber.d(\"Starting movies runner...\")\n        moviesSyncRunner.run()\n      } catch (error: Throwable) {\n        Timber.e(error)\n        0\n      }\n    }\n\n    val (showsCount, moviesCount) = awaitAll(showsAsync, moviesAsync)\n    eventsManager.sendEvent(ShowsMoviesSyncComplete(showsCount + moviesCount))\n\n    Timber.d(\"Work finished. Shows: $showsCount Movies: $moviesCount\")\n    Result.success()\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/sync/runners/MoviesSyncRunner.kt",
    "content": "package com.michaldrabik.ui_base.sync.runners\n\nimport com.michaldrabik.common.ConfigVariant.MOVIE_SYNC_COOLDOWN\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.MovieStatus.IN_PRODUCTION\nimport com.michaldrabik.ui_model.MovieStatus.PLANNED\nimport com.michaldrabik.ui_model.MovieStatus.POST_PRODUCTION\nimport com.michaldrabik.ui_model.MovieStatus.RUMORED\nimport kotlinx.coroutines.delay\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * This class is responsible for fetching and syncing missing/updated movies data.\n */\n@Singleton\nclass MoviesSyncRunner @Inject constructor(\n  private val localSource: LocalDataSource,\n  private val moviesRepository: MoviesRepository,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  companion object {\n    private const val DELAY_MS = 10L\n  }\n\n  suspend fun run(): Int {\n    Timber.i(\"Movies sync initialized.\")\n\n    if (!settingsRepository.isMoviesEnabled) {\n      Timber.i(\"Movies are disabled. Exiting...\")\n      return 0\n    }\n\n    val movies = moviesRepository.loadCollection()\n\n    val moviesToSync = movies.filter { it.status in arrayOf(PLANNED, IN_PRODUCTION, POST_PRODUCTION, RUMORED) }\n    if (moviesToSync.isEmpty()) {\n      Timber.i(\"Nothing to process. Exiting...\")\n      return 0\n    }\n    Timber.i(\"Movies to sync count: ${moviesToSync.size}.\")\n\n    var syncCount = 0\n    val syncLog = localSource.moviesSyncLog.getAll()\n    moviesToSync.forEach { movie ->\n      val lastSync = syncLog.find { it.idTrakt == movie.ids.trakt.id }?.syncedAt ?: 0\n      if (nowUtcMillis() - lastSync < MOVIE_SYNC_COOLDOWN) {\n        Timber.i(\"${movie.title} is on cooldown. No need to sync.\")\n        return@forEach\n      }\n\n      try {\n        Timber.i(\"Syncing ${movie.title}(${movie.ids.trakt}) details...\")\n        moviesRepository.movieDetails.load(movie.ids.trakt, force = true)\n        syncCount++\n        Timber.i(\"${movie.title}(${movie.ids.trakt}) movie synced.\")\n      } catch (t: Throwable) {\n        Timber.e(\"${movie.title}(${movie.ids.trakt}) movie sync error. Skipping... \\n$t\")\n      } finally {\n        delay(DELAY_MS)\n      }\n    }\n\n    return syncCount\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/sync/runners/ShowsSyncRunner.kt",
    "content": "package com.michaldrabik.ui_base.sync.runners\n\nimport com.michaldrabik.common.ConfigVariant.SHOW_SYNC_COOLDOWN\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.EpisodesSyncLog\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.EpisodesManager\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_model.ShowStatus.CANCELED\nimport com.michaldrabik.ui_model.ShowStatus.ENDED\nimport com.michaldrabik.ui_model.ShowStatus.UNKNOWN\nimport kotlinx.coroutines.delay\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * This class is responsible for fetching and syncing missing/updated episodes data for current progress shows.\n */\n@Singleton\nclass ShowsSyncRunner @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n  private val episodesManager: EpisodesManager,\n  private val showsRepository: ShowsRepository,\n) {\n\n  companion object {\n    private const val DELAY_MS = 10L\n  }\n\n  suspend fun run(): Int {\n    Timber.i(\"Shows sync initialized.\")\n\n    val myShows = showsRepository.myShows.loadAll()\n    val watchlistShows = showsRepository.watchlistShows.loadAll()\n    val watchlistShowsIds = watchlistShows.map { it.traktId }\n\n    val showsToSync = (myShows + watchlistShows)\n      .filter { it.status !in arrayOf(ENDED, CANCELED, UNKNOWN) }\n\n    if (showsToSync.isEmpty()) {\n      Timber.i(\"Nothing to process. Exiting...\")\n      return 0\n    }\n    Timber.i(\"Shows to sync: ${showsToSync.size}.\")\n\n    var syncCount = 0\n    val syncLog = localSource.episodesSyncLog.getAll()\n    showsToSync.forEach { show ->\n      val isInWatchlist = show.traktId in watchlistShowsIds\n\n      val lastSync = syncLog.find { it.idTrakt == show.traktId }?.syncedAt ?: 0\n      if (nowUtcMillis() - lastSync < SHOW_SYNC_COOLDOWN) {\n        Timber.i(\"${show.title} is on cooldown. No need to sync.\")\n        return@forEach\n      }\n\n      try {\n        Timber.i(\"Syncing ${show.title}(${show.ids.trakt}) details...\")\n        showsRepository.detailsShow.load(show.ids.trakt, force = true)\n        syncCount++\n        Timber.i(\"${show.title}(${show.ids.trakt}) show synced.\")\n      } catch (t: Throwable) {\n        Timber.e(\"${show.title}(${show.ids.trakt}) show sync error. Skipping... \\n$t\")\n      }\n\n      if (isInWatchlist) {\n        localSource.episodesSyncLog.upsert(EpisodesSyncLog(show.traktId, nowUtcMillis()))\n      } else {\n        try {\n          Timber.i(\"Syncing ${show.title}(${show.ids.trakt}) episodes...\")\n\n          val remoteSeasons = remoteSource.trakt.fetchSeasons(show.traktId)\n            .map { mappers.season.fromNetwork(it) }\n          episodesManager.invalidateSeasons(show, remoteSeasons)\n          syncCount++\n\n          Timber.i(\"${show.title}(${show.ids.trakt}) episodes synced.\")\n        } catch (t: Throwable) {\n          Timber.e(\"${show.title}(${show.ids.trakt}) episodes sync error. Skipping... \\n$t\")\n        } finally {\n          delay(DELAY_MS)\n        }\n      }\n    }\n\n    return syncCount\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/trakt/TraktNotificationWorker.kt",
    "content": "package com.michaldrabik.ui_base.trakt\n\nimport android.app.Notification\nimport android.app.NotificationChannel\nimport android.app.NotificationManager\nimport android.app.UiModeManager\nimport android.content.Context\nimport android.os.Build\nimport androidx.annotation.StringRes\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE\nimport androidx.core.content.ContextCompat\nimport androidx.work.CoroutineWorker\nimport androidx.work.WorkerParameters\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.utilities.extensions.notificationManager\nimport java.util.concurrent.TimeUnit\n\nabstract class TraktNotificationWorker constructor(\n  val context: Context,\n  workerParams: WorkerParameters\n) : CoroutineWorker(context, workerParams) {\n\n  private fun createBaseNotification(theme: Int): NotificationCompat.Builder {\n    val color = when (theme) {\n      UiModeManager.MODE_NIGHT_YES -> R.color.colorNotificationDark\n      UiModeManager.MODE_NIGHT_NO -> R.color.colorNotificationLight\n      else -> R.color.colorNotificationDark\n    }\n    return NotificationCompat.Builder(applicationContext, createNotificationChannel())\n      .setForegroundServiceBehavior(FOREGROUND_SERVICE_IMMEDIATE)\n      .setContentTitle(context.getString(R.string.textTraktSync))\n      .setSmallIcon(R.drawable.ic_notification)\n      .setAutoCancel(true)\n      .setColor(ContextCompat.getColor(applicationContext, color))\n  }\n\n  protected fun createProgressNotification(\n    theme: Int,\n    content: String?,\n    maxProgress: Int,\n    progress: Int,\n    isIntermediate: Boolean\n  ): Notification =\n    createBaseNotification(theme)\n      .setContentText(content ?: context.getString(R.string.textTraktSyncRunning))\n      .setCategory(NotificationCompat.CATEGORY_SERVICE)\n      .setOngoing(true)\n      .setAutoCancel(false)\n      .setProgress(maxProgress, progress, isIntermediate)\n      .build()\n\n  protected fun createSuccessNotification(theme: Int): Notification =\n    createBaseNotification(theme)\n      .setTimeoutAfter(TimeUnit.SECONDS.toMillis(3))\n      .setContentText(context.getString(R.string.textTraktSyncComplete))\n      .setPriority(NotificationCompat.PRIORITY_HIGH)\n      .build()\n\n  protected fun createErrorNotification(\n    theme: Int,\n    @StringRes titleTextRes: Int,\n    @StringRes bigTextRes: Int,\n    action: NotificationCompat.Action? = null\n  ): Notification =\n    createBaseNotification(theme)\n      .setContentTitle(context.getString(titleTextRes))\n      .setContentText(context.getString(bigTextRes))\n      .setStyle(NotificationCompat.BigTextStyle().bigText(context.getString(bigTextRes)))\n      .setPriority(NotificationCompat.PRIORITY_HIGH)\n      .apply { action?.let { addAction(it) } }\n      .build()\n\n  private fun createNotificationChannel(): String {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n      val id = \"Showly Trakt Sync Service\"\n      val name = \"Showly Trakt Sync\"\n      val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW).apply {\n        lockscreenVisibility = Notification.VISIBILITY_PRIVATE\n        setSound(null, null)\n      }\n      applicationContext.notificationManager().createNotificationChannel(channel)\n      return id\n    }\n    return \"\"\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/trakt/TraktSyncRunner.kt",
    "content": "package com.michaldrabik.ui_base.trakt\n\nimport com.michaldrabik.common.errors.ShowlyError\nimport com.michaldrabik.repository.UserTraktManager\nimport timber.log.Timber\nimport java.util.concurrent.atomic.AtomicInteger\n\nabstract class TraktSyncRunner(\n  private val userTraktManager: UserTraktManager,\n) {\n\n  companion object {\n    const val TRAKT_LIMIT_DELAY_MS = 1250L\n    const val RETRY_DELAY_MS = 5000L\n    const val MAX_EXPORT_RETRY_COUNT = 1\n    const val MAX_IMPORT_RETRY_COUNT = 3\n  }\n\n  val retryCount = AtomicInteger(0)\n  var progressListener: (suspend (String, Int, Int) -> Unit)? = null\n\n  abstract suspend fun run(): Int\n\n  protected fun checkAuthorization() {\n    try {\n      Timber.d(\"Checking authorization...\")\n      userTraktManager.checkAuthorization()\n    } catch (error: Throwable) {\n      throw ShowlyError.UnauthorizedError(error.message)\n    }\n  }\n\n  protected fun resetRetries() {\n    retryCount.set(0)\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/trakt/TraktSyncWorker.kt",
    "content": "package com.michaldrabik.ui_base.trakt\n\nimport android.app.PendingIntent\nimport android.app.PendingIntent.FLAG_IMMUTABLE\nimport android.app.PendingIntent.FLAG_UPDATE_CURRENT\nimport android.content.Context\nimport android.content.Intent\nimport android.content.SharedPreferences\nimport android.net.Uri\nimport androidx.annotation.StringRes\nimport androidx.core.app.NotificationCompat\nimport androidx.hilt.work.HiltWorker\nimport androidx.work.Constraints\nimport androidx.work.ExistingPeriodicWorkPolicy\nimport androidx.work.ExistingWorkPolicy\nimport androidx.work.ForegroundInfo\nimport androidx.work.NetworkType\nimport androidx.work.OneTimeWorkRequestBuilder\nimport androidx.work.OutOfQuotaPolicy\nimport androidx.work.PeriodicWorkRequestBuilder\nimport androidx.work.WorkManager\nimport androidx.work.WorkerParameters\nimport androidx.work.workDataOf\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.Analytics\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.TraktSyncAuthError\nimport com.michaldrabik.ui_base.events.TraktSyncError\nimport com.michaldrabik.ui_base.events.TraktSyncProgress\nimport com.michaldrabik.ui_base.events.TraktSyncStart\nimport com.michaldrabik.ui_base.events.TraktSyncSuccess\nimport com.michaldrabik.ui_base.trakt.exports.TraktExportListsRunner\nimport com.michaldrabik.ui_base.trakt.exports.TraktExportWatchedRunner\nimport com.michaldrabik.ui_base.trakt.exports.TraktExportWatchlistRunner\nimport com.michaldrabik.ui_base.trakt.imports.TraktImportListsRunner\nimport com.michaldrabik.ui_base.trakt.imports.TraktImportWatchedRunner\nimport com.michaldrabik.ui_base.trakt.imports.TraktImportWatchlistRunner\nimport com.michaldrabik.ui_base.utilities.extensions.notificationManager\nimport com.michaldrabik.ui_model.TraktSyncSchedule\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedInject\nimport timber.log.Timber\nimport javax.inject.Named\n\n@HiltWorker\nclass TraktSyncWorker @AssistedInject constructor(\n  @Assisted context: Context,\n  @Assisted workerParams: WorkerParameters,\n  private val importWatchedRunner: TraktImportWatchedRunner,\n  private val importWatchlistRunner: TraktImportWatchlistRunner,\n  private val importListsRunner: TraktImportListsRunner,\n  private val exportWatchedRunner: TraktExportWatchedRunner,\n  private val exportWatchlistRunner: TraktExportWatchlistRunner,\n  private val exportListsRunner: TraktExportListsRunner,\n  private val settingsRepository: SettingsRepository,\n  private val eventsManager: EventsManager,\n  private val userManager: UserTraktManager,\n  @Named(\"miscPreferences\") private val miscPreferences: SharedPreferences,\n) : TraktNotificationWorker(context, workerParams) {\n\n  companion object {\n    const val TAG_ID = \"TRAKT_SYNC_WORK_ID\"\n    private const val TAG = \"TRAKT_SYNC_WORK\"\n    private const val TAG_ONE_OFF = \"TRAKT_SYNC_WORK_ONE_OFF\"\n\n    private const val SYNC_NOTIFICATION_COMPLETE_SUCCESS_ID = 827\n    private const val SYNC_NOTIFICATION_COMPLETE_PROGRESS_ID = 823\n    private const val SYNC_NOTIFICATION_COMPLETE_ERROR_ID = 828\n    private const val SYNC_NOTIFICATION_COMPLETE_ERROR_LISTS_ID = 832\n\n    const val KEY_LAST_SYNC_TIMESTAMP = \"KEY_LAST_SYNC_TIMESTAMP\"\n    private const val ARG_IS_IMPORT = \"ARG_IS_IMPORT\"\n    private const val ARG_IS_EXPORT = \"ARG_IS_EXPORT\"\n    private const val ARG_IS_SILENT = \"ARG_IS_SILENT\"\n\n    private const val TRAKT_LISTS_INFO_URL = \"https://twitter.com/trakt/status/1536751362943332352?s=20&t=bdlxpzlDIclkLqdihaAXqw\"\n\n    fun scheduleOneOff(\n      workManager: WorkManager,\n      isImport: Boolean,\n      isExport: Boolean,\n      isSilent: Boolean,\n    ) {\n      val inputData = workDataOf(\n        ARG_IS_IMPORT to isImport,\n        ARG_IS_EXPORT to isExport,\n        ARG_IS_SILENT to isSilent\n      )\n\n      val request = OneTimeWorkRequestBuilder<TraktSyncWorker>()\n        .setConstraints(\n          Constraints.Builder()\n            .setRequiredNetworkType(NetworkType.CONNECTED)\n            .setRequiresBatteryNotLow(false)\n            .setRequiresStorageNotLow(false)\n            .build()\n        )\n        .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)\n        .setInputData(inputData)\n        .addTag(TAG_ID)\n        .addTag(TAG_ONE_OFF)\n        .build()\n\n      workManager.enqueueUniqueWork(TAG_ONE_OFF, ExistingWorkPolicy.KEEP, request)\n    }\n\n    fun schedulePeriodic(\n      workManager: WorkManager,\n      schedule: TraktSyncSchedule,\n      cancelExisting: Boolean,\n    ) {\n      if (cancelExisting) {\n        workManager.cancelUniqueWork(TAG)\n      }\n\n      if (schedule == TraktSyncSchedule.OFF) {\n        cancelAllPeriodic(workManager)\n        Timber.i(\"Trakt sync scheduled: $schedule\")\n        return\n      }\n\n      val inputData = workDataOf(\n        ARG_IS_IMPORT to true,\n        ARG_IS_EXPORT to true,\n        ARG_IS_SILENT to true\n      )\n\n      val request = PeriodicWorkRequestBuilder<TraktSyncWorker>(schedule.duration, schedule.durationUnit)\n        .setConstraints(\n          Constraints.Builder()\n            .setRequiredNetworkType(NetworkType.CONNECTED)\n            .build()\n        )\n        .setInputData(inputData)\n        .setInitialDelay(schedule.duration, schedule.durationUnit)\n        .addTag(TAG_ID)\n        .addTag(TAG)\n        .build()\n\n      workManager.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.KEEP, request)\n      Timber.i(\"Trakt sync scheduled: $schedule\")\n    }\n\n    fun cancelAllPeriodic(workManager: WorkManager) {\n      workManager.cancelUniqueWork(TAG)\n    }\n  }\n\n  override suspend fun doWork(): Result {\n    val isImport = inputData.getBoolean(ARG_IS_IMPORT, false)\n    val isExport = inputData.getBoolean(ARG_IS_EXPORT, false)\n    val isSilent = inputData.getBoolean(ARG_IS_SILENT, false)\n    val theme = settingsRepository.theme\n\n    try {\n      eventsManager.sendEvent(TraktSyncStart)\n\n      if (isImport) {\n        var resultCount = runImportWatched()\n        resultCount += runImportWatchlist(resultCount)\n        runImportLists(resultCount)\n      }\n      if (isExport) {\n        runExportWatched()\n        runExportWatchlist()\n        runExportLists()\n      }\n\n      miscPreferences.edit().putLong(KEY_LAST_SYNC_TIMESTAMP, nowUtcMillis()).apply()\n\n      eventsManager.sendEvent(TraktSyncSuccess)\n      Analytics.logTraktFullSyncSuccess(isImport, isExport)\n      if (!isSilent) {\n        notificationManager().notify(SYNC_NOTIFICATION_COMPLETE_SUCCESS_ID, createSuccessNotification(theme))\n      }\n      return Result.success()\n    } catch (error: Throwable) {\n      handleError(error, isSilent)\n      return Result.failure()\n    } finally {\n      clearRunners()\n      notificationManager().cancel(SYNC_NOTIFICATION_COMPLETE_PROGRESS_ID)\n    }\n  }\n\n  override suspend fun getForegroundInfo(): ForegroundInfo {\n    val theme = settingsRepository.theme\n    val notification = createProgressNotification(theme, null, 0, 0, true)\n    return ForegroundInfo(SYNC_NOTIFICATION_COMPLETE_PROGRESS_ID, notification)\n  }\n\n  private suspend fun runImportWatched(): Int {\n    val theme = settingsRepository.theme\n    importWatchedRunner.progressListener = { title: String, progress: Int, total: Int ->\n      val status = \"Importing \\'$title\\'...\"\n      setProgressNotification(theme, status, total, progress, false)\n      eventsManager.sendEvent(TraktSyncProgress(status))\n    }\n    return importWatchedRunner.run()\n  }\n\n  private suspend fun runImportWatchlist(totalProgress: Int): Int {\n    val theme = settingsRepository.theme\n    importWatchlistRunner.progressListener = { title: String, progress: Int, total: Int ->\n      val status = \"Importing \\'$title\\'...\"\n      setProgressNotification(theme, status, totalProgress + total, totalProgress + progress, false)\n      eventsManager.sendEvent(TraktSyncProgress(status))\n    }\n    return importWatchlistRunner.run()\n  }\n\n  private suspend fun runImportLists(totalProgress: Int) {\n    val theme = settingsRepository.theme\n    importListsRunner.progressListener = { title: String, progress: Int, total: Int ->\n      val status = \"Importing \\'$title\\'...\"\n      setProgressNotification(theme, status, totalProgress + total, totalProgress + progress, false)\n      eventsManager.sendEvent(TraktSyncProgress(status))\n    }\n    importListsRunner.run()\n  }\n\n  private suspend fun runExportWatched() {\n    val status = \"Exporting progress...\"\n    val theme = settingsRepository.theme\n    setProgressNotification(theme, status, 0, 0, true)\n    eventsManager.sendEvent(TraktSyncProgress(status))\n    exportWatchedRunner.run()\n  }\n\n  private suspend fun runExportWatchlist() {\n    val status = \"Exporting watchlist...\"\n    val theme = settingsRepository.theme\n    setProgressNotification(theme, status, 0, 0, true)\n    eventsManager.sendEvent(TraktSyncProgress(status))\n    try {\n      exportWatchlistRunner.run()\n    } catch (error: Throwable) {\n      handleListsError(error, R.string.errorTraktSyncWatchlistLimitsReached)\n    }\n  }\n\n  private suspend fun runExportLists() {\n    val status = \"Exporting custom lists...\"\n    val theme = settingsRepository.theme\n    setProgressNotification(theme, status, 0, 0, true)\n    eventsManager.sendEvent(TraktSyncProgress(status))\n    try {\n      exportListsRunner.run()\n    } catch (error: Throwable) {\n      handleListsError(error, R.string.errorTraktSyncListsLimitsReached)\n    }\n  }\n\n  private fun setProgressNotification(\n    theme: Int,\n    content: String?,\n    maxProgress: Int,\n    progress: Int,\n    isIntermediate: Boolean,\n  ) {\n    notificationManager().notify(\n      SYNC_NOTIFICATION_COMPLETE_PROGRESS_ID,\n      createProgressNotification(theme, content, maxProgress, progress, isIntermediate)\n    )\n  }\n\n  private suspend fun handleError(error: Throwable, isSilent: Boolean) {\n    val showlyError = ErrorHelper.parse(error)\n    if (showlyError is ShowlyError.UnauthorizedError) {\n      eventsManager.sendEvent(TraktSyncAuthError)\n      userManager.revokeToken()\n    } else {\n      eventsManager.sendEvent(TraktSyncError)\n    }\n    if (!isSilent) {\n      val message =\n        if (showlyError is ShowlyError.UnauthorizedError) R.string.errorTraktAuthorization\n        else R.string.textTraktSyncErrorFull\n\n      val theme = settingsRepository.theme\n      notificationManager().notify(\n        SYNC_NOTIFICATION_COMPLETE_ERROR_ID,\n        createErrorNotification(theme, R.string.textTraktSyncError, message)\n      )\n    }\n    Logger.record(error, \"TraktSyncWorker::handleError()\")\n  }\n\n  private fun handleListsError(\n    error: Throwable,\n    @StringRes notificationMessageResId: Int,\n  ) {\n    when (ErrorHelper.parse(error)) {\n      ShowlyError.AccountLimitsError -> {\n        val theme = settingsRepository.theme\n\n        val intent = Intent(Intent.ACTION_VIEW, Uri.parse(TRAKT_LISTS_INFO_URL))\n        val pendingIntent = PendingIntent.getActivity(context, 0, intent, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)\n        val action = NotificationCompat.Action(R.drawable.ic_info, \"More Info\", pendingIntent)\n\n        notificationManager().notify(\n          SYNC_NOTIFICATION_COMPLETE_ERROR_LISTS_ID,\n          createErrorNotification(theme, R.string.textTraktSync, notificationMessageResId, action)\n        )\n      }\n\n      else -> throw error\n    }\n  }\n\n  private fun clearRunners() {\n    arrayOf(\n      importWatchedRunner,\n      importWatchedRunner,\n      importWatchlistRunner,\n      importListsRunner,\n      exportWatchedRunner,\n      exportWatchlistRunner,\n      exportListsRunner,\n    ).forEach {\n      it.progressListener = null\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/trakt/exports/TraktExportListsRunner.kt",
    "content": "package com.michaldrabik.ui_base.trakt.exports\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_remote.trakt.TraktRemoteDataSource\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.trakt.TraktSyncRunner\nimport com.michaldrabik.ui_model.CustomList\nimport kotlinx.coroutines.delay\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass TraktExportListsRunner @Inject constructor(\n  private val remoteSource: TraktRemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n  private val settingsRepository: SettingsRepository,\n  userTraktManager: UserTraktManager,\n) : TraktSyncRunner(userTraktManager) {\n\n  private var hasAccountLimitsOccurred = false\n\n  override suspend fun run(): Int {\n    Timber.d(\"Initialized.\")\n    hasAccountLimitsOccurred = false\n\n    checkAuthorization()\n    resetRetries()\n    runExport()\n\n    if (hasAccountLimitsOccurred) {\n      throw ShowlyError.AccountLimitsError\n    }\n    Timber.d(\"Finished with success.\")\n    return 0\n  }\n\n  private suspend fun runExport() {\n    try {\n      exportLists()\n    } catch (error: Throwable) {\n      if (retryCount.getAndIncrement() < MAX_EXPORT_RETRY_COUNT) {\n        Timber.w(\"exportLists failed. Will retry in $RETRY_DELAY_MS ms... $error\")\n        delay(RETRY_DELAY_MS)\n        runExport()\n      } else {\n        throw error\n      }\n    }\n  }\n\n  private suspend fun exportLists() {\n    Timber.d(\"Exporting lists...\")\n\n    val localLists = localSource.customLists.getAll()\n      .map { mappers.customList.fromDatabase(it) }\n    val remoteLists = remoteSource.fetchSyncLists()\n      .map { mappers.customList.fromNetwork(it) }\n\n    localLists\n      .sortedByDescending { it.updatedAt }\n      .forEach { localList ->\n        Timber.d(\"Processing ${localList.name}...\")\n        try {\n          val isNewList = remoteLists.none { it.idTrakt == localList.idTrakt }\n          if (isNewList) {\n            Timber.d(\"List not found in Trakt. Creating and uploading items...\")\n            exportNewList(localList)\n          } else {\n            Timber.d(\"List found in Trakt.\")\n            exportExistingList(remoteLists, localList)\n          }\n        } catch (error: Throwable) {\n          val showlyError = ErrorHelper.parse(error)\n          if (showlyError == ShowlyError.AccountLimitsError) {\n            Timber.w(\"Account limits reached. Skipping the rest of lists exporting.\")\n            hasAccountLimitsOccurred = true\n            return@forEach\n          }\n          throw error\n        }\n      }\n  }\n\n  private suspend fun exportNewList(localList: CustomList) {\n    delay(TRAKT_LIMIT_DELAY_MS)\n\n    val listNet = remoteSource.postCreateList(localList.name, localList.description)\n    Timber.d(\"List created in Trakt.\")\n\n    val list = mappers.customList.fromNetwork(listNet)\n    val listDb = mappers.customList.toDatabase(list).copy(id = localList.id)\n\n    localSource.customLists.update(listOf(listDb))\n    delay(TRAKT_LIMIT_DELAY_MS)\n\n    val localItems = localSource.customListsItems.getItemsById(localList.id)\n    if (localItems.isNotEmpty()) {\n      val showsIds = localItems.filter { it.type == Mode.SHOWS.type }.map { it.idTrakt }\n      val moviesIds = localItems.filter { it.type == Mode.MOVIES.type }.map { it.idTrakt }\n      remoteSource.postAddListItems(listNet.ids.trakt, showsIds, moviesIds)\n      Timber.d(\"Items added into Trakt list.\")\n      delay(TRAKT_LIMIT_DELAY_MS)\n    }\n  }\n\n  private suspend fun exportExistingList(\n    remoteLists: List<CustomList>,\n    localList: CustomList,\n  ) {\n    val moviesEnabled = settingsRepository.isMoviesEnabled\n\n    val remoteList = remoteLists.first { it.idTrakt == localList.idTrakt }\n    if (localList.updatedAt.isEqual(remoteList.updatedAt)) {\n      Timber.d(\"Timestamps are the same.\")\n      return\n    }\n\n    Timber.d(\"Timestamps are different.\")\n    if (localList.updatedAt.isAfter(remoteList.updatedAt)) {\n      Timber.d(\"Local list timestamp is newer.\")\n      if (localList.name != remoteList.name || localList.description != remoteList.description) {\n        Timber.d(\"Name or description are different. Updating...\")\n        val updateList = mappers.customList.toNetwork(localList)\n        val resultList = remoteSource.postUpdateList(updateList)\n        val listDb = mappers.customList.fromNetwork(resultList).copy(id = localList.id)\n        localSource.customLists.update(listOf(mappers.customList.toDatabase(listDb)))\n        delay(TRAKT_LIMIT_DELAY_MS)\n      }\n    }\n\n    Timber.d(\"Processing list items...\")\n    val listTraktId = localList.idTrakt!!\n    val remoteItems = remoteSource.fetchSyncListItems(listTraktId, moviesEnabled)\n      .filter { it.movie != null || it.show != null }\n    val localItems = localSource.customListsItems.getItemsById(localList.id)\n      .filter { localItem ->\n        remoteItems.none {\n          it.getTraktId() == localItem.idTrakt && it.getType() == localItem.type\n        }\n      }\n\n    if (localItems.isNotEmpty()) {\n      Timber.d(\"${localItems.size} to be exported...\")\n\n      val showsIds = localItems.filter { it.type == Mode.SHOWS.type }.map { it.idTrakt }\n      val moviesIds = localItems.filter { it.type == Mode.MOVIES.type }.map { it.idTrakt }\n      remoteSource.postAddListItems(listTraktId, showsIds, moviesIds)\n\n      Timber.d(\"Exported!\")\n      delay(TRAKT_LIMIT_DELAY_MS)\n    }\n\n    updateListTimestamp(localList.id, listTraktId)\n  }\n\n  private suspend fun updateListTimestamp(listId: Long, listTraktId: Long) {\n    try {\n      Timber.d(\"Updating timestamp...\")\n      val list = remoteSource.fetchSyncList(listTraktId)\n        .run { mappers.customList.fromNetwork(this) }\n      localSource.customLists.updateTimestamp(listId, list.updatedAt.toMillis())\n      Timber.d(\"Local list timestamp updated.\")\n    } catch (error: Throwable) {\n      Timber.w(error)\n      // Skip timestamp update in case of failure.\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/trakt/exports/TraktExportWatchedRunner.kt",
    "content": "package com.michaldrabik.ui_base.trakt.exports\n\nimport com.michaldrabik.common.extensions.dateIsoStringFromMillis\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.Episode\nimport com.michaldrabik.data_local.database.model.Movie\nimport com.michaldrabik.data_local.database.model.Show\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.data_remote.trakt.model.SyncExportItem\nimport com.michaldrabik.data_remote.trakt.model.SyncExportRequest\nimport com.michaldrabik.data_remote.trakt.model.SyncItem\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.Analytics\nimport com.michaldrabik.ui_base.trakt.TraktSyncRunner\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.delay\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass TraktExportWatchedRunner @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val settingsRepository: SettingsRepository,\n  userTraktManager: UserTraktManager,\n) : TraktSyncRunner(userTraktManager) {\n\n  override suspend fun run(): Int {\n    Timber.d(\"Initialized.\")\n\n    checkAuthorization()\n    resetRetries()\n    runExport()\n\n    Timber.d(\"Finished with success.\")\n    return 0\n  }\n\n  private suspend fun runExport() {\n    try {\n      exportWatched()\n    } catch (error: Throwable) {\n      rethrowCancellation(error)\n      if (retryCount.getAndIncrement() < MAX_EXPORT_RETRY_COUNT) {\n        Timber.w(\"exportWatched failed. Will retry in $RETRY_DELAY_MS ms... $error\")\n        delay(RETRY_DELAY_MS)\n        runExport()\n      } else {\n        throw error\n      }\n    }\n  }\n\n  private suspend fun exportWatched() {\n    Timber.d(\"Exporting watched...\")\n\n    val remoteShows = remoteSource.trakt.fetchSyncWatchedShows()\n      .filter { it.show != null }\n\n    val localMyShows = localSource.myShows.getAll()\n    val localEpisodes = batchEpisodes(localMyShows.map { it.idTrakt })\n      .filter { !hasEpisodeBeenWatched(remoteShows, it) }\n\n    val movies = mutableListOf<SyncExportItem>()\n    if (settingsRepository.isMoviesEnabled) {\n      val remoteMovies = remoteSource.trakt.fetchSyncWatchedMovies()\n        .filter { it.movie != null }\n      val localMyMoviesIds = localSource.myMovies.getAllTraktIds()\n      val localMyMovies = batchMovies(localMyMoviesIds)\n        .filter { movie -> remoteMovies.none { it.movie?.ids?.trakt == movie.idTrakt } }\n\n      localMyMovies.mapTo(movies) {\n        SyncExportItem.create(it.idTrakt, dateIsoStringFromMillis(it.updatedAt))\n      }\n    }\n\n    val episodes = localEpisodes.map { ep ->\n      val episodeTimestamp = ep.lastWatchedAt?.toMillis() ?: 0\n      val showTimestamp = localMyShows.find { it.idTrakt == ep.idShowTrakt }?.updatedAt ?: 0\n      val timestamp = when {\n        episodeTimestamp > 0 -> episodeTimestamp\n        showTimestamp > 0 -> showTimestamp\n        else -> nowUtcMillis()\n      }\n      SyncExportItem.create(ep.idTrakt, dateIsoStringFromMillis(timestamp))\n    }\n\n    Timber.d(\"Exporting ${episodes.size} episodes & ${movies.size} movies...\")\n    if (episodes.isNotEmpty() || movies.isNotEmpty()) {\n      Analytics.logExportHistory(episodes.size, movies.size, retryCount.get())\n      val request = SyncExportRequest(episodes = episodes, movies = movies)\n      postExportWatched(request)\n    } else {\n      Timber.d(\"Nothing to export. Skipping...\")\n    }\n\n    delay(TRAKT_LIMIT_DELAY_MS)\n    exportHidden()\n  }\n\n  private suspend fun postExportWatched(\n    request: SyncExportRequest,\n  ) {\n    val episodes = request.episodes.toList()\n    val movies = request.movies.toList()\n\n    if (episodes.isEmpty() && movies.isEmpty()) {\n      Timber.d(\"All batches exported.\")\n      return\n    }\n\n    val batchRequest = request.copy(\n      episodes = episodes.take(1000),\n      movies = movies.take(500)\n    )\n\n    Timber.d(\"Exporting batch ${batchRequest.episodes.size} episodes & ${batchRequest.movies.size} movies...\")\n    remoteSource.trakt.postSyncWatched(batchRequest)\n\n    delay(TRAKT_LIMIT_DELAY_MS)\n    postExportWatched(\n      request.copy(\n        episodes = request.episodes.filter { it !in batchRequest.episodes },\n        movies = request.movies.filter { it !in batchRequest.movies }\n      )\n    )\n  }\n\n  private suspend fun exportHidden() = coroutineScope {\n    Timber.d(\"Exporting hidden items...\")\n\n    val remoteShowsAsync = async { remoteSource.trakt.fetchHiddenShows() }\n    val remoteMoviesAsync = async { remoteSource.trakt.fetchHiddenMovies() }\n    val (remoteShows, remoteMovies) = awaitAll(remoteShowsAsync, remoteMoviesAsync)\n\n    val showsAsync = async { localSource.archiveShows.getAll() }\n    val moviesAsync = async { localSource.archiveMovies.getAll() }\n    val (localShows, localMovies) = awaitAll(showsAsync, moviesAsync)\n\n    val remoteShowsIds = remoteShows.mapNotNull { it.show?.ids?.trakt }\n    val remoteMoviesIds = remoteMovies.mapNotNull { it.movie?.ids?.trakt }\n\n    val showsItems = localShows\n      .filter { (it as Show).idTrakt !in remoteShowsIds }\n      .map {\n        (it as Show).let { show ->\n          SyncExportItem.create(\n            traktId = show.idTrakt,\n            hiddenAt = dateIsoStringFromMillis(show.updatedAt)\n          )\n        }\n      }\n    val moviesItems = localMovies\n      .filter { (it as Movie).idTrakt !in remoteMoviesIds }\n      .map {\n        (it as Movie).let { movie ->\n          SyncExportItem.create(\n            traktId = movie.idTrakt,\n            hiddenAt = dateIsoStringFromMillis(movie.updatedAt)\n          )\n        }\n      }\n\n    Timber.d(\"Exporting ${showsItems.size} hidden shows...\")\n    if (showsItems.isNotEmpty()) {\n      showsItems.chunked(500).forEach { chunk ->\n        remoteSource.trakt.postHiddenShows(shows = chunk)\n        delay(TRAKT_LIMIT_DELAY_MS)\n      }\n      delay(TRAKT_LIMIT_DELAY_MS)\n    } else {\n      Timber.d(\"Nothing to export. Skipping...\")\n    }\n\n    Timber.d(\"Exporting ${moviesItems.size} hidden movies...\")\n    if (moviesItems.isNotEmpty()) {\n      moviesItems.chunked(500).forEach { chunk ->\n        remoteSource.trakt.postHiddenMovies(movies = chunk)\n        delay(TRAKT_LIMIT_DELAY_MS)\n      }\n      delay(TRAKT_LIMIT_DELAY_MS)\n    } else {\n      Timber.d(\"Nothing to export. Skipping...\")\n    }\n  }\n\n  private suspend fun batchEpisodes(\n    showsIds: List<Long>,\n    allEpisodes: MutableList<Episode> = mutableListOf(),\n  ): List<Episode> {\n    val batch = showsIds.take(250)\n    if (batch.isEmpty()) return allEpisodes\n\n    val episodes = localSource.episodes.getAllWatchedForShows(batch)\n    allEpisodes.addAll(episodes)\n\n    return batchEpisodes(showsIds.filter { it !in batch }, allEpisodes)\n  }\n\n  private suspend fun batchMovies(\n    moviesIds: List<Long>,\n    result: MutableList<Movie> = mutableListOf(),\n  ): List<Movie> {\n    val batch = moviesIds.take(500)\n    if (batch.isEmpty()) return result\n\n    val movies = localSource.myMovies.getAll(batch)\n    result.addAll(movies)\n\n    return batchMovies(moviesIds.filter { it !in batch }, result)\n  }\n\n  private fun hasEpisodeBeenWatched(remoteWatched: List<SyncItem>, episodeDb: Episode): Boolean {\n    val find = remoteWatched\n      .find { it.show?.ids?.trakt == episodeDb.idShowTrakt }\n      ?.seasons?.find { it.number == episodeDb.seasonNumber }\n      ?.episodes?.find { it.number == episodeDb.episodeNumber }\n    return find != null\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/trakt/exports/TraktExportWatchlistRunner.kt",
    "content": "package com.michaldrabik.ui_base.trakt.exports\n\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.data_remote.trakt.model.SyncExportItem\nimport com.michaldrabik.data_remote.trakt.model.SyncExportRequest\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.trakt.TraktSyncRunner\nimport kotlinx.coroutines.delay\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass TraktExportWatchlistRunner @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val settingsRepository: SettingsRepository,\n  userTraktManager: UserTraktManager,\n) : TraktSyncRunner(userTraktManager) {\n\n  override suspend fun run(): Int {\n    Timber.d(\"Initialized.\")\n\n    checkAuthorization()\n    resetRetries()\n    runExport()\n\n    Timber.d(\"Finished with success.\")\n    return 0\n  }\n\n  private suspend fun runExport() {\n    try {\n      exportWatchlist()\n    } catch (error: Throwable) {\n      handleError(error)\n    }\n  }\n\n  private suspend fun exportWatchlist() {\n    Timber.d(\"Exporting watchlist...\")\n\n    val shows = localSource.watchlistShows.getAll()\n      .map { SyncExportItem.create(it.idTrakt) }\n      .toMutableList()\n\n    val movies = mutableListOf<SyncExportItem>()\n    if (settingsRepository.isMoviesEnabled) {\n      localSource.watchlistMovies.getAll()\n        .mapTo(movies) { SyncExportItem.create(it.idTrakt) }\n    }\n\n    Timber.d(\"Exporting ${shows.size} shows & ${movies.size} movies...\")\n\n    while (true) {\n      val showsChunk = shows.take(250)\n      val moviesChunk = movies.take(250)\n      if (showsChunk.isEmpty() && moviesChunk.isEmpty()) {\n        Timber.d(\"No more chunks. Breaking.\")\n        break\n      }\n      Timber.d(\"Exporting chunk of ${showsChunk.size} shows & ${moviesChunk.size} movies...\")\n      val request = SyncExportRequest(shows = showsChunk, movies = moviesChunk)\n      remoteSource.trakt.postSyncWatchlist(request)\n\n      shows.removeAll(showsChunk)\n      movies.removeAll(moviesChunk)\n\n      delay(TRAKT_LIMIT_DELAY_MS)\n    }\n  }\n\n  private suspend fun handleError(error: Throwable) {\n    val showlyError = ErrorHelper.parse(error)\n    when {\n      showlyError == ShowlyError.AccountLimitsError -> {\n        Timber.w(\"Account limits reached for Watchlist.\")\n        throw error\n      }\n      retryCount.getAndIncrement() < MAX_EXPORT_RETRY_COUNT -> {\n        Timber.w(\"exportWatchlist failed. Will retry in $RETRY_DELAY_MS ms... $error\")\n        delay(RETRY_DELAY_MS)\n        runExport()\n      }\n      else -> throw error\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/trakt/imports/TraktImportListsRunner.kt",
    "content": "package com.michaldrabik.ui_base.trakt.imports\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.CustomListItem\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.trakt.TraktSyncRunner\nimport kotlinx.coroutines.delay\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass TraktImportListsRunner @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers,\n  private val settingsRepository: SettingsRepository,\n  userTraktManager: UserTraktManager,\n) : TraktSyncRunner(userTraktManager) {\n\n  override suspend fun run(): Int {\n    Timber.d(\"Initialized.\")\n\n    var syncedCount = 0\n    checkAuthorization()\n\n    resetRetries()\n    syncedCount += runLists()\n\n    Timber.d(\"Finished with success.\")\n    return syncedCount\n  }\n\n  private suspend fun runLists(): Int {\n    return try {\n      importLists()\n    } catch (error: Throwable) {\n      if (retryCount.getAndIncrement() < MAX_IMPORT_RETRY_COUNT) {\n        Timber.w(\"runLists HTTP failed. Will retry in $RETRY_DELAY_MS ms... $error\")\n        delay(RETRY_DELAY_MS)\n        runLists()\n      } else {\n        throw error\n      }\n    }\n  }\n\n  private suspend fun importLists(): Int {\n    Timber.d(\"Importing custom lists...\")\n    val nowUtcMillis = nowUtcMillis()\n    val moviesEnabled = settingsRepository.isMoviesEnabled\n\n    val localLists = localSource.customLists.getAll()\n      .map { mappers.customList.fromDatabase(it) }\n    val remoteLists = remoteSource.trakt.fetchSyncLists()\n      .map { mappers.customList.fromNetwork(it) }\n\n    remoteLists.forEach { remoteList ->\n      Timber.d(\"Processing '${remoteList.name}' ...\")\n      val local = localLists.find { it.idTrakt == remoteList.idTrakt }\n      transactions.withTransaction {\n        when {\n          local == null -> {\n            Timber.d(\"Local list not found. Creating...\")\n            val listDb = mappers.customList.toDatabase(remoteList)\n            val id = localSource.customLists.insert(listOf(listDb)).first()\n            importListItems(id, remoteList.idTrakt!!, moviesEnabled, nowUtcMillis)\n          }\n          remoteList.updatedAt.isEqual(local.updatedAt).not() -> {\n            Timber.d(\"Local list found and timestamp is different. Updating...\")\n            if (remoteList.updatedAt.isAfter(local.updatedAt)) {\n              val listDb = mappers.customList.toDatabase(remoteList)\n                .copy(id = local.id)\n              localSource.customLists.update(listOf(listDb))\n            }\n            importListItems(local.id, local.idTrakt!!, moviesEnabled, nowUtcMillis)\n          }\n          else -> {\n            Timber.d(\"Local list found but timestamp is the same. Skipping...\")\n          }\n        }\n      }\n    }\n\n    return remoteLists.size\n  }\n\n  private suspend fun importListItems(\n    listId: Long,\n    listIdTrakt: Long,\n    moviesEnabled: Boolean,\n    nowUtcMillis: Long,\n  ) {\n    Timber.d(\"Importing list items...\")\n\n    val localItems = localSource.customListsItems.getItemsById(listId)\n    val items = remoteSource.trakt.fetchSyncListItems(listIdTrakt, moviesEnabled)\n      .filter { item ->\n        localItems.none {\n          it.idTrakt == item.getTraktId() && it.type == item.getType()\n        }\n      }\n      .filter { it.movie != null || it.show != null }\n\n    val shows = items\n      .filter { it.show != null }\n      .map { mappers.show.fromNetwork(it.show!!) }\n    localSource.shows.upsert(shows.map { mappers.show.toDatabase(it) })\n    Timber.d(\"Shows to insert: ${shows.size}\")\n\n    val movies = items\n      .filter { it.movie != null }\n      .map { mappers.movie.fromNetwork(it.movie!!) }\n    localSource.movies.upsert(movies.map { mappers.movie.toDatabase(it) })\n    Timber.d(\"Movies to insert: ${movies.size}\")\n\n    items.forEach { remoteItem ->\n      remoteItem.show?.let { remoteShow ->\n        val show = shows.first { remoteShow.ids?.trakt == it.traktId }\n        val itemDb = CustomListItem(\n          id = 0,\n          idList = listId,\n          idTrakt = show.traktId,\n          type = Mode.SHOWS.type,\n          rank = 0,\n          listedAt = remoteItem.lastListedMillis(),\n          createdAt = nowUtcMillis,\n          updatedAt = nowUtcMillis\n        )\n        localSource.customListsItems.insertItem(itemDb)\n      }\n      remoteItem.movie?.let { remoteMovie ->\n        val movie = movies.first { remoteMovie.ids?.trakt == it.traktId }\n        localSource.movies.upsert(listOf(mappers.movie.toDatabase(movie)))\n        val itemDb = CustomListItem(\n          id = 0,\n          idList = listId,\n          idTrakt = movie.traktId,\n          type = Mode.MOVIES.type,\n          rank = 0,\n          listedAt = remoteItem.lastListedMillis(),\n          createdAt = nowUtcMillis,\n          updatedAt = nowUtcMillis\n        )\n        localSource.customListsItems.insertItem(itemDb)\n      }\n    }\n\n    if (items.isNotEmpty()) {\n      Timber.d(\"Updating list timestamp...\")\n      localSource.customLists.updateTimestamp(listId, nowUtcMillis)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/trakt/imports/TraktImportWatchedRunner.kt",
    "content": "package com.michaldrabik.ui_base.trakt.imports\n\nimport com.michaldrabik.common.extensions.toZonedDateTime\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.ArchiveMovie\nimport com.michaldrabik.data_local.database.model.ArchiveShow\nimport com.michaldrabik.data_local.database.model.Episode\nimport com.michaldrabik.data_local.database.model.MyMovie\nimport com.michaldrabik.data_local.database.model.MyShow\nimport com.michaldrabik.data_local.database.model.Season\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.data_remote.trakt.model.SyncItem\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.trakt.TraktSyncRunner\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.ImageType.FANART\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\nimport kotlinx.coroutines.delay\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass TraktImportWatchedRunner @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n  private val transactions: TransactionsProvider,\n  private val showImagesProvider: ShowImagesProvider,\n  private val movieImagesProvider: MovieImagesProvider,\n  private val settingsRepository: SettingsRepository,\n  userTraktManager: UserTraktManager,\n) : TraktSyncRunner(userTraktManager) {\n\n  override suspend fun run(): Int {\n    Timber.d(\"Initialized.\")\n\n    var syncedCount = 0\n    checkAuthorization()\n\n    resetRetries()\n    syncedCount += runShows()\n\n    resetRetries()\n    syncedCount += runMovies()\n\n    Timber.d(\"Finished with success.\")\n\n    return syncedCount\n  }\n\n  private suspend fun runShows(): Int =\n    try {\n      importWatchedShows()\n    } catch (error: Throwable) {\n      if (retryCount.getAndIncrement() < MAX_IMPORT_RETRY_COUNT) {\n        Timber.w(\"runShows HTTP failed. Will retry in $RETRY_DELAY_MS ms... $error\")\n        delay(RETRY_DELAY_MS)\n        runShows()\n      } else {\n        throw error\n      }\n    }\n\n  private suspend fun runMovies(): Int {\n    if (!settingsRepository.isMoviesEnabled) {\n      Timber.d(\"Movies are disabled. Exiting...\")\n      return 0\n    }\n    return try {\n      importWatchedMovies()\n    } catch (error: Throwable) {\n      if (retryCount.getAndIncrement() < MAX_IMPORT_RETRY_COUNT) {\n        Timber.w(\"runMovies HTTP failed. Will retry in $RETRY_DELAY_MS ms... $error\")\n        delay(RETRY_DELAY_MS)\n        runMovies()\n      } else {\n        throw error\n      }\n    }\n  }\n\n  private suspend fun importWatchedShows(): Int {\n    Timber.d(\"Importing watched shows...\")\n    val syncResults = remoteSource.trakt.fetchSyncWatchedShows(\"full\")\n      .filter { it.show != null }\n      .distinctBy { it.show?.ids?.trakt }\n\n    Timber.d(\"Importing hidden shows...\")\n    val hiddenShows = remoteSource.trakt.fetchHiddenShows()\n    hiddenShows.forEach { hiddenShow ->\n      hiddenShow.show?.let {\n        val show = mappers.show.fromNetwork(it)\n        val dbShow = mappers.show.toDatabase(show)\n        val archiveShow = ArchiveShow.fromTraktId(show.traktId, hiddenShow.hiddenAtMillis())\n        transactions.withTransaction {\n          with(localSource) {\n            shows.upsert(listOf(dbShow))\n            archiveShows.insert(archiveShow)\n            myShows.deleteById(show.traktId)\n            watchlistShows.deleteById(show.traktId)\n          }\n        }\n      }\n    }\n\n    val myShowsIds = localSource.myShows.getAllTraktIds()\n    val watchlistShowsIds = localSource.watchlistShows.getAllTraktIds()\n    val hiddenShowsIds = localSource.archiveShows.getAllTraktIds()\n    val traktSyncLogs = localSource.traktSyncLog.getAllShows()\n\n    syncResults\n      .forEachIndexed { index, result ->\n        val showUi = mappers.show.fromNetwork(result.show!!)\n        progressListener?.invoke(showUi.title, index, syncResults.size)\n\n        Timber.d(\"Processing \\'${showUi.title}\\'...\")\n\n        val log = traktSyncLogs.firstOrNull { it.idTrakt == result.show?.ids?.trakt }\n        if (result.lastUpdateMillis() == (log?.syncedAt ?: 0)) {\n          Timber.d(\"Nothing changed in \\'${result.show!!.title}\\'. Skipping...\")\n          return@forEachIndexed\n        }\n\n        try {\n          val showId = result.show!!.ids!!.trakt!!\n          val (seasons, episodes) = loadSeasons(showId, result)\n\n          transactions.withTransaction {\n            if (showId !in myShowsIds && showId !in hiddenShowsIds) {\n              val show = mappers.show.fromNetwork(result.show!!)\n              val showDb = mappers.show.toDatabase(show)\n\n              val myShow = MyShow.fromTraktId(\n                traktId = showDb.idTrakt,\n                createdAt = result.lastWatchedMillis(),\n                updatedAt = result.lastWatchedMillis(),\n                watchedAt = result.lastWatchedMillis()\n              )\n              localSource.shows.upsert(listOf(showDb))\n              localSource.myShows.insert(listOf(myShow))\n\n              loadImage(show)\n\n              if (showId in watchlistShowsIds) {\n                localSource.watchlistShows.deleteById(showId)\n              }\n            }\n            localSource.seasons.upsert(seasons)\n            localSource.episodes.upsert(episodes)\n\n            localSource.myShows.updateWatchedAt(showId, result.lastWatchedMillis())\n            localSource.traktSyncLog.upsertShow(showId, result.lastUpdateMillis())\n          }\n        } catch (error: Throwable) {\n          Timber.w(\"Processing \\'${result.show!!.title}\\' failed. Skipping...\")\n          Logger.record(error, \"TraktImportWatchedRunner::importWatchedShows()\")\n        }\n      }\n\n    return syncResults.size\n  }\n\n  private suspend fun loadSeasons(showId: Long, syncItem: SyncItem): Pair<List<Season>, List<Episode>> {\n    val remoteSeasons = remoteSource.trakt.fetchSeasons(showId)\n    val localSeasonsIds = localSource.seasons.getAllWatchedIdsForShows(listOf(showId))\n    val localEpisodesIds = localSource.episodes.getAllWatchedIdsForShows(listOf(showId))\n\n    val seasons = remoteSeasons\n      .filterNot { localSeasonsIds.contains(it.ids?.trakt) }\n      .map { mappers.season.fromNetwork(it) }\n      .map { remoteSeason ->\n        val isWatched = syncItem.seasons?.any {\n          it.number == remoteSeason.number && it.episodes?.size == remoteSeason.episodes.size\n        } ?: false\n        mappers.season.toDatabase(remoteSeason, IdTrakt(showId), isWatched)\n      }\n\n    val episodes = remoteSeasons.flatMap { season ->\n      season.episodes\n        ?.filterNot { localEpisodesIds.contains(it.ids?.trakt) }\n        ?.map { episode ->\n          val syncEpisode = syncItem.seasons\n            ?.find { it.number == season.number }?.episodes\n            ?.find { it.number == episode.number }\n\n          val isWatched = syncEpisode != null\n          val watchedAt = syncEpisode?.last_watched_at?.toZonedDateTime()\n\n          val seasonDb = mappers.season.fromNetwork(season)\n          val episodeDb = mappers.episode.fromNetwork(episode)\n          mappers.episode.toDatabase(episodeDb, seasonDb, IdTrakt(showId), isWatched, watchedAt)\n        } ?: emptyList()\n    }\n\n    return Pair(seasons, episodes)\n  }\n\n  private suspend fun importWatchedMovies(): Int {\n    Timber.d(\"Importing watched movies...\")\n\n    val syncResults = remoteSource.trakt.fetchSyncWatchedMovies(\"full\")\n      .filter { it.movie != null }\n      .distinctBy { it.movie?.ids?.trakt }\n\n    Timber.d(\"Importing hidden movies...\")\n\n    val hiddenMovies = remoteSource.trakt.fetchHiddenMovies()\n    hiddenMovies.forEach { hiddenMovie ->\n      hiddenMovie.movie?.let {\n        val movie = mappers.movie.fromNetwork(it)\n        val dbMovie = mappers.movie.toDatabase(movie)\n        val archiveMovie = ArchiveMovie.fromTraktId(movie.traktId, hiddenMovie.hiddenAtMillis())\n        transactions.withTransaction {\n          with(localSource) {\n            movies.upsert(listOf(dbMovie))\n            archiveMovies.insert(archiveMovie)\n            myMovies.deleteById(movie.traktId)\n            watchlistMovies.deleteById(movie.traktId)\n          }\n        }\n      }\n    }\n\n    val myMoviesIds = localSource.myMovies.getAllTraktIds()\n    val watchlistMoviesIds = localSource.watchlistMovies.getAllTraktIds()\n    val hiddenMoviesIds = localSource.archiveMovies.getAllTraktIds()\n\n    syncResults\n      .forEachIndexed { index, result ->\n        Timber.d(\"Processing \\'${result.movie!!.title}\\'...\")\n        val movieUi = mappers.movie.fromNetwork(result.movie!!)\n        progressListener?.invoke(movieUi.title, index, syncResults.size)\n\n        try {\n          val movieId = result.movie!!.ids!!.trakt!!\n\n          transactions.withTransaction {\n            if (movieId !in myMoviesIds && movieId !in hiddenMoviesIds) {\n              val movie = mappers.movie.fromNetwork(result.movie!!)\n              val movieDb = mappers.movie.toDatabase(movie)\n\n              val myMovie = MyMovie.fromTraktId(movieDb.idTrakt, result.lastWatchedMillis())\n              localSource.movies.upsert(listOf(movieDb))\n              localSource.myMovies.insert(listOf(myMovie))\n\n              loadImage(movie)\n\n              if (movieId in watchlistMoviesIds) {\n                localSource.watchlistMovies.deleteById(movieId)\n              }\n            }\n          }\n        } catch (error: Throwable) {\n          Timber.w(\"Processing \\'${result.movie!!.title}\\' failed. Skipping...\")\n          Logger.record(error, \"TraktImportWatchedRunner::importWatchedMovies()\")\n        }\n      }\n\n    return syncResults.size\n  }\n\n  private suspend fun loadImage(show: Show) {\n    try {\n      showImagesProvider.loadRemoteImage(show, FANART)\n    } catch (error: Throwable) {\n      Timber.w(error)\n      // Ignore image for now. It will be fetched later if needed.\n    }\n  }\n\n  private suspend fun loadImage(movie: Movie) {\n    try {\n      movieImagesProvider.loadRemoteImage(movie, FANART)\n    } catch (error: Throwable) {\n      Timber.w(error)\n      // Ignore image for now. It will be fetched later if needed.\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/trakt/imports/TraktImportWatchlistRunner.kt",
    "content": "package com.michaldrabik.ui_base.trakt.imports\n\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.WatchlistMovie\nimport com.michaldrabik.data_local.database.model.WatchlistShow\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.trakt.TraktSyncRunner\nimport kotlinx.coroutines.delay\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass TraktImportWatchlistRunner @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val mappers: Mappers,\n  private val settingsRepository: SettingsRepository,\n  userTraktManager: UserTraktManager,\n) : TraktSyncRunner(userTraktManager) {\n\n  override suspend fun run(): Int {\n    Timber.d(\"Initialized.\")\n\n    var syncedCount = 0\n    checkAuthorization()\n\n    resetRetries()\n    syncedCount += runShows()\n\n    resetRetries()\n    syncedCount += runMovies()\n\n    Timber.d(\"Finished with success.\")\n    return syncedCount\n  }\n\n  private suspend fun runShows(): Int = try {\n    importShowsWatchlist()\n  } catch (error: Throwable) {\n    if (retryCount.getAndIncrement() < MAX_IMPORT_RETRY_COUNT) {\n      Timber.w(\"runShows HTTP failed. Will retry in $RETRY_DELAY_MS ms... $error\")\n      delay(RETRY_DELAY_MS)\n      runShows()\n    } else {\n      throw error\n    }\n  }\n\n  private suspend fun runMovies(): Int {\n    if (!settingsRepository.isMoviesEnabled) {\n      Timber.d(\"Movies are disabled. Exiting...\")\n      return 0\n    }\n\n    return try {\n      importMoviesWatchlist()\n    } catch (error: Throwable) {\n      if (retryCount.getAndIncrement() < MAX_IMPORT_RETRY_COUNT) {\n        Timber.w(\"runMovies HTTP failed. Will retry in $RETRY_DELAY_MS ms... $error\")\n        delay(RETRY_DELAY_MS)\n        runMovies()\n      } else {\n        throw error\n      }\n    }\n  }\n\n  private suspend fun importShowsWatchlist(): Int {\n    Timber.d(\"Importing shows watchlist...\")\n    val syncResults = remoteSource.trakt.fetchSyncShowsWatchlist()\n      .filter { it.show != null }\n      .distinctBy { it.show!!.ids?.trakt }\n\n    val localShowsIds =\n      localSource.watchlistShows.getAllTraktIds()\n        .plus(localSource.myShows.getAllTraktIds())\n        .plus(localSource.archiveShows.getAllTraktIds())\n        .distinct()\n\n    syncResults\n      .forEachIndexed { index, result ->\n        Timber.d(\"Processing \\'${result.show!!.title}\\'...\")\n        val showUi = mappers.show.fromNetwork(result.show!!)\n        progressListener?.invoke(showUi.title, index, syncResults.size)\n        try {\n          val showId = result.show!!.ids?.trakt ?: -1\n          transactions.withTransaction {\n            if (showId !in localShowsIds) {\n              val show = mappers.show.fromNetwork(result.show!!)\n              val showDb = mappers.show.toDatabase(show)\n              localSource.shows.upsert(listOf(showDb))\n              localSource.watchlistShows.insert(WatchlistShow.fromTraktId(showId, result.lastListedMillis()))\n            }\n          }\n        } catch (error: Throwable) {\n          Timber.w(\"Processing \\'${result.show!!.title}\\' failed. Skipping...\")\n          Logger.record(error, \"TraktImportWatchlistRunner::importShowsWatchlist()\")\n        }\n      }\n\n    return syncResults.size\n  }\n\n  private suspend fun importMoviesWatchlist(): Int {\n    Timber.d(\"Importing movies watchlist...\")\n    val syncResults = remoteSource.trakt.fetchSyncMoviesWatchlist()\n      .filter { it.movie != null }\n      .distinctBy { it.movie!!.ids?.trakt }\n\n    val localMoviesIds =\n      localSource.watchlistMovies.getAllTraktIds()\n        .plus(localSource.myMovies.getAllTraktIds())\n        .plus(localSource.archiveMovies.getAllTraktIds())\n        .distinct()\n\n    syncResults\n      .forEachIndexed { index, result ->\n        Timber.d(\"Processing \\'${result.movie!!.title}\\'...\")\n        val movieUi = mappers.movie.fromNetwork(result.movie!!)\n        progressListener?.invoke(movieUi.title, index, syncResults.size)\n        try {\n          val movieId = result.movie!!.ids?.trakt ?: -1\n          transactions.withTransaction {\n            if (movieId !in localMoviesIds) {\n              val movie = mappers.movie.fromNetwork(result.movie!!)\n              val movieDb = mappers.movie.toDatabase(movie)\n              localSource.movies.upsert(listOf(movieDb))\n              localSource.watchlistMovies.insert(WatchlistMovie.fromTraktId(movieId, result.lastListedMillis()))\n            }\n          }\n        } catch (error: Throwable) {\n          Timber.w(\"Processing \\'${result.movie!!.title}\\' failed. Skipping...\")\n          Logger.record(error, \"TraktImportWatchlistRunner::importMoviesWatchlist()\")\n        }\n      }\n\n    return syncResults.size\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/trakt/quicksync/QuickSyncManager.kt",
    "content": "package com.michaldrabik.ui_base.trakt.quicksync\n\nimport androidx.work.WorkManager\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue.Operation\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue.Type\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass QuickSyncManager @Inject constructor(\n  private val userTraktManager: UserTraktManager,\n  private val settingsRepository: SettingsRepository,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val workManager: WorkManager\n) {\n\n  suspend fun scheduleEpisodes(\n    episodesIds: List<Long>,\n    showId: Long,\n    clearProgress: Boolean = false\n  ) {\n    if (!ensureQuickSync() && !(clearProgress && ensureQuickRemove())) {\n      return\n    }\n\n    val time = nowUtcMillis()\n    val items = episodesIds.map { TraktSyncQueue.createEpisode(it, showId, time, time, clearProgress) }\n    localSource.traktSyncQueue.insert(items)\n    Timber.d(\"Episodes added into sync queue. Count: ${items.size}\")\n\n    QuickSyncWorker.schedule(workManager)\n  }\n\n  suspend fun scheduleMovies(moviesIds: List<Long>) {\n    if (!ensureQuickSync()) return\n\n    val time = nowUtcMillis()\n    val items = moviesIds.map { TraktSyncQueue.createMovie(it, time, time) }\n    localSource.traktSyncQueue.insert(items)\n    Timber.d(\"Movies added into sync queue. Count: ${items.size}\")\n\n    QuickSyncWorker.schedule(workManager)\n  }\n\n  suspend fun scheduleShowsWatchlist(showsIds: List<Long>) {\n    if (!ensureQuickSync()) return\n\n    val time = nowUtcMillis()\n    val items = showsIds.map { TraktSyncQueue.createShowWatchlist(it, time, time) }\n    localSource.traktSyncQueue.insert(items)\n    Timber.d(\"Shows added into sync queue. Count: ${items.size}\")\n\n    QuickSyncWorker.schedule(workManager)\n  }\n\n  suspend fun scheduleMoviesWatchlist(moviesIds: List<Long>) {\n    if (!ensureQuickSync()) return\n\n    val time = nowUtcMillis()\n    val items = moviesIds.map { TraktSyncQueue.createMovieWatchlist(it, time, time) }\n    localSource.traktSyncQueue.insert(items)\n    Timber.d(\"Movies added into sync queue. Count: ${items.size}\")\n\n    QuickSyncWorker.schedule(workManager)\n  }\n\n  suspend fun scheduleAddToList(idTrakt: Long, idList: Long, type: Mode) {\n    if (!ensureQuickSync()) return\n\n    val time = nowUtcMillis()\n    val item = when (type) {\n      Mode.SHOWS -> TraktSyncQueue.createListShow(idTrakt, idList, Operation.ADD, time, time)\n      Mode.MOVIES -> TraktSyncQueue.createListMovie(idTrakt, idList, Operation.ADD, time, time)\n    }\n\n    val itemType = when (type) {\n      Mode.SHOWS -> Type.LIST_ITEM_SHOW\n      Mode.MOVIES -> Type.LIST_ITEM_MOVIE\n    }\n\n    transactions.withTransaction {\n      localSource.traktSyncQueue.delete(idTrakt, idList, itemType.slug, Operation.ADD.slug)\n      val count = localSource.traktSyncQueue.delete(idTrakt, idList, itemType.slug, Operation.REMOVE.slug)\n      if (count == 0) {\n        localSource.traktSyncQueue.insert(listOf(item))\n        Timber.d(\"Added ${type.type} list item into add to list queue.\")\n      }\n    }\n\n    QuickSyncWorker.schedule(workManager)\n  }\n\n  suspend fun scheduleRemoveFromList(idTrakt: Long, idList: Long, type: Mode) {\n    if (!ensureQuickRemove()) return\n\n    val time = nowUtcMillis()\n    val item = when (type) {\n      Mode.SHOWS -> TraktSyncQueue.createListShow(idTrakt, idList, Operation.REMOVE, time, time)\n      Mode.MOVIES -> TraktSyncQueue.createListMovie(idTrakt, idList, Operation.REMOVE, time, time)\n    }\n\n    val itemType = when (type) {\n      Mode.SHOWS -> Type.LIST_ITEM_SHOW\n      Mode.MOVIES -> Type.LIST_ITEM_MOVIE\n    }\n\n    transactions.withTransaction {\n      localSource.traktSyncQueue.delete(idTrakt, idList, itemType.slug, Operation.REMOVE.slug)\n      val count = localSource.traktSyncQueue.delete(idTrakt, idList, itemType.slug, Operation.ADD.slug)\n      if (count == 0 && ensureQuickRemove()) {\n        localSource.traktSyncQueue.insert(listOf(item))\n        Timber.d(\"Added ${type.type} list item into remove from list queue.\")\n      }\n    }\n\n    QuickSyncWorker.schedule(workManager)\n  }\n\n  suspend fun scheduleHidden(idTrakt: Long, type: Mode, operation: Operation) {\n    if (!ensureQuickSync()) return\n\n    val time = nowUtcMillis()\n    val item = when (type) {\n      Mode.SHOWS -> TraktSyncQueue.createHiddenShow(idTrakt, operation, time, time)\n      Mode.MOVIES -> TraktSyncQueue.createHiddenMovie(idTrakt, operation, time, time)\n    }\n\n    localSource.traktSyncQueue.insert(listOf(item))\n\n    when (type) {\n      Mode.SHOWS -> Timber.d(\"Hidden show added into sync queue. #$idTrakt\")\n      Mode.MOVIES -> Timber.d(\"Hidden movie added into sync queue. #$idTrakt\")\n    }\n\n    QuickSyncWorker.schedule(workManager)\n  }\n\n  suspend fun clearEpisodes(episodesIds: List<Long>) {\n    if (!ensureQuickRemove()) return\n\n    val count = localSource.traktSyncQueue.deleteAll(episodesIds, Type.EPISODE.slug)\n    Timber.d(\"Episodes removed from sync queue. Count: $count\")\n  }\n\n  suspend fun clearEpisodes() {\n    if (!ensureQuickRemove()) return\n\n    localSource.traktSyncQueue.deleteAll(Type.EPISODE.slug)\n    Timber.d(\"Episodes removed from sync queue.\")\n  }\n\n  suspend fun clearMovies(moviesIds: List<Long>) {\n    if (!ensureQuickRemove()) return\n\n    localSource.traktSyncQueue.deleteAll(moviesIds, Type.MOVIE.slug)\n    Timber.d(\"Movies removed from sync queue. Count: ${moviesIds.size}\")\n  }\n\n  suspend fun clearWatchlistShows(showsIds: List<Long>) {\n    if (!ensureQuickRemove()) return\n\n    localSource.traktSyncQueue.deleteAll(showsIds, Type.SHOW_WATCHLIST.slug)\n    Timber.d(\"Shows removed from sync queue. Count: ${showsIds.size}\")\n  }\n\n  suspend fun clearWatchlistMovies(moviesIds: List<Long>) {\n    if (!ensureQuickRemove()) return\n\n    localSource.traktSyncQueue.deleteAll(moviesIds, Type.MOVIE_WATCHLIST.slug)\n    Timber.d(\"Movies removed from sync queue. Count: ${moviesIds.size}\")\n  }\n\n  suspend fun clearHiddenShows(ids: List<Long>) {\n    if (!ensureQuickRemove()) return\n\n    localSource.traktSyncQueue.deleteAll(ids, Type.HIDDEN_SHOW.slug)\n    Timber.d(\"Hidden shows removed from sync queue. Count: ${ids.size}\")\n  }\n\n  suspend fun clearHiddenMovies(ids: List<Long>) {\n    if (!ensureQuickRemove()) return\n\n    localSource.traktSyncQueue.deleteAll(ids, Type.HIDDEN_MOVIE.slug)\n    Timber.d(\"Hidden shows removed from sync queue. Count: ${ids.size}\")\n  }\n\n  suspend fun isAnyScheduled(): Boolean {\n    if (!ensureAuthorized()) return false\n\n    return localSource.traktSyncQueue.getAll().isNotEmpty()\n  }\n\n  private suspend fun ensureQuickSync(): Boolean {\n    if (!ensureAuthorized()) return false\n\n    val settings = settingsRepository.load()\n    if (!settings.traktQuickSyncEnabled) {\n      Timber.d(\"Quick Sync is disabled. Skipping...\")\n      return false\n    }\n    return true\n  }\n\n  private suspend fun ensureQuickRemove(): Boolean {\n    if (!ensureAuthorized()) return false\n\n    val settings = settingsRepository.load()\n    if (!settings.traktQuickRemoveEnabled) {\n      Timber.d(\"Quick Remove is disabled. Skipping...\")\n      return false\n    }\n    return true\n  }\n\n  private fun ensureAuthorized(): Boolean {\n    if (!userTraktManager.isAuthorized()) {\n      Timber.d(\"User not logged into Trakt. Skipping...\")\n      return false\n    }\n    return true\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/trakt/quicksync/QuickSyncWorker.kt",
    "content": "package com.michaldrabik.ui_base.trakt.quicksync\n\nimport android.content.Context\nimport androidx.hilt.work.HiltWorker\nimport androidx.work.Constraints\nimport androidx.work.ExistingWorkPolicy.REPLACE\nimport androidx.work.NetworkType\nimport androidx.work.OneTimeWorkRequestBuilder\nimport androidx.work.WorkManager\nimport androidx.work.WorkerParameters\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError.AccountLimitsError\nimport com.michaldrabik.common.errors.ShowlyError.UnauthorizedError\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.Analytics\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.TraktQuickSyncSuccess\nimport com.michaldrabik.ui_base.events.TraktSyncAuthError\nimport com.michaldrabik.ui_base.trakt.TraktNotificationWorker\nimport com.michaldrabik.ui_base.trakt.quicksync.runners.QuickSyncListsRunner\nimport com.michaldrabik.ui_base.trakt.quicksync.runners.QuickSyncRunner\nimport com.michaldrabik.ui_base.utilities.extensions.notificationManager\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedInject\nimport timber.log.Timber\nimport java.util.concurrent.TimeUnit.SECONDS\n\n@HiltWorker\nclass QuickSyncWorker @AssistedInject constructor(\n  @Assisted context: Context,\n  @Assisted workerParams: WorkerParameters,\n  private val quickSyncRunner: QuickSyncRunner,\n  private val quickSyncListsRunner: QuickSyncListsRunner,\n  private val settingsRepository: SettingsRepository,\n  private val userManager: UserTraktManager,\n  private val eventsManager: EventsManager,\n) : TraktNotificationWorker(context, workerParams) {\n\n  companion object {\n    private const val TAG = \"TRAKT_QUICK_SYNC_WORK\"\n    private const val SYNC_NOTIFICATION_PROGRESS_ID = 916\n    private const val SYNC_NOTIFICATION_ERROR_ID = 917\n\n    fun schedule(workManager: WorkManager) {\n      val request = OneTimeWorkRequestBuilder<QuickSyncWorker>()\n        .setConstraints(\n          Constraints.Builder()\n            .setRequiredNetworkType(NetworkType.CONNECTED)\n            .build()\n        )\n        .setInitialDelay(3, SECONDS)\n        .addTag(TAG)\n        .build()\n\n      workManager.enqueueUniqueWork(TAG, REPLACE, request)\n      Timber.i(\"Trakt QuickSync scheduled.\")\n    }\n  }\n\n  override suspend fun doWork(): Result {\n    Timber.d(\"Initialized.\")\n    val theme = settingsRepository.theme\n\n    notificationManager().notify(\n      SYNC_NOTIFICATION_PROGRESS_ID,\n      createProgressNotification(theme, null, 0, 0, true)\n    )\n\n    try {\n      var count = quickSyncRunner.run()\n      count += quickSyncListsRunner.run()\n      if (count > 0) {\n        eventsManager.sendEvent(TraktQuickSyncSuccess(count))\n        Analytics.logTraktQuickSyncSuccess(count)\n      }\n    } catch (error: Throwable) {\n      handleError(error)\n    } finally {\n      clearRunners()\n      notificationManager().cancel(SYNC_NOTIFICATION_PROGRESS_ID)\n      Timber.d(\"Quick Sync completed.\")\n    }\n\n    return Result.success()\n  }\n\n  private suspend fun handleError(error: Throwable) {\n    val showlyError = ErrorHelper.parse(error)\n    if (showlyError is UnauthorizedError) {\n      eventsManager.sendEvent(TraktSyncAuthError)\n      userManager.revokeToken()\n    }\n    val notificationMessage = when (showlyError) {\n      is AccountLimitsError -> R.string.errorAccountListsLimitsReached\n      is UnauthorizedError -> R.string.errorTraktAuthorization\n      else -> R.string.textTraktSyncErrorFull\n    }\n    val theme = settingsRepository.theme\n    applicationContext.notificationManager().notify(\n      SYNC_NOTIFICATION_ERROR_ID,\n      createErrorNotification(theme, R.string.textTraktQuickSyncError, notificationMessage)\n    )\n    Logger.record(error, \"QuickSyncWorker::handleError()\")\n  }\n\n  private fun clearRunners() {\n    arrayOf(\n      quickSyncRunner,\n      quickSyncListsRunner,\n    ).forEach {\n      it.progressListener = null\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/trakt/quicksync/runners/QuickSyncListsRunner.kt",
    "content": "package com.michaldrabik.ui_base.trakt.quicksync.runners\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue.Operation\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue.Type\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.ListsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_base.trakt.TraktSyncRunner\nimport com.michaldrabik.ui_model.CustomList\nimport kotlinx.coroutines.delay\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass QuickSyncListsRunner @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n  private val listsRepository: ListsRepository,\n  userTraktManager: UserTraktManager\n) : TraktSyncRunner(userTraktManager) {\n\n  companion object {\n    private const val TRAKT_DELAY = 1200L\n  }\n\n  private val syncTypes = listOf(\n    Type.LIST_ITEM_SHOW,\n    Type.LIST_ITEM_MOVIE\n  ).map { it.slug }\n\n  override suspend fun run(): Int {\n    Timber.d(\"Initialized.\")\n\n    var count = 0\n    checkAuthorization()\n\n    val items = localSource.traktSyncQueue.getAll(syncTypes)\n      .groupBy { it.idList }\n      .filter { it.key != null }\n\n    if (items.isEmpty()) {\n      Timber.d(\"Nothing to sync. Cancelling..\")\n      return 0\n    }\n\n    count += processItems(items, count)\n\n    Timber.d(\"Finished with success.\")\n    return count\n  }\n\n  private suspend fun processItems(\n    items: Map<Long?, List<TraktSyncQueue>>,\n    count: Int\n  ): Int {\n    var counted = count\n\n    for (syncListItem in items) {\n      val listId = syncListItem.key!!\n      var localList = localSource.customLists.getById(listId)?.run {\n        mappers.customList.fromDatabase(this)\n      }\n      if (localList == null) {\n        localSource.traktSyncQueue.deleteAllForList(listId)\n        Timber.d(\"List with ID: $listId does not exist anymore. Skipping...\")\n        continue\n      }\n\n      val addItems = syncListItem.value.filter { it.operation == Operation.ADD.slug }\n      val removeItems = syncListItem.value.filter { it.operation == Operation.REMOVE.slug }\n\n      if (localList.idTrakt == null && addItems.isNotEmpty()) {\n        Timber.d(\"List with ID: $listId does not exist in Trakt. Creating...\")\n        localList = createMissingList(localList, addItems)\n      } else if (localList.idTrakt == null) {\n        Timber.d(\"List with ID: $listId does not exist in Trakt. No need to remove items...\")\n        localSource.traktSyncQueue.delete(removeItems)\n        continue\n      }\n\n      // Handle remove items operation\n      handleRemoveItems(removeItems, localList)\n\n      // Handle add items operation\n      handleAddItems(addItems, localList)\n\n      counted++\n      delay(TRAKT_DELAY)\n    }\n\n    // Check in case more items appeared in the meantime.\n    val itemsCheck = localSource.traktSyncQueue.getAll(syncTypes)\n      .groupBy { it.idList }\n      .filter { it.key != null }\n\n    if (itemsCheck.isNotEmpty()) {\n      return processItems(itemsCheck, counted)\n    }\n\n    return counted\n  }\n\n  private suspend fun handleRemoveItems(\n    removeItems: List<TraktSyncQueue>,\n    list: CustomList\n  ) {\n    try {\n      val showIds = removeItems\n        .filter { it.type == Type.LIST_ITEM_SHOW.slug }\n        .map { it.idTrakt }\n\n      val movieIds = removeItems\n        .filter { it.type == Type.LIST_ITEM_MOVIE.slug }\n        .map { it.idTrakt }\n\n      if (showIds.isNotEmpty() || movieIds.isNotEmpty()) {\n        remoteSource.trakt.postRemoveListItems(list.idTrakt!!, showIds, movieIds)\n        localSource.traktSyncQueue.delete(removeItems)\n      }\n    } catch (error: Throwable) {\n      when (ErrorHelper.parse(error)) {\n        is ShowlyError.ResourceNotFoundError -> {\n          localSource.traktSyncQueue.delete(removeItems)\n          Timber.d(\"Tried to remove from list but it does not exist anymore. Skipping...\")\n        }\n        else -> throw error\n      }\n    }\n  }\n\n  private suspend fun handleAddItems(\n    addItems: List<TraktSyncQueue>,\n    localList: CustomList\n  ) {\n    val showIds = addItems\n      .filter { it.type == Type.LIST_ITEM_SHOW.slug }\n      .map { it.idTrakt }\n\n    val movieIds = addItems\n      .filter { it.type == Type.LIST_ITEM_MOVIE.slug }\n      .map { it.idTrakt }\n\n    try {\n      if (showIds.isNotEmpty() || movieIds.isNotEmpty()) {\n        remoteSource.trakt.postAddListItems(localList.idTrakt!!, showIds, movieIds)\n        localSource.traktSyncQueue.delete(addItems)\n      }\n    } catch (error: Throwable) {\n      when (ErrorHelper.parse(error)) {\n        is ShowlyError.AccountLimitsError -> {\n          Timber.d(\"Account limits for lists reached.\")\n          localSource.traktSyncQueue.delete(addItems)\n          throw error\n        }\n        is ShowlyError.ResourceNotFoundError -> {\n          Timber.d(\"Tried to add to list but it does not exist. Creating...\")\n          delay(TRAKT_DELAY)\n          createMissingList(localList, addItems)\n          localSource.traktSyncQueue.delete(addItems)\n        }\n        else -> throw error\n      }\n    }\n  }\n\n  private suspend fun createMissingList(\n    localList: CustomList,\n    addItems: List<TraktSyncQueue>\n  ): CustomList {\n    try {\n      val result = remoteSource.trakt.postCreateList(localList.name, localList.description)\n        .run { mappers.customList.fromNetwork(this) }\n\n      listsRepository.updateList(localList.id, result.idTrakt, result.idSlug, result.name, result.description)\n\n      val localItems = listsRepository.loadListItemsForId(localList.id)\n      if (localItems.isNotEmpty()) {\n        val showsIds = localItems.filter { it.type == Mode.SHOWS.type }.map { it.idTrakt }\n        val moviesIds = localItems.filter { it.type == Mode.MOVIES.type }.map { it.idTrakt }\n        delay(TRAKT_DELAY)\n        remoteSource.trakt.postAddListItems(result.idTrakt!!, showsIds, moviesIds)\n      }\n\n      return listsRepository.updateList(localList.id, result.idTrakt, result.idSlug, result.name, result.description)\n    } catch (error: Throwable) {\n      when (ErrorHelper.parse(error)) {\n        ShowlyError.AccountLimitsError -> {\n          localSource.traktSyncQueue.delete(addItems)\n          throw error\n        }\n        else -> throw error\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/trakt/quicksync/runners/QuickSyncRunner.kt",
    "content": "package com.michaldrabik.ui_base.trakt.quicksync.runners\n\nimport com.michaldrabik.common.extensions.dateIsoStringFromMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue.Type.EPISODE\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue.Type.HIDDEN_MOVIE\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue.Type.HIDDEN_SHOW\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue.Type.MOVIE\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue.Type.MOVIE_WATCHLIST\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue.Type.SHOW_WATCHLIST\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.data_remote.trakt.model.SyncExportItem\nimport com.michaldrabik.data_remote.trakt.model.SyncExportRequest\nimport com.michaldrabik.data_remote.trakt.model.SyncItem\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.Analytics\nimport com.michaldrabik.ui_base.trakt.TraktSyncRunner\nimport com.michaldrabik.ui_base.trakt.quicksync.runners.cases.QuickSyncDuplicateEpisodesCase\nimport com.michaldrabik.ui_base.trakt.quicksync.runners.cases.QuickSyncDuplicateMoviesCase\nimport kotlinx.coroutines.delay\nimport timber.log.Timber\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass QuickSyncRunner @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val settingsRepository: SettingsRepository,\n  private val duplicateEpisodesCase: QuickSyncDuplicateEpisodesCase,\n  private val duplicateMoviesCase: QuickSyncDuplicateMoviesCase,\n  userTraktManager: UserTraktManager,\n) : TraktSyncRunner(userTraktManager) {\n\n  companion object {\n    private const val BATCH_LIMIT = 100\n    private const val DELAY = 2000L\n  }\n\n  private val syncTypes = listOf(\n    MOVIE,\n    EPISODE,\n    MOVIE_WATCHLIST,\n    SHOW_WATCHLIST,\n    HIDDEN_SHOW,\n    HIDDEN_MOVIE\n  ).map { it.slug }\n\n  override suspend fun run(): Int {\n    Timber.d(\"Initialized.\")\n    try {\n      checkAuthorization()\n      val moviesEnabled = settingsRepository.isMoviesEnabled\n\n      val historyCount = exportHistoryItems(moviesEnabled)\n      val watchlistCount = exportWatchlistItems(moviesEnabled)\n      val hiddenCount = exportHiddenItems(moviesEnabled)\n\n      Timber.d(\"Finished with success.\")\n      return historyCount + watchlistCount + hiddenCount\n    } catch (error: Throwable) {\n      throw error\n    } finally {\n      syncTypes.forEach {\n        localSource.traktSyncQueue.deleteAll(it)\n      }\n    }\n  }\n\n  private suspend fun exportHistoryItems(\n    moviesEnabled: Boolean,\n    remoteFetchedShows: List<SyncItem> = emptyList(),\n    remoteFetchedMovies: List<SyncItem> = emptyList(),\n    count: Int = 0,\n    clearedProgressIds: MutableSet<Long> = mutableSetOf(),\n  ): Int {\n    val types = if (moviesEnabled) listOf(MOVIE, EPISODE) else listOf(EPISODE)\n    val items = localSource.traktSyncQueue.getAll(types.map { it.slug })\n    if (items.isEmpty()) {\n      Timber.d(\"Nothing to export. Cancelling..\")\n      return count\n    }\n\n    Timber.d(\"Exporting ${items.size} items...\")\n\n    val batch = items.take(BATCH_LIMIT)\n    val exportEpisodes = batch.filter { it.type == EPISODE.slug }.distinctBy { it.idTrakt }\n    val exportMovies = batch.filter { it.type == MOVIE.slug }.distinctBy { it.idTrakt }\n    val clearProgress = items.any { it.operation == TraktSyncQueue.Operation.ADD_WITH_CLEAR.slug }\n\n    if (clearProgress) {\n      Timber.d(\"Clearing progress for shows...\")\n\n      val requestItems = items\n        .mapNotNull { it.idList?.let { id -> SyncExportItem.create(id) } }\n        .distinctBy { it.ids.trakt }\n        .filterNot { clearedProgressIds.contains(it.ids.trakt) }\n\n      if (requestItems.isNotEmpty()) {\n        remoteSource.trakt.postDeleteProgress(SyncExportRequest(shows = requestItems))\n        clearedProgressIds.addAll(requestItems.map { it.ids.trakt })\n        delay(DELAY)\n      }\n    }\n\n    transactions.withTransaction {\n      val batchIds = batch.map { it.idTrakt }\n      with(localSource.traktSyncQueue) {\n        deleteAll(batchIds, EPISODE.slug)\n        deleteAll(batchIds, MOVIE.slug)\n      }\n    }\n\n    val (duplicateEpisodes, remoteShows) =\n      duplicateEpisodesCase.checkDuplicateEpisodes(exportEpisodes, remoteFetchedShows)\n    val (duplicateMovies, remoteMovies) =\n      duplicateMoviesCase.checkDuplicateMovies(exportMovies, remoteFetchedMovies)\n\n    val request = SyncExportRequest(\n      episodes = exportEpisodes\n        .map { SyncExportItem.create(it.idTrakt, dateIsoStringFromMillis(it.updatedAt)) }\n        .filter { it.ids.trakt !in duplicateEpisodes },\n      movies = exportMovies\n        .map { SyncExportItem.create(it.idTrakt, dateIsoStringFromMillis(it.updatedAt)) }\n        .filter { it.ids.trakt !in duplicateMovies },\n    )\n\n    if (request.episodes.isNotEmpty() || request.movies.isNotEmpty()) {\n      Analytics.logQuickExportHistory(request.episodes.size, request.movies.size, retryCount.get())\n      remoteSource.trakt.postSyncWatched(request)\n    }\n\n    val currentCount = count + exportEpisodes.count() + exportMovies.count()\n\n    // Check for more items\n    val newItems = localSource.traktSyncQueue.getAll(types.map { it.slug })\n    if (newItems.isNotEmpty()) {\n      delay(DELAY)\n      return exportHistoryItems(\n        moviesEnabled = moviesEnabled,\n        remoteFetchedShows = remoteShows,\n        remoteFetchedMovies = remoteMovies,\n        count = currentCount,\n        clearedProgressIds = clearedProgressIds.toMutableSet()\n      )\n    }\n\n    return currentCount\n  }\n\n  private suspend fun exportWatchlistItems(\n    moviesEnabled: Boolean,\n    count: Int = 0,\n  ): Int {\n    val types = if (moviesEnabled) listOf(MOVIE_WATCHLIST, SHOW_WATCHLIST) else listOf(SHOW_WATCHLIST)\n    val items = localSource.traktSyncQueue.getAll(types.map { it.slug }).take(BATCH_LIMIT)\n    if (items.isEmpty()) {\n      Timber.d(\"Nothing to export. Cancelling..\")\n      return count\n    }\n\n    Timber.d(\"Exporting watchlist items...\")\n\n    val exportShows = items.filter { it.type == SHOW_WATCHLIST.slug }.distinctBy { it.idTrakt }\n    val exportMovies = items.filter { it.type == MOVIE_WATCHLIST.slug }.distinctBy { it.idTrakt }\n\n    val request = SyncExportRequest(\n      shows = exportShows.map { SyncExportItem.create(it.idTrakt, dateIsoStringFromMillis(it.updatedAt)) },\n      movies = exportMovies.map { SyncExportItem.create(it.idTrakt, dateIsoStringFromMillis(it.updatedAt)) }\n    )\n\n    transactions.withTransaction {\n      val ids = items.map { it.idTrakt }\n      localSource.traktSyncQueue.deleteAll(ids, MOVIE_WATCHLIST.slug)\n      localSource.traktSyncQueue.deleteAll(ids, SHOW_WATCHLIST.slug)\n    }\n    remoteSource.trakt.postSyncWatchlist(request)\n\n    val currentCount = count + exportShows.count() + exportMovies.count()\n\n    // Check for more items\n    val newItems = localSource.traktSyncQueue.getAll(types.map { it.slug })\n    if (newItems.isNotEmpty()) {\n      delay(DELAY)\n      return exportWatchlistItems(moviesEnabled, currentCount)\n    }\n\n    return currentCount\n  }\n\n  private suspend fun exportHiddenItems(\n    moviesEnabled: Boolean,\n    count: Int = 0,\n  ): Int {\n    val types = if (moviesEnabled) listOf(HIDDEN_SHOW, HIDDEN_MOVIE) else listOf(HIDDEN_SHOW)\n    val items = localSource.traktSyncQueue.getAll(types.map { it.slug }).take(BATCH_LIMIT)\n    if (items.isEmpty()) {\n      Timber.d(\"Nothing to export. Cancelling..\")\n      return count\n    }\n\n    Timber.d(\"Exporting hidden items...\")\n\n    val exportShows = items.filter { it.type == HIDDEN_SHOW.slug }.distinctBy { it.idTrakt }\n    val exportMovies = items.filter { it.type == HIDDEN_MOVIE.slug }.distinctBy { it.idTrakt }\n\n    transactions.withTransaction {\n      val ids = items.map { it.idTrakt }\n      with(localSource.traktSyncQueue) {\n        deleteAll(ids, HIDDEN_SHOW.slug)\n        deleteAll(ids, HIDDEN_MOVIE.slug)\n      }\n    }\n\n    if (exportShows.isNotEmpty()) {\n      remoteSource.trakt.postHiddenShows(\n        shows = exportShows.map { SyncExportItem.create(it.idTrakt, hiddenAt = dateIsoStringFromMillis(it.updatedAt)) }\n      )\n      delay(1500)\n    }\n\n    if (exportMovies.isNotEmpty()) {\n      remoteSource.trakt.postHiddenMovies(\n        movies = exportMovies.map { SyncExportItem.create(it.idTrakt, hiddenAt = dateIsoStringFromMillis(it.updatedAt)) }\n      )\n    }\n\n    val currentCount = count + exportShows.count() + exportMovies.count()\n\n    // Check for more items\n    val newItems = localSource.traktSyncQueue.getAll(types.map { it.slug })\n    if (newItems.isNotEmpty()) {\n      delay(DELAY)\n      return exportHiddenItems(moviesEnabled, currentCount)\n    }\n\n    return currentCount\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/trakt/quicksync/runners/cases/QuickSyncDuplicateEpisodesCase.kt",
    "content": "package com.michaldrabik.ui_base.trakt.quicksync.runners.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.data_remote.trakt.model.SyncItem\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport javax.inject.Inject\n\nclass QuickSyncDuplicateEpisodesCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource\n) {\n\n  suspend fun checkDuplicateEpisodes(\n    episodes: List<TraktSyncQueue>,\n    fetchedSyncItems: List<SyncItem>\n  ): Result {\n    if (episodes.isEmpty()) {\n      return Result(emptyList(), fetchedSyncItems)\n    }\n    return withContext(dispatchers.IO) {\n      val remoteShows = if (fetchedSyncItems.isNotEmpty()) {\n        fetchedSyncItems.toList()\n      } else {\n        remoteSource.trakt.fetchSyncWatchedShows()\n      }\n      val duplicateEpisodesIds = mutableListOf<Long>()\n\n      val localEpisodes = localSource.episodes.getAllByShowsIds(\n        episodes.filter { it.idList != null }.map { it.idList!! }\n      )\n\n      episodes.forEach { item ->\n        val showId = item.idList\n        showId?.let {\n          val localEpisode = localEpisodes.find { it.idTrakt == item.idTrakt }\n          if (localEpisode == null) {\n            duplicateEpisodesIds.add(item.idTrakt)\n          } else {\n            if (remoteShows\n              .filter { it.show?.ids?.trakt == showId }\n              .any { remoteShow ->\n                remoteShow.seasons\n                  ?.find { it.number == localEpisode.seasonNumber }\n                  ?.episodes\n                  ?.any { it.number == localEpisode.episodeNumber } == true\n              }\n            ) {\n              duplicateEpisodesIds.add(item.idTrakt)\n            }\n          }\n        }\n      }\n\n      Timber.d(\"Duplicated episodes count: ${duplicateEpisodesIds.size}\")\n\n      Result(\n        duplicateEpisodesIds = duplicateEpisodesIds,\n        remoteShows = remoteShows\n      )\n    }\n  }\n\n  data class Result(\n    val duplicateEpisodesIds: List<Long>,\n    val remoteShows: List<SyncItem>\n  )\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/trakt/quicksync/runners/cases/QuickSyncDuplicateMoviesCase.kt",
    "content": "package com.michaldrabik.ui_base.trakt.quicksync.runners.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.data_remote.trakt.model.SyncItem\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport javax.inject.Inject\n\nclass QuickSyncDuplicateMoviesCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val remoteSource: RemoteDataSource\n) {\n\n  suspend fun checkDuplicateMovies(\n    exportMovies: List<TraktSyncQueue>,\n    fetchedSyncItems: List<SyncItem>\n  ): Result {\n    if (exportMovies.isEmpty()) {\n      return Result(emptyList(), fetchedSyncItems)\n    }\n    return withContext(dispatchers.IO) {\n      val remoteMovies = if (fetchedSyncItems.isNotEmpty()) {\n        fetchedSyncItems.toList()\n      } else {\n        remoteSource.trakt.fetchSyncWatchedMovies()\n      }\n      val duplicateMoviesIds = mutableListOf<Long>()\n\n      exportMovies.forEach { movie ->\n        remoteMovies\n          .find { it.getTraktId() == movie.idTrakt }\n          ?.let {\n            duplicateMoviesIds.add(movie.idTrakt)\n          }\n      }\n\n      Timber.d(\"Duplicated movies count: ${duplicateMoviesIds.size}\")\n\n      Result(\n        duplicateMoviesIds = duplicateMoviesIds,\n        remoteMovies = remoteMovies\n      )\n    }\n  }\n\n  data class Result(\n    val duplicateMoviesIds: List<Long>,\n    val remoteMovies: List<SyncItem>\n  )\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/DurationPrinter.kt",
    "content": "package com.michaldrabik.ui_base.utilities\n\nimport android.content.Context\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.ui_base.R\nimport java.time.Duration\nimport java.time.ZonedDateTime\n\nclass DurationPrinter(private val context: Context) {\n\n  fun print(date: ZonedDateTime?): String {\n    if (date == null) return context.getString(R.string.textTba)\n\n    val duration = Duration.between(nowUtc(), date)\n    if (duration.isNegative) return context.getString(R.string.textAiredAlready)\n\n    val days = duration.toDays().toInt()\n    if (days == 0) {\n      val hours = duration.toHours().toInt()\n      if (hours == 0) {\n        val minutes = duration.toMinutes().toInt()\n        if (minutes == 0) {\n          return context.getString(R.string.textAirsNow)\n        }\n        return context.resources.getQuantityString(R.plurals.textMinutesToAir, minutes, minutes)\n      }\n      return context.resources.getQuantityString(R.plurals.textHoursToAir, hours + 1, hours + 1)\n    }\n    return context.resources.getQuantityString(R.plurals.textDaysToAir, days + 1, days + 1)\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/FragmentViewBindingDelegate.kt",
    "content": "/*\n * Copyright 2021 Gabor Varadi\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.michaldrabik.ui_base.utilities\n\nimport android.view.View\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.Observer\nimport androidx.viewbinding.ViewBinding\nimport kotlin.properties.ReadOnlyProperty\nimport kotlin.reflect.KProperty\n\nclass FragmentViewBindingDelegate<T : ViewBinding>(\n  val fragment: Fragment,\n  val viewBindingFactory: (View) -> T,\n) : ReadOnlyProperty<Fragment, T> {\n  private var binding: T? = null\n\n  init {\n    fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {\n      val viewLifecycleOwnerObserver = Observer<LifecycleOwner?> { owner ->\n        if (owner == null) {\n          binding = null\n        }\n      }\n\n      override fun onCreate(owner: LifecycleOwner) {\n        fragment.viewLifecycleOwnerLiveData.observeForever(viewLifecycleOwnerObserver)\n      }\n\n      override fun onDestroy(owner: LifecycleOwner) {\n        fragment.viewLifecycleOwnerLiveData.removeObserver(viewLifecycleOwnerObserver)\n      }\n    })\n  }\n\n  override fun getValue(thisRef: Fragment, property: KProperty<*>): T {\n    val binding = binding\n\n    if (binding != null && binding.root === thisRef.view) {\n      return binding\n    }\n\n    val view = thisRef.view\n\n    @Suppress(\"FoldInitializerAndIfToElvis\")\n    if (view == null) {\n      throw IllegalStateException(\"Should not attempt to get bindings when the Fragment's view is null.\")\n    }\n\n    return viewBindingFactory(view).also { this.binding = it }\n  }\n}\n\nfun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) =\n  FragmentViewBindingDelegate(this, viewBindingFactory)\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/ModeHost.kt",
    "content": "package com.michaldrabik.ui_base.utilities\n\nimport com.michaldrabik.common.Mode\n\ninterface ModeHost {\n  fun setMode(mode: Mode, force: Boolean = false)\n  fun getMode(): Mode\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/MoviesStatusHost.kt",
    "content": "package com.michaldrabik.ui_base.utilities\n\ninterface MoviesStatusHost {\n  fun hasMoviesEnabled(): Boolean\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/NavigationHost.kt",
    "content": "package com.michaldrabik.ui_base.utilities\n\nimport androidx.navigation.NavController\n\ninterface NavigationHost {\n  fun findNavControl(): NavController?\n\n  fun hideNavigation(animate: Boolean)\n  fun showNavigation(animate: Boolean)\n\n  fun navigateToDiscover()\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/NetworkIconProvider.kt",
    "content": "package com.michaldrabik.ui_base.utilities\n\nimport androidx.annotation.DrawableRes\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_model.Network\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass NetworkIconProvider @Inject constructor() {\n\n  @DrawableRes\n  fun getIcon(network: Network): Int =\n    when (network) {\n      Network.ABC -> R.drawable.ic_abc\n      Network.AMC -> R.drawable.ic_amc\n      Network.APPLE -> R.drawable.ic_apple\n      Network.AMAZON -> R.drawable.ic_amazon\n      Network.BBC -> R.drawable.ic_bbc\n      Network.CBS -> R.drawable.ic_cbs\n      Network.CW -> R.drawable.ic_cw\n      Network.DISCOVERY -> R.drawable.ic_discovery\n      Network.DISNEY -> R.drawable.ic_disney\n      Network.HBO -> R.drawable.ic_hbo\n      Network.FOX -> R.drawable.ic_fox\n      Network.HULU -> R.drawable.ic_hulu\n      Network.ITV -> R.drawable.ic_itv\n      Network.NBC -> R.drawable.ic_nbc\n      Network.NETFLIX -> R.drawable.ic_netflix\n      Network.SHOWTIME -> R.drawable.ic_showtime\n      Network.RAKUTEN -> R.drawable.ic_rakuten\n      Network.PARAMOUNT -> R.drawable.ic_paramount\n      Network.PEACOCK -> R.drawable.ic_peacock\n    }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/SnackbarHost.kt",
    "content": "package com.michaldrabik.ui_base.utilities\n\nimport android.view.ViewGroup\n\ninterface SnackbarHost {\n  fun provideSnackbarLayout(): ViewGroup\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/TipsHost.kt",
    "content": "package com.michaldrabik.ui_base.utilities\n\nimport com.michaldrabik.ui_model.Tip\n\ninterface TipsHost {\n  fun isTipShown(tip: Tip): Boolean\n  fun showTip(tip: Tip)\n  fun setTipShow(tip: Tip)\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/events/Event.kt",
    "content": "package com.michaldrabik.ui_base.utilities.events\n\nopen class Event<T>(\n  private val action: T,\n) {\n\n  private var isConsumed: Boolean = false\n\n  fun peek(): T? = action\n\n  fun consume(): T? =\n    if (!isConsumed) {\n      isConsumed = true\n      action\n    } else {\n      null\n    }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/events/MessageEvent.kt",
    "content": "package com.michaldrabik.ui_base.utilities.events\n\nimport androidx.annotation.StringRes\n\nsealed class MessageEvent(\n  val textResId: Int\n) : Event<Int>(textResId) {\n\n  data class Info(\n    @StringRes val textRestId: Int,\n    val isIndefinite: Boolean = false\n  ) : MessageEvent(textRestId)\n\n  data class Error(\n    @StringRes val textRestId: Int\n  ) : MessageEvent(textRestId)\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/extensions/BuildExtensions.kt",
    "content": "package com.michaldrabik.ui_base.utilities.extensions\n\nimport android.os.Build\nimport androidx.annotation.ChecksSdkIntAtLeast\n\n@ChecksSdkIntAtLeast(parameter = 0, lambda = 1)\ninline fun withApiAtLeast(value: Int, action: () -> Unit) {\n  if (Build.VERSION.SDK_INT >= value) {\n    action()\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/extensions/BundleExtensions.kt",
    "content": "package com.michaldrabik.ui_base.utilities.extensions\n\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.Parcelable\nimport androidx.fragment.app.Fragment\n\nfun Fragment.requireString(key: String?, default: String? = null) =\n  requireArguments().getString(key, default)!!\n\nfun Fragment.requireStringArray(key: String?) =\n  requireArguments().getStringArrayList(key)!!\n\nfun Fragment.requireLong(key: String?) =\n  requireArguments().getLong(key)\n\nfun Fragment.requireLongArray(key: String?) =\n  requireArguments().getLongArray(key)!!\n\nfun Fragment.requireBoolean(key: String?) =\n  requireArguments().getBoolean(key)\n\n@Suppress(\"UNCHECKED_CAST\")\nfun <T> Fragment.requireSerializable(key: String?) =\n  requireArguments().getSerializable(key) as T\n\nfun <T : Parcelable> Fragment.requireParcelable(key: String?) =\n  optionalParcelable<T>(key)!!\n\nfun <T : Parcelable> Fragment.optionalParcelable(key: String?) =\n  requireArguments().getParcelable<T>(key)\n\ninline fun <reified T : Parcelable> Bundle.requireParcelable(key: String): T = when {\n  Build.VERSION.SDK_INT >= 33 -> getParcelable(key, T::class.java)!!\n  else -> @Suppress(\"DEPRECATION\") (getParcelable(key) as? T)!!\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/extensions/ContextExtensions.kt",
    "content": "package com.michaldrabik.ui_base.utilities.extensions\n\nimport android.content.ClipData\nimport android.content.ClipboardManager\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.content.res.Configuration\nimport android.util.TypedValue\nimport androidx.annotation.AttrRes\nimport androidx.annotation.ColorInt\nimport androidx.annotation.DimenRes\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.content.ContextCompat\nimport com.michaldrabik.ui_base.R\nimport java.util.Locale\n\nfun Context.isTablet() = resources.getBoolean(R.bool.isTablet)\n\nfun Context.notificationManager() = NotificationManagerCompat.from(this)\n\nfun Context.dimenToPx(@DimenRes dimenResId: Int) = resources.getDimensionPixelSize(dimenResId)\n\n@ColorInt\nfun Context.colorFromAttr(\n  @AttrRes attrColor: Int,\n  typedValue: TypedValue = TypedValue(),\n  resolveRefs: Boolean = true,\n): Int {\n  theme.resolveAttribute(attrColor, typedValue, resolveRefs)\n  return typedValue.data\n}\n\nfun Context.colorStateListFromAttr(\n  @AttrRes attrColor: Int,\n  typedValue: TypedValue = TypedValue(),\n  resolveRefs: Boolean = true,\n): ColorStateList =\n  ColorStateList.valueOf(colorFromAttr(attrColor, typedValue, resolveRefs))\n\nfun Context.getLocaleStringResource(requestedLocale: Locale?, resourceId: Int): String {\n  val result: String\n  val config = Configuration(resources.configuration)\n  config.setLocale(requestedLocale)\n  result = createConfigurationContext(config).getText(resourceId).toString()\n  return result\n}\n\nfun Context.copyToClipboard(text: String) {\n  val clip = ClipData.newPlainText(\"label\", text)\n  ContextCompat.getSystemService(this, ClipboardManager::class.java)\n    .apply {\n      this?.setPrimaryClip(clip)\n    }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/extensions/Extensions.kt",
    "content": "package com.michaldrabik.ui_base.utilities.extensions\n\nimport android.content.Context\nimport android.content.res.Resources\nimport android.graphics.Rect\nimport android.util.TypedValue\nimport android.view.TouchDelegate\nimport android.view.View\nimport android.view.inputmethod.InputMethodManager\nimport android.widget.CompoundButton\nimport androidx.annotation.DimenRes\nimport androidx.fragment.app.Fragment\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.viewpager.widget.ViewPager\nimport androidx.viewpager2.widget.ViewPager2\nimport androidx.work.CoroutineWorker\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.SafeOnClickListener\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\n\nfun CoroutineWorker.notificationManager() = applicationContext.notificationManager()\n\nfun View.onClick(safe: Boolean = true, action: (View) -> Unit) = setOnClickListener(SafeOnClickListener(safe, action))\n\nfun View.onLongClick(action: (View) -> Unit) = setOnLongClickListener {\n  action(it)\n  true\n}\n\nfun List<View>.onClick(safe: Boolean = true, action: (View) -> Unit) = forEach { it.onClick(safe, action) }\n\nfun Fragment.dimenToPx(@DimenRes dimenResId: Int) = resources.getDimensionPixelSize(dimenResId)\n\nfun screenWidth() = Resources.getSystem().displayMetrics.widthPixels\n\nfun screenHeight() = Resources.getSystem().displayMetrics.heightPixels\n\nfun GridLayoutManager.withSpanSizeLookup(action: (Int) -> Int) {\n  spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {\n    override fun getSpanSize(position: Int) = action(position)\n  }\n}\n\nfun View.showKeyboard() {\n  val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager\n  requestFocus()\n  inputMethodManager.showSoftInput(this, 0)\n}\n\nfun View.hideKeyboard() {\n  (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).apply {\n    hideSoftInputFromWindow(windowToken, 0)\n  }\n}\n\nfun View.addRipple() = with(TypedValue()) {\n  context.theme.resolveAttribute(R.attr.selectableItemBackground, this, true)\n  setBackgroundResource(resourceId)\n}\n\nfun View.expandTouch(amount: Int = 50) {\n  val rect = Rect()\n  this.getHitRect(rect)\n  rect.top -= amount\n  rect.right += amount\n  rect.bottom += amount\n  rect.left -= amount\n  (this.parent as View).touchDelegate = TouchDelegate(rect, this)\n}\n\nfun CompoundButton.setCheckedSilent(isChecked: Boolean, action: (View, Boolean) -> Unit = { _, _ -> }) {\n  setOnCheckedChangeListener { _, _ -> }\n  setChecked(isChecked)\n  setOnCheckedChangeListener(action)\n}\n\nfun ViewPager.nextPage() {\n  val itemsCount = adapter?.count ?: 0\n  if (itemsCount == 0) return\n\n  when (currentItem) {\n    itemsCount - 1 -> currentItem = 0\n    else -> currentItem += 1\n  }\n}\n\nfun ViewPager2.nextPage() {\n  val itemsCount = adapter?.itemCount ?: 0\n  if (itemsCount == 0) return\n\n  when (currentItem) {\n    itemsCount - 1 -> currentItem = 0\n    else -> currentItem += 1\n  }\n}\n\nfun <T> MutableList<T>.replaceItem(oldItem: T, newItem: T) {\n  val index = indexOf(oldItem)\n  removeAt(index)\n  add(index, newItem)\n}\n\ninline fun <T> MutableList<T>.findReplace(newItem: T, predicate: (T) -> Boolean) {\n  find(predicate)?.let { replaceItem(it, newItem) }\n}\n\nfun <T> MutableList<T>.replace(newItems: Collection<T>) {\n  clear()\n  addAll(newItems)\n}\n\nfun CoroutineScope.launchDelayed(delayMs: Long, action: suspend () -> Unit): Job {\n  return launch {\n    delay(delayMs)\n    action()\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/extensions/FlowCombineExtensions.kt",
    "content": "package com.michaldrabik.ui_base.utilities.extensions\n\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.emptyFlow\nimport kotlinx.coroutines.flow.combine as combineKtx\n\nfun <T1, R> combine(\n  flow: Flow<T1>,\n  transform: suspend (T1) -> R,\n): Flow<R> = combineKtx(\n  flow,\n  emptyFlow<Any>()\n) { t1, _ ->\n  transform(t1)\n}\n\nfun <T1, T2, T3, T4, T5, T6, R> combine(\n  flow: Flow<T1>,\n  flow2: Flow<T2>,\n  flow3: Flow<T3>,\n  flow4: Flow<T4>,\n  flow5: Flow<T5>,\n  flow6: Flow<T6>,\n  transform: suspend (T1, T2, T3, T4, T5, T6) -> R,\n): Flow<R> = combineKtx(\n  combineKtx(flow, flow2, flow3, ::Triple),\n  combineKtx(flow4, flow5, flow6, ::Triple)\n) { t1, t2 ->\n  transform(\n    t1.first,\n    t1.second,\n    t1.third,\n    t2.first,\n    t2.second,\n    t2.third\n  )\n}\n\nfun <T1, T2, T3, T4, T5, T6, T7, R> combine(\n  flow: Flow<T1>,\n  flow2: Flow<T2>,\n  flow3: Flow<T3>,\n  flow4: Flow<T4>,\n  flow5: Flow<T5>,\n  flow6: Flow<T6>,\n  flow7: Flow<T7>,\n  transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R,\n): Flow<R> = combineKtx(\n  combineKtx(flow, flow2, flow3) { t1, t2, t3 -> Triple(t1, t2, t3) },\n  combineKtx(flow4, flow5) { t1, t2 -> Pair(t1, t2) },\n  combineKtx(flow6, flow7) { t1, t2 -> Pair(t1, t2) },\n) { t1, t2, t3 ->\n  transform(\n    t1.first,\n    t1.second,\n    t1.third,\n    t2.first,\n    t2.second,\n    t3.first,\n    t3.second\n  )\n}\n\nfun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(\n  flow: Flow<T1>,\n  flow2: Flow<T2>,\n  flow3: Flow<T3>,\n  flow4: Flow<T4>,\n  flow5: Flow<T5>,\n  flow6: Flow<T6>,\n  flow7: Flow<T7>,\n  flow8: Flow<T8>,\n  transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R,\n): Flow<R> = combineKtx(\n  combineKtx(flow, flow2, flow3) { t1, t2, t3 -> Triple(t1, t2, t3) },\n  combineKtx(flow4, flow5) { t1, t2 -> Pair(t1, t2) },\n  combineKtx(flow6, flow7, flow8) { t1, t2, t3 -> Triple(t1, t2, t3) },\n) { t1, t2, t3 ->\n  transform(\n    t1.first,\n    t1.second,\n    t1.third,\n    t2.first,\n    t2.second,\n    t3.first,\n    t3.second,\n    t3.third\n  )\n}\n\nfun <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> combine(\n  flow: Flow<T1>,\n  flow2: Flow<T2>,\n  flow3: Flow<T3>,\n  flow4: Flow<T4>,\n  flow5: Flow<T5>,\n  flow6: Flow<T6>,\n  flow7: Flow<T7>,\n  flow8: Flow<T8>,\n  flow9: Flow<T9>,\n  transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9) -> R,\n): Flow<R> = combineKtx(\n  combineKtx(flow, flow2, flow3) { t1, t2, t3 -> Triple(t1, t2, t3) },\n  combineKtx(flow4, flow5, flow6) { t1, t2, t3 -> Triple(t1, t2, t3) },\n  combineKtx(flow7, flow8, flow9) { t1, t2, t3 -> Triple(t1, t2, t3) }\n) { t1, t2, t3 ->\n  transform(\n    t1.first,\n    t1.second,\n    t1.third,\n    t2.first,\n    t2.second,\n    t2.third,\n    t3.first,\n    t3.second,\n    t3.third\n  )\n}\n\nfun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R> combine(\n  flow: Flow<T1>,\n  flow2: Flow<T2>,\n  flow3: Flow<T3>,\n  flow4: Flow<T4>,\n  flow5: Flow<T5>,\n  flow6: Flow<T6>,\n  flow7: Flow<T7>,\n  flow8: Flow<T8>,\n  flow9: Flow<T9>,\n  flow10: Flow<T10>,\n  transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) -> R,\n): Flow<R> = combineKtx(\n  combineKtx(flow, flow2, flow3) { t1, t2, t3 -> Triple(t1, t2, t3) },\n  combineKtx(flow4, flow5, flow6) { t1, t2, t3 -> Triple(t1, t2, t3) },\n  combineKtx(flow7, flow8) { t1, t2 -> Pair(t1, t2) },\n  combineKtx(flow9, flow10) { t1, t2 -> Pair(t1, t2) }\n) { t1, t2, t3, t4 ->\n  transform(\n    t1.first,\n    t1.second,\n    t1.third,\n    t2.first,\n    t2.second,\n    t2.third,\n    t3.first,\n    t3.second,\n    t4.first,\n    t4.second\n  )\n}\n\nfun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R> combine(\n  flow: Flow<T1>,\n  flow2: Flow<T2>,\n  flow3: Flow<T3>,\n  flow4: Flow<T4>,\n  flow5: Flow<T5>,\n  flow6: Flow<T6>,\n  flow7: Flow<T7>,\n  flow8: Flow<T8>,\n  flow9: Flow<T9>,\n  flow10: Flow<T10>,\n  flow11: Flow<T11>,\n  transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) -> R,\n): Flow<R> = combineKtx(\n  combineKtx(flow, flow2, flow3) { t1, t2, t3 -> Triple(t1, t2, t3) },\n  combineKtx(flow4, flow5, flow6) { t1, t2, t3 -> Triple(t1, t2, t3) },\n  combineKtx(flow7, flow8, flow9) { t1, t2, t3 -> Triple(t1, t2, t3) },\n  combineKtx(flow10, flow11) { t1, t2 -> Pair(t1, t2) }\n) { t1, t2, t3, t4 ->\n  transform(\n    t1.first,\n    t1.second,\n    t1.third,\n    t2.first,\n    t2.second,\n    t2.third,\n    t3.first,\n    t3.second,\n    t3.third,\n    t4.first,\n    t4.second\n  )\n}\n\nfun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R> combine(\n  flow: Flow<T1>,\n  flow2: Flow<T2>,\n  flow3: Flow<T3>,\n  flow4: Flow<T4>,\n  flow5: Flow<T5>,\n  flow6: Flow<T6>,\n  flow7: Flow<T7>,\n  flow8: Flow<T8>,\n  flow9: Flow<T9>,\n  flow10: Flow<T10>,\n  flow11: Flow<T11>,\n  flow12: Flow<T12>,\n  transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) -> R,\n): Flow<R> = combineKtx(\n  combineKtx(flow, flow2, flow3) { t1, t2, t3 -> Triple(t1, t2, t3) },\n  combineKtx(flow4, flow5, flow6) { t1, t2, t3 -> Triple(t1, t2, t3) },\n  combineKtx(flow7, flow8, flow9) { t1, t2, t3 -> Triple(t1, t2, t3) },\n  combineKtx(flow10, flow11, flow12) { t1, t2, t3 -> Triple(t1, t2, t3) }\n) { t1, t2, t3, t4 ->\n  transform(\n    t1.first,\n    t1.second,\n    t1.third,\n    t2.first,\n    t2.second,\n    t2.third,\n    t3.first,\n    t3.second,\n    t3.third,\n    t4.first,\n    t4.second,\n    t4.third\n  )\n}\n\nfun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R> combine(\n  flow: Flow<T1>,\n  flow2: Flow<T2>,\n  flow3: Flow<T3>,\n  flow4: Flow<T4>,\n  flow5: Flow<T5>,\n  flow6: Flow<T6>,\n  flow7: Flow<T7>,\n  flow8: Flow<T8>,\n  flow9: Flow<T9>,\n  flow10: Flow<T10>,\n  flow11: Flow<T11>,\n  flow12: Flow<T12>,\n  flow13: Flow<T13>,\n  transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) -> R,\n): Flow<R> = combineKtx(\n  combineKtx(flow, flow2, flow3) { t1, t2, t3 -> Triple(t1, t2, t3) },\n  combineKtx(flow4, flow5, flow6) { t1, t2, t3 -> Triple(t1, t2, t3) },\n  combineKtx(flow7, flow8, flow9) { t1, t2, t3 -> Triple(t1, t2, t3) },\n  combineKtx(flow10, flow11) { t1, t2 -> Pair(t1, t2) },\n  combineKtx(flow12, flow13) { t1, t2 -> Pair(t1, t2) },\n) { t1, t2, t3, t4, t5 ->\n  transform(\n    t1.first,\n    t1.second,\n    t1.third,\n    t2.first,\n    t2.second,\n    t2.third,\n    t3.first,\n    t3.second,\n    t3.third,\n    t4.first,\n    t4.second,\n    t5.first,\n    t5.second\n  )\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/extensions/GlideExtensions.kt",
    "content": "package com.michaldrabik.ui_base.utilities.extensions\n\nimport android.graphics.drawable.Drawable\nimport com.bumptech.glide.RequestBuilder\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.engine.GlideException\nimport com.bumptech.glide.request.RequestListener\nimport com.bumptech.glide.request.target.Target\n\ninline fun RequestBuilder<Drawable>.withFailListener(crossinline action: () -> Unit) =\n  addListener(object : RequestListener<Drawable?> {\n    override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable?>?, isFirstResource: Boolean): Boolean {\n      action()\n      return false\n    }\n\n    override fun onResourceReady(\n      resource: Drawable?,\n      model: Any?,\n      target: Target<Drawable?>?,\n      dataSource: DataSource?,\n      isFirstResource: Boolean\n    ) = false\n  })\n\ninline fun RequestBuilder<Drawable>.withSuccessListener(crossinline action: () -> Unit) =\n  addListener(object : RequestListener<Drawable?> {\n    override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable?>?, isFirstResource: Boolean) = false\n\n    override fun onResourceReady(\n      resource: Drawable?,\n      model: Any?,\n      target: Target<Drawable?>?,\n      dataSource: DataSource?,\n      isFirstResource: Boolean\n    ): Boolean {\n      action()\n      return false\n    }\n  })\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/extensions/LayoutExtensions.kt",
    "content": "package com.michaldrabik.ui_base.utilities.extensions\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.DrawableRes\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.recyclerview.widget.DividerItemDecoration\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport androidx.recyclerview.widget.RecyclerView\n\nfun RecyclerView.addDivider(@DrawableRes dividerRes: Int, direction: Int = VERTICAL) {\n  addItemDecoration(\n    DividerItemDecoration(context, direction).apply {\n      setDrawable(ContextCompat.getDrawable(context, dividerRes)!!)\n    }\n  )\n}\n\n/**\n * https://chris.banes.dev/2019/04/12/insets-listeners-to-layouts/\n */\nfun View.requestApplyInsetsWhenAttached() {\n  if (isAttachedToWindow) {\n    requestApplyInsets()\n  } else {\n    addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {\n      override fun onViewAttachedToWindow(v: View) {\n        v.removeOnAttachStateChangeListener(this)\n        v.requestApplyInsets()\n      }\n\n      override fun onViewDetachedFromWindow(v: View) = Unit\n    })\n  }\n}\n\n/**\n * https://chris.banes.dev/2019/04/12/insets-listeners-to-layouts/\n */\nfun View.doOnApplyWindowInsets(f: (View, WindowInsetsCompat, InitialSpacing, InitialSpacing) -> Unit) {\n  // Create a snapshot of the view's padding state\n  val initialPadding = recordInitialPaddingForView(this)\n  val initialMargin = recordInitialMarginForView(this)\n  // Set an actual OnApplyWindowInsetsListener which proxies to the given\n  // lambda, also passing in the original padding state\n  ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets ->\n    f(v, insets, initialPadding, initialMargin)\n    insets\n  }\n  // request some insets\n  requestApplyInsetsWhenAttached()\n}\n\ndata class InitialSpacing(\n  val left: Int,\n  val top: Int,\n  val right: Int,\n  val bottom: Int,\n)\n\nprivate fun recordInitialPaddingForView(view: View) = InitialSpacing(\n  view.paddingLeft, view.paddingTop, view.paddingRight, view.paddingBottom\n)\n\nprivate fun recordInitialMarginForView(view: View): InitialSpacing {\n  val lp = view.layoutParams as? ViewGroup.MarginLayoutParams\n  return InitialSpacing(\n    lp?.leftMargin ?: 0, lp?.topMargin ?: 0, lp?.rightMargin ?: 0, lp?.bottomMargin ?: 0\n  )\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/extensions/LifecycleExtensions.kt",
    "content": "package com.michaldrabik.ui_base.utilities.extensions\n\nimport android.os.Bundle\nimport androidx.annotation.IdRes\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.NavigationHost\nimport kotlinx.coroutines.launch\n\nfun Fragment.launchAndRepeatStarted(\n  vararg launchBlock: suspend () -> Unit,\n  doAfterLaunch: (() -> Unit)? = null\n) {\n  viewLifecycleOwner.lifecycleScope.launch {\n    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {\n      launchBlock.forEach {\n        launch { it.invoke() }\n      }\n      doAfterLaunch?.invoke()\n    }\n  }\n}\n\nfun BaseFragment<*>.navigateTo(\n  @IdRes destination: Int,\n  bundle: Bundle? = null\n) {\n  (requireActivity() as NavigationHost).findNavControl()?.navigate(destination, bundle)\n}\n\nfun BaseFragment<*>.navigateToSafe(\n  @IdRes destination: Int,\n  bundle: Bundle? = null\n) {\n  check(navigationId != 0) { \"Navigation ID not provided!\" }\n  (requireActivity() as NavigationHost).findNavControl()?.let { navController ->\n    if (navController.currentDestination?.id == navigationId) {\n      navigateTo(destination, bundle)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/extensions/SnackbarExtensions.kt",
    "content": "package com.michaldrabik.ui_base.utilities.extensions\n\nimport android.view.ViewGroup\nimport androidx.annotation.ColorInt\nimport com.google.android.material.snackbar.Snackbar\nimport com.google.android.material.snackbar.Snackbar.LENGTH_INDEFINITE\nimport com.google.android.material.snackbar.Snackbar.LENGTH_SHORT\nimport com.michaldrabik.ui_base.R\n\nfun ViewGroup.showSnackbar(\n  message: String,\n  actionText: Int,\n  textColor: Int,\n  @ColorInt backgroundColor: Int,\n  length: Int,\n  action: (() -> Unit)? = null,\n): Snackbar {\n  return Snackbar.make(this, message, length).apply {\n    setTextMaxLines(5)\n    setTextColor(textColor)\n    setBackgroundTint(backgroundColor)\n    setActionTextColor(textColor)\n    if (action != null) {\n      setAction(actionText) {\n        dismiss()\n        action()\n      }\n    }\n    show()\n  }\n}\n\nfun ViewGroup.showInfoSnackbar(\n  message: String,\n  actionText: Int = R.string.textOk,\n  length: Int = LENGTH_SHORT,\n  action: (() -> Unit)? = null,\n): Snackbar {\n  return showSnackbar(\n    message = message,\n    actionText = actionText,\n    textColor = context.colorFromAttr(R.attr.textColorInfoSnackbar),\n    backgroundColor = context.colorFromAttr(R.attr.colorInfoSnackbar),\n    length = length,\n    action = action\n  )\n}\n\nfun ViewGroup.showErrorSnackbar(\n  message: String,\n  actionText: Int = R.string.textOk,\n  action: () -> Unit = {},\n): Snackbar {\n  return showSnackbar(\n    message = message,\n    actionText = actionText,\n    textColor = context.colorFromAttr(R.attr.textColorErrorSnackbar),\n    backgroundColor = context.colorFromAttr(R.attr.colorErrorSnackbar),\n    length = LENGTH_INDEFINITE,\n    action = action\n  )\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/extensions/UiExtensions.kt",
    "content": "package com.michaldrabik.ui_base.utilities.extensions\n\nimport android.animation.Animator\nimport android.animation.AnimatorSet\nimport android.animation.ObjectAnimator\nimport android.content.res.ColorStateList\nimport android.graphics.Canvas\nimport android.graphics.Path\nimport android.graphics.RectF\nimport android.graphics.drawable.RippleDrawable\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.ViewPropertyAnimator\nimport android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE\nimport android.widget.TextView\nimport androidx.annotation.Px\nimport androidx.core.animation.doOnEnd\nimport androidx.core.graphics.toRect\nimport androidx.core.view.doOnLayout\nimport androidx.core.view.updateMargins\nimport androidx.fragment.app.Fragment\nimport timber.log.Timber\nimport java.util.Locale\n\nfun View.visible() {\n  if (visibility != View.VISIBLE) visibility = View.VISIBLE\n}\n\nfun View.gone() {\n  if (visibility != View.GONE) visibility = View.GONE\n}\n\nfun View.invisible() {\n  if (visibility != View.INVISIBLE) visibility = View.INVISIBLE\n}\n\nfun View.visibleIf(condition: Boolean, gone: Boolean = true) =\n  if (condition) {\n    visible()\n  } else {\n    if (gone) gone() else invisible()\n  }\n\nfun View.fadeIf(\n  condition: Boolean,\n  duration: Long = 250,\n  startDelay: Long = 0,\n  hardware: Boolean = false,\n) = if (condition) {\n  fadeIn(duration, startDelay, hardware)\n} else {\n  fadeOut(duration, startDelay, hardware)\n}\n\nfun View.fadeIn(\n  duration: Long = 250,\n  startDelay: Long = 0,\n  withHardware: Boolean = false,\n  endAction: () -> Unit = {},\n): ViewPropertyAnimator? {\n  if (visibility == View.VISIBLE) {\n    endAction()\n    return null\n  }\n  visibility = View.VISIBLE\n  alpha = 0F\n  val animation = animate()\n    .alpha(1F)\n    .setDuration(duration)\n    .setStartDelay(startDelay)\n    .apply { if (withHardware) withLayer() }\n    .withEndAction(endAction)\n  return animation.also { it.start() }\n}\n\nfun View.fadeOut(\n  duration: Long = 250,\n  startDelay: Long = 0,\n  withHardware: Boolean = false,\n  endAction: () -> Unit = {},\n): ViewPropertyAnimator? {\n  if (visibility == View.GONE) {\n    endAction()\n    return null\n  }\n  val animation = animate()\n    .alpha(0F)\n    .setDuration(duration)\n    .setStartDelay(startDelay)\n    .apply { if (withHardware) withLayer() }\n    .withEndAction {\n      gone()\n      endAction()\n    }\n  return animation.also { it.start() }\n}\n\nfun ViewPropertyAnimator?.add(animations: MutableList<ViewPropertyAnimator?>): ViewPropertyAnimator? {\n  animations.add(this)\n  return this\n}\n\nfun Animator?.add(animators: MutableList<Animator?>) {\n  animators.add(this)\n}\n\nfun View.shake() = ObjectAnimator.ofFloat(this, \"translationX\", 0F, -15F, 15F, -10F, 10F, -5F, 5F, 0F)\n  .setDuration(500)\n  .start()\n\nfun View.bump(\n  duration: Long = 250,\n  startDelay: Long = 0,\n  action: () -> Unit = {}\n) {\n  val x = ObjectAnimator.ofFloat(this, \"scaleX\", 1F, 1.1F, 1F)\n  val y = ObjectAnimator.ofFloat(this, \"scaleY\", 1F, 1.1F, 1F)\n\n  AnimatorSet().apply {\n    playTogether(x, y)\n    this.startDelay = startDelay\n    this.duration = duration\n    doOnEnd { action() }\n    start()\n  }\n}\n\nfun View.updateTopMargin(margin: Int) {\n  (layoutParams as ViewGroup.MarginLayoutParams).updateMargins(top = margin)\n}\n\nfun TextView.setTextFade(text: String, duration: Long = 125) {\n  fadeOut(\n    duration = duration,\n    endAction = {\n      setText(text)\n      fadeIn(duration = duration)\n    }\n  )\n}\n\nfun TextView.isSpoilerHidden(): Boolean {\n  return text.toString()\n    .trim()\n    .replace(\" \", \"\")\n    .none { it.isLetterOrDigit() }\n}\n\nfun Fragment.disableUi() {\n  activity?.window?.setFlags(FLAG_NOT_TOUCHABLE, FLAG_NOT_TOUCHABLE)\n  Timber.d(\"UI disabled.\")\n}\n\nfun Fragment.enableUi() {\n  activity?.window?.clearFlags(FLAG_NOT_TOUCHABLE)\n  Timber.d(\"UI enabled.\")\n}\n\nfun String.capitalizeWords() = this\n  .split(\" \")\n  .joinToString(separator = \" \") {\n    it.replaceFirstChar { string -> if (string.isLowerCase()) string.titlecase(Locale.getDefault()) else string.toString() }\n  }\n\nfun String.trimWithSuffix(length: Int, suffix: String): String {\n  if (this.length <= length) return this\n  return this.take(length).plus(suffix)\n}\n\nfun View.setOutboundRipple(\n  @Px size: Float = 0F,\n  @Px corner: Float = 0F,\n) {\n  val color = context.colorFromAttr(android.R.attr.colorControlHighlight)\n  doOnLayout { view ->\n    val boundsF = RectF(-size, -size, (view.width + size), (view.height + size))\n    val path = Path().apply {\n      addRoundRect(boundsF, corner, corner, Path.Direction.CW)\n    }\n    background = object : RippleDrawable(\n      ColorStateList.valueOf(color),\n      null,\n      null\n    ) {\n      override fun draw(canvas: Canvas) {\n        canvas.clipPath(path)\n        super.draw(canvas)\n      }\n    }.apply {\n      val bounds = boundsF.toRect()\n      setHotspotBounds(\n        bounds.left,\n        bounds.top,\n        bounds.right,\n        bounds.bottom\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/extensions/ViewModelExtensions.kt",
    "content": "package com.michaldrabik.ui_base.utilities.extensions\n\nimport kotlinx.coroutines.CancellationException\nimport timber.log.Timber\n\nconst val SUBSCRIBE_STOP_TIMEOUT = 5000L\n\nfun rethrowCancellation(error: Throwable) {\n  if (error is CancellationException) {\n    Timber.d(\"Rethrowing CancellationException\")\n    throw error\n  } else {\n    Timber.e(error)\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/extensions/WebExtensions.kt",
    "content": "package com.michaldrabik.ui_base.utilities.extensions\n\nimport android.content.ActivityNotFoundException\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport android.view.View\nimport androidx.fragment.app.Fragment\nimport com.michaldrabik.ui_model.IdImdb\n\nfun Context.openWebUrl(url: String): String? {\n  val i = Intent(Intent.ACTION_VIEW)\n  i.data = Uri.parse(url)\n  return try {\n    startActivity(i)\n    url\n  } catch (error: ActivityNotFoundException) {\n    null\n  }\n}\n\nfun Context.openImdbUrl(idImdb: IdImdb): String? {\n  val i = Intent(Intent.ACTION_VIEW)\n  i.data = Uri.parse(\"imdb:///title/${idImdb.id}\")\n  return try {\n    startActivity(i)\n    i.data?.toString()\n  } catch (e: ActivityNotFoundException) {\n    // IMDb App not installed. Start in web browser\n    openWebUrl(\"http://www.imdb.com/title/${idImdb.id}\")\n  }\n}\n\nfun Fragment.openWebUrl(url: String) = requireContext().openWebUrl(url)\n\nfun Fragment.openImdbUrl(id: IdImdb) = requireContext().openImdbUrl(id)\n\nfun View.openWebUrl(url: String) = context.openWebUrl(url)\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/utilities/ui/EqualSpacingItemDecoration.kt",
    "content": "package com.michaldrabik.ui_base.utilities.ui\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.annotation.DimenRes\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.OrientationHelper\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ItemDecoration\nimport androidx.recyclerview.widget.StaggeredGridLayoutManager\n\nclass EqualSpacingItemDecoration : ItemDecoration {\n\n  private var orientation = -1\n  private var spanCount = -1\n  private var spacing: Int\n  private var halfSpacing: Int\n\n  constructor(context: Context, @DimenRes spacingDimen: Int) {\n    spacing = context.resources.getDimensionPixelSize(spacingDimen)\n    halfSpacing = spacing / 2\n  }\n\n  constructor(spacingPx: Int) {\n    spacing = spacingPx\n    halfSpacing = spacing / 2\n  }\n\n  override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {\n    super.getItemOffsets(outRect, view, parent, state)\n    if (orientation == -1) {\n      orientation = getOrientation(parent)\n    }\n    if (spanCount == -1) {\n      spanCount = getTotalSpan(parent)\n    }\n    val childCount = parent.layoutManager!!.itemCount\n    val childIndex = parent.getChildAdapterPosition(view)\n    val itemSpanSize = getItemSpanSize(parent, childIndex)\n    val spanIndex = getItemSpanIndex(parent, childIndex)\n\n    /* INVALID SPAN */if (spanCount < 1) return\n    setSpacings(outRect, parent, childCount, childIndex, itemSpanSize, spanIndex)\n  }\n\n  private fun setSpacings(outRect: Rect, parent: RecyclerView, childCount: Int, childIndex: Int, itemSpanSize: Int, spanIndex: Int) {\n    outRect.top = halfSpacing\n    outRect.bottom = halfSpacing\n    outRect.left = halfSpacing\n    outRect.right = halfSpacing\n    if (isTopEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {\n      outRect.top = spacing\n    }\n    if (isLeftEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {\n      outRect.left = spacing\n    }\n    if (isRightEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {\n      outRect.right = spacing\n    }\n    if (isBottomEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {\n      outRect.bottom = spacing\n    }\n  }\n\n  private fun getTotalSpan(parent: RecyclerView): Int {\n    val mgr = parent.layoutManager\n    if (mgr is GridLayoutManager) {\n      return mgr.spanCount\n    } else if (mgr is StaggeredGridLayoutManager) {\n      return mgr.spanCount\n    } else if (mgr is LinearLayoutManager) {\n      return 1\n    }\n    return -1\n  }\n\n  private fun getItemSpanSize(parent: RecyclerView, childIndex: Int): Int {\n    val mgr = parent.layoutManager\n    if (mgr is GridLayoutManager) {\n      return mgr.spanSizeLookup.getSpanSize(childIndex)\n    } else if (mgr is StaggeredGridLayoutManager) {\n      return 1\n    } else if (mgr is LinearLayoutManager) {\n      return 1\n    }\n    return -1\n  }\n\n  private fun getItemSpanIndex(parent: RecyclerView, childIndex: Int): Int {\n    val mgr = parent.layoutManager\n    if (mgr is GridLayoutManager) {\n      return mgr.spanSizeLookup.getSpanIndex(childIndex, spanCount)\n    } else if (mgr is StaggeredGridLayoutManager) {\n      return childIndex % spanCount\n    } else if (mgr is LinearLayoutManager) {\n      return 0\n    }\n    return -1\n  }\n\n  private fun getOrientation(parent: RecyclerView): Int {\n    val mgr = parent.layoutManager\n    if (mgr is LinearLayoutManager) {\n      return mgr.orientation\n    } else if (mgr is GridLayoutManager) {\n      return mgr.orientation\n    } else if (mgr is StaggeredGridLayoutManager) {\n      return mgr.orientation\n    }\n    return VERTICAL\n  }\n\n  private fun isLeftEdge(parent: RecyclerView, childCount: Int, childIndex: Int, itemSpanSize: Int, spanIndex: Int): Boolean {\n    return if (orientation == VERTICAL) {\n      spanIndex == 0\n    } else {\n      childIndex == 0 || isFirstItemEdgeValid(childIndex < spanCount, parent, childIndex)\n    }\n  }\n\n  private fun isRightEdge(parent: RecyclerView, childCount: Int, childIndex: Int, itemSpanSize: Int, spanIndex: Int): Boolean {\n    return if (orientation == VERTICAL) {\n      spanIndex + itemSpanSize == spanCount\n    } else {\n      isLastItemEdgeValid(childIndex >= childCount - spanCount, parent, childCount, childIndex, spanIndex)\n    }\n  }\n\n  private fun isTopEdge(parent: RecyclerView, childCount: Int, childIndex: Int, itemSpanSize: Int, spanIndex: Int): Boolean {\n    return if (orientation == VERTICAL) {\n      childIndex == 0 || isFirstItemEdgeValid(childIndex < spanCount, parent, childIndex)\n    } else {\n      spanIndex == 0\n    }\n  }\n\n  private fun isBottomEdge(parent: RecyclerView, childCount: Int, childIndex: Int, itemSpanSize: Int, spanIndex: Int): Boolean {\n    return if (orientation == VERTICAL) {\n      isLastItemEdgeValid(childIndex >= childCount - spanCount, parent, childCount, childIndex, spanIndex)\n    } else {\n      spanIndex + itemSpanSize == spanCount\n    }\n  }\n\n  private fun isFirstItemEdgeValid(isOneOfFirstItems: Boolean, parent: RecyclerView, childIndex: Int): Boolean {\n    var totalSpanArea = 0\n    if (isOneOfFirstItems) {\n      for (i in childIndex downTo 0) {\n        totalSpanArea += getItemSpanSize(parent, i)\n      }\n    }\n    return isOneOfFirstItems && totalSpanArea <= spanCount\n  }\n\n  private fun isLastItemEdgeValid(isOneOfLastItems: Boolean, parent: RecyclerView, childCount: Int, childIndex: Int, spanIndex: Int): Boolean {\n    var totalSpanRemaining = 0\n    if (isOneOfLastItems) {\n      for (i in childIndex until childCount) {\n        totalSpanRemaining += getItemSpanSize(parent, i)\n      }\n    }\n    return isOneOfLastItems && totalSpanRemaining <= spanCount - spanIndex\n  }\n\n  companion object {\n    private const val VERTICAL = OrientationHelper.VERTICAL\n  }\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/viewmodel/ChannelsDelegate.kt",
    "content": "package com.michaldrabik.ui_base.viewmodel\n\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.flow.Flow\n\ninterface ChannelsDelegate {\n\n  val messageFlow: Flow<MessageEvent>\n  val messageChannel: Channel<MessageEvent>\n\n  val eventFlow: Flow<Event<*>>\n  val eventChannel: Channel<Event<*>>\n}\n"
  },
  {
    "path": "ui-base/src/main/java/com/michaldrabik/ui_base/viewmodel/DefaultChannelsDelegate.kt",
    "content": "package com.michaldrabik.ui_base.viewmodel\n\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.flow.receiveAsFlow\n\nclass DefaultChannelsDelegate : ChannelsDelegate {\n\n  override val messageChannel = Channel<MessageEvent>(Channel.BUFFERED)\n  override val messageFlow = messageChannel.receiveAsFlow()\n\n  override val eventChannel = Channel<Event<*>>(Channel.BUFFERED)\n  override val eventFlow = eventChannel.receiveAsFlow()\n}\n"
  },
  {
    "path": "ui-base/src/main/res/anim/anim_recycler_fall_down.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layoutAnimation\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:animation=\"@anim/anim_recycler_fall_down_item\"\n  android:animationOrder=\"normal\"\n  android:delay=\"20%\"\n  />"
  },
  {
    "path": "ui-base/src/main/res/anim/anim_recycler_fall_down_fast.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layoutAnimation\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:animation=\"@anim/anim_recycler_fall_down_item_fast\"\n  android:animationOrder=\"normal\"\n  android:delay=\"15%\"\n  />"
  },
  {
    "path": "ui-base/src/main/res/anim/anim_recycler_fall_down_item.xml",
    "content": "<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:duration=\"250\"\n  >\n\n  <translate\n    android:fromYDelta=\"-20%\"\n    android:interpolator=\"@android:anim/decelerate_interpolator\"\n    android:toYDelta=\"0\"\n    />\n\n  <alpha\n    android:fromAlpha=\"0\"\n    android:interpolator=\"@android:anim/decelerate_interpolator\"\n    android:toAlpha=\"1\"\n    />\n\n</set>"
  },
  {
    "path": "ui-base/src/main/res/anim/anim_recycler_fall_down_item_fast.xml",
    "content": "<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:duration=\"175\"\n  >\n\n  <translate\n    android:fromYDelta=\"-20%\"\n    android:interpolator=\"@android:anim/decelerate_interpolator\"\n    android:toYDelta=\"0\"\n    />\n\n  <alpha\n    android:fromAlpha=\"0\"\n    android:interpolator=\"@android:anim/decelerate_interpolator\"\n    android:toAlpha=\"1\"\n    />\n\n</set>"
  },
  {
    "path": "ui-base/src/main/res/anim/anim_slide_in_from_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:duration=\"250\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <translate\n    android:fromXDelta=\"-15%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"0%\"\n    android:toYDelta=\"0%\"\n    />\n</set>"
  },
  {
    "path": "ui-base/src/main/res/anim/anim_slide_in_from_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:duration=\"250\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <translate\n    android:fromXDelta=\"100%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"0%\"\n    android:toYDelta=\"0%\"\n    />\n</set>"
  },
  {
    "path": "ui-base/src/main/res/anim/anim_slide_out_from_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:duration=\"250\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <translate\n    android:fromXDelta=\"0%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"100%\"\n    android:toYDelta=\"0%\"\n    />\n</set>"
  },
  {
    "path": "ui-base/src/main/res/anim/anim_slide_out_from_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:duration=\"250\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <translate\n    android:fromXDelta=\"0%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"-15%\"\n    android:toYDelta=\"0%\"\n    />\n</set>"
  },
  {
    "path": "ui-base/src/main/res/anim-ar/anim_slide_in_from_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:duration=\"250\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <translate\n    android:fromXDelta=\"15%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"0%\"\n    android:toYDelta=\"0%\"\n    />\n</set>"
  },
  {
    "path": "ui-base/src/main/res/anim-ar/anim_slide_in_from_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:duration=\"250\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <translate\n    android:fromXDelta=\"-100%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"0%\"\n    android:toYDelta=\"0%\"\n    />\n</set>"
  },
  {
    "path": "ui-base/src/main/res/anim-ar/anim_slide_out_from_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:duration=\"250\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <translate\n    android:fromXDelta=\"0%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"-100%\"\n    android:toYDelta=\"0%\"\n    />\n</set>"
  },
  {
    "path": "ui-base/src/main/res/anim-ar/anim_slide_out_from_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:duration=\"250\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <translate\n    android:fromXDelta=\"0%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"15%\"\n    android:toYDelta=\"0%\"\n    />\n</set>"
  },
  {
    "path": "ui-base/src/main/res/color/selector_chip_background.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?attr/colorBackgroundChipSelected\" android:state_checked=\"true\" />\n  <item android:color=\"?attr/colorBackgroundChipSelected\" android:state_selected=\"true\" />\n  <item android:color=\"?android:windowBackground\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "ui-base/src/main/res/color/selector_chip_stroke.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?attr/textColorChipSelected\" android:state_checked=\"true\" />\n  <item android:color=\"?attr/textColorChipSelected\" android:state_selected=\"true\" />\n  <item android:color=\"?android:textColorSecondary\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "ui-base/src/main/res/color/selector_chip_text.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?attr/textColorChipSelected\" android:state_checked=\"true\" />\n  <item android:color=\"?attr/textColorChipSelected\" android:state_selected=\"true\" />\n  <item android:color=\"?attr/textColorChip\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "ui-base/src/main/res/color/selector_discover_chip_background.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?attr/colorBackgroundChipSelectedLight\" android:state_checked=\"true\" />\n  <item android:color=\"?attr/colorSearchViewBackground\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "ui-base/src/main/res/color/selector_discover_chip_text.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?attr/textColorChipSelected\" android:state_checked=\"true\" />\n  <item android:color=\"?attr/textColorChip\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "ui-base/src/main/res/color/selector_main_button.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"@color/colorGrayLight\" android:state_enabled=\"false\" />\n  <item android:color=\"?attr/colorAccent\" />\n</selector>"
  },
  {
    "path": "ui-base/src/main/res/color/selector_main_checkbox.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"@color/colorAccent\" android:state_checked=\"true\" />\n  <item android:color=\"@color/colorAccent\" android:state_selected=\"true\" />\n  <item android:color=\"@color/colorGrayDark\" android:state_enabled=\"false\" />\n  <item android:color=\"@color/colorGrayLight\" />\n</selector>"
  },
  {
    "path": "ui-base/src/main/res/color-notnight/selector_chip_background.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?attr/colorBackgroundChipSelected\" android:state_checked=\"true\" />\n  <item android:color=\"?attr/colorBackgroundChipSelected\" android:state_selected=\"true\" />\n  <item android:color=\"?android:windowBackground\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "ui-base/src/main/res/color-notnight/selector_chip_stroke.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?attr/textColorChipSelected\" android:state_checked=\"true\" />\n  <item android:color=\"?attr/textColorChipSelected\" android:state_selected=\"true\" />\n  <item android:color=\"?android:textColorSecondary\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "ui-base/src/main/res/color-notnight/selector_main_checkbox.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"@color/colorAccent\" android:state_checked=\"true\" />\n  <item android:color=\"#4DAAAAAA\" android:state_enabled=\"false\" />\n  <item android:color=\"@color/colorGrayLight\" />\n</selector>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_badge.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"4dp\" />\n  <solid android:color=\"?attr/colorBadgeBackground\" />\n</shape>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_bottom_sheet.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners\n    android:topLeftRadius=\"16dp\"\n    android:topRightRadius=\"16dp\"\n    />\n  <solid android:color=\"?android:windowBackground\" />\n</shape>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <solid android:color=\"?attr/colorPrimary\" />\n  <corners android:radius=\"8dp\" />\n</shape>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_filters_sheet.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners\n    android:topLeftRadius=\"16dp\"\n    android:topRightRadius=\"16dp\"\n    />\n  <solid android:color=\"?attr/colorBottomMenuBackground\" />\n</shape>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_item_menu_elevation.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <solid android:color=\"?android:windowBackground\" />\n  <corners\n    android:radius=\"@dimen/mediaTileCorner\"\n    android:topLeftRadius=\"@dimen/collectionItemCorner\"\n    />\n</shape>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_item_menu_placeholder.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <solid android:color=\"?attr/colorPlaceholderBackground\" />\n  <corners\n    android:radius=\"@dimen/mediaTileCorner\"\n    android:topLeftRadius=\"@dimen/collectionItemCorner\"\n    />\n  <stroke\n    android:width=\"1dp\"\n    android:color=\"?attr/colorPlaceholderStroke\"\n    />\n</shape>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_link_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"6dp\" />\n  <solid android:color=\"?attr/colorBadgeBackground\" />\n</shape>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_link_item_ripple.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:color=\"?android:attr/colorControlHighlight\"\n  >\n  <item android:id=\"@android:id/mask\">\n    <shape android:shape=\"rectangle\">\n      <solid android:color=\"#000000\" />\n      <corners android:radius=\"6dp\" />\n    </shape>\n  </item>\n  <item android:drawable=\"@drawable/bg_link_item\" />\n</ripple>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_media_view_elevation.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <solid android:color=\"?android:windowBackground\" />\n  <corners android:radius=\"@dimen/mediaTileCorner\" />\n</shape>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_media_view_elevation_card.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <solid android:color=\"?attr/colorCardBackground\" />\n  <corners android:radius=\"@dimen/mediaTileCorner\" />\n</shape>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_media_view_placeholder.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <solid android:color=\"?attr/colorPlaceholderBackground\" />\n  <corners android:radius=\"@dimen/mediaTileCorner\" />\n  <stroke\n    android:width=\"1dp\"\n    android:color=\"?attr/colorPlaceholderStroke\"\n    />\n</shape>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_media_view_ripple.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:color=\"?android:attr/colorControlHighlight\"\n  >\n  <item android:id=\"@android:id/mask\">\n    <shape android:shape=\"rectangle\">\n      <solid android:color=\"#000000\" />\n      <corners android:radius=\"@dimen/mediaTileCorner\" />\n    </shape>\n  </item>\n</ripple>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_premium_ad.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"6dp\" />\n  <stroke android:width=\"1dp\" android:color=\"?android:attr/textColorPrimary\"/>\n</shape>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_snackbar_error.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <solid android:color=\"?colorErrorSnackbar\" />\n  <corners android:radius=\"4dp\" />\n</shape>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_snackbar_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <solid android:color=\"?attr/colorInfoSnackbar\" />\n  <corners android:radius=\"4dp\" />\n</shape>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_sort_item_badge.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"10dp\" />\n  <solid android:color=\"?attr/colorAccent\" />\n</shape>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_text_on_surface.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"4dp\" />\n  <solid android:color=\"@color/colorBlackTranslucent\" />\n</shape>"
  },
  {
    "path": "ui-base/src/main/res/drawable/bg_tip_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <solid android:color=\"?attr/colorPrimary\" />\n  <corners android:radius=\"16dp\" />\n</shape>"
  },
  {
    "path": "ui-base/src/main/res/drawable/divider_item_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <size\n    android:width=\"1dp\"\n    android:height=\"2dp\"\n    />\n</shape>"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_abc.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"100dp\"\n  android:height=\"100dp\"\n  android:viewportWidth=\"202\"\n  android:viewportHeight=\"202\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"m101.09,1.29c-55.47,0 -100.02,44.69 -100.02,100.13s44.56,100.13 100.02,100.13c55.46,0 99.98,-44.69 99.98,-100.13s-44.52,-100.13 -99.98,-100.13z\"\n    />\n  <path\n    android:fillColor=\"#fff\"\n    android:pathData=\"m35.51,70.02c-17.4,0 -31.36,14.02 -31.36,31.39 0,17.38 13.96,31.39 31.36,31.39 10.68,0 14.86,-7.54 14.86,-7.54l0,6.86l16.5,0l0,-30.71c0,-17.38 -13.96,-31.39 -31.36,-31.39zM35.51,86.43c8.29,0 14.94,6.69 14.94,14.98s-6.65,14.98 -14.94,14.98 -14.94,-6.69 -14.94,-14.98 6.65,-14.98 14.94,-14.98z\"\n    />\n  <path\n    android:fillColor=\"#fff\"\n    android:pathData=\"m70.86,48.4l0,53.01c0,17.38 13.97,31.4 31.37,31.4s31.33,-14.03 31.33,-31.4c0,-17.38 -13.93,-31.4 -31.33,-31.4 -10.68,0 -14.87,7.56 -14.87,7.56l0,-29.17zM102.22,86.43c8.29,0 14.95,6.69 14.95,14.98s-6.65,14.98 -14.95,14.98c-8.29,0 -14.95,-6.69 -14.95,-14.98s6.65,-14.98 14.95,-14.98z\"\n    />\n  <path\n    android:fillColor=\"#fff\"\n    android:pathData=\"m167.6,70.02c-17.4,0 -31.36,14.02 -31.36,31.39 0,17.38 13.96,31.39 31.36,31.39 15.02,0 27.48,-10.44 30.6,-24.48l-17.34,0c-2.49,4.79 -7.47,8.06 -13.26,8.06 -8.29,0 -14.94,-6.69 -14.94,-14.98s6.65,-14.98 14.94,-14.98c5.81,0 10.81,3.29 13.3,8.1l17.31,0c-3.11,-14.05 -15.57,-24.51 -30.6,-24.51z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_amc.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"100dp\"\n  android:height=\"100dp\"\n  android:viewportWidth=\"512\"\n  android:viewportHeight=\"512\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M512,256c0,43.02 -10.62,83.57 -29.36,119.16C439.77,456.53 354.36,512 256,512S72.23,456.53 29.36,375.16C10.62,339.57 0,299.02 0,256s10.62,-83.57 29.36,-119.16C72.23,55.47 157.64,0 256,0s183.77,55.47 226.64,136.84C501.38,172.43 512,212.98 512,256z\"\n    />\n  <path\n    android:fillColor=\"#F2F2F2\"\n    android:pathData=\"M512,256c0,43.02 -10.62,83.57 -29.36,119.16H29.36C10.62,339.57 0,299.02 0,256s10.62,-83.57 29.36,-119.16h453.28C501.38,172.43 512,212.98 512,256z\"\n    />\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M124.49,190.6c-31.1,0 -59.47,6 -64.93,40.38l28.92,0.55c0,0 2.18,-16.92 25.1,-16.92s28.38,15.82 28.38,26.74c0,0 -2.18,0 -21.28,0c-19.1,0 -69.3,3.81 -65.21,46.1s66.85,40.65 86.49,21.01v9.82h27.28c0,0 0,-31.1 0,-67.12S155.59,190.6 124.49,190.6zM133.22,289.9c-7.91,7.91 -45.83,9.82 -48.56,-4.36c-2.73,-14.19 13.1,-21.83 32.19,-21.83c19.1,0 22.92,0 22.92,0S141.14,281.99 133.22,289.9z\"\n    />\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M295.28,194.41l0,0l-16.92,0l-22.92,90.58l-22.92,-90.58l-16.91,0l0,0l0,0l-28.37,0l0,123.86l28.37,0l0,-93.87l27.28,93.87l12.55,0l12.55,0l27.28,-93.87l0,93.87l28.37,0l0,-123.86z\"\n    />\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M428.2,270.67c-4.92,14.31 -16.84,24.42 -30.77,24.42c-18.32,0 -33.17,-17.47 -33.17,-39.01c0,-21.55 14.85,-39.02 33.17,-39.02c13.85,0 25.71,9.98 30.68,24.15h29.15c-6.32,-29 -30.7,-50.61 -59.82,-50.61c-33.91,0 -61.4,29.31 -61.4,65.48c0,36.16 27.49,65.48 61.4,65.48c29.21,0 53.66,-21.75 59.88,-50.88h-29.11V270.67z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_anim_search_to_close.xml",
    "content": "<animated-vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:aapt=\"http://schemas.android.com/aapt\"\n  >\n  <aapt:attr name=\"android:drawable\">\n    <vector\n      android:name=\"searchtoclose\"\n      android:width=\"24dp\"\n      android:height=\"24dp\"\n      android:viewportWidth=\"24\"\n      android:viewportHeight=\"24\"\n      >\n      <group android:name=\"oval_container\">\n        <path\n          android:name=\"oval\"\n          android:pathData=\"M 13.389 13.389 C 15.537 11.241 15.537 7.759 13.389 5.611 C 11.241 3.463 7.759 3.463 5.611 5.611 C 3.463 7.759 3.463 11.241 5.611 13.389 C 7.759 15.537 11.241 15.537 13.389 13.389 Z\"\n          android:strokeWidth=\"1.8\"\n          android:strokeColor=\"#FFFFFF\"\n          />\n      </group>\n      <path\n        android:name=\"ne_stem\"\n        android:pathData=\"M 18 6 L 6 18\"\n        android:strokeWidth=\"1.8\"\n        android:strokeColor=\"#FFFFFF\"\n        android:trimPathStart=\"1\"\n        />\n      <path\n        android:name=\"nw_stem\"\n        android:pathData=\"M 6 6 L 20 20\"\n        android:strokeWidth=\"1.8\"\n        android:strokeColor=\"#FFFFFF\"\n        android:trimPathStart=\"0.48\"\n        />\n    </vector>\n  </aapt:attr>\n  <target android:name=\"nw_stem\">\n    <aapt:attr name=\"android:animation\">\n      <set>\n        <objectAnimator\n          android:duration=\"420\"\n          android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n          android:propertyName=\"trimPathStart\"\n          android:startOffset=\"166\"\n          android:valueFrom=\"0.48\"\n          android:valueTo=\"0\"\n          android:valueType=\"floatType\"\n          />\n        <objectAnimator\n          android:duration=\"420\"\n          android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n          android:propertyName=\"trimPathEnd\"\n          android:startOffset=\"166\"\n          android:valueFrom=\"1\"\n          android:valueTo=\"0.86\"\n          android:valueType=\"floatType\"\n          />\n      </set>\n    </aapt:attr>\n  </target>\n  <target android:name=\"ne_stem\">\n    <aapt:attr name=\"android:animation\">\n      <objectAnimator\n        android:duration=\"234\"\n        android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n        android:propertyName=\"trimPathStart\"\n        android:startOffset=\"382\"\n        android:valueFrom=\"1\"\n        android:valueTo=\"0\"\n        android:valueType=\"floatType\"\n        />\n    </aapt:attr>\n  </target>\n  <target android:name=\"oval\">\n    <aapt:attr name=\"android:animation\">\n      <objectAnimator\n        android:duration=\"336\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"\n        android:propertyName=\"trimPathStart\"\n        android:startOffset=\"0\"\n        android:valueFrom=\"0\"\n        android:valueTo=\"1\"\n        android:valueType=\"floatType\"\n        />\n    </aapt:attr>\n  </target>\n  <target android:name=\"oval_container\">\n    <aapt:attr name=\"android:animation\">\n      <set>\n        <objectAnimator\n          android:duration=\"420\"\n          android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n          android:propertyName=\"translateX\"\n          android:startOffset=\"166\"\n          android:valueFrom=\"0\"\n          android:valueTo=\"-6.7\"\n          android:valueType=\"floatType\"\n          />\n        <objectAnimator\n          android:duration=\"420\"\n          android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n          android:propertyName=\"translateY\"\n          android:startOffset=\"166\"\n          android:valueFrom=\"0\"\n          android:valueTo=\"-6.7\"\n          android:valueType=\"floatType\"\n          />\n      </set>\n    </aapt:attr>\n  </target>\n</animated-vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_arrow_alt.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:tint=\"#FFFFFF\" android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M16.01,11H4v2h12.01v3L20,12l-3.99,-4z\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_arrow_alt_down.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:autoMirrored=\"true\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24\"\n  android:viewportHeight=\"24\"\n  >\n  <group\n    android:pivotX=\"12\"\n    android:pivotY=\"12\"\n    android:rotation=\"90\"\n    >\n    <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M16.01,11H4v2h12.01v3L20,12l-3.99,-4z\"\n      />\n  </group>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_arrow_alt_up.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:autoMirrored=\"true\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24\"\n  android:viewportHeight=\"24\"\n  >\n  <group\n    android:pivotX=\"12\"\n    android:pivotY=\"12\"\n    android:rotation=\"270\"\n    >\n    <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M16.01,11H4v2h12.01v3L20,12l-3.99,-4z\"\n      />\n  </group>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_arrow_back.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_arrow_right.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M8.59,16.34l4.58,-4.59 -4.58,-4.59L10,5.75l6,6 -6,6z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_bookmark.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M17,3L7,3c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3L19,5c0,-1.1 -0.9,-2 -2,-2zM17,18l-5,-2.18L7,18L7,5h10v13z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_bookmark_full.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_calendar.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,8h14v11zM7,10h5v5L7,15z\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_check.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_check_small.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"22dp\"\n  android:height=\"22dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_circle.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_clock.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_clock_compact.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"12dp\"\n  android:height=\"12dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_clock_small.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"16dp\"\n  android:height=\"16dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_close.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_comment.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M21.99,4c0,-1.1 -0.89,-2 -1.99,-2L4,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14l4,4 -0.01,-18zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_crown.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:viewportWidth=\"94.5\"\n  android:viewportHeight=\"94.5\"\n  >\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M84.762,41.232c-4.92,3.529 -11.826,8.222 -14.941,8.222c-0.617,0 -0.815,-0.186 -0.959,-0.361c-1.272,-1.568 -0.928,-7.066 1.025,-16.335c0.193,-0.918 -0.291,-1.841 -1.152,-2.204c-0.861,-0.365 -1.861,-0.066 -2.383,0.71c-6.721,10.021 -9.799,12.123 -11.194,12.123c-1.979,0 -3.99,-5.692 -5.986,-16.92c-0.159,-0.896 -0.915,-1.549 -1.813,-1.601v-0.011c-0.019,0 -0.037,0.004 -0.056,0.004c-0.019,0 -0.04,-0.004 -0.058,-0.004v0.011c-0.895,0.052 -1.649,0.705 -1.811,1.601c-1.998,11.228 -4.011,16.92 -5.988,16.92c-1.396,0 -4.472,-2.103 -11.192,-12.123c-0.521,-0.776 -1.521,-1.075 -2.384,-0.71c-0.862,0.363 -1.346,1.286 -1.152,2.204c1.952,9.269 2.299,14.767 1.024,16.335c-0.141,0.176 -0.342,0.361 -0.958,0.361c-3.115,0 -10.021,-4.692 -14.943,-8.222c-0.778,-0.559 -1.845,-0.468 -2.52,0.209c-0.676,0.674 -0.765,1.741 -0.207,2.52c11.253,14.392 9.135,30.91 9.135,30.91c-0.041,0.229 -0.063,0.463 -0.063,0.709c0,4.058 5.682,5.664 10.631,6.61c5.453,1.045 12.635,1.627 20.266,1.647v0.002c0.073,0 0.147,0 0.22,-0.002c0.073,0.002 0.146,0.002 0.221,0.002v-0.002c7.629,-0.021 14.812,-0.603 20.263,-1.647c4.95,-0.946 10.633,-2.555 10.633,-6.61c0,-0.246 -0.022,-0.479 -0.063,-0.709c0,0 -2.117,-16.521 9.134,-30.91c0.559,-0.778 0.471,-1.846 -0.205,-2.52C86.607,40.766 85.542,40.674 84.762,41.232zM47.522,78.238h-0.015c-0.07,0 -0.137,0.003 -0.206,0.003c-0.07,0 -0.135,-0.003 -0.205,-0.003h-0.015c-12.127,-0.035 -20.144,-1.446 -23.682,-2.657c3.538,-1.213 11.555,-2.624 23.682,-2.658h0.029c0.063,0 0.126,0 0.19,0s0.124,0 0.19,0h0.03c12.126,0.034 20.142,1.445 23.681,2.658C67.664,76.792 59.648,78.203 47.522,78.238z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M47.302,16.573m-5.914,0a5.914,5.914 0,1 1,11.828 0a5.914,5.914 0,1 1,-11.828 0\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M22.339,23.718m-4.928,0a4.928,4.928 0,1 1,9.856 0a4.928,4.928 0,1 1,-9.856 0\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M71.855,23.718m-4.928,0a4.928,4.928 0,1 1,9.856 0a4.928,4.928 0,1 1,-9.856 0\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M3.322,37.759m-3.322,0a3.322,3.322 0,1 1,6.644 0a3.322,3.322 0,1 1,-6.644 0\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M91.178,37.759m-3.322,0a3.322,3.322 0,1 1,6.644 0a3.322,3.322 0,1 1,-6.644 0\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_crown_small.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"20dp\"\n  android:height=\"20dp\"\n  android:viewportWidth=\"94.5\"\n  android:viewportHeight=\"94.5\"\n  >\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M84.762,41.232c-4.92,3.529 -11.826,8.222 -14.941,8.222c-0.617,0 -0.815,-0.186 -0.959,-0.361c-1.272,-1.568 -0.928,-7.066 1.025,-16.335c0.193,-0.918 -0.291,-1.841 -1.152,-2.204c-0.861,-0.365 -1.861,-0.066 -2.383,0.71c-6.721,10.021 -9.799,12.123 -11.194,12.123c-1.979,0 -3.99,-5.692 -5.986,-16.92c-0.159,-0.896 -0.915,-1.549 -1.813,-1.601v-0.011c-0.019,0 -0.037,0.004 -0.056,0.004c-0.019,0 -0.04,-0.004 -0.058,-0.004v0.011c-0.895,0.052 -1.649,0.705 -1.811,1.601c-1.998,11.228 -4.011,16.92 -5.988,16.92c-1.396,0 -4.472,-2.103 -11.192,-12.123c-0.521,-0.776 -1.521,-1.075 -2.384,-0.71c-0.862,0.363 -1.346,1.286 -1.152,2.204c1.952,9.269 2.299,14.767 1.024,16.335c-0.141,0.176 -0.342,0.361 -0.958,0.361c-3.115,0 -10.021,-4.692 -14.943,-8.222c-0.778,-0.559 -1.845,-0.468 -2.52,0.209c-0.676,0.674 -0.765,1.741 -0.207,2.52c11.253,14.392 9.135,30.91 9.135,30.91c-0.041,0.229 -0.063,0.463 -0.063,0.709c0,4.058 5.682,5.664 10.631,6.61c5.453,1.045 12.635,1.627 20.266,1.647v0.002c0.073,0 0.147,0 0.22,-0.002c0.073,0.002 0.146,0.002 0.221,0.002v-0.002c7.629,-0.021 14.812,-0.603 20.263,-1.647c4.95,-0.946 10.633,-2.555 10.633,-6.61c0,-0.246 -0.022,-0.479 -0.063,-0.709c0,0 -2.117,-16.521 9.134,-30.91c0.559,-0.778 0.471,-1.846 -0.205,-2.52C86.607,40.766 85.542,40.674 84.762,41.232zM47.522,78.238h-0.015c-0.07,0 -0.137,0.003 -0.206,0.003c-0.07,0 -0.135,-0.003 -0.205,-0.003h-0.015c-12.127,-0.035 -20.144,-1.446 -23.682,-2.657c3.538,-1.213 11.555,-2.624 23.682,-2.658h0.029c0.063,0 0.126,0 0.19,0s0.124,0 0.19,0h0.03c12.126,0.034 20.142,1.445 23.681,2.658C67.664,76.792 59.648,78.203 47.522,78.238z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M47.302,16.573m-5.914,0a5.914,5.914 0,1 1,11.828 0a5.914,5.914 0,1 1,-11.828 0\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M22.339,23.718m-4.928,0a4.928,4.928 0,1 1,9.856 0a4.928,4.928 0,1 1,-9.856 0\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M71.855,23.718m-4.928,0a4.928,4.928 0,1 1,9.856 0a4.928,4.928 0,1 1,-9.856 0\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M3.322,37.759m-3.322,0a3.322,3.322 0,1 1,6.644 0a3.322,3.322 0,1 1,-6.644 0\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M91.178,37.759m-3.322,0a3.322,3.322 0,1 1,6.644 0a3.322,3.322 0,1 1,-6.644 0\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_custom_image.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24\"\n  android:viewportHeight=\"24\"\n  >\n  <path\n    android:fillColor=\"@android:color/white\"\n    android:pathData=\"M18,13v7L4,20L4,6h5.02c0.05,-0.71 0.22,-1.38 0.48,-2L4,4c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-5l-2,-2zM16.5,18h-11l2.75,-3.53 1.96,2.36 2.75,-3.54zM19.3,8.89c0.44,-0.7 0.7,-1.51 0.7,-2.39C20,4.01 17.99,2 15.5,2S11,4.01 11,6.5s2.01,4.5 4.49,4.5c0.88,0 1.7,-0.26 2.39,-0.7L21,13.42 22.42,12 19.3,8.89zM15.5,9C14.12,9 13,7.88 13,6.5S14.12,4 15.5,4 18,5.12 18,6.5 16.88,9 15.5,9z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_delete.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8.46,11.88l1.41,-1.41L12,12.59l2.12,-2.12 1.41,1.41L13.41,14l2.12,2.12 -1.41,1.41L12,15.41l-2.12,2.12 -1.41,-1.41L10.59,14l-2.13,-2.12zM15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4z\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_duckduck.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"256dp\"\n    android:height=\"255dp\"\n    android:viewportWidth=\"256\"\n    android:viewportHeight=\"255\">\n  <path\n      android:pathData=\"M128.145,18.841C188.147,18.841 236.788,67.482 236.788,127.484C236.788,187.485 188.147,236.126 128.145,236.126C68.144,236.126 19.503,187.485 19.503,127.484C19.503,67.482 68.144,18.841 128.145,18.841\"\n      android:fillColor=\"#DE5833\"/>\n  <path\n      android:pathData=\"M128.143,254.922C198.526,254.922 255.583,197.865 255.583,127.482C255.583,57.099 198.526,0.042 128.143,0.042C57.76,0.042 0.703,57.099 0.703,127.482C0.703,197.865 57.76,254.922 128.143,254.922L128.143,254.922ZM128.143,244.302C63.625,244.302 11.323,192 11.323,127.482C11.323,62.964 63.625,10.662 128.143,10.662C192.661,10.662 244.963,62.964 244.963,127.482C244.963,192 192.661,244.302 128.143,244.302L128.143,244.302Z\"\n      android:fillColor=\"#DE5833\"/>\n  <path\n      android:pathData=\"M75.219,54.13C75.198,52.303 76.525,51.28 78.288,50.563C77.481,50.695 76.708,50.897 76.012,51.209C74.174,52.042 72.8,55.196 72.813,56.689C81.736,55.787 94.931,56.411 104.58,59.291C105.292,59.191 106.004,59.091 106.727,59.008C97.103,54.735 85.661,53.085 75.219,54.13\"\n      android:fillColor=\"#D5D7D8\"/>\n  <path\n      android:pathData=\"M77.048,43.15C77.249,43.114 77.453,43.089 77.655,43.055C75.73,43.294 74.715,43.982 73.27,44.219C74.839,44.358 80.784,47.133 84.52,48.659C85.044,48.46 85.516,48.239 85.901,47.978C83.964,47.692 79.151,43.342 77.048,43.15\"\n      android:fillColor=\"#D5D7D8\"/>\n  <path\n      android:pathData=\"M86.326,64.756C85.791,64.962 85.283,65.177 84.843,65.408C70.298,73.071 63.873,90.97 67.702,112.418C71.197,131.936 85.522,198.623 92.081,229.989C93.996,230.662 95.934,231.287 97.896,231.854C92.055,203.614 76.112,129.614 72.392,108.831C68.621,87.688 72.29,72.517 86.326,64.756\"\n      android:fillColor=\"#D5D7D8\"/>\n  <path\n      android:pathData=\"M145.184,218.618C144.944,218.778 144.658,218.928 144.353,219.074C144.127,219.95 143.782,220.614 143.279,220.948C140.275,222.941 131.79,223.945 127.31,222.941C126.539,222.77 125.955,222.487 125.499,222.098C117.85,226.345 106.85,231.999 104.616,230.725C101.126,228.724 100.62,202.274 101.126,195.783C101.495,190.883 118.707,198.82 127.08,202.983C128.931,201.254 133.466,200.097 137.48,199.704C131.415,184.943 126.942,168.059 129.684,156.086C125.894,153.445 120.871,147.32 121.917,140.927C122.723,136.061 135.297,126.855 144.2,126.299C153.123,125.736 155.904,125.863 163.338,124.083C163.71,123.994 164.119,123.896 164.547,123.793C169.123,107.706 158.148,79.719 145.906,67.472C141.915,63.481 135.779,60.969 128.865,59.643C126.206,55.994 121.917,52.505 115.845,49.274C104.57,43.288 90.636,40.851 77.655,43.055C77.453,43.089 77.249,43.114 77.048,43.15C79.151,43.342 83.964,47.692 85.901,47.978C85.516,48.239 85.044,48.46 84.52,48.659C82.702,49.351 80.231,49.771 78.288,50.563C76.525,51.28 75.198,52.303 75.219,54.13C85.661,53.085 97.103,54.735 106.727,59.008C106.004,59.091 105.292,59.191 104.58,59.291C97.755,60.251 91.482,62.019 86.95,64.41C86.737,64.521 86.535,64.641 86.326,64.756C72.29,72.517 68.621,87.688 72.392,108.831C76.112,129.614 92.159,204.76 98,233C107.603,235.779 116.582,238 127.08,238C135.992,238 146.711,236.025 155,234C152.089,228.387 148.344,222.18 146.036,217.729C145.728,218.096 145.485,218.417 145.184,218.618ZM151.057,112.057C147.253,112.057 144.15,108.963 144.15,105.137C144.15,101.329 147.253,98.234 151.057,98.234C154.882,98.234 157.973,101.329 157.973,105.137C157.973,108.963 154.882,112.057 151.057,112.057L151.057,112.057ZM155.218,86.049C155.218,86.049 150.861,83.56 147.48,83.602C140.532,83.693 138.64,86.763 138.64,86.763C138.64,86.763 139.806,79.445 148.691,80.913C153.508,81.714 155.218,86.049 155.218,86.049L155.218,86.049ZM81.963,95.046C81.963,95.046 78.834,88.073 87.173,84.656C95.521,81.238 99.586,86.601 99.586,86.601C99.586,86.601 93.524,83.859 87.63,87.563C81.746,91.263 81.963,95.046 81.963,95.046L81.963,95.046ZM89.253,109.908C89.253,105.462 92.848,101.864 97.3,101.864C101.74,101.864 105.34,105.462 105.34,109.908C105.34,114.355 101.74,117.949 97.3,117.949C92.848,117.951 89.253,114.355 89.253,109.908L89.253,109.908Z\"\n      android:fillColor=\"#FFFFFF\"/>\n  <path\n      android:pathData=\"M105.34,109.908C105.34,105.462 101.74,101.864 97.3,101.864C92.848,101.864 89.253,105.462 89.253,109.908C89.253,114.355 92.848,117.951 97.3,117.949C101.74,117.949 105.34,114.355 105.34,109.908L105.34,109.908ZM100.862,109.317C99.72,109.32 98.781,108.381 98.781,107.227C98.781,106.07 99.717,105.139 100.862,105.139C102.016,105.139 102.952,106.07 102.952,107.227C102.952,108.381 102.016,109.317 100.862,109.317L100.862,109.317Z\"\n      android:fillColor=\"#2D4F8E\"/>\n  <path\n      android:pathData=\"M100.862,105.139C99.717,105.139 98.781,106.07 98.781,107.227C98.781,108.381 99.72,109.32 100.862,109.317C102.016,109.317 102.952,108.381 102.952,107.227C102.952,106.07 102.016,105.139 100.862,105.139\"\n      android:fillColor=\"#FFFFFF\"/>\n  <path\n      android:pathData=\"M151.057,98.234C147.253,98.234 144.15,101.329 144.15,105.137C144.15,108.963 147.253,112.057 151.057,112.057C154.882,112.057 157.973,108.963 157.973,105.137C157.973,101.329 154.882,98.234 151.057,98.234L151.057,98.234ZM154.124,104.628C153.149,104.628 152.344,103.835 152.344,102.839C152.344,101.856 153.149,101.049 154.124,101.049C155.141,101.049 155.921,101.856 155.921,102.839C155.921,103.835 155.141,104.628 154.124,104.628L154.124,104.628Z\"\n      android:fillColor=\"#2D4F8E\"/>\n  <path\n      android:pathData=\"M154.124,101.049C153.149,101.049 152.344,101.856 152.344,102.839C152.344,103.835 153.149,104.628 154.124,104.628C155.141,104.628 155.921,103.835 155.921,102.839C155.921,101.856 155.141,101.049 154.124,101.049\"\n      android:fillColor=\"#FFFFFF\"/>\n  <path\n      android:pathData=\"M99.586,86.601C99.586,86.601 95.521,81.238 87.173,84.656C78.834,88.073 81.963,95.046 81.963,95.046C81.963,95.046 81.746,91.263 87.63,87.563C93.524,83.859 99.586,86.601 99.586,86.601\">\n    <aapt:attr name=\"android:fillColor\">\n      <gradient \n          android:startY=\"95.046\"\n          android:startX=\"93.56888\"\n          android:endY=\"95.046\"\n          android:endX=\"78.834\"\n          android:type=\"linear\">\n        <item android:offset=\"0\" android:color=\"#FF394A9F\"/>\n        <item android:offset=\"1\" android:color=\"#FF6176B9\"/>\n      </gradient>\n    </aapt:attr>\n  </path>\n  <path\n      android:pathData=\"M148.691,80.913C139.806,79.445 138.64,86.763 138.64,86.763C138.64,86.763 140.532,83.693 147.48,83.602C150.861,83.56 155.218,86.049 155.218,86.049C155.218,86.049 153.508,81.714 148.691,80.913\">\n    <aapt:attr name=\"android:fillColor\">\n      <gradient \n          android:startY=\"86.763\"\n          android:startX=\"150.41115\"\n          android:endY=\"86.763\"\n          android:endX=\"138.64\"\n          android:type=\"linear\">\n        <item android:offset=\"0\" android:color=\"#FF394A9F\"/>\n        <item android:offset=\"1\" android:color=\"#FF6176B9\"/>\n      </gradient>\n    </aapt:attr>\n  </path>\n  <path\n      android:pathData=\"M144.2,126.299C135.297,126.855 122.723,136.061 121.917,140.927C120.871,147.32 125.894,153.445 129.684,156.086C129.694,156.093 129.705,156.102 129.715,156.109C133.504,158.745 158.733,167.256 171.25,167.02C183.781,166.776 204.361,159.102 202.101,152.953C199.851,146.802 179.412,158.38 158.094,156.404C142.306,154.937 139.519,147.864 143.015,142.698C147.412,136.205 155.421,143.93 168.631,139.977C181.861,136.035 200.363,128.979 207.228,125.137C223.101,116.288 200.586,112.618 195.268,115.073C190.227,117.402 172.681,121.83 164.547,123.793C164.119,123.896 163.71,123.994 163.338,124.083C155.904,125.863 153.123,125.736 144.2,126.299\"\n      android:fillColor=\"#FDD209\"/>\n  <path\n      android:pathData=\"M124.316,206.97C124.316,206.049 125.057,205.234 126.233,204.539C126.266,203.98 126.57,203.46 127.08,202.983C118.707,198.82 101.495,190.883 101.126,195.783C100.62,202.274 101.126,228.724 104.616,230.725C106.85,231.999 117.85,226.345 125.499,222.098C123.292,220.217 124.316,215.651 124.316,206.97\"\n      android:fillColor=\"#65BC46\"/>\n  <path\n      android:pathData=\"M145.925,217.686C145.961,217.7 146,217.715 146.036,217.729C152.89,220.374 166.534,225.353 169.497,224.266C173.492,222.739 172.492,190.813 168.008,189.796C164.416,188.999 150.665,198.688 145.255,202.635C146.212,206.676 147.37,214.68 145.925,217.686\"\n      android:fillColor=\"#65BC46\"/>\n  <path\n      android:pathData=\"M129.214,220.611C124.719,219.615 126.221,215.118 126.221,204.64C126.221,204.606 126.231,204.573 126.233,204.539C125.057,205.234 124.316,206.049 124.316,206.97C124.316,215.651 123.292,220.217 125.499,222.098C125.955,222.487 126.539,222.77 127.31,222.941C131.79,223.945 140.275,222.941 143.279,220.948C143.782,220.614 144.127,219.95 144.353,219.074C140.837,220.758 133.329,221.547 129.214,220.611\"\n      android:fillColor=\"#43A244\"/>\n  <path\n      android:pathData=\"M127.08,202.983C126.57,203.46 126.266,203.98 126.233,204.539C126.231,204.573 126.221,204.606 126.221,204.64C126.221,215.118 124.719,219.615 129.214,220.611C133.329,221.547 140.837,220.758 144.353,219.074C144.658,218.928 144.944,218.778 145.184,218.618C145.485,218.417 145.728,218.096 145.925,217.686C147.37,214.68 146.212,206.676 145.255,202.635C145.044,201.746 144.844,201.046 144.683,200.636C144.27,199.614 141.089,199.351 137.48,199.704C133.466,200.097 128.931,201.254 127.08,202.983\"\n      android:fillColor=\"#65BC46\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_eye.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_eye_no.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"20dp\"\n  android:height=\"20dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24\"\n  android:viewportHeight=\"24\"\n  >\n  <path\n    android:fillColor=\"@android:color/white\"\n    android:pathData=\"M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_film.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:viewportWidth=\"512\"\n  android:viewportHeight=\"512\"\n  >\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M503.467,136.466H191.309l308.215,-51.703c4.625,-0.768 7.757,-5.129 7.014,-9.754L495.761,7.254c-0.358,-2.244 -1.596,-4.25 -3.439,-5.581c-1.843,-1.323 -4.173,-1.86 -6.383,-1.493L129.271,59.248c-4.651,0.768 -7.791,5.163 -7.023,9.813c0.777,4.651 5.12,7.799 9.813,7.023l35.618,-5.905l35.49,46.993l-25.114,4.207c-4.651,0.785 -7.782,5.18 -7.006,9.83c0.469,2.765 2.27,4.898 4.591,6.093c-2.918,1.348 -4.975,4.267 -4.975,7.697c0,4.71 3.823,8.533 8.533,8.533h34.133l-38.4,51.2h-29.867c-4.71,0 -8.533,3.823 -8.533,8.533c0,4.71 3.823,8.533 8.533,8.533h349.867v247.467c0,14.114 -11.486,25.6 -25.6,25.6H42.667c-14.114,0 -25.6,-11.486 -25.6,-25.6v-384H89.6l44.8,59.733l-44.8,59.733H42.667c-4.71,0 -8.533,3.823 -8.533,8.533c0,4.71 3.823,8.533 8.533,8.533h51.2c2.688,0 5.214,-1.263 6.827,-3.413l51.2,-68.267c2.278,-3.029 2.278,-7.211 0,-10.24l-51.2,-68.267c-1.613,-2.15 -4.139,-3.413 -6.827,-3.413H8.533C3.823,68.199 0,72.022 0,76.733v392.533c0,23.526 19.14,42.667 42.667,42.667h426.667c23.526,0 42.667,-19.14 42.667,-42.667V144.999C512,140.289 508.186,136.466 503.467,136.466zM480.256,18.424l8.098,50.91l-33.28,5.581l-34.842,-46.558L480.256,18.424zM401.263,31.497l34.867,46.592l-60.783,10.197l-34.816,-46.729L401.263,31.497zM321.596,44.698l34.842,46.763l-54.997,9.224l-34.987,-46.857L321.596,44.698zM186.684,67.039l60.817,-10.069l35.012,46.891l-60.365,10.129L186.684,67.039zM196.267,204.733l38.4,-51.2h55.467l-38.4,51.2H196.267zM273.067,204.733l38.4,-51.2h55.467l-38.4,51.2H273.067zM349.867,204.733l38.4,-51.2h55.467l-38.4,51.2H349.867zM494.933,204.733h-68.267l38.4,-51.2h29.867V204.733z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_giphy.xml",
    "content": "<vector android:height=\"24dp\" android:viewportHeight=\"20\"\n    android:viewportWidth=\"16.32\" android:width=\"20dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#000\" android:fillType=\"evenOdd\" android:pathData=\"M2.331,2.286L13.99,2.286v15.428L2.33,17.714z\"/>\n    <path android:fillColor=\"#04ff8e\" android:fillType=\"nonZero\" android:pathData=\"M0,1.714h2.331v16.572L0,18.286z\"/>\n    <path android:fillColor=\"#8e2eff\" android:fillType=\"nonZero\" android:pathData=\"M13.989,6.286h2.331v12h-2.331z\"/>\n    <path android:fillColor=\"#00c5ff\" android:fillType=\"nonZero\" android:pathData=\"M0,17.714h16.32L16.32,20L0,20z\"/>\n    <path android:fillColor=\"#fff152\" android:fillType=\"nonZero\" android:pathData=\"M0,0h9.326v2.286L0,2.286z\"/>\n    <path android:fillColor=\"#ff5b5b\" android:fillType=\"nonZero\" android:pathData=\"M13.989,4.571L13.989,2.286h-2.332L11.657,0h-2.331v6.857h6.994L16.32,4.571\"/>\n    <path android:fillColor=\"#551c99\" android:fillType=\"nonZero\" android:pathData=\"M13.989,9.143L13.989,6.857h2.331\"/>\n    <path android:fillColor=\"#999131\" android:fillType=\"evenOdd\" android:pathData=\"M9.326,0v2.286h-2.332\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_github.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"98dp\"\n    android:height=\"96dp\"\n    android:viewportWidth=\"98\"\n    android:viewportHeight=\"96\">\n  <path\n      android:pathData=\"M48.854,0C21.839,0 0,22 0,49.217c0,21.756 13.993,40.172 33.405,46.69 2.427,0.49 3.316,-1.059 3.316,-2.362 0,-1.141 -0.08,-5.052 -0.08,-9.127 -13.59,2.934 -16.42,-5.867 -16.42,-5.867 -2.184,-5.704 -5.42,-7.17 -5.42,-7.17 -4.448,-3.015 0.324,-3.015 0.324,-3.015 4.934,0.326 7.523,5.052 7.523,5.052 4.367,7.496 11.404,5.378 14.235,4.074 0.404,-3.178 1.699,-5.378 3.074,-6.6 -10.839,-1.141 -22.243,-5.378 -22.243,-24.283 0,-5.378 1.94,-9.778 5.014,-13.2 -0.485,-1.222 -2.184,-6.275 0.486,-13.038 0,0 4.125,-1.304 13.426,5.052a46.97,46.97 0,0 1,12.214 -1.63c4.125,0 8.33,0.571 12.213,1.63 9.302,-6.356 13.427,-5.052 13.427,-5.052 2.67,6.763 0.97,11.816 0.485,13.038 3.155,3.422 5.015,7.822 5.015,13.2 0,18.905 -11.404,23.06 -22.324,24.283 1.78,1.548 3.316,4.481 3.316,9.126 0,6.6 -0.08,11.897 -0.08,13.526 0,1.304 0.89,2.853 3.316,2.364 19.412,-6.52 33.405,-24.935 33.405,-46.691C97.707,22 75.788,0 48.854,0z\"\n      android:fillColor=\"#fff\"\n      android:fillType=\"evenOdd\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_google.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  <path\n      android:pathData=\"M23.745,12.27C23.745,11.48 23.675,10.73 23.555,10L12.255,10L12.255,14.51L18.725,14.51C18.435,15.99 17.585,17.24 16.325,18.09L16.325,21.09L20.185,21.09C22.445,19 23.745,15.92 23.745,12.27Z\"\n      android:fillColor=\"#4285F4\"/>\n  <path\n      android:pathData=\"M12.255,24C15.495,24 18.205,22.92 20.185,21.09L16.325,18.09C15.245,18.81 13.875,19.25 12.255,19.25C9.125,19.25 6.475,17.14 5.525,14.29L1.545,14.29L1.545,17.38C3.515,21.3 7.565,24 12.255,24Z\"\n      android:fillColor=\"#34A853\"/>\n  <path\n      android:pathData=\"M5.525,14.29C5.275,13.57 5.145,12.8 5.145,12C5.145,11.2 5.285,10.43 5.525,9.71L5.525,6.62L1.545,6.62C0.725,8.24 0.255,10.06 0.255,12C0.255,13.94 0.725,15.76 1.545,17.38L5.525,14.29Z\"\n      android:fillColor=\"#FBBC05\"/>\n  <path\n      android:pathData=\"M12.255,4.75C14.025,4.75 15.605,5.36 16.855,6.55L20.275,3.13C18.205,1.19 15.495,0 12.255,0C7.565,0 3.515,2.7 1.545,6.62L5.525,9.71C6.475,6.86 9.125,4.75 12.255,4.75Z\"\n      android:fillColor=\"#EA4335\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_history.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_imdb.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"575dp\"\n    android:height=\"289.83dp\"\n    android:viewportWidth=\"575\"\n    android:viewportHeight=\"289.83\">\n  <path\n      android:pathData=\"M575,24.91C573.44,12.15 563.97,1.98 551.91,0C499.05,0 76.18,0 23.32,0C10.11,2.17 0,14.16 0,28.61C0,51.84 0,237.64 0,260.86C0,276.86 12.37,289.83 27.64,289.83C79.63,289.83 495.6,289.83 547.59,289.83C561.65,289.83 573.26,278.82 575,264.57C575,216.64 575,48.87 575,24.91Z\"\n      android:fillColor=\"#f6c700\"\n      android:fillAlpha=\"1\"/>\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"M575,24.91C573.44,12.15 563.97,1.98 551.91,0C499.05,0 76.18,0 23.32,0C10.11,2.17 0,14.16 0,28.61C0,51.84 0,237.64 0,260.86C0,276.86 12.37,289.83 27.64,289.83C79.63,289.83 495.6,289.83 547.59,289.83C561.65,289.83 573.26,278.82 575,264.57C575,216.64 575,48.87 575,24.91Z\"\n      android:strokeAlpha=\"0\"\n      android:strokeWidth=\"1\"\n      android:fillAlpha=\"0\"\n      android:strokeColor=\"#000000\"/>\n  <path\n      android:pathData=\"M69.35,58.24L114.98,58.24L114.98,233.89L69.35,233.89L69.35,58.24Z\"\n      android:fillColor=\"#000000\"\n      android:fillAlpha=\"1\"/>\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"M69.35,58.24L114.98,58.24L114.98,233.89L69.35,233.89L69.35,58.24Z\"\n      android:strokeAlpha=\"0\"\n      android:strokeWidth=\"1\"\n      android:fillAlpha=\"0\"\n      android:strokeColor=\"#000000\"/>\n  <path\n      android:pathData=\"M201.2,139.15C197.28,112.38 195.1,97.5 194.67,94.53C192.76,80.2 190.94,67.73 189.2,57.09C185.25,57.09 165.54,57.09 130.04,57.09L130.04,232.74L170.01,232.74L170.15,116.76L186.97,232.74L215.44,232.74L231.39,114.18L231.54,232.74L271.38,232.74L271.38,57.09L211.77,57.09L201.2,139.15Z\"\n      android:fillColor=\"#000000\"\n      android:fillAlpha=\"1\"/>\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"M201.2,139.15C197.28,112.38 195.1,97.5 194.67,94.53C192.76,80.2 190.94,67.73 189.2,57.09C185.25,57.09 165.54,57.09 130.04,57.09L130.04,232.74L170.01,232.74L170.15,116.76L186.97,232.74L215.44,232.74L231.39,114.18L231.54,232.74L271.38,232.74L271.38,57.09L211.77,57.09L201.2,139.15Z\"\n      android:strokeAlpha=\"0\"\n      android:strokeWidth=\"1\"\n      android:fillAlpha=\"0\"\n      android:strokeColor=\"#000000\"/>\n  <path\n      android:pathData=\"M346.71,93.63C347.21,95.87 347.47,100.95 347.47,108.89C347.47,115.7 347.47,170.18 347.47,176.99C347.47,188.68 346.71,195.84 345.2,198.48C343.68,201.12 339.64,202.43 333.09,202.43C333.09,190.9 333.09,98.66 333.09,87.13C338.06,87.13 341.45,87.66 343.25,88.7C345.05,89.75 346.21,91.39 346.71,93.63ZM367.32,230.95C372.75,229.76 377.31,227.66 381.01,224.67C384.7,221.67 387.29,217.52 388.77,212.21C390.26,206.91 391.14,196.38 391.14,180.63C391.14,174.47 391.14,125.12 391.14,118.95C391.14,102.33 390.49,91.19 389.48,85.53C388.46,79.86 385.93,74.71 381.88,70.09C377.82,65.47 371.9,62.15 364.12,60.13C356.33,58.11 343.63,57.09 321.54,57.09C319.27,57.09 307.93,57.09 287.5,57.09L287.5,232.74L342.78,232.74C355.52,232.34 363.7,231.75 367.32,230.95Z\"\n      android:fillColor=\"#000000\"\n      android:fillAlpha=\"1\"/>\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"M346.71,93.63C347.21,95.87 347.47,100.95 347.47,108.89C347.47,115.7 347.47,170.18 347.47,176.99C347.47,188.68 346.71,195.84 345.2,198.48C343.68,201.12 339.64,202.43 333.09,202.43C333.09,190.9 333.09,98.66 333.09,87.13C338.06,87.13 341.45,87.66 343.25,88.7C345.05,89.75 346.21,91.39 346.71,93.63ZM367.32,230.95C372.75,229.76 377.31,227.66 381.01,224.67C384.7,221.67 387.29,217.52 388.77,212.21C390.26,206.91 391.14,196.38 391.14,180.63C391.14,174.47 391.14,125.12 391.14,118.95C391.14,102.33 390.49,91.19 389.48,85.53C388.46,79.86 385.93,74.71 381.88,70.09C377.82,65.47 371.9,62.15 364.12,60.13C356.33,58.11 343.63,57.09 321.54,57.09C319.27,57.09 307.93,57.09 287.5,57.09L287.5,232.74L342.78,232.74C355.52,232.34 363.7,231.75 367.32,230.95Z\"\n      android:strokeAlpha=\"0\"\n      android:strokeWidth=\"1\"\n      android:fillAlpha=\"0\"\n      android:strokeColor=\"#000000\"/>\n  <path\n      android:pathData=\"M464.76,204.7C463.92,206.93 460.24,208.06 457.46,208.06C454.74,208.06 452.93,206.98 452.01,204.81C451.09,202.65 450.64,197.72 450.64,190C450.64,185.36 450.64,148.22 450.64,143.58C450.64,135.58 451.04,130.59 451.85,128.6C452.65,126.63 454.41,125.63 457.13,125.63C459.91,125.63 463.64,126.76 464.6,129.03C465.55,131.3 466.03,136.15 466.03,143.58C466.03,146.58 466.03,161.58 466.03,188.59C465.74,197.84 465.32,203.21 464.76,204.7ZM406.68,231.21L447.76,231.21C449.47,224.5 450.41,220.77 450.6,220.02C454.32,224.52 458.41,227.9 462.9,230.14C467.37,232.39 474.06,233.51 479.24,233.51C486.45,233.51 492.67,231.62 497.92,227.83C503.16,224.05 506.5,219.57 507.92,214.42C509.34,209.26 510.05,201.42 510.05,190.88C510.05,185.95 510.05,146.53 510.05,141.6C510.05,131 509.81,124.08 509.34,120.83C508.87,117.58 507.47,114.27 505.14,110.88C502.81,107.49 499.42,104.86 494.98,102.98C490.54,101.1 485.3,100.16 479.26,100.16C474.01,100.16 467.29,101.21 462.81,103.28C458.34,105.35 454.28,108.49 450.64,112.7C450.64,108.89 450.64,89.85 450.64,55.56L406.68,55.56L406.68,231.21Z\"\n      android:fillColor=\"#000000\"\n      android:fillAlpha=\"1\"/>\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"M464.76,204.7C463.92,206.93 460.24,208.06 457.46,208.06C454.74,208.06 452.93,206.98 452.01,204.81C451.09,202.65 450.64,197.72 450.64,190C450.64,185.36 450.64,148.22 450.64,143.58C450.64,135.58 451.04,130.59 451.85,128.6C452.65,126.63 454.41,125.63 457.13,125.63C459.91,125.63 463.64,126.76 464.6,129.03C465.55,131.3 466.03,136.15 466.03,143.58C466.03,146.58 466.03,161.58 466.03,188.59C465.74,197.84 465.32,203.21 464.76,204.7ZM406.68,231.21L447.76,231.21C449.47,224.5 450.41,220.77 450.6,220.02C454.32,224.52 458.41,227.9 462.9,230.14C467.37,232.39 474.06,233.51 479.24,233.51C486.45,233.51 492.67,231.62 497.92,227.83C503.16,224.05 506.5,219.57 507.92,214.42C509.34,209.26 510.05,201.42 510.05,190.88C510.05,185.95 510.05,146.53 510.05,141.6C510.05,131 509.81,124.08 509.34,120.83C508.87,117.58 507.47,114.27 505.14,110.88C502.81,107.49 499.42,104.86 494.98,102.98C490.54,101.1 485.3,100.16 479.26,100.16C474.01,100.16 467.29,101.21 462.81,103.28C458.34,105.35 454.28,108.49 450.64,112.7C450.64,108.89 450.64,89.85 450.64,55.56L406.68,55.56L406.68,231.21Z\"\n      android:strokeAlpha=\"0\"\n      android:strokeWidth=\"1\"\n      android:fillAlpha=\"0\"\n      android:strokeColor=\"#000000\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_info.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_insight.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24\"\n  android:viewportHeight=\"24\"\n  >\n  <path\n    android:fillColor=\"@android:color/white\"\n    android:pathData=\"M21,8c-1.45,0 -2.26,1.44 -1.93,2.51l-3.55,3.56c-0.3,-0.09 -0.74,-0.09 -1.04,0l-2.55,-2.55C12.27,10.45 11.46,9 10,9c-1.45,0 -2.27,1.44 -1.93,2.52l-4.56,4.55C2.44,15.74 1,16.55 1,18c0,1.1 0.9,2 2,2c1.45,0 2.26,-1.44 1.93,-2.51l4.55,-4.56c0.3,0.09 0.74,0.09 1.04,0l2.55,2.55C12.73,16.55 13.54,18 15,18c1.45,0 2.27,-1.44 1.93,-2.52l3.56,-3.55C21.56,12.26 23,11.45 23,10C23,8.9 22.1,8 21,8z\"\n    />\n  <path\n    android:fillColor=\"@android:color/white\"\n    android:pathData=\"M15,9l0.94,-2.07l2.06,-0.93l-2.06,-0.93l-0.94,-2.07l-0.92,2.07l-2.08,0.93l2.08,0.93z\"\n    />\n  <path\n    android:fillColor=\"@android:color/white\"\n    android:pathData=\"M3.5,11l0.5,-2l2,-0.5l-2,-0.5l-0.5,-2l-0.5,2l-2,0.5l2,0.5z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_link.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_link_color.xml",
    "content": "<vector android:height=\"24dp\" android:viewportHeight=\"408.759\"\n    android:viewportWidth=\"408.759\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FFBE00\" android:pathData=\"M204.384,408.759c-58.121,0 -113.674,-24.869 -152.417,-68.227C18.459,303.057 0.004,254.703 0.004,204.378C0.004,91.683 91.689,0 204.384,0c50.317,0 98.674,18.453 136.165,51.964c43.346,38.761 68.205,94.314 68.205,152.415C408.755,317.074 317.075,408.759 204.384,408.759\"/>\n    <path android:fillColor=\"#F4502A\" android:pathData=\"M377.752,204.38c0,18.081 -2.77,35.522 -7.911,51.901c-20.391,65.09 -78.11,113.66 -148.079,120.61c-5.721,0.58 -11.51,0.87 -17.38,0.87c-3.551,0 -7.071,-0.111 -10.561,-0.321c-47.13,-2.829 -89.19,-24.488 -118.74,-57.559c-0.26,-0.29 -0.521,-0.589 -0.79,-0.879C47.34,288.44 30.999,248.321 30.999,204.38c0,-95.749 77.621,-173.379 173.381,-173.379c43.95,0 84.069,16.35 114.629,43.291c0.29,0.27 0.591,0.53 0.881,0.79c33.06,29.57 54.71,71.599 57.539,118.731C377.641,197.312 377.752,200.831 377.752,204.38\"/>\n    <path android:fillColor=\"#C29100\" android:pathData=\"M336.32,91.898c0.047,0.056 0.094,0.111 0.142,0.167C336.413,92.007 336.368,91.956 336.32,91.898M335.92,91.432c0.081,0.095 0.164,0.191 0.245,0.286C336.083,91.622 336.003,91.527 335.92,91.432M319.275,74.533c0.206,0.185 0.414,0.367 0.616,0.549c5.678,5.079 11.019,10.524 15.986,16.3C330.732,85.397 325.183,79.766 319.275,74.533M296.518,57.479c0.066,0.041 0.132,0.083 0.198,0.125C296.65,57.563 296.585,57.521 296.518,57.479M296.052,57.188c0.117,0.073 0.234,0.146 0.351,0.219C296.288,57.334 296.167,57.259 296.052,57.188M204.384,30.999c-66.957,0 -125.049,37.955 -153.935,93.516c28.887,-55.563 86.974,-93.514 153.931,-93.514c33.641,0 65.035,9.578 91.618,26.154C269.421,40.577 238.022,30.999 204.384,30.999\"/>\n    <path android:fillColor=\"#BA3D20\" android:pathData=\"M204.382,31.001c-66.957,0 -125.045,37.951 -153.931,93.514c-12.426,23.901 -19.445,51.054 -19.445,79.854c0,53.1 23.858,100.621 61.459,132.419c-25.53,-30.2 -40.919,-69.239 -40.919,-111.88c0,-95.751 77.619,-173.37 173.38,-173.37c42.629,0 81.68,15.39 111.87,40.919c-0.111,-0.131 -0.223,-0.263 -0.334,-0.394c-0.047,-0.056 -0.094,-0.111 -0.142,-0.167c-0.051,-0.06 -0.104,-0.122 -0.154,-0.181c-0.081,-0.095 -0.164,-0.191 -0.245,-0.286c-0.014,-0.016 -0.029,-0.034 -0.043,-0.05c-4.968,-5.776 -10.308,-11.221 -15.986,-16.3c-0.203,-0.182 -0.41,-0.364 -0.616,-0.549c-7.034,-6.23 -14.574,-11.895 -22.559,-16.929c-0.066,-0.041 -0.132,-0.083 -0.198,-0.125c-0.038,-0.024 -0.078,-0.049 -0.116,-0.073c-0.116,-0.073 -0.234,-0.146 -0.351,-0.219c-0.018,-0.011 -0.034,-0.021 -0.052,-0.033C269.417,40.579 238.021,31.001 204.382,31.001\"/>\n    <path android:fillColor=\"#BA3D20\" android:pathData=\"M167.02,201.44L167.02,201.44l-38.89,38.892c-11.12,11.109 -11.12,29.189 0,40.299c5.556,5.556 12.853,8.333 20.15,8.333s14.595,-2.778 20.15,-8.333l32.65,-32.65l6.24,-6.24l0,0c0.132,0.025 0.265,0.049 0.397,0.072l0,0c-0.132,-0.023 -0.265,-0.047 -0.397,-0.072l0,0c-9.671,-1.8 -18.921,-6.441 -26.39,-13.911C173.462,220.361 168.82,211.111 167.02,201.44M192.145,188.156c-0.007,0 -0.013,0 -0.02,0c-1.245,0.001 -2.49,0.083 -3.726,0.246l0,0c-1.119,8.479 1.581,17.369 8.09,23.869c5.548,5.548 12.828,8.327 20.115,8.327c1.251,0 2.503,-0.082 3.746,-0.246l0,0l0,0c0.166,-1.259 0.248,-2.526 0.246,-3.793c-0.012,-7.271 -2.792,-14.532 -8.327,-20.067l0,0c-0.045,-0.045 -0.09,-0.09 -0.135,-0.134c-0.062,-0.061 -0.124,-0.122 -0.186,-0.182c-5.484,-5.323 -12.597,-7.998 -19.717,-8.02C192.202,188.156 192.174,188.156 192.145,188.156M260.48,119.789c-7.297,0 -14.595,2.778 -20.149,8.333l-38.901,38.899c9.68,1.8 18.93,6.439 26.401,13.911c7.469,7.47 12.108,16.719 13.909,26.39l0,0l0,0l38.89,-38.89c11.111,-11.111 11.111,-29.191 0,-40.31C275.076,122.567 267.778,119.789 260.48,119.789M296.19,112.572c19.691,19.69 19.691,51.729 0,71.42l-43.839,43.839c-4.31,4.31 -9.2,7.671 -14.43,10.09c3.136,-6.757 4.695,-14.073 4.68,-21.383c0.015,7.311 -1.544,14.626 -4.68,21.383c-0.147,0.068 -0.295,0.136 -0.443,0.203l0,0c0.148,-0.066 0.296,-0.134 0.443,-0.203l0,0l0,0l0,0l0,0l0,0c-2.421,5.231 -5.78,10.121 -10.09,14.431l-43.841,43.839c-9.845,9.845 -22.778,14.768 -35.71,14.768s-25.865,-4.923 -35.71,-14.768l0,0l81.25,81.25c3.49,0.21 7.01,0.321 10.561,0.321c5.87,0 11.66,-0.29 17.38,-0.87c69.969,-6.951 127.688,-55.521 148.079,-120.61c5.141,-16.379 7.911,-33.821 7.911,-51.901c0,-3.549 -0.111,-7.069 -0.321,-10.568l0,0L296.19,112.572\"/>\n    <path android:fillColor=\"#FFFFFF\" android:pathData=\"M207.32,241.741l-38.89,38.89c-11.111,11.111 -29.19,11.111 -40.301,0c-11.12,-11.109 -11.12,-29.189 0,-40.299l38.89,-38.892l4.951,-4.949c1.939,-1.941 4.09,-3.54 6.38,-4.801c3.139,-1.739 6.559,-2.838 10.049,-3.289c8.48,-1.119 17.369,1.581 23.87,8.09c6.5,6.5 9.2,15.38 8.081,23.86c6.02,-0.77 11.829,-3.47 16.44,-8.081l4.949,-4.949c-1.8,-9.671 -6.439,-18.92 -13.909,-26.39c-7.471,-7.472 -16.721,-12.11 -26.401,-13.911c-1.93,-0.36 -3.88,-0.609 -5.839,-0.741c-8.421,-0.568 -16.95,0.951 -24.75,4.562c-5.23,2.419 -10.12,5.78 -14.429,10.09L112.57,224.77c-19.691,19.69 -19.691,51.731 0,71.42c19.691,19.69 51.729,19.69 71.42,0l43.841,-43.839c4.31,-4.31 7.669,-9.2 10.09,-14.431C228.311,242.382 217.572,243.651 207.32,241.741\"/>\n    <path android:fillColor=\"#C2C2C2\" android:pathData=\"M237.921,237.921c-0.147,0.068 -0.295,0.136 -0.443,0.203c-0.238,0.108 -0.476,0.213 -0.715,0.317c-6.372,2.764 -13.204,4.149 -20.037,4.161c-0.029,0 -0.057,0 -0.085,0l0,0l0,0c-0.014,0 -0.028,0 -0.042,0c-2.974,-0.002 -5.946,-0.265 -8.881,-0.788c-0.132,-0.023 -0.265,-0.047 -0.397,-0.072l0,0l-6.24,6.24l15.56,15.56l11.191,-11.188C232.141,248.042 235.501,243.152 237.921,237.921\"/>\n    <path android:fillColor=\"#8E2E18\" android:pathData=\"M212.134,196.357c0.045,0.044 0.09,0.089 0.135,0.134C212.225,196.447 212.179,196.402 212.134,196.357M192.231,188.156c7.12,0.021 14.233,2.696 19.717,8.02C206.464,190.852 199.352,188.177 192.231,188.156M192.145,188.156c-0.006,0 -0.013,0 -0.02,0C192.133,188.156 192.139,188.156 192.145,188.156L192.145,188.156\"/>\n    <path android:fillColor=\"#C2C2C2\" android:pathData=\"M201.43,167.021l-4.94,4.939c-4.609,4.611 -7.311,10.421 -8.09,16.441l0,0l0,0c1.236,-0.163 2.481,-0.245 3.726,-0.246c0.006,0 0.013,0 0.02,0l0,0l0,0c0.029,0 0.057,0 0.085,0c7.12,0.021 14.233,2.696 19.717,8.02c0.062,0.061 0.124,0.121 0.186,0.182c0.045,0.044 0.09,0.089 0.135,0.134l0,0c5.535,5.535 8.315,12.796 8.327,20.067c0.002,1.267 -0.08,2.535 -0.246,3.793c6.02,-0.77 11.829,-3.47 16.44,-8.081l4.949,-4.949c-1.8,-9.671 -6.439,-18.92 -13.909,-26.39C220.359,173.46 211.11,168.822 201.43,167.021\"/>\n    <path android:fillColor=\"#1CA4BA\" android:pathData=\"M296.19,112.572c-19.689,-19.69 -51.729,-19.69 -71.418,0l-43.841,43.839c-4.31,4.31 -7.671,9.2 -10.09,14.432c-4.46,9.61 -5.73,20.35 -3.821,30.598c1.8,9.671 6.441,18.921 13.911,26.39c7.47,7.47 16.719,12.11 26.39,13.911l4.949,-4.949c4.611,-4.611 7.311,-10.42 8.081,-16.44c-8.48,1.119 -17.36,-1.581 -23.86,-8.081c-6.509,-6.5 -9.209,-15.39 -8.09,-23.869c0.779,-6.02 3.481,-11.83 8.09,-16.441l43.841,-43.838c11.109,-11.111 29.19,-11.111 40.299,0c11.111,11.118 11.111,29.199 0,40.31l-38.89,38.89c1.911,10.249 0.641,20.989 -3.819,30.599c5.23,-2.419 10.12,-5.78 14.43,-10.09l43.839,-43.839C315.881,164.301 315.881,132.262 296.19,112.572\"/>\n    <path android:fillColor=\"#FFFFFF\" android:pathData=\"M237.921,237.921c-9.61,4.461 -20.35,5.73 -30.6,3.82l4.949,-4.949c4.611,-4.611 7.311,-10.42 8.081,-16.44c6.02,-0.77 11.829,-3.47 16.44,-8.081l4.949,-4.949C243.651,217.571 242.38,228.311 237.921,237.921\"/>\n    <path android:fillColor=\"#C2C2C2\" android:pathData=\"M241.74,207.322L241.74,207.322l-6.545,0.398c-4.611,4.611 -8.875,2.438 -14.845,12.632l0,0c-0.77,6.02 -3.47,11.83 -8.081,16.44l-4.949,4.949c0.5,2.979 4.627,5.479 7.749,5.479c7.276,0 19.25,-1.5 22.851,-9.299c3.123,-6.763 4.695,-14.073 4.68,-21.383C242.594,213.451 242.307,210.365 241.74,207.322L241.74,207.322\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_list_alt.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M19,5v14L5,19L5,5h14m1.1,-2L3.9,3c-0.5,0 -0.9,0.4 -0.9,0.9v16.2c0,0.4 0.4,0.9 0.9,0.9h16.2c0.4,0 0.9,-0.5 0.9,-0.9L21,3.9c0,-0.5 -0.5,-0.9 -0.9,-0.9zM11,7h6v2h-6L11,7zM11,11h6v2h-6v-2zM11,15h6v2h-6zM7,7h2v2L7,9zM7,11h2v2L7,13zM7,15h2v2L7,17z\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_lists.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"18dp\"\n  android:height=\"18dp\"\n  android:viewportWidth=\"512.18\"\n  android:viewportHeight=\"512.18\"\n  >\n  <path\n    android:fillColor=\"#FFFFFFFF\"\n    android:pathData=\"M448.18,80h-320c-17.673,0 -32,14.327 -32,32s14.327,32 32,32h320c17.673,0 32,-14.327 32,-32S465.853,80 448.18,80z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFFFF\"\n    android:pathData=\"M64.18,112c-0.036,-8.473 -3.431,-16.586 -9.44,-22.56c-12.481,-12.407 -32.639,-12.407 -45.12,0C3.61,95.414 0.215,103.527 0.18,112c-0.239,2.073 -0.239,4.167 0,6.24c0.362,2.085 0.952,4.124 1.76,6.08c0.859,1.895 1.876,3.715 3.04,5.44c1.149,1.794 2.49,3.457 4,4.96c1.456,1.45 3.065,2.738 4.8,3.84c1.685,1.227 3.512,2.248 5.44,3.04c2.121,1.1 4.382,1.908 6.72,2.4c2.073,0.232 4.167,0.232 6.24,0c8.45,0.007 16.56,-3.329 22.56,-9.28c1.51,-1.503 2.851,-3.166 4,-4.96c1.164,-1.725 2.181,-3.545 3.04,-5.44c1.021,-1.932 1.826,-3.971 2.4,-6.08C64.419,116.167 64.419,114.073 64.18,112z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFFFF\"\n    android:pathData=\"M64.18,256c0.238,-2.073 0.238,-4.167 0,-6.24c-0.553,-2.065 -1.359,-4.053 -2.4,-5.92c-0.824,-1.963 -1.843,-3.839 -3.04,-5.6c-1.109,-1.774 -2.455,-3.389 -4,-4.8c-12.481,-12.407 -32.639,-12.407 -45.12,0C3.61,239.414 0.215,247.527 0.18,256c0.062,4.217 0.875,8.388 2.4,12.32c0.802,1.893 1.766,3.713 2.88,5.44c1.217,1.739 2.611,3.348 4.16,4.8c1.414,1.542 3.028,2.888 4.8,4c1.685,1.228 3.511,2.249 5.44,3.04c1.951,0.821 3.992,1.412 6.08,1.76c2.047,0.459 4.142,0.674 6.24,0.64c2.073,0.239 4.167,0.239 6.24,0c2.036,-0.349 4.024,-0.94 5.92,-1.76c1.981,-0.786 3.861,-1.807 5.6,-3.04c1.772,-1.112 3.386,-2.458 4.8,-4c1.542,-1.414 2.888,-3.028 4,-4.8c1.23,-1.684 2.251,-3.51 3.04,-5.44c1.093,-2.124 1.9,-4.384 2.4,-6.72C64.426,260.167 64.426,258.073 64.18,256z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFFFF\"\n    android:pathData=\"M64.18,400c0.237,-2.073 0.237,-4.167 0,-6.24c-0.553,-2.116 -1.359,-4.157 -2.4,-6.08c-0.859,-1.895 -1.876,-3.715 -3.04,-5.44c-1.112,-1.772 -2.458,-3.386 -4,-4.8c-12.481,-12.407 -32.639,-12.407 -45.12,0c-1.542,1.414 -2.888,3.028 -4,4.8c-1.164,1.725 -2.181,3.545 -3.04,5.44c-0.83,1.948 -1.421,3.99 -1.76,6.08c-0.451,2.049 -0.665,4.142 -0.64,6.24c0.036,8.473 3.431,16.586 9.44,22.56c1.414,1.542 3.028,2.888 4.8,4c1.685,1.228 3.511,2.249 5.44,3.04c1.951,0.821 3.992,1.412 6.08,1.76c2.047,0.459 4.142,0.674 6.24,0.64c2.073,0.239 4.167,0.239 6.24,0c2.036,-0.349 4.024,-0.94 5.92,-1.76c1.981,-0.786 3.861,-1.807 5.6,-3.04c1.772,-1.112 3.386,-2.458 4.8,-4c1.542,-1.414 2.888,-3.028 4,-4.8c1.231,-1.683 2.252,-3.51 3.04,-5.44c1.092,-2.125 1.899,-4.384 2.4,-6.72C64.426,404.167 64.426,402.073 64.18,400z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFFFF\"\n    android:pathData=\"M480.18,224h-352c-17.673,0 -32,14.327 -32,32s14.327,32 32,32h352c17.673,0 32,-14.327 32,-32S497.853,224 480.18,224z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFFFF\"\n    android:pathData=\"M336.18,368h-208c-17.673,0 -32,14.327 -32,32c0,17.673 14.327,32 32,32h208c17.673,0 32,-14.327 32,-32C368.18,382.327 353.853,368 336.18,368z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_news.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24\"\n  android:viewportHeight=\"24\"\n  >\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-0.61 0.08,-1.21 0.21,-1.78L8.99,15v1c0,1.1 0.9,2 2,2v1.93C7.06,19.43 4,16.07 4,12zM17.89,17.4c-0.26,-0.81 -1,-1.4 -1.9,-1.4h-1v-3c0,-0.55 -0.45,-1 -1,-1h-6v-2h2c0.55,0 1,-0.45 1,-1L10.99,7h2c1.1,0 2,-0.9 2,-2v-0.41C17.92,5.77 20,8.65 20,12c0,2.08 -0.81,3.98 -2.11,5.4z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_notification_bell.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"100dp\"\n  android:height=\"100dp\"\n  android:viewportWidth=\"512\"\n  android:viewportHeight=\"512\"\n  >\n  <path\n    android:fillColor=\"#FFAA00\"\n    android:pathData=\"M256,100.17c-27.62,0 -50.09,-22.47 -50.09,-50.09S228.38,0 256,0s50.09,22.47 50.09,50.09S283.62,100.17 256,100.17zM256,33.39c-9.2,0 -16.7,7.5 -16.7,16.7s7.5,16.7 16.7,16.7c9.2,0 16.7,-7.5 16.7,-16.7S265.2,33.39 256,33.39z\"\n    />\n  <path\n    android:fillColor=\"#F28D00\"\n    android:pathData=\"M256.01,0v33.39c9.19,0 16.69,7.5 16.69,16.69s-7.5,16.69 -16.69,16.69v33.39c27.62,-0 50.08,-22.47 50.08,-50.09S283.62,0 256.01,0z\"\n    />\n  <path\n    android:fillColor=\"#FFAA00\"\n    android:pathData=\"M256,512c-46.04,0 -83.48,-37.44 -83.48,-83.48c0,-9.23 7.47,-16.7 16.7,-16.7h133.57c9.23,0 16.7,7.47 16.7,16.7C339.48,474.57 302.04,512 256,512z\"\n    />\n  <path\n    android:fillColor=\"#F28D00\"\n    android:pathData=\"M322.78,411.83h-66.78V512c46.04,-0 83.47,-37.44 83.47,-83.48C339.48,419.29 332.01,411.83 322.78,411.83z\"\n    />\n  <path\n    android:fillColor=\"#FFDA44\"\n    android:pathData=\"M439.65,348.11v-97.68C439.64,149 357.43,66.79 256,66.78C154.57,66.79 72.36,149 72.35,250.43v97.68c-19.41,6.9 -33.38,25.23 -33.39,47.02c0.01,27.67 22.42,50.08 50.09,50.08h333.91c27.67,-0.01 50.08,-22.42 50.09,-50.08C473.04,373.35 459.06,355.01 439.65,348.11z\"\n    />\n  <path\n    android:fillColor=\"#FFAA00\"\n    android:pathData=\"M439.65,348.11v-97.68C439.64,149 357.43,66.79 256,66.78v378.43h166.96c27.67,-0.01 50.08,-22.42 50.09,-50.08C473.04,373.35 459.06,355.01 439.65,348.11z\"\n    />\n  <path\n    android:fillColor=\"#FFF3DB\"\n    android:pathData=\"M155.83,267.13c-9.23,0 -16.7,-7.47 -16.7,-16.7c0,-47.02 28.01,-89.28 71.38,-107.64c8.45,-3.59 18.29,0.33 21.88,8.84c3.62,8.51 -0.36,18.29 -8.84,21.88c-31.01,13.14 -51.03,43.34 -51.03,76.93C172.52,259.66 165.05,267.13 155.83,267.13z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_open_browser.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h4v-2L5,18L5,8h14v10h-4v2h4c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.89,-2 -2,-2zM12,10l-4,4h3v6h2v-6h3l-4,-4z\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_pause.xml",
    "content": "<vector\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportHeight=\"24\"\n  android:viewportWidth=\"24\"\n  android:width=\"24dp\"\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  >\n  <path\n    android:fillColor=\"@android:color/white\"\n    android:pathData=\"M9,16h2L11,8L9,8v8zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM13,16h2L15,8h-2v8z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_person_outline.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M12,5.9c1.16,0 2.1,0.94 2.1,2.1s-0.94,2.1 -2.1,2.1S9.9,9.16 9.9,8s0.94,-2.1 2.1,-2.1m0,9c2.97,0 6.1,1.46 6.1,2.1v1.1L5.9,18.1L5.9,17c0,-0.64 3.13,-2.1 6.1,-2.1M12,4C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,13c-2.67,0 -8,1.34 -8,4v3h16v-3c0,-2.66 -5.33,-4 -8,-4z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_pin.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  <path\n      android:fillColor=\"#FFFFFF\"\n      android:pathData=\"M7,2h10v2l-2,1v5l3,3v3h-5v4l-1,3l-1,-3v-4L6,16v-3l3,-3L9,5L7,4L7,2zM13,4h-2v6.828l-3,3L8,14h8v-0.172l-3,-3L13,4z\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_play.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M10,16.5l6,-4.5 -6,-4.5v9zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_remove_list.xml",
    "content": "<vector\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportHeight=\"24\"\n  android:viewportWidth=\"24\"\n  android:width=\"24dp\"\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  >\n  <path\n    android:fillColor=\"@android:color/white\"\n    android:pathData=\"M14,10H3v2h11V10zM14,6H3v2h11V6zM3,16h7v-2H3V16zM14.41,22L17,19.41L19.59,22L21,20.59L18.41,18L21,15.41L19.59,14L17,16.59L14.41,14L13,15.41L15.59,18L13,20.59L14.41,22z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_search.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_settings.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24\"\n  android:viewportHeight=\"24\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_share.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_star.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_star_empty.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24.0\" android:viewportWidth=\"24.0\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_star_small.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"12dp\"\n  android:height=\"12dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_stars_round.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24.0\" android:viewportWidth=\"24.0\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM16.23,18L12,15.45 7.77,18l1.12,-4.81 -3.73,-3.23 4.92,-0.42L12,5l1.92,4.53 4.92,0.42 -3.73,3.23L16.23,18z\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_television.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:viewportWidth=\"512\"\n  android:viewportHeight=\"512\"\n  >\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M435.457,443.735h-0.085c-4.71,0 -8.491,3.823 -8.491,8.533c0,4.71 3.866,8.533 8.576,8.533c4.719,0 8.533,-3.823 8.533,-8.533C443.99,447.558 440.176,443.735 435.457,443.735z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M307.201,204.802c-4.71,0 -8.525,3.814 -8.533,8.525c0,4.71 3.814,8.533 8.525,8.542c9.267,0.017 24.192,2.125 28.109,6.033c3.908,3.917 6.016,18.842 6.033,28.109c0.008,4.71 3.823,8.525 8.533,8.525c4.719,-0.008 8.533,-3.823 8.533,-8.533c0,-4.864 -0.529,-29.67 -11.034,-40.166C336.871,205.331 312.065,204.802 307.201,204.802z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M351.941,162.391c-1.442,-0.358 -36.233,-8.789 -130.074,-8.789s-128.631,8.431 -129.835,8.73c-25.429,5.615 -42.411,22.519 -49.109,48.93c-0.358,1.442 -8.789,35.942 -8.789,104.474s8.431,103.031 8.841,104.653c7.1,25.975 23.04,41.899 48.819,48.691c1.442,0.358 36.233,8.789 130.074,8.789s128.631,-8.431 129.715,-8.713c24.201,-4.949 41.19,-21.751 49.229,-48.947c0.358,-1.442 8.789,-35.942 8.789,-104.474s-8.431,-103.031 -8.721,-104.175C395.478,186.148 378.565,169.15 351.941,162.391zM384.351,415.712c-6.212,21 -18.057,33.015 -36.54,36.804c-0.333,0.085 -34.773,8.286 -125.943,8.286s-125.611,-8.201 -125.824,-8.252c-19.814,-5.222 -31.104,-16.529 -36.565,-36.471c-0.077,-0.341 -8.277,-34.21 -8.277,-100.343s8.201,-100.002 8.269,-100.301c5.146,-20.275 16.998,-32.188 36.454,-36.48c0.333,-0.085 34.773,-8.286 125.943,-8.286s125.611,8.201 125.901,8.269c20.437,5.188 32.35,17.024 36.488,36.454c0.077,0.341 8.277,34.21 8.277,100.343C392.534,381.843 384.342,415.712 384.351,415.712z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M349.868,281.602c-4.71,0 -8.533,3.823 -8.533,8.533v8.533c0,4.71 3.823,8.533 8.533,8.533s8.533,-3.823 8.533,-8.533v-8.533C358.401,285.425 354.578,281.602 349.868,281.602z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M200.099,75.633c-3.968,4.224 -7.569,8.832 -10.539,13.961c-2.364,4.079 -0.973,9.293 3.106,11.656c4.087,2.364 9.293,0.973 11.657,-3.106c10.675,-18.423 30.481,-29.875 51.678,-29.875c21.197,0 41.003,11.452 51.678,29.875c1.579,2.731 4.446,4.258 7.39,4.258c1.451,0 2.918,-0.367 4.267,-1.152c4.079,-2.364 5.47,-7.578 3.106,-11.656c-2.97,-5.129 -6.571,-9.737 -10.539,-13.961l61.065,-61.065c3.337,-3.337 3.337,-8.73 0,-12.066c-3.337,-3.336 -8.73,-3.336 -12.066,0l-62.054,62.046c-12.501,-8.457 -27.341,-13.346 -42.846,-13.346s-30.345,4.89 -42.846,13.346L151.101,2.502c-3.337,-3.336 -8.73,-3.336 -12.066,0c-3.337,3.337 -3.337,8.73 0,12.066L200.099,75.633z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M452.268,221.869c14.114,0 25.6,-11.486 25.6,-25.6s-11.486,-25.6 -25.6,-25.6c-14.114,0 -25.6,11.486 -25.6,25.6S438.154,221.869 452.268,221.869zM452.268,187.735c4.702,0 8.533,3.831 8.533,8.533c0,4.702 -3.831,8.533 -8.533,8.533c-4.702,0 -8.533,-3.831 -8.533,-8.533C443.734,191.567 447.566,187.735 452.268,187.735z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M469.59,341.336h-0.085c-4.71,0 -8.491,3.823 -8.491,8.533s3.866,8.533 8.576,8.533c4.719,0 8.533,-3.823 8.533,-8.533S474.309,341.336 469.59,341.336z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M469.59,375.469h-0.085c-4.71,0 -8.491,3.823 -8.491,8.533s3.866,8.533 8.576,8.533c4.719,0 8.533,-3.823 8.533,-8.533S474.309,375.469 469.59,375.469z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M469.59,409.602h-0.085c-4.71,0 -8.491,3.823 -8.491,8.533s3.866,8.533 8.576,8.533c4.719,0 8.533,-3.823 8.533,-8.533S474.309,409.602 469.59,409.602z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M460.801,119.469h-409.6c-28.237,0 -51.2,22.963 -51.2,51.2v290.133c0,28.237 22.963,51.2 51.2,51.2h409.6c28.237,0 51.2,-22.963 51.2,-51.2V170.669C512.001,142.432 489.038,119.469 460.801,119.469zM494.934,460.802c0,18.825 -15.309,34.133 -34.133,34.133h-409.6c-18.825,0 -34.133,-15.309 -34.133,-34.133V170.669c0,-18.825 15.309,-34.133 34.133,-34.133h409.6c18.825,0 34.133,15.309 34.133,34.133V460.802z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M469.59,443.735h-0.085c-4.71,0 -8.491,3.823 -8.491,8.533c0,4.71 3.866,8.533 8.576,8.533c4.719,0 8.533,-3.823 8.533,-8.533C478.124,447.558 474.309,443.735 469.59,443.735z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M435.457,409.602h-0.085c-4.71,0 -8.491,3.823 -8.491,8.533s3.866,8.533 8.576,8.533c4.719,0 8.533,-3.823 8.533,-8.533S440.176,409.602 435.457,409.602z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M435.457,375.469h-0.085c-4.71,0 -8.491,3.823 -8.491,8.533s3.866,8.533 8.576,8.533c4.719,0 8.533,-3.823 8.533,-8.533S440.176,375.469 435.457,375.469z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M477.868,264.535c0,-14.114 -11.486,-25.6 -25.6,-25.6c-14.114,0 -25.6,11.486 -25.6,25.6s11.486,25.6 25.6,25.6C466.382,290.135 477.868,278.65 477.868,264.535zM452.268,273.069c-4.702,0 -8.533,-3.831 -8.533,-8.533c0,-4.702 3.831,-8.533 8.533,-8.533c4.702,0 8.533,3.831 8.533,8.533C460.801,269.237 456.97,273.069 452.268,273.069z\"\n    />\n  <path\n    android:fillColor=\"#FFFFFF\"\n    android:pathData=\"M435.457,341.336h-0.085c-4.71,0 -8.491,3.823 -8.491,8.533s3.866,8.533 8.576,8.533c4.719,0 8.533,-3.823 8.533,-8.533S440.176,341.336 435.457,341.336z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_tmdb.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:aapt=\"http://schemas.android.com/aapt\"\n  android:width=\"190.24dp\"\n  android:height=\"81.52dp\"\n  android:viewportWidth=\"190.24\"\n  android:viewportHeight=\"81.52\"\n  >\n  <path android:pathData=\"M105.67,36.06h66.9A17.67,17.67 0,0 0,190.24 18.4h0A17.67,17.67 0,0 0,172.57 0.73h-66.9A17.67,17.67 0,0 0,88 18.4h0A17.67,17.67 0,0 0,105.67 36.06ZM17.67,81.06h76.9A17.67,17.67 0,0 0,112.24 63.4h0A17.67,17.67 0,0 0,94.57 45.73L17.67,45.73A17.67,17.67 0,0 0,0 63.4L0,63.4A17.67,17.67 0,0 0,17.67 81.06ZM10.41,35.42h7.8L18.21,6.92h10.1L28.31,0L0.31,0v6.9h10.1ZM38.51,35.42h7.8L46.31,8.25h0.1l9,27.15h6l9.3,-27.15h0.1L70.81,35.4h7.8L78.61,0L66.76,0l-8.2,23.1h-0.1L50.31,0L38.51,0ZM152.43,55.67a15.07,15.07 0,0 0,-4.52 -5.52,18.57 18.57,0 0,0 -6.68,-3.08 33.54,33.54 0,0 0,-8.07 -1h-11.7v35.4h12.75a24.58,24.58 0,0 0,7.55 -1.15A19.34,19.34 0,0 0,148.11 77a16.27,16.27 0,0 0,4.37 -5.5,16.91 16.91,0 0,0 1.63,-7.58A18.5,18.5 0,0 0,152.43 55.67ZM145,68.6A8.8,8.8 0,0 1,142.36 72a10.7,10.7 0,0 1,-4 1.82,21.57 21.57,0 0,1 -5,0.55h-4.05v-21h4.6a17,17 0,0 1,4.67 0.63,11.66 11.66,0 0,1 3.88,1.87A9.14,9.14 0,0 1,145 59a9.87,9.87 0,0 1,1 4.52A11.89,11.89 0,0 1,145 68.6ZM189.63,68.47a8,8 0,0 0,-1.58 -2.62A8.38,8.38 0,0 0,185.63 64a10.31,10.31 0,0 0,-3.17 -1v-0.1a9.22,9.22 0,0 0,4.42 -2.82,7.43 7.43,0 0,0 1.68,-5 8.42,8.42 0,0 0,-1.15 -4.65,8.09 8.09,0 0,0 -3,-2.72 12.56,12.56 0,0 0,-4.18 -1.3,32.84 32.84,0 0,0 -4.62,-0.33h-13.2v35.4h14.5a22.41,22.41 0,0 0,4.72 -0.5,13.53 13.53,0 0,0 4.28,-1.65 9.42,9.42 0,0 0,3.1 -3,8.52 8.52,0 0,0 1.2,-4.68A9.39,9.39 0,0 0,189.66 68.47ZM170.21,52.72h5.3a10,10 0,0 1,1.85 0.18,6.18 6.18,0 0,1 1.7,0.57 3.39,3.39 0,0 1,1.22 1.13,3.22 3.22,0 0,1 0.48,1.82 3.63,3.63 0,0 1,-0.43 1.8,3.4 3.4,0 0,1 -1.12,1.2 4.92,4.92 0,0 1,-1.58 0.65,7.51 7.51,0 0,1 -1.77,0.2h-5.65ZM181.93,72.72a3.9,3.9 0,0 1,-1.22 1.3,4.64 4.64,0 0,1 -1.68,0.7 8.18,8.18 0,0 1,-1.82 0.2h-7v-8h5.9a15.35,15.35 0,0 1,2 0.15,8.47 8.47,0 0,1 2.05,0.55 4,4 0,0 1,1.57 1.18,3.11 3.11,0 0,1 0.63,2A3.71,3.71 0,0 1,181.93 72.72Z\">\n    <aapt:attr name=\"android:fillColor\">\n      <gradient\n        android:endX=\"190.24\"\n        android:endY=\"40.76\"\n        android:startX=\"0\"\n        android:startY=\"40.76\"\n        android:type=\"linear\"\n        >\n        <item\n          android:color=\"#FF90CEA1\"\n          android:offset=\"0\"\n          />\n        <item\n          android:color=\"#FF3CBEC9\"\n          android:offset=\"0.56\"\n          />\n        <item\n          android:color=\"#FF00B3E5\"\n          android:offset=\"1\"\n          />\n      </gradient>\n    </aapt:attr>\n  </path>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_trakt.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:viewportWidth=\"144.8\"\n  android:viewportHeight=\"144.8\"\n  >\n  <path\n    android:fillColor=\"#ED2224\"\n    android:pathData=\"M29.5,111.8c10.6,11.6 25.9,18.8 42.9,18.8c8.7,0 16.9,-1.9 24.3,-5.3L56.3,85L29.5,111.8z\"\n    />\n  <path\n    android:fillColor=\"#ED2224\"\n    android:pathData=\"M56.1,60.6L25.5,91.1L21.4,87l32.2,-32.2h0l37.6,-37.6c-5.9,-2 -12.2,-3.1 -18.8,-3.1c-32.2,0 -58.3,26.1 -58.3,58.3c0,13.1 4.3,25.2 11.7,35l30.5,-30.5l2.1,2l43.7,43.7c0.9,-0.5 1.7,-1 2.5,-1.6L56.3,72.7L27,102l-4.1,-4.1l33.4,-33.4l2.1,2l51,50.9c0.8,-0.6 1.5,-1.3 2.2,-1.9l-55,-55L56.1,60.6z\"\n    />\n  <path\n    android:fillColor=\"#ED1C24\"\n    android:pathData=\"M115.7,111.4c9.3,-10.3 15,-24 15,-39c0,-23.4 -13.8,-43.5 -33.6,-52.8L60.4,56.2L115.7,111.4zM74.5,66.8l-4.1,-4.1l28.9,-28.9l4.1,4.1L74.5,66.8zM101.9,27.1L68.6,60.4l-4.1,-4.1L97.8,23L101.9,27.1z\"\n    />\n  <path\n    android:fillColor=\"#ED2224\"\n    android:pathData=\"M72.4,144.8C32.5,144.8 0,112.3 0,72.4C0,32.5 32.5,0 72.4,0s72.4,32.5 72.4,72.4C144.8,112.3 112.3,144.8 72.4,144.8zM72.4,7.3C36.5,7.3 7.3,36.5 7.3,72.4s29.2,65.1 65.1,65.1s65.1,-29.2 65.1,-65.1S108.3,7.3 72.4,7.3z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_twitter.xml",
    "content": "<vector\n  android:height=\"50dp\"\n  android:viewportHeight=\"400\"\n  android:viewportWidth=\"400\"\n  android:width=\"50dp\"\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  >\n    <path\n      android:fillColor=\"#1DA1F2\"\n      android:pathData=\"M200,200m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0\"\n      />\n    <path\n      android:fillColor=\"#FFFFFF\"\n      android:pathData=\"M163.4,305.5c88.7,0 137.2,-73.5 137.2,-137.2c0,-2.1 0,-4.2 -0.1,-6.2c9.4,-6.8 17.6,-15.3 24.1,-25c-8.6,3.8 -17.9,6.4 -27.7,7.6c10,-6 17.6,-15.4 21.2,-26.7c-9.3,5.5 -19.6,9.5 -30.6,11.7c-8.8,-9.4 -21.3,-15.2 -35.2,-15.2c-26.6,0 -48.2,21.6 -48.2,48.2c0,3.8 0.4,7.5 1.3,11c-40.1,-2 -75.6,-21.2 -99.4,-50.4c-4.1,7.1 -6.5,15.4 -6.5,24.2c0,16.7 8.5,31.5 21.5,40.1c-7.9,-0.2 -15.3,-2.4 -21.8,-6c0,0.2 0,0.4 0,0.6c0,23.4 16.6,42.8 38.7,47.3c-4,1.1 -8.3,1.7 -12.7,1.7c-3.1,0 -6.1,-0.3 -9.1,-0.9c6.1,19.2 23.9,33.1 45,33.5c-16.5,12.9 -37.3,20.6 -59.9,20.6c-3.9,0 -7.7,-0.2 -11.5,-0.7C110.8,297.5 136.2,305.5 163.4,305.5\"\n      />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_view_grid.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#000000\"\n  android:viewportWidth=\"24\"\n  android:viewportHeight=\"24\"\n  >\n  <path\n    android:fillColor=\"#000000\"\n    android:fillType=\"evenOdd\"\n    android:pathData=\"M3,3v8h8L11,3L3,3zM9,9L5,9L5,5h4v4zM3,13v8h8v-8L3,13zM9,19L5,19v-4h4v4zM13,3v8h8L21,3h-8zM19,9h-4L15,5h4v4zM13,13v8h8v-8h-8zM19,19h-4v-4h4v4z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_view_list.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#000000\"\n  android:viewportWidth=\"24\"\n  android:viewportHeight=\"24\"\n  >\n  <path\n    android:fillColor=\"@android:color/white\"\n    android:pathData=\"M19,13H5c-1.1,0 -2,0.9 -2,2v4c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-4C21,13.9 20.1,13 19,13zM19,19H5v-4h14V19z\"\n    />\n  <path\n    android:fillColor=\"@android:color/white\"\n    android:pathData=\"M19,3H5C3.9,3 3,3.9 3,5v4c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM19,9H5V5h14V9z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_wikipedia.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"128dp\"\n  android:height=\"128dp\"\n  android:viewportWidth=\"128\"\n  android:viewportHeight=\"128\"\n  >\n  <path\n      android:pathData=\"M96.869,23.909L96.869,26.048C94.047,26.549 91.912,27.436 90.462,28.707C88.385,30.596 85.937,33.486 84.33,37.379L51.645,104.091L49.47,104.091L16.657,36.512C15.129,33.043 13.051,30.923 12.424,30.153C11.445,28.958 10.24,28.023 8.81,27.349C7.379,26.674 5.449,26.241 3.02,26.048L3.02,23.909L34.948,23.909L34.948,26.048C31.265,26.395 29.509,27.012 28.411,27.898C27.314,28.784 26.766,29.921 26.766,31.309C26.766,33.236 27.667,36.242 29.469,40.327L53.702,86.286L77.394,40.905C79.236,36.435 80.764,33.332 80.764,31.598C80.764,30.48 80.196,29.411 79.06,28.389C77.923,27.368 76.637,26.645 73.934,26.221C73.738,26.183 73.405,26.125 72.934,26.048L72.934,23.909L96.869,23.909z\"\n      android:strokeLineJoin=\"miter\"\n      android:strokeWidth=\"1\"\n      android:fillColor=\"#ffffff\"\n      android:strokeColor=\"#ffffff\"\n      android:strokeLineCap=\"butt\"/>\n  <path\n      android:pathData=\"M124.98,23.909L124.98,26.048C122.159,26.549 120.023,27.436 118.573,28.707C116.497,30.596 114.048,33.486 112.441,37.379L83.756,104.091L81.581,104.091L51.268,36.512C49.74,33.043 47.662,30.923 47.036,30.153C46.056,28.958 44.851,28.023 43.421,27.349C41.991,26.674 40.695,26.241 38.265,26.048L38.265,23.909L69.559,23.909L69.559,26.048C65.876,26.395 64.12,27.012 63.023,27.898C61.925,28.784 61.377,29.921 61.377,31.309C61.377,33.236 62.278,36.242 64.081,40.327L85.813,86.286L105.506,40.905C107.347,36.435 108.875,33.332 108.875,31.598C108.875,30.48 108.307,29.411 107.171,28.389C106.034,27.368 104.114,26.645 101.411,26.221C101.215,26.183 100.882,26.125 100.411,26.048L100.411,23.909L124.98,23.909z\"\n      android:strokeLineJoin=\"miter\"\n      android:strokeWidth=\"1\"\n      android:fillColor=\"#ffffff\"\n      android:strokeColor=\"#ffffff\"\n      android:strokeLineCap=\"butt\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable/ic_youtube.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"159dp\"\n    android:height=\"110dp\"\n    android:viewportWidth=\"159\"\n    android:viewportHeight=\"110\">\n  <path\n      android:pathData=\"m154,17.5c-1.82,-6.73 -7.07,-12 -13.8,-13.8 -9.04,-3.49 -96.6,-5.2 -122,0.1 -6.73,1.82 -12,7.07 -13.8,13.8 -4.08,17.9 -4.39,56.6 0.1,74.9 1.82,6.73 7.07,12 13.8,13.8 17.9,4.12 103,4.7 122,0 6.73,-1.82 12,-7.07 13.8,-13.8 4.35,-19.5 4.66,-55.8 -0.1,-75z\"\n      android:fillColor=\"#f00\"/>\n  <path\n      android:pathData=\"m105,55 l-40.8,-23.4v46.8z\"\n      android:fillColor=\"#fff\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable-ar/ic_arrow_back.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24\"\n  android:viewportHeight=\"24\"\n  >\n  <path\n    android:fillColor=\"@android:color/white\"\n    android:pathData=\"M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable-notnight/ic_github.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"98dp\"\n    android:height=\"96dp\"\n    android:viewportWidth=\"98\"\n    android:viewportHeight=\"96\">\n  <path\n      android:pathData=\"M48.854,0C21.839,0 0,22 0,49.217c0,21.756 13.993,40.172 33.405,46.69 2.427,0.49 3.316,-1.059 3.316,-2.362 0,-1.141 -0.08,-5.052 -0.08,-9.127 -13.59,2.934 -16.42,-5.867 -16.42,-5.867 -2.184,-5.704 -5.42,-7.17 -5.42,-7.17 -4.448,-3.015 0.324,-3.015 0.324,-3.015 4.934,0.326 7.523,5.052 7.523,5.052 4.367,7.496 11.404,5.378 14.235,4.074 0.404,-3.178 1.699,-5.378 3.074,-6.6 -10.839,-1.141 -22.243,-5.378 -22.243,-24.283 0,-5.378 1.94,-9.778 5.014,-13.2 -0.485,-1.222 -2.184,-6.275 0.486,-13.038 0,0 4.125,-1.304 13.426,5.052a46.97,46.97 0,0 1,12.214 -1.63c4.125,0 8.33,0.571 12.213,1.63 9.302,-6.356 13.427,-5.052 13.427,-5.052 2.67,6.763 0.97,11.816 0.485,13.038 3.155,3.422 5.015,7.822 5.015,13.2 0,18.905 -11.404,23.06 -22.324,24.283 1.78,1.548 3.316,4.481 3.316,9.126 0,6.6 -0.08,11.897 -0.08,13.526 0,1.304 0.89,2.853 3.316,2.364 19.412,-6.52 33.405,-24.935 33.405,-46.691C97.707,22 75.788,0 48.854,0z\"\n      android:fillColor=\"#24292f\"\n      android:fillType=\"evenOdd\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/drawable-notnight/ic_wikipedia.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"128dp\"\n  android:height=\"128dp\"\n  android:viewportWidth=\"128\"\n  android:viewportHeight=\"128\"\n  >\n  <path\n      android:pathData=\"M96.869,23.909L96.869,26.048C94.047,26.549 91.912,27.436 90.462,28.707C88.385,30.596 85.937,33.486 84.33,37.379L51.645,104.091L49.47,104.091L16.657,36.512C15.129,33.043 13.051,30.923 12.424,30.153C11.445,28.958 10.24,28.023 8.81,27.349C7.379,26.674 5.449,26.241 3.02,26.048L3.02,23.909L34.948,23.909L34.948,26.048C31.265,26.395 29.509,27.012 28.411,27.898C27.314,28.784 26.766,29.921 26.766,31.309C26.766,33.236 27.667,36.242 29.469,40.327L53.702,86.286L77.394,40.905C79.236,36.435 80.764,33.332 80.764,31.598C80.764,30.48 80.196,29.411 79.06,28.389C77.923,27.368 76.637,26.645 73.934,26.221C73.738,26.183 73.405,26.125 72.934,26.048L72.934,23.909L96.869,23.909z\"\n      android:strokeLineJoin=\"miter\"\n      android:strokeWidth=\"1\"\n      android:fillColor=\"#000000\"\n      android:strokeColor=\"#000000\"\n      android:strokeLineCap=\"butt\"/>\n  <path\n      android:pathData=\"M124.98,23.909L124.98,26.048C122.159,26.549 120.023,27.436 118.573,28.707C116.497,30.596 114.048,33.486 112.441,37.379L83.756,104.091L81.581,104.091L51.268,36.512C49.74,33.043 47.662,30.923 47.036,30.153C46.056,28.958 44.851,28.023 43.421,27.349C41.991,26.674 40.695,26.241 38.265,26.048L38.265,23.909L69.559,23.909L69.559,26.048C65.876,26.395 64.12,27.012 63.023,27.898C61.925,28.784 61.377,29.921 61.377,31.309C61.377,33.236 62.278,36.242 64.081,40.327L85.813,86.286L105.506,40.905C107.347,36.435 108.875,33.332 108.875,31.598C108.875,30.48 108.307,29.411 107.171,28.389C106.034,27.368 104.114,26.645 101.411,26.221C101.215,26.183 100.882,26.125 100.411,26.048L100.411,23.909L124.98,23.909z\"\n      android:strokeLineJoin=\"miter\"\n      android:strokeWidth=\"1\"\n      android:fillColor=\"#000000\"\n      android:strokeColor=\"#000000\"\n      android:strokeLineCap=\"butt\"/>\n</vector>\n"
  },
  {
    "path": "ui-base/src/main/res/layout/view_context_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/contextMenuItemRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  android:paddingStart=\"@dimen/spaceNormal\"\n  android:paddingTop=\"@dimen/spaceNormal\"\n  android:paddingEnd=\"@dimen/spaceNormal\"\n  android:paddingBottom=\"@dimen/spaceMedium\"\n  >\n\n  <androidx.constraintlayout.widget.Guideline\n    android:id=\"@+id/contextMenuItemGuide1\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    app:layout_constraintGuide_begin=\"@dimen/collectionItemImageWidth\"\n    />\n\n  <androidx.constraintlayout.widget.Guideline\n    android:id=\"@+id/contextMenuItemGuide2\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    app:layout_constraintGuide_begin=\"0dp\"\n    />\n\n  <androidx.constraintlayout.widget.Barrier\n    android:id=\"@+id/contextMenuItemBarrier\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    app:barrierDirection=\"bottom\"\n    app:constraint_referenced_ids=\"contextMenuItemDescription, contextMenuItemImage, contextMenuItemPlaceholder\"\n    />\n\n  <ImageView\n    android:id=\"@+id/contextMenuItemImage\"\n    android:layout_width=\"@dimen/collectionItemImageWidth\"\n    android:layout_height=\"@dimen/collectionItemImageHeight\"\n    android:background=\"@drawable/bg_item_menu_elevation\"\n    android:elevation=\"@dimen/elevationSmall\"\n    app:layout_constraintEnd_toStartOf=\"@id/contextMenuItemGuide1\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <ImageView\n    android:id=\"@+id/contextMenuItemPlaceholder\"\n    android:layout_width=\"@dimen/collectionItemImageWidth\"\n    android:layout_height=\"@dimen/collectionItemImageHeight\"\n    android:layout_gravity=\"center\"\n    android:background=\"@drawable/bg_item_menu_placeholder\"\n    android:elevation=\"@dimen/elevationSmall\"\n    android:padding=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    app:layout_constraintEnd_toStartOf=\"@id/contextMenuItemGuide1\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_television\"\n    app:tint=\"?attr/colorPlaceholderIcon\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/contextMenuItemBadge\"\n    style=\"@style/Badge\"\n    android:layout_width=\"24dp\"\n    android:layout_height=\"24dp\"\n    android:layout_marginEnd=\"@dimen/spaceMicro\"\n    android:elevation=\"@dimen/elevationSmall\"\n    android:translationY=\"-4dp\"\n    app:layout_constraintEnd_toStartOf=\"@id/contextMenuItemGuide1\"\n    app:layout_constraintTop_toBottomOf=\"@id/contextMenuItemGuide2\"\n    app:srcCompat=\"@drawable/ic_bookmark_full\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/contextMenuItemTitle\"\n    style=\"@style/CollectionItem.Title\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    app:layout_constraintBottom_toTopOf=\"@id/contextMenuItemDescription\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/contextMenuItemGuide1\"\n    app:layout_constraintTop_toBottomOf=\"@+id/contextMenuItemNetwork\"\n    app:layout_constraintVertical_chainStyle=\"packed\"\n    tools:text=\"Game of Thrones\"\n    />\n\n  <TextView\n    android:id=\"@+id/contextMenuItemNetwork\"\n    style=\"@style/CollectionItem.Header\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    app:layout_constraintBottom_toTopOf=\"@+id/contextMenuItemTitle\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/contextMenuItemGuide1\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_constraintVertical_chainStyle=\"packed\"\n    tools:ignore=\"SmallSp\"\n    tools:text=\"Netflix\"\n    />\n\n  <ImageView\n    android:id=\"@+id/contextMenuRatingStar\"\n    android:layout_width=\"20dp\"\n    android:layout_height=\"0dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@+id/contextMenuItemTitle\"\n    app:layout_constraintEnd_toStartOf=\"@id/contextMenuRating\"\n    app:layout_constraintTop_toTopOf=\"@id/contextMenuItemNetwork\"\n    app:srcCompat=\"@drawable/ic_star\"\n    app:tint=\"?attr/colorAccent\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/contextMenuRating\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"end\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@+id/contextMenuItemTitle\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/contextMenuItemNetwork\"\n    tools:text=\"7.6\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/contextMenuUserRatingStar\"\n    android:layout_width=\"20dp\"\n    android:layout_height=\"0dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/contextMenuItemTitle\"\n    app:layout_constraintEnd_toStartOf=\"@id/contextMenuUserRating\"\n    app:layout_constraintTop_toTopOf=\"@id/contextMenuItemNetwork\"\n    app:srcCompat=\"@drawable/ic_star\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/contextMenuUserRating\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/spaceTiny\"\n    android:gravity=\"end\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/contextMenuItemTitle\"\n    app:layout_constraintEnd_toStartOf=\"@id/contextMenuRatingStar\"\n    app:layout_constraintTop_toTopOf=\"@id/contextMenuItemNetwork\"\n    tools:text=\"10\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.FoldableTextView\n    android:id=\"@+id/contextMenuItemDescription\"\n    style=\"@style/CollectionItem.Description\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    app:layout_constraintBottom_toBottomOf=\"@id/contextMenuItemBarrier\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/contextMenuItemGuide1\"\n    app:layout_constraintTop_toBottomOf=\"@id/contextMenuItemTitle\"\n    tools:text=\"@tools:sample/lorem/random\"\n    />\n\n  <!-- Pinning Buttons -->\n\n  <LinearLayout\n    android:id=\"@+id/contextMenuItemPinButtonsLayout\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:divider=\"@drawable/divider_item_menu\"\n    android:orientation=\"vertical\"\n    android:showDividers=\"middle\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/contextMenuItemSeparator2\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/contextMenuItemBarrier\"\n    tools:visibility=\"visible\"\n    >\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/contextMenuItemPinButton\"\n      style=\"@style/ContextMenuItemButton.Move\"\n      android:text=\"@string/menuPin\"\n      android:visibility=\"gone\"\n      app:icon=\"@drawable/ic_pin\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/contextMenuItemAddOnHoldButton\"\n      style=\"@style/ContextMenuItemButton.Move\"\n      android:text=\"@string/menuAddOnHold\"\n      android:visibility=\"gone\"\n      app:icon=\"@drawable/ic_pause\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/contextMenuItemUnpinButton\"\n      style=\"@style/ContextMenuItemButton.Remove\"\n      android:text=\"@string/menuUnpin\"\n      android:visibility=\"gone\"\n      app:icon=\"@drawable/ic_pin\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/contextMenuItemRemoveOnHoldButton\"\n      style=\"@style/ContextMenuItemButton.Remove\"\n      android:text=\"@string/menuRemoveOnHold\"\n      android:visibility=\"gone\"\n      app:icon=\"@drawable/ic_pause\"\n      tools:visibility=\"visible\"\n      />\n\n  </LinearLayout>\n\n  <View\n    android:id=\"@+id/contextMenuItemSeparator2\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"1dp\"\n    android:layout_marginTop=\"11dp\"\n    android:background=\"?attr/colorSeparator\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/contextMenuItemButtonsLayout\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/contextMenuItemPinButtonsLayout\"\n    app:layout_goneMarginTop=\"8dp\"\n    tools:visibility=\"visible\"\n    />\n\n  <!-- Main Buttons -->\n\n  <LinearLayout\n    android:id=\"@+id/contextMenuItemButtonsLayout\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:divider=\"@drawable/divider_item_menu\"\n    android:orientation=\"vertical\"\n    android:showDividers=\"middle\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/contextMenuItemSeparator2\"\n    >\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/contextMenuItemMoveToMyButton\"\n      style=\"@style/ContextMenuItemButton.Move\"\n      android:text=\"@string/textMoveToMyShows\"\n      android:visibility=\"gone\"\n      app:icon=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/contextMenuItemMoveToWatchlistButton\"\n      style=\"@style/ContextMenuItemButton.Move\"\n      android:text=\"@string/textMoveToWatchlist\"\n      android:visibility=\"gone\"\n      app:icon=\"@drawable/ic_bookmark\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/contextMenuItemMoveToHiddenButton\"\n      style=\"@style/ContextMenuItemButton.Move\"\n      android:text=\"@string/textMoveToHidden\"\n      android:visibility=\"gone\"\n      app:icon=\"@drawable/ic_eye_no\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/contextMenuItemRemoveFromMyButton\"\n      style=\"@style/ContextMenuItemButton.Remove\"\n      android:text=\"@string/textRemoveFromMyShows\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/contextMenuItemRemoveFromWatchlistButton\"\n      style=\"@style/ContextMenuItemButton.Remove\"\n      android:text=\"@string/textRemoveFromWatchlist\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/contextMenuItemRemoveFromHiddenButton\"\n      style=\"@style/ContextMenuItemButton.Remove\"\n      android:text=\"@string/textRemoveFromHidden\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n  </LinearLayout>\n\n  <androidx.core.widget.ContentLoadingProgressBar\n    android:id=\"@+id/contextMenuItemProgress\"\n    style=\"@style/ContentProgressBar.Accent\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/contextMenuItemProgressSecondary\"\n    style=\"@style/ContentProgressBar.Dark\"\n    android:layout_width=\"36dp\"\n    android:layout_height=\"36dp\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/contextMenuItemBarrier\"\n    tools:visibility=\"visible\"\n    />\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/contextMenuItemSnackbarHost\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-base/src/main/res/layout/view_links.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n  xmlns:custom=\"http://schemas.android.com/apk/res-auto\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/viewLinksRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  android:paddingTop=\"@dimen/spaceMedium\"\n  android:paddingBottom=\"@dimen/spaceNormal\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <androidx.constraintlayout.widget.Guideline\n    android:id=\"@+id/viewLinksGuideline\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    app:layout_constraintGuide_percent=\"0.5\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewLinksTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:gravity=\"start\"\n    android:text=\"@string/textLink\"\n    android:textAlignment=\"viewStart\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewLinksWebsite\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewLinksGuideline\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewLinksTitle\"\n    custom:icon=\"@drawable/ic_link_color\"\n    custom:text=\"Website\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewLinksTrakt\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewLinksGuideline\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewLinksWebsite\"\n    custom:icon=\"@drawable/ic_trakt\"\n    custom:text=\"Trakt.tv\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewLinksImdb\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewLinksGuideline\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewLinksTrakt\"\n    custom:icon=\"@drawable/ic_imdb\"\n    custom:text=\"IMDb\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewLinksTmdb\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewLinksGuideline\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewLinksImdb\"\n    custom:icon=\"@drawable/ic_tmdb\"\n    custom:text=\"TMDB\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewLinksTvdb\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewLinksGuideline\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewLinksTmdb\"\n    custom:icon=\"@drawable/ic_tvdb\"\n    custom:text=\"TVDB\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewLinksJustWatch\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewLinksGuideline\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewLinksTvdb\"\n    custom:icon=\"@drawable/ic_justwatch\"\n    custom:text=\"JustWatch\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewLinksGoogle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewLinksGuideline\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewLinksTitle\"\n    custom:icon=\"@drawable/ic_google\"\n    custom:text=\"Google\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewLinksDuckDuck\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewLinksGuideline\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewLinksGoogle\"\n    custom:icon=\"@drawable/ic_duckduck\"\n    custom:text=\"DuckDuckGo\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewLinksWiki\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewLinksGuideline\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewLinksDuckDuck\"\n    custom:icon=\"@drawable/ic_wikipedia\"\n    custom:text=\"Wikipedia\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewLinksYouTube\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewLinksGuideline\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewLinksWiki\"\n    custom:icon=\"@drawable/ic_youtube\"\n    custom:text=\"YouTube\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewLinksTwitter\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewLinksGuideline\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewLinksYouTube\"\n    custom:icon=\"@drawable/ic_twitter\"\n    custom:text=\"Twitter\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewLinksGif\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewLinksGuideline\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewLinksTwitter\"\n    custom:icon=\"@drawable/ic_giphy\"\n    custom:text=\"Giphy\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewLinksButtonClose\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:backgroundTint=\"@color/selector_main_button\"\n    android:gravity=\"center\"\n    android:text=\"@string/textClose\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewLinksJustWatch\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-base/src/main/res/layout/view_links_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/viewLinkItemContent\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/linkItemTileHeight\"\n    android:background=\"@drawable/bg_link_item_ripple\"\n    android:elevation=\"@dimen/elevationTiny\"\n    android:paddingStart=\"@dimen/spaceSmall\"\n    android:paddingTop=\"@dimen/spaceSmall\"\n    android:paddingEnd=\"@dimen/spaceMedium\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <ImageView\n      android:id=\"@+id/viewLinkItemImage\"\n      android:layout_width=\"@dimen/linkItemImageWidth\"\n      android:layout_height=\"@dimen/linkItemImageHeight\"\n      android:adjustViewBounds=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:src=\"@drawable/ic_imdb\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewLinkItemName\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start|center_vertical\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"16sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toEndOf=\"@id/viewLinkItemImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"iMDB\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-base/src/main/res/layout/view_mode_tabs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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  tools:parentTag=\"android.widget.LinearLayout\"\n  >\n\n  <TextView\n    android:id=\"@+id/viewShows\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"6dp\"\n    android:gravity=\"center\"\n    android:text=\"@string/textShows\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"25sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewMovies\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"6dp\"\n    android:gravity=\"center\"\n    android:text=\"@string/textMovies\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"25sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <Space\n    android:id=\"@+id/viewSpacer\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"match_parent\"\n    android:layout_weight=\"1\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewLists\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"6dp\"\n    android:layout_marginEnd=\"2dp\"\n    android:gravity=\"end\"\n    android:text=\"@string/textLists\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"25sp\"\n    android:textStyle=\"bold\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-base/src/main/res/layout/view_premium_ad.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n  android:id=\"@+id/viewPremiumAdRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:background=\"@drawable/bg_premium_ad\"\n  android:paddingStart=\"@dimen/spaceMedium\"\n  android:paddingTop=\"@dimen/spaceMedium\"\n  android:paddingEnd=\"@dimen/spaceSmall\"\n  android:paddingBottom=\"@dimen/spaceMedium\"\n  >\n\n  <TextView\n    android:id=\"@+id/viewPremiumAdTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:drawablePadding=\"@dimen/spaceSmall\"\n    android:gravity=\"start|center_vertical\"\n    android:text=\"@string/textPremium\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    app:drawableStartCompat=\"@drawable/ic_crown\"\n    app:drawableTint=\"?android:attr/textColorPrimary\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewPremiumAdDescription\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_constraintVertical_chainStyle=\"packed\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewPremiumAdDescription\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceTiny\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:drawablePadding=\"@dimen/spaceSmall\"\n    android:gravity=\"start|center_vertical\"\n    android:text=\"@string/textPremiumAd\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"13sp\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewPremiumAdArrow\"\n    app:layout_constraintHorizontal_bias=\"0\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPremiumAdTitle\"\n    />\n\n  <ImageView\n    android:id=\"@+id/viewPremiumAdArrow\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_arrow_right\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-base/src/main/res/layout/view_premium_ad_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:targetApi=\"m\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/viewPremiumAdListRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_margin=\"@dimen/gridSpacing\"\n    android:background=\"@drawable/bg_premium_ad\"\n    android:elevation=\"@dimen/elevationSmall\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/viewPremiumAdListLogo\"\n      android:layout_width=\"120dp\"\n      android:layout_height=\"120dp\"\n      android:layout_gravity=\"start|center_vertical\"\n      android:layout_marginStart=\"@dimen/elevationNormal\"\n      android:padding=\"22dp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_crown\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewPremiumAdListTitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:gravity=\"start\"\n      android:text=\"@string/textPremium\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"@color/colorWhite\"\n      android:textSize=\"22sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewPremiumAdListDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewPremiumAdListLogo\"\n      app:layout_constraintTop_toTopOf=\"@id/viewPremiumAdListLogo\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewPremiumAdListDescription\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceMicro\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:gravity=\"start\"\n      android:text=\"@string/textPremiumAd\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"@color/colorWhite\"\n      android:textSize=\"13sp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/viewPremiumAdListLogo\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"@id/viewPremiumAdListTitle\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewPremiumAdListTitle\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-base/src/main/res/layout/view_rate_sheet.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/viewRateSheetRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  android:padding=\"@dimen/spaceNormal\"\n  >\n\n  <com.michaldrabik.ui_base.common.views.RateValueView\n    android:id=\"@+id/viewRateSheetRating\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceNormal\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewRateSheetStarsLayout\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_constraintVertical_bias=\"0\"\n    app:layout_constraintVertical_chainStyle=\"packed\"\n    />\n\n  <LinearLayout\n    android:id=\"@+id/viewRateSheetStarsLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewRateSheetSaveButton\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewRateSheetRating\"\n    tools:visibility=\"visible\"\n    >\n\n    <ImageView\n      android:id=\"@+id/star1\"\n      android:layout_width=\"25dp\"\n      android:layout_height=\"30dp\"\n      android:layout_marginEnd=\"2dp\"\n      android:layout_weight=\"1\"\n      android:adjustViewBounds=\"true\"\n      android:src=\"@drawable/ic_star_empty\"\n      android:tag=\"1\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <ImageView\n      android:id=\"@+id/star2\"\n      android:layout_width=\"25dp\"\n      android:layout_height=\"30dp\"\n      android:layout_marginEnd=\"2dp\"\n      android:layout_weight=\"1\"\n      android:adjustViewBounds=\"true\"\n      android:src=\"@drawable/ic_star_empty\"\n      android:tag=\"2\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <ImageView\n      android:id=\"@+id/star3\"\n      android:layout_width=\"25dp\"\n      android:layout_height=\"30dp\"\n      android:layout_marginEnd=\"2dp\"\n      android:layout_weight=\"1\"\n      android:adjustViewBounds=\"true\"\n      android:src=\"@drawable/ic_star_empty\"\n      android:tag=\"3\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <ImageView\n      android:id=\"@+id/star4\"\n      android:layout_width=\"25dp\"\n      android:layout_height=\"30dp\"\n      android:layout_marginEnd=\"2dp\"\n      android:layout_weight=\"1\"\n      android:adjustViewBounds=\"true\"\n      android:src=\"@drawable/ic_star_empty\"\n      android:tag=\"4\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <ImageView\n      android:id=\"@+id/star5\"\n      android:layout_width=\"25dp\"\n      android:layout_height=\"30dp\"\n      android:layout_marginEnd=\"2dp\"\n      android:layout_weight=\"1\"\n      android:adjustViewBounds=\"true\"\n      android:src=\"@drawable/ic_star_empty\"\n      android:tag=\"5\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <ImageView\n      android:id=\"@+id/star6\"\n      android:layout_width=\"25dp\"\n      android:layout_height=\"30dp\"\n      android:layout_marginEnd=\"2dp\"\n      android:layout_weight=\"1\"\n      android:adjustViewBounds=\"true\"\n      android:src=\"@drawable/ic_star_empty\"\n      android:tag=\"6\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <ImageView\n      android:id=\"@+id/star7\"\n      android:layout_width=\"25dp\"\n      android:layout_height=\"30dp\"\n      android:layout_marginEnd=\"2dp\"\n      android:layout_weight=\"1\"\n      android:adjustViewBounds=\"true\"\n      android:src=\"@drawable/ic_star_empty\"\n      android:tag=\"7\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <ImageView\n      android:id=\"@+id/star8\"\n      android:layout_width=\"25dp\"\n      android:layout_height=\"30dp\"\n      android:layout_marginEnd=\"2dp\"\n      android:layout_weight=\"1\"\n      android:adjustViewBounds=\"true\"\n      android:src=\"@drawable/ic_star_empty\"\n      android:tag=\"8\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <ImageView\n      android:id=\"@+id/star9\"\n      android:layout_width=\"25dp\"\n      android:layout_height=\"30dp\"\n      android:layout_marginEnd=\"2dp\"\n      android:layout_weight=\"1\"\n      android:adjustViewBounds=\"true\"\n      android:src=\"@drawable/ic_star_empty\"\n      android:tag=\"9\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <ImageView\n      android:id=\"@+id/star10\"\n      android:layout_width=\"25dp\"\n      android:layout_height=\"30dp\"\n      android:layout_weight=\"1\"\n      android:adjustViewBounds=\"true\"\n      android:src=\"@drawable/ic_star_empty\"\n      android:tag=\"10\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n  </LinearLayout>\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewRateSheetSaveButton\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:backgroundTint=\"@color/selector_main_button\"\n    android:enabled=\"false\"\n    android:gravity=\"center\"\n    android:text=\"@string/textRate\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewRateSheetRemoveButton\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewRateSheetStarsLayout\"\n    app:layout_goneMarginStart=\"0dp\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewRateSheetRemoveButton\"\n    style=\"@style/DeclineOutlinedButton\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    android:gravity=\"center\"\n    android:text=\"@string/textRemove\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewRateSheetSaveButton\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewRateSheetStarsLayout\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/viewRateSheetProgress\"\n    style=\"@style/ProgressBar.Accent\"\n    android:layout_width=\"36dp\"\n    android:layout_height=\"36dp\"\n    android:layout_gravity=\"center\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/viewRateSheetSaveButton\"\n    app:layout_goneMarginBottom=\"@dimen/spaceMedium\"\n    tools:visibility=\"visible\"\n    />\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/viewRateSheetSnackHost\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-base/src/main/res/layout/view_rate_value.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/viewPremiumAdRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <TextView\n    android:id=\"@+id/viewRateValueText1\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"match_parent\"\n    android:layout_gravity=\"center\"\n    android:gravity=\"center\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"60sp\"\n    android:textStyle=\"bold\"\n    tools:text=\"10\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewRateValueText2\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"match_parent\"\n    android:layout_gravity=\"center\"\n    android:gravity=\"center\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"60sp\"\n    android:textStyle=\"bold\"\n    android:alpha=\"0\"\n    tools:text=\"10\"\n    />\n\n</merge>\n"
  },
  {
    "path": "ui-base/src/main/res/layout/view_ratings_strip.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:gravity=\"top\"\n  android:orientation=\"horizontal\"\n  tools:parentTag=\"android.widget.LinearLayout\"\n  >\n\n  <LinearLayout\n    android:id=\"@+id/viewRatingsStripTrakt\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_weight=\"1\"\n    android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n    android:gravity=\"center\"\n    android:orientation=\"vertical\"\n    >\n\n    <FrameLayout\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceMicro\"\n      >\n\n      <TextView\n        android:id=\"@+id/viewRatingsStripTraktValue\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:fontFamily=\"sans-serif-medium\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"20sp\"\n        />\n\n      <ProgressBar\n        android:id=\"@+id/viewRatingsStripTraktProgress\"\n        style=\"@style/ProgressBar.Dark\"\n        android:layout_width=\"16dp\"\n        android:layout_height=\"16dp\"\n        android:layout_gravity=\"center\"\n        />\n\n\n      <ImageView\n        android:id=\"@+id/viewRatingsStripTraktLinkIcon\"\n        android:layout_width=\"20dp\"\n        android:layout_height=\"20dp\"\n        android:layout_gravity=\"center\"\n        android:visibility=\"gone\"\n        app:srcCompat=\"@drawable/ic_link\"\n        app:tint=\"?android:attr/textColorSecondary\"\n        />\n\n    </FrameLayout>\n\n    <TextView\n      android:id=\"@+id/viewRatingsStripTraktLabel\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"center\"\n      android:lineSpacingExtra=\"2dp\"\n      android:text=\"Trakt.tv\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      tools:ignore=\"HardcodedText\"\n      />\n\n  </LinearLayout>\n\n  <LinearLayout\n    android:id=\"@+id/viewRatingsStripImdb\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_weight=\"1\"\n    android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n    android:gravity=\"center\"\n    android:orientation=\"vertical\"\n    >\n\n    <FrameLayout\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceMicro\"\n      >\n\n      <TextView\n        android:id=\"@+id/viewRatingsStripImdbValue\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:fontFamily=\"sans-serif-medium\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"20sp\"\n        />\n\n      <ProgressBar\n        android:id=\"@+id/viewRatingsStripImdbProgress\"\n        style=\"@style/ProgressBar.Dark\"\n        android:layout_width=\"16dp\"\n        android:layout_height=\"16dp\"\n        android:layout_gravity=\"center\"\n        />\n\n      <ImageView\n        android:id=\"@+id/viewRatingsStripImdbLinkIcon\"\n        android:layout_width=\"20dp\"\n        android:layout_height=\"20dp\"\n        android:layout_gravity=\"center\"\n        android:visibility=\"gone\"\n        app:srcCompat=\"@drawable/ic_link\"\n        app:tint=\"?android:attr/textColorSecondary\"\n        />\n\n    </FrameLayout>\n\n    <TextView\n      android:id=\"@+id/viewRatingsStripImdbLabel\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"center\"\n      android:lineSpacingExtra=\"2dp\"\n      android:text=\"IMDb\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      tools:ignore=\"HardcodedText\"\n      />\n\n  </LinearLayout>\n\n  <LinearLayout\n    android:id=\"@+id/viewRatingsStripMeta\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_weight=\"1\"\n    android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n    android:gravity=\"center\"\n    android:orientation=\"vertical\"\n    >\n\n    <FrameLayout\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceMicro\"\n      >\n\n      <TextView\n        android:id=\"@+id/viewRatingsStripMetaValue\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:fontFamily=\"sans-serif-medium\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"20sp\"\n        />\n\n      <ProgressBar\n        android:id=\"@+id/viewRatingsStripMetaProgress\"\n        style=\"@style/ProgressBar.Dark\"\n        android:layout_width=\"16dp\"\n        android:layout_height=\"16dp\"\n        android:layout_gravity=\"center\"\n        />\n\n      <ImageView\n        android:id=\"@+id/viewRatingsStripMetaLinkIcon\"\n        android:layout_width=\"20dp\"\n        android:layout_height=\"20dp\"\n        android:layout_gravity=\"center\"\n        android:visibility=\"gone\"\n        app:srcCompat=\"@drawable/ic_link\"\n        app:tint=\"?android:attr/textColorSecondary\"\n        />\n\n    </FrameLayout>\n\n    <TextView\n      android:id=\"@+id/viewRatingsStripMetaLabel\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:text=\"Metascore\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      tools:ignore=\"HardcodedText\"\n      />\n\n  </LinearLayout>\n\n  <LinearLayout\n    android:id=\"@+id/viewRatingsStripRotten\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_weight=\"1\"\n    android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n    android:gravity=\"center\"\n    android:orientation=\"vertical\"\n    >\n\n    <FrameLayout\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceMicro\"\n      >\n\n      <TextView\n        android:id=\"@+id/viewRatingsStripRottenValue\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:fontFamily=\"sans-serif-medium\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"20sp\"\n        />\n\n      <ProgressBar\n        android:id=\"@+id/viewRatingsStripRottenProgress\"\n        style=\"@style/ProgressBar.Dark\"\n        android:layout_width=\"16dp\"\n        android:layout_height=\"16dp\"\n        android:layout_gravity=\"center\"\n        />\n\n      <ImageView\n        android:id=\"@+id/viewRatingsStripRottenLinkIcon\"\n        android:layout_width=\"20dp\"\n        android:layout_height=\"20dp\"\n        android:layout_gravity=\"center\"\n        android:visibility=\"gone\"\n        app:srcCompat=\"@drawable/ic_link\"\n        app:tint=\"?android:attr/textColorSecondary\"\n        />\n\n    </FrameLayout>\n\n    <TextView\n      android:id=\"@+id/viewRatingsRottenLabel\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"center\"\n      android:lineSpacingExtra=\"2dp\"\n      android:text=\"R. Tomatoes\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      tools:ignore=\"HardcodedText\"\n      />\n\n  </LinearLayout>\n\n</merge>"
  },
  {
    "path": "ui-base/src/main/res/layout/view_remove_trakt_hidden.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/viewRemoveTraktHiddenRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  android:paddingStart=\"@dimen/spaceNormal\"\n  android:paddingTop=\"@dimen/spaceMedium\"\n  android:paddingEnd=\"@dimen/spaceNormal\"\n  android:paddingBottom=\"@dimen/spaceNormal\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <TextView\n    android:id=\"@+id/viewRemoveTraktHiddenTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"start\"\n    android:text=\"@string/textRemoveFromTrakt\"\n    android:textAlignment=\"viewStart\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewRemoveTraktHiddenSubtitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:gravity=\"start\"\n    android:text=\"@string/textRemoveFromTraktHidden\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewRemoveTraktHiddenTitle\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewRemoveTraktHiddenButtonNo\"\n    style=\"@style/DeclineOutlinedButton\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    android:gravity=\"center\"\n    android:text=\"@string/textNo\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewRemoveTraktHiddenButtonYes\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewRemoveTraktHiddenSubtitle\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewRemoveTraktHiddenButtonYes\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:backgroundTint=\"@color/selector_main_button\"\n    android:gravity=\"center\"\n    android:text=\"@string/textYes\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewRemoveTraktHiddenButtonNo\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewRemoveTraktHiddenSubtitle\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/viewRemoveTraktHiddenProgress\"\n    style=\"@style/ProgressBar.Accent\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/viewRemoveTraktHiddenButtonYes\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/viewRemoveTraktHiddenButtonYes\"\n    />\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/viewRemoveTraktHiddenSnackHost\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:translationZ=\"10dp\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-base/src/main/res/layout/view_remove_trakt_progress.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/viewRemoveTraktProgressRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  android:paddingStart=\"@dimen/spaceNormal\"\n  android:paddingTop=\"@dimen/spaceMedium\"\n  android:paddingEnd=\"@dimen/spaceNormal\"\n  android:paddingBottom=\"@dimen/spaceNormal\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <TextView\n    android:id=\"@+id/viewRemoveTraktProgressTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"start\"\n    android:text=\"@string/textRemoveFromTrakt\"\n    android:textAlignment=\"viewStart\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewRemoveTraktProgressSubtitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:gravity=\"start\"\n    android:text=\"@string/textRemoveFromTraktProgress\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewRemoveTraktProgressTitle\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewRemoveTraktProgressButtonNo\"\n    style=\"@style/DeclineOutlinedButton\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    android:gravity=\"center\"\n    android:text=\"@string/textNo\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewRemoveTraktProgressButtonYes\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewRemoveTraktProgressSubtitle\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewRemoveTraktProgressButtonYes\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:backgroundTint=\"@color/selector_main_button\"\n    android:gravity=\"center\"\n    android:text=\"@string/textYes\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewRemoveTraktProgressButtonNo\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewRemoveTraktProgressSubtitle\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/viewRemoveTraktProgressProgress\"\n    style=\"@style/ProgressBar.Accent\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/viewRemoveTraktProgressButtonYes\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/viewRemoveTraktProgressButtonYes\"\n    />\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/viewRemoveTraktProgressSnackHost\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:translationZ=\"10dp\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-base/src/main/res/layout/view_remove_trakt_watchlist.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/viewRemoveTraktWatchlistRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  android:paddingStart=\"@dimen/spaceNormal\"\n  android:paddingTop=\"@dimen/spaceMedium\"\n  android:paddingEnd=\"@dimen/spaceNormal\"\n  android:paddingBottom=\"@dimen/spaceNormal\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <TextView\n    android:id=\"@+id/viewRemoveTraktWatchlistTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"start\"\n    android:text=\"@string/textRemoveFromTrakt\"\n    android:textAlignment=\"viewStart\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewRemoveTraktWatchlistSubtitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:gravity=\"start\"\n    android:text=\"@string/textRemoveFromTraktWatchlist\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewRemoveTraktWatchlistTitle\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewRemoveTraktWatchlistButtonNo\"\n    style=\"@style/DeclineOutlinedButton\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    android:gravity=\"center\"\n    android:text=\"@string/textNo\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewRemoveTraktWatchlistButtonYes\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewRemoveTraktWatchlistSubtitle\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewRemoveTraktWatchlistButtonYes\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:backgroundTint=\"@color/selector_main_button\"\n    android:gravity=\"center\"\n    android:text=\"@string/textYes\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewRemoveTraktWatchlistButtonNo\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewRemoveTraktWatchlistSubtitle\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/viewRemoveTraktWatchlistProgress\"\n    style=\"@style/ProgressBar.Accent\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/viewRemoveTraktWatchlistButtonYes\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/viewRemoveTraktWatchlistButtonYes\"\n    />\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/viewRemoveTraktWatchlistSnackHost\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:translationZ=\"10dp\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-base/src/main/res/layout/view_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n  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/searchViewRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  app:cardBackgroundColor=\"?attr/colorSearchViewBackground\"\n  app:cardCornerRadius=\"@dimen/searchViewCorner\"\n  app:cardElevation=\"@dimen/elevationSmall\"\n  app:strokeWidth=\"0dp\"\n  >\n\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginStart=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceMicro\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    >\n\n    <ImageView\n      android:id=\"@+id/searchViewIcon\"\n      android:layout_width=\"24dp\"\n      android:layout_height=\"match_parent\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      app:srcCompat=\"@drawable/ic_anim_search_to_close\"\n      app:tint=\"?attr/colorSearchViewControl\"\n      />\n\n    <FrameLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:paddingEnd=\"@dimen/spaceTiny\"\n      >\n\n      <TextView\n        android:id=\"@+id/searchViewText\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"center_vertical|start\"\n        android:text=\"@string/textSearchFor\"\n        android:textAlignment=\"viewStart\"\n        android:textColor=\"?attr/colorSearchViewControl\"\n        android:textSize=\"16sp\"\n        />\n\n      <com.google.android.material.textfield.TextInputEditText\n        android:id=\"@+id/searchViewInput\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_marginStart=\"0dp\"\n        android:layout_marginEnd=\"84dp\"\n        android:background=\"@android:color/transparent\"\n        android:gravity=\"start|center_vertical\"\n        android:hint=\"@string/textSearchFor\"\n        android:imeOptions=\"actionSearch\"\n        android:inputType=\"text\"\n        android:maxLength=\"50\"\n        android:maxLines=\"1\"\n        android:selectAllOnFocus=\"true\"\n        android:textAlignment=\"viewStart\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textColorHint=\"?attr/colorSearchViewControl\"\n        android:textSize=\"16sp\"\n        android:visibility=\"gone\"\n        tools:visibility=\"visible\"\n        />\n\n      <ImageView\n        android:id=\"@+id/searchStatsIcon\"\n        android:layout_width=\"40dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"end\"\n        android:layout_marginEnd=\"40dp\"\n        android:padding=\"8dp\"\n        android:visibility=\"gone\"\n        app:srcCompat=\"@drawable/ic_insight\"\n        app:tint=\"?attr/colorSearchViewControl\"\n        tools:visibility=\"visible\"\n        />\n\n      <ImageView\n        android:id=\"@+id/searchTraktIcon\"\n        android:layout_width=\"40dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"end\"\n        android:layout_marginEnd=\"40dp\"\n        android:padding=\"11dp\"\n        android:visibility=\"gone\"\n        app:srcCompat=\"@drawable/ic_trakt\"\n        app:tint=\"?attr/colorSearchViewControl\"\n        tools:visibility=\"visible\"\n        />\n\n      <ImageView\n        android:id=\"@+id/searchSettingsIcon\"\n        android:layout_width=\"40dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"end\"\n        android:padding=\"9dp\"\n        app:srcCompat=\"@drawable/ic_settings\"\n        app:tint=\"?attr/colorSearchViewControl\"\n        />\n\n      <ImageView\n        android:id=\"@+id/searchDotBadge\"\n        android:layout_width=\"6dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"end\"\n        android:translationX=\"@dimen/searchViewDotTranslation\"\n        android:translationY=\"6dp\"\n        android:visibility=\"gone\"\n        app:srcCompat=\"@drawable/ic_circle\"\n        app:tint=\"?attr/colorAccent\"\n        />\n\n    </FrameLayout>\n\n  </LinearLayout>\n\n  <LinearLayout\n    android:id=\"@+id/searchViewTraktSync\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginStart=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceMicro\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:visibility=\"gone\"\n    >\n\n    <ProgressBar\n      style=\"@style/ProgressBar.Accent\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"match_parent\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      />\n\n    <TextView\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:text=\"@string/textTraktSync\"\n      android:textColor=\"?attr/colorAccent\"\n      />\n\n  </LinearLayout>\n\n</com.google.android.material.card.MaterialCardView>"
  },
  {
    "path": "ui-base/src/main/res/layout/view_search_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"wrap_content\"\n  android:layout_height=\"wrap_content\"\n  android:gravity=\"center\"\n  android:orientation=\"vertical\"\n  tools:background=\"@color/colorBackground\"\n  tools:parentTag=\"android.widget.LinearLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <ImageView\n    android:layout_width=\"130dp\"\n    android:layout_height=\"130dp\"\n    android:adjustViewBounds=\"true\"\n    android:src=\"@drawable/vincent_vega\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n  <TextView\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textEmptyResults\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    />\n\n</merge>\n"
  },
  {
    "path": "ui-base/src/main/res/layout/view_search_local.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n  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/searchViewLocalRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  app:cardBackgroundColor=\"?attr/colorSearchLocalViewBackground\"\n  app:cardCornerRadius=\"@dimen/searchLocalViewCorner\"\n  app:cardElevation=\"@dimen/elevationSmall\"\n  app:strokeWidth=\"0dp\"\n  >\n\n  <FrameLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginStart=\"14dp\"\n    android:layout_marginEnd=\"8dp\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    >\n\n    <com.google.android.material.textfield.TextInputEditText\n      android:id=\"@+id/searchViewLocalInput\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:layout_marginStart=\"0dp\"\n      android:layout_marginEnd=\"@dimen/spaceHuge\"\n      android:background=\"@android:color/transparent\"\n      android:gravity=\"start|center_vertical\"\n      android:hint=\"@string/textSearchFor\"\n      android:imeOptions=\"actionSearch\"\n      android:inputType=\"text\"\n      android:maxLength=\"50\"\n      android:maxLines=\"1\"\n      android:selectAllOnFocus=\"true\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textColorHint=\"?attr/colorSearchLocalViewControl\"\n      android:textSize=\"13sp\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/searchViewLocalIcon\"\n      android:layout_width=\"30dp\"\n      android:layout_height=\"match_parent\"\n      android:layout_gravity=\"end\"\n      android:paddingStart=\"10dp\"\n      app:srcCompat=\"@drawable/ic_close\"\n      app:tint=\"?attr/colorSearchLocalViewControl\"\n      />\n\n  </FrameLayout>\n\n</com.google.android.material.card.MaterialCardView>"
  },
  {
    "path": "ui-base/src/main/res/layout/view_sort_order.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/viewSortOrderRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_filters_sheet\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  android:paddingTop=\"@dimen/spaceMedium\"\n  android:paddingBottom=\"@dimen/spaceNormal\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <TextView\n    android:id=\"@+id/viewSortOrderTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:gravity=\"start\"\n    android:text=\"@string/textSortBy\"\n    android:textAlignment=\"viewStart\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <LinearLayout\n    android:id=\"@+id/viewSortOrderItemsLayout\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:orientation=\"vertical\"\n    app:layout_constrainedHeight=\"true\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewSortOrderTitle\"\n    />\n\n  <com.google.android.material.checkbox.MaterialCheckBox\n    android:id=\"@+id/viewSortOrderNewCheckbox\"\n    style=\"@style/ShowlyCheckbox.Secondary\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:paddingStart=\"@dimen/spaceSmall\"\n    android:paddingEnd=\"@dimen/spaceSmall\"\n    android:text=\"@string/textNewAlwaysAtTop\"\n    android:textSize=\"16sp\"\n    android:translationX=\"-2dp\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewSortOrderButtonApply\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewSortOrderItemsLayout\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewSortOrderButtonApply\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:backgroundTint=\"@color/selector_main_button\"\n    android:gravity=\"center\"\n    android:text=\"@string/textApply\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewSortOrderNewCheckbox\"\n    app:layout_goneMarginTop=\"@dimen/spaceNormal\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-base/src/main/res/layout/view_sort_order_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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/viewSortOrderItemRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <View\n    android:id=\"@+id/viewSortOrderItemBadge\"\n    android:layout_width=\"3dp\"\n    android:layout_height=\"0dp\"\n    android:background=\"@drawable/bg_sort_item_badge\"\n    android:translationY=\"1dp\"\n    app:layout_constraintBottom_toBottomOf=\"@id/viewSortOrderItemTitle\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/viewSortOrderItemTitle\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewSortOrderItemTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"10dp\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    android:layout_marginBottom=\"10dp\"\n    android:gravity=\"start\"\n    android:maxLines=\"1\"\n    android:text=\"@string/textSortNewest\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"16sp\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewSortOrderItemBadge\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_goneMarginStart=\"0dp\"\n    />\n\n  <ImageView\n    android:id=\"@+id/viewSortOrderItemAscDesc\"\n    android:layout_width=\"28dp\"\n    android:layout_height=\"28dp\"\n    android:layout_marginEnd=\"-6dp\"\n    android:rotation=\"-90\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_arrow_alt\"\n    app:tint=\"@color/colorAccent\"\n    />\n\n</merge>\n"
  },
  {
    "path": "ui-base/src/main/res/layout/view_tip.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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/tutorialTipRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <ImageView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:scaleX=\"0.9\"\n    android:scaleY=\"0.9\"\n    app:srcCompat=\"@drawable/ic_circle\"\n    app:tint=\"@color/colorWhite\"\n    />\n\n\n  <ImageView\n    android:id=\"@+id/tutorialTipIcon\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    app:srcCompat=\"@drawable/ic_info\"\n    app:tint=\"?attr/colorAccent\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-base/src/main/res/layout/view_tip_overlay.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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/tutorialViewRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/tutorialTipView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"@dimen/tutorialTipMargin\"\n    android:background=\"@drawable/bg_tip_view\"\n    android:elevation=\"8dp\"\n    android:paddingTop=\"@dimen/spaceNormal\"\n    >\n\n    <TextView\n      android:id=\"@+id/tutorialViewTitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceBig\"\n      android:layout_marginEnd=\"@dimen/spaceBig\"\n      android:text=\"@string/textTip\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"20sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintBottom_toTopOf=\"@id/tutorialViewText\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <TextView\n      android:id=\"@+id/tutorialViewText\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceBig\"\n      android:layout_marginTop=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceBig\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/tutorialViewButton\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/tutorialViewTitle\"\n      tools:text=\"Double tap Discover menu button to quickly open search screen.\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/tutorialViewButton\"\n      style=\"@style/RoundTextButton\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:text=\"@string/textOk\"\n      android:textColor=\"?attr/colorAccent\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/tutorialViewText\"\n      app:rippleColor=\"?attr/colorAccent\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-base/src/main/res/layout-sw600dp/view_ratings_strip.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:gravity=\"top\"\n  android:orientation=\"horizontal\"\n  tools:parentTag=\"android.widget.LinearLayout\"\n  >\n\n  <LinearLayout\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    android:layout_weight=\"1\"\n    android:orientation=\"vertical\"\n    >\n\n    <LinearLayout\n      android:id=\"@+id/viewRatingsStripTrakt\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n      android:gravity=\"center\"\n      android:orientation=\"vertical\"\n      >\n\n      <FrameLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"@dimen/spaceMicro\"\n        >\n\n        <TextView\n          android:id=\"@+id/viewRatingsStripTraktValue\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:fontFamily=\"sans-serif-medium\"\n          android:textColor=\"?android:attr/textColorPrimary\"\n          android:textSize=\"21sp\"\n          />\n\n        <ProgressBar\n          android:id=\"@+id/viewRatingsStripTraktProgress\"\n          style=\"@style/ProgressBar.Dark\"\n          android:layout_width=\"16dp\"\n          android:layout_height=\"16dp\"\n          android:layout_gravity=\"center\"\n          />\n\n\n        <ImageView\n          android:id=\"@+id/viewRatingsStripTraktLinkIcon\"\n          android:layout_width=\"20dp\"\n          android:layout_height=\"20dp\"\n          android:layout_gravity=\"center\"\n          android:visibility=\"gone\"\n          app:srcCompat=\"@drawable/ic_link\"\n          app:tint=\"?android:attr/textColorSecondary\"\n          />\n\n      </FrameLayout>\n\n      <TextView\n        android:id=\"@+id/viewRatingsStripTraktLabel\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:lineSpacingExtra=\"2dp\"\n        android:text=\"Trakt.tv\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"13sp\"\n        tools:ignore=\"HardcodedText\"\n        />\n\n    </LinearLayout>\n\n    <LinearLayout\n      android:id=\"@+id/viewRatingsStripMeta\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n      android:gravity=\"center\"\n      android:orientation=\"vertical\"\n      >\n\n      <FrameLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"@dimen/spaceMicro\"\n        >\n\n        <TextView\n          android:id=\"@+id/viewRatingsStripMetaValue\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:fontFamily=\"sans-serif-medium\"\n          android:textColor=\"?android:attr/textColorPrimary\"\n          android:textSize=\"21sp\"\n          />\n\n        <ProgressBar\n          android:id=\"@+id/viewRatingsStripMetaProgress\"\n          style=\"@style/ProgressBar.Dark\"\n          android:layout_width=\"16dp\"\n          android:layout_height=\"16dp\"\n          android:layout_gravity=\"center\"\n          />\n\n        <ImageView\n          android:id=\"@+id/viewRatingsStripMetaLinkIcon\"\n          android:layout_width=\"20dp\"\n          android:layout_height=\"20dp\"\n          android:layout_gravity=\"center\"\n          android:visibility=\"gone\"\n          app:srcCompat=\"@drawable/ic_link\"\n          app:tint=\"?android:attr/textColorSecondary\"\n          />\n\n      </FrameLayout>\n\n      <TextView\n        android:id=\"@+id/viewRatingsStripMetaLabel\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Metascore\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"13sp\"\n        tools:ignore=\"HardcodedText\"\n        />\n\n    </LinearLayout>\n\n  </LinearLayout>\n\n  <LinearLayout\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_weight=\"1\"\n    android:orientation=\"vertical\"\n    >\n\n    <LinearLayout\n      android:id=\"@+id/viewRatingsStripImdb\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n      android:gravity=\"center\"\n      android:orientation=\"vertical\"\n      >\n\n      <FrameLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"@dimen/spaceMicro\"\n        >\n\n        <TextView\n          android:id=\"@+id/viewRatingsStripImdbValue\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:fontFamily=\"sans-serif-medium\"\n          android:textColor=\"?android:attr/textColorPrimary\"\n          android:textSize=\"21sp\"\n          />\n\n        <ProgressBar\n          android:id=\"@+id/viewRatingsStripImdbProgress\"\n          style=\"@style/ProgressBar.Dark\"\n          android:layout_width=\"16dp\"\n          android:layout_height=\"16dp\"\n          android:layout_gravity=\"center\"\n          />\n\n        <ImageView\n          android:id=\"@+id/viewRatingsStripImdbLinkIcon\"\n          android:layout_width=\"20dp\"\n          android:layout_height=\"20dp\"\n          android:layout_gravity=\"center\"\n          android:visibility=\"gone\"\n          app:srcCompat=\"@drawable/ic_link\"\n          app:tint=\"?android:attr/textColorSecondary\"\n          />\n\n      </FrameLayout>\n\n      <TextView\n        android:id=\"@+id/viewRatingsStripImdbLabel\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:lineSpacingExtra=\"2dp\"\n        android:text=\"IMDb\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"13sp\"\n        tools:ignore=\"HardcodedText\"\n        />\n\n    </LinearLayout>\n\n    <LinearLayout\n      android:id=\"@+id/viewRatingsStripRotten\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n      android:gravity=\"center\"\n      android:orientation=\"vertical\"\n      >\n\n      <FrameLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"@dimen/spaceMicro\"\n        >\n\n        <TextView\n          android:id=\"@+id/viewRatingsStripRottenValue\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:fontFamily=\"sans-serif-medium\"\n          android:textColor=\"?android:attr/textColorPrimary\"\n          android:textSize=\"21sp\"\n          />\n\n        <ProgressBar\n          android:id=\"@+id/viewRatingsStripRottenProgress\"\n          style=\"@style/ProgressBar.Dark\"\n          android:layout_width=\"16dp\"\n          android:layout_height=\"16dp\"\n          android:layout_gravity=\"center\"\n          />\n\n        <ImageView\n          android:id=\"@+id/viewRatingsStripRottenLinkIcon\"\n          android:layout_width=\"20dp\"\n          android:layout_height=\"20dp\"\n          android:layout_gravity=\"center\"\n          android:visibility=\"gone\"\n          app:srcCompat=\"@drawable/ic_link\"\n          app:tint=\"?android:attr/textColorSecondary\"\n          />\n\n      </FrameLayout>\n\n      <TextView\n        android:id=\"@+id/viewRatingsRottenLabel\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:lineSpacingExtra=\"2dp\"\n        android:text=\"R. Tomatoes\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"13sp\"\n        tools:ignore=\"HardcodedText\"\n        />\n\n    </LinearLayout>\n\n  </LinearLayout>\n\n</merge>"
  },
  {
    "path": "ui-base/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <attr name=\"colorInfoSnackbar\" format=\"color\" />\n  <attr name=\"colorErrorSnackbar\" format=\"color\" />\n  <attr name=\"textColorInfoSnackbar\" format=\"color\" />\n  <attr name=\"textColorErrorSnackbar\" format=\"color\" />\n\n  <attr name=\"colorBottomMenuBackground\" format=\"color\" />\n  <attr name=\"colorBottomMenuItemChecked\" format=\"color\" />\n  <attr name=\"colorBottomMenuItem\" format=\"color\" />\n  <attr name=\"colorBottomMenuSeparator\" format=\"color\" />\n  <attr name=\"colorBottomMenuIndicator\" format=\"color\" />\n\n  <attr name=\"colorSearchViewBackground\" format=\"color\" />\n  <attr name=\"colorSearchViewControl\" format=\"color\" />\n\n  <attr name=\"colorSearchLocalViewBackground\" format=\"color\" />\n  <attr name=\"colorSearchLocalViewControl\" format=\"color\" />\n\n  <attr name=\"colorSeparator\" format=\"color\" />\n  <attr name=\"colorCardBackground\" format=\"color\" />\n  <attr name=\"colorBadgeBackground\" format=\"color\" />\n\n  <attr name=\"colorPlaceholderBackground\" format=\"color\" />\n  <attr name=\"colorPlaceholderStroke\" format=\"color\" />\n  <attr name=\"colorPlaceholderIcon\" format=\"color\" />\n\n  <attr name=\"textColorOnSurface\" format=\"color\" />\n  <attr name=\"textColorGridTitle\" format=\"color\" />\n\n  <attr name=\"textColorChip\" format=\"color\" />\n  <attr name=\"textColorChipSelected\" format=\"color\" />\n  <attr name=\"colorBackgroundChipSelected\" format=\"color\" />\n  <attr name=\"colorBackgroundChipSelectedLight\" format=\"color\" />\n  <attr name=\"colorProgressTrack\" format=\"color\" />\n\n  <attr name=\"textColorTab\" format=\"color\" />\n  <attr name=\"textColorTabSelected\" format=\"color\" />\n\n  <attr name=\"colorWidgetBackground\" format=\"color\" />\n  <attr name=\"colorWidgetBackground75\" format=\"color\" />\n  <attr name=\"colorWidgetBackground50\" format=\"color\" />\n  <attr name=\"colorWidgetBackground25\" format=\"color\" />\n  <attr name=\"colorWidgetBackground0\" format=\"color\" />\n  <attr name=\"colorWidgetStatusBackground\" format=\"color\" />\n\n  <declare-styleable name=\"LinkItem\">\n    <attr name=\"text\" format=\"string\" />\n    <attr name=\"icon\" format=\"reference\" />\n  </declare-styleable>\n\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values/bool.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <bool name=\"isTablet\">false</bool>\n</resources>"
  },
  {
    "path": "ui-base/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <color name=\"colorBackground\">#131313</color>\n\n  <color name=\"colorPrimary\">#222327</color>\n  <color name=\"colorPrimaryDark\">#121212</color>\n  <color name=\"colorAccent\">#F44336</color>\n  <color name=\"colorError\">#F44336</color>\n\n  <color name=\"colorTransparent\">#00000000</color>\n  <color name=\"colorBlack\">#000000</color>\n  <color name=\"colorBlackTranslucent\">#B2000000</color>\n  <color name=\"colorBlackTranslucentLight\">#40000000</color>\n  <color name=\"colorWhite\">#FFFFFF</color>\n  <color name=\"colorGrayLight\">#AAAAAA</color>\n  <color name=\"colorGray\">#636363</color>\n  <color name=\"colorGrayDark\">#303030</color>\n\n  <color name=\"colorBlue\">#17283A</color>\n  <color name=\"colorBlueLight\">#70767C</color>\n  <color name=\"colorBlueLight2x\">#91989F</color>\n\n  <color name=\"colorNotificationDark\">#F44336</color>\n  <color name=\"colorNotificationLight\">#4B6383</color>\n\n  <color name=\"colorTwitterBlue\">#1DA1F2</color>\n</resources>"
  },
  {
    "path": "ui-base/src/main/res/values/dimens.xml",
    "content": "<resources>\n  <dimen name=\"spaceNano\">1dp</dimen>\n  <dimen name=\"spaceMicro\">2dp</dimen>\n  <dimen name=\"spaceTiny\">4dp</dimen>\n  <dimen name=\"spaceSmall\">8dp</dimen>\n  <dimen name=\"spaceMedium\">12dp</dimen>\n  <dimen name=\"spaceNormal\">16dp</dimen>\n  <dimen name=\"spaceBig\">24dp</dimen>\n  <dimen name=\"spaceHuge\">48dp</dimen>\n\n  <dimen name=\"screenMarginHorizontal\">12dp</dimen>\n\n  <dimen name=\"elevationTiny\">0dp</dimen>\n  <dimen name=\"elevationSmall\">0dp</dimen>\n  <dimen name=\"elevationNormal\">0dp</dimen>\n  <dimen name=\"elevationSuggestions\">1dp</dimen>\n\n  <dimen name=\"bottomNavigationHeight\">68dp</dimen>\n  <dimen name=\"bottomNavigationHeightPadded\">76dp</dimen>\n  <dimen name=\"bottomNoInternetViewHeight\">24dp</dimen>\n\n  <dimen name=\"backArrowSize\">56dp</dimen>\n  <dimen name=\"backArrowPadding\">16dp</dimen>\n\n  <dimen name=\"gridSpacing\">4dp</dimen>\n  <dimen name=\"gridListsSpacing\">4dp</dimen>\n  <dimen name=\"gridPadding\">8dp</dimen>\n  <dimen name=\"gridListsPadding\">9dp</dimen>\n  <dimen name=\"gridRecyclerPadding\">9dp</dimen>\n\n  <dimen name=\"mediaTileCorner\">4dp</dimen>\n  <dimen name=\"galleryImageCorner\">8dp</dimen>\n  <dimen name=\"showTilePlaceholder\">40dp</dimen>\n  <dimen name=\"movieTilePlaceholder\">40dp</dimen>\n\n  <dimen name=\"bottomSheetCorner\">16dp</dimen>\n  <dimen name=\"relatedTileMoviePlaceholder\">22dp</dimen>\n  <dimen name=\"relatedTileShowPlaceholder\">24dp</dimen>\n  <dimen name=\"collectionTileMoviePlaceholder\">22dp</dimen>\n  <dimen name=\"collectionTileShowPlaceholder\">24dp</dimen>\n\n  <dimen name=\"searchViewHeight\">48dp</dimen>\n  <dimen name=\"searchViewHeightPadded\">64dp</dimen>\n  <dimen name=\"searchViewCorner\">24dp</dimen>\n  <dimen name=\"searchViewDotTranslation\">-10dp</dimen>\n  <dimen name=\"searchViewImageWidth\">67dp</dimen>\n  <dimen name=\"searchViewImageHeight\">100dp</dimen>\n  <dimen name=\"searchSuggestionViewImageWidth\">40dp</dimen>\n  <dimen name=\"searchSuggestionViewImageHeight\">60dp</dimen>\n  <dimen name=\"searchViewItemPaddingHorizontal\">12dp</dimen>\n\n  <dimen name=\"searchLocalViewHeight\">32dp</dimen>\n  <dimen name=\"searchLocalViewCorner\">16dp</dimen>\n\n  <dimen name=\"discoverFilterChipStroke\">1dp</dimen>\n  <dimen name=\"discoverRecyclerPadding\">158dp</dimen>\n  <dimen name=\"discoverRecyclerPaddingNoTabs\">116dp</dimen>\n\n  <dimen name=\"linkItemTileHeight\">42dp</dimen>\n  <dimen name=\"linkItemImageWidth\">50dp</dimen>\n  <dimen name=\"linkItemImageHeight\">26dp</dimen>\n\n  <dimen name=\"swipeRefreshStartOffset\">8dp</dimen>\n  <dimen name=\"swipeRefreshEndOffset\">194dp</dimen>\n\n  <dimen name=\"collectionTabsMargin\">70dp</dimen>\n  <dimen name=\"collectionFiltersMargin\">106dp</dimen>\n  <dimen name=\"collectionFiltersMarginNoTabs\">66dp</dimen>\n  <dimen name=\"commentViewSpace\">16dp</dimen>\n  <dimen name=\"tutorialTipSize\">28dp</dimen>\n  <dimen name=\"tutorialTipMargin\">32dp</dimen>\n  <dimen name=\"scrollBarWidth\">3dp</dimen>\n  <dimen name=\"rateValueTranslation\">80dp</dimen>\n\n  <dimen name=\"collectionItemCorner\">16dp</dimen>\n  <dimen name=\"collectionItemImageHeight\">120dp</dimen>\n  <dimen name=\"collectionItemImageWidth\">80dp</dimen>\n  <dimen name=\"collectionItemRippleSpace\">6dp</dimen>\n  <dimen name=\"collectionItemRippleSpaceSmall\">4dp</dimen>\n\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values/misc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <item name=\"detailsImageRatio\" format=\"float\" translatable=\"false\" type=\"string\">0.35</item>\n  <bool name=\"detailsImagePadded\">true</bool>\n</resources>"
  },
  {
    "path": "ui-base/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textOk\">OK</string>\n  <string name=\"textYes\">Yes</string>\n  <string name=\"textNo\">No</string>\n  <string name=\"textApply\">Apply</string>\n  <string name=\"textCancel\">Cancel</string>\n  <string name=\"textClose\">Close</string>\n  <string name=\"textNotNow\">Not Now</string>\n  <string name=\"textNew\">New</string>\n  <string name=\"textSelect\">Select</string>\n  <string name=\"textHot\">Hot</string>\n  <string name=\"textSubmit\">Submit</string>\n  <string name=\"textHide\">Hide</string>\n  <string name=\"textRemove\">Remove</string>\n  <string name=\"textEmptyResults\">There were no results…</string>\n  <string name=\"textSearchFor\">Search</string>\n  <string name=\"textTip\">Tip:</string>\n  <string name=\"textSeason\">Season %1$d</string>\n  <string name=\"textSpecials\">Specials</string>\n  <string name=\"textEpisode\">Episode %1$d</string>\n  <string name=\"textSeasonEpisode\">S.%02d E.%02d</string>\n  <string name=\"textSortBy\">Sort by:</string>\n  <string name=\"textSpoilersWarning\">This comment contains spoilers.\\nTap to read.</string>\n  <string name=\"textCommentedOn\">Commented by %s</string>\n  <string name=\"textNetwork\">%1$s (%2$s)</string>\n  <string name=\"textNetworks\" comment=\"TV network like HBO, AMC, Showtime, Disney etc.\">Network:</string>\n  <string name=\"textGenres\">Genres:</string>\n  <string name=\"textTba\" translatable=\"false\">TBA</string>\n  <string name=\"textShows\">Shows</string>\n  <string name=\"textMovies\">Movies</string>\n  <string name=\"textLists\">Lists</string>\n  <string name=\"textMinutesShort\">min</string>\n  <string name=\"textActorRole\" translatable=\"false\">%s\\n(%s)</string>\n  <string name=\"textNoDescription\">Overview not available.</string>\n  <string name=\"textPleaseWait\">Please wait…</string>\n  <string name=\"textRate\">Rate</string>\n  <string name=\"textLink\">Link</string>\n  <string name=\"textDisabled\">Disabled</string>\n  <string name=\"textRateSaved\">Your rating has been saved.</string>\n  <string name=\"textRateRemoved\">Your rating has been removed.</string>\n  <string name=\"textCopiedToClipboard\" translatable=\"false\">Copied to clipboard</string>\n  <string name=\"textDays\">%1$d days</string>\n\n  <plurals name=\"textEpisodesLeft\" comment=\"Number of episodes left to see\">\n    <item quantity=\"one\">%d left</item>\n    <item quantity=\"other\">%d left</item>\n  </plurals>\n\n  <string name=\"textPeople\">People:</string>\n  <string name=\"textDirecting\">Directing</string>\n  <string name=\"textDirector\">Director</string>\n  <string name=\"textWriting\">Writing</string>\n  <string name=\"textScreenplay\">Screenplay</string>\n  <string name=\"textSound\">Sound</string>\n  <string name=\"textMusic\">Music</string>\n  <string name=\"textActing\">Acting</string>\n\n  <string name=\"textSetCustomImages\">Custom Images</string>\n  <string name=\"textSetCustomImagesDescription\">Pick your own poster and fanart that are going to be used across the app.</string>\n\n  <string name=\"textAiredAlready\">Aired</string>\n  <string name=\"textAirsNow\">Airs Now</string>\n\n  <plurals name=\"textDaysToAir\">\n    <item quantity=\"one\">Airs tomorrow</item>\n    <item quantity=\"other\">Airs in %d days</item>\n  </plurals>\n\n  <plurals name=\"textHoursToAir\">\n    <item quantity=\"one\">Airs in 1 hour</item>\n    <item quantity=\"other\">Airs in %d hours</item>\n  </plurals>\n\n  <plurals name=\"textMinutesToAir\">\n    <item quantity=\"one\">Airs now</item>\n    <item quantity=\"other\">Airs in %d minutes</item>\n  </plurals>\n\n  <plurals name=\"textTraktQuickSyncComplete\">\n    <item quantity=\"one\">1 item synced successfully.</item>\n    <item quantity=\"other\">%d items synced successfully.</item>\n  </plurals>\n\n  <string name=\"textToday\">Today</string>\n  <string name=\"textTomorrow\">Tomorrow</string>\n  <string name=\"textThisWeek\">This Week</string>\n  <string name=\"textNextWeek\">Next Week</string>\n  <string name=\"textThisMonth\">This Month</string>\n  <string name=\"textNextMonth\">Next Month</string>\n  <string name=\"textThisYear\">This Year</string>\n  <string name=\"textNextYear\">Next Year</string>\n  <string name=\"textLater\">Later</string>\n  <string name=\"textYesterday\">Yesterday</string>\n  <string name=\"textLast7Days\">Last 7 Days</string>\n  <string name=\"textLast30Days\">Last 30 Days</string>\n  <string name=\"textLast90Days\">Last 90 Days</string>\n\n  <string name=\"textNewEpisodeAvailable\">New episode is available now!</string>\n  <string name=\"textNewEpisodeAvailableSoon\">New episode will be available soon!</string>\n  <string name=\"textNewSeasonAvailable\">New season is available now!</string>\n  <string name=\"textNewSeasonAvailableSoon\">New season will be available soon!</string>\n  <string name=\"textNewMovieAvailable\">Movie has been released!</string>\n\n  <string name=\"textTraktSync\">Trakt.tv Sync</string>\n  <string name=\"textTraktSyncComplete\">Sync completed successfully.</string>\n  <string name=\"textTraktSyncRunning\">Running…</string>\n  <string name=\"textTraktSyncError\">Trakt.tv sync failed.</string>\n  <string name=\"textTraktSyncErrorFull\">Trakt.tv sync failed. Please check your internet connection and try again or contact us if this keeps happening.</string>\n  <string name=\"textTraktQuickSyncError\">Instant Sync failed.</string>\n  <string name=\"textTraktQuickSyncErrorFull\">Instant Sync failed. We will try again next time you open the app. Please check your internet connection.</string>\n  <string name=\"textTraktNotificationsRationale\">Please grant the application a permission to display information about synchronization progress and results.\\n\\nWould you like to do it now?</string>\n\n  <string name=\"textRemoveFromTrakt\">Remove from Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktHidden\">Would you like to remove this item from your Trakt.tv account \\'Hidden Items\\'?</string>\n  <string name=\"textRemoveFromTraktWatchlist\">Would you like to remove this item from your Trakt.tv account \\'Watchlist\\'?</string>\n  <string name=\"textRemoveFromTraktProgress\">Would you like to remove this item from your Trakt.tv account \\'Progress\\'?</string>\n\n  <string name=\"textAddToMyShows\">Add to My Shows</string>\n  <string name=\"textAddToMyMovies\">Add to My Movies</string>\n  <string name=\"textAddToWatchlist\">Add to Watchlist</string>\n  <string name=\"textAddToHidden\">Add to Hidden</string>\n  <string name=\"textMoveToMyShows\">Move to My Shows</string>\n  <string name=\"textMoveToMyMovies\">Move to My Movies</string>\n  <string name=\"textMoveToWatchlist\">Move to Watchlist</string>\n  <string name=\"textMoveToHidden\">Move to Hidden</string>\n  <string name=\"textRemoveFromMyShows\">Remove from My Shows</string>\n  <string name=\"textRemoveFromMyMovies\">Remove from My Movies</string>\n  <string name=\"textRemoveFromWatchlist\">Remove from Watchlist</string>\n  <string name=\"textRemoveFromHidden\">Remove from Hidden</string>\n  <string name=\"textWatchlist\">Watchlist</string>\n  <string name=\"textWatchlistIncoming\">Upcoming</string>\n  <string name=\"textNewAlwaysAtTop\">New episodes always first</string>\n\n  <string name=\"textPremium\" translatable=\"false\">Showly Premium</string>\n  <string name=\"textPremiumAd\">Become a supporter and get access to bonus features! Click to see more.</string>\n\n  <string name=\"menuPin\">Pin to top</string>\n  <string name=\"menuUnpin\">Unpin from top</string>\n  <string name=\"menuAddOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Pin to \\'on hold\\'</string>\n  <string name=\"menuRemoveOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Unpin from \\'on hold\\'</string>\n\n  <string name=\"errorGeneral\">Oops… Something went wrong.\\nPlease contact us if this keeps happening.</string>\n  <string name=\"errorAuthorization\">Authorization failed. Please contact us if this keeps happening.</string>\n  <string name=\"errorTraktAuthorization\">Trakt account authorization failed.\\nPlease login and try again.</string>\n  <string name=\"errorSeasonsNotLoaded\">Please wait for seasons data to load.</string>\n  <string name=\"errorEpisodeNotAired\">This episode hasn\\'t been aired yet.</string>\n  <string name=\"errorCouldNotLoadDiscover\">We could not load discover page at this moment.\\nPlease contact us if this keeps happening.</string>\n  <string name=\"errorMalformedShow\">It looks like this show no longer exists in Trakt.tv database or is a duplicate.\\n\\nTap \\'OK\\' to remove it from the app.</string>\n  <string name=\"errorMalformedMovie\">It looks like this movie no longer exists in Trakt.tv database or is a duplicate.\\n\\nTap \\'OK\\' to remove it from the app.</string>\n  <string name=\"errorCouldNotLoadShow\">We could not load this show\\'s details at this moment.\\nPlease contact us if this keeps happening.</string>\n  <string name=\"errorCouldNotLoadMovie\">We could not load this movie\\'s details at this moment.\\nPlease contact us if this keeps happening.</string>\n  <string name=\"errorCouldNotLoadSearchResults\">We could not load search results at this moment.\\nPlease contact us if this keeps happening.</string>\n  <string name=\"errorNoInternetConnection\">No Internet. Please check your connection.</string>\n  <string name=\"errorTraktSyncGeneral\">Trakt.tv sync error.\\nPlease check your internet connection and try again.</string>\n  <string name=\"errorTraktLocked\">Your Trakt.tv account is currently locked.\\nPlease contact Trakt.tv support at https://support.trakt.tv/ to unlock your account.</string>\n  <string name=\"errorBillingProductsNotAvailable\" translatable=\"false\">We were not able to load purchase options. Please make sure your Google Play Store app is updated to the latest version and try again.</string>\n  <string name=\"errorSubscriptionsNotAvailable\">Google Play Subscriptions are not available on this device.</string>\n  <string name=\"errorCouldNotFindApp\" translatable=\"false\">Sorry, we could not find app on this device that could handle this link.</string>\n  <string name=\"errorAccountListsLimitsReached\" translatable=\"false\">Sorry, you have reached your Trakt account limit for personal lists!\\n\\nPlease contact us if you need more assistance.</string>\n  <string name=\"errorTraktSyncListsLimitsReached\" translatable=\"false\">You have reached your Trakt account limit for personal lists! Not all of your personal lists were synchronized.</string>\n  <string name=\"errorTraktSyncWatchlistLimitsReached\" translatable=\"false\">You have reached your Trakt account limit for watchlist items! Please contact us if you need more assistance.</string>\n\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values/styles.xml",
    "content": "<resources>\n\n  <!-- Base app theme -->\n  <style name=\"AppTheme\" parent=\"Theme.Material3.DayNight.NoActionBar\">\n    <item name=\"android:windowBackground\">@color/colorBackground</item>\n    <item name=\"android:navigationBarColor\">@color/colorPrimary</item>\n    <item name=\"android:windowTranslucentStatus\">true</item>\n    <item name=\"toolbarNavigationButtonStyle\">@style/Toolbar.Button.Navigation.Tinted</item>\n    <item name=\"switchStyle\">@style/ShowlySwitch</item>\n\n    <item name=\"colorPrimary\">@color/colorPrimary</item>\n    <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n    <item name=\"colorAccent\">@color/colorAccent</item>\n\n    <item name=\"android:textColorPrimary\">@color/colorWhite</item>\n    <item name=\"android:textColorSecondary\">@color/colorGrayLight</item>\n    <item name=\"textColorOnSurface\">@color/colorWhite</item>\n    <item name=\"textColorGridTitle\">@color/colorWhite</item>\n    <item name=\"textColorTab\">#A1A1A1</item>\n    <item name=\"textColorTabSelected\">@color/colorWhite</item>\n    <item name=\"textColorChip\">?android:textColorPrimary</item>\n    <item name=\"textColorChipSelected\">@color/colorAccent</item>\n    <item name=\"colorBackgroundChipSelected\">@color/colorBackground</item>\n    <item name=\"colorBackgroundChipSelectedLight\">?colorSearchViewBackground</item>\n    <item name=\"colorProgressTrack\">#3C3C3C</item>\n\n    <item name=\"colorInfoSnackbar\">@color/colorWhite</item>\n    <item name=\"textColorInfoSnackbar\">@color/colorBlack</item>\n    <item name=\"colorErrorSnackbar\">@color/colorError</item>\n    <item name=\"textColorErrorSnackbar\">@color/colorWhite</item>\n\n    <item name=\"colorBottomMenuBackground\">@color/colorPrimary</item>\n    <item name=\"colorBottomMenuItem\">#838383</item>\n    <item name=\"colorBottomMenuItemChecked\">@color/colorWhite</item>\n    <item name=\"colorBottomMenuSeparator\">#464646</item>\n    <item name=\"colorBottomMenuIndicator\">#E6323339</item>\n\n    <item name=\"colorSearchViewBackground\">@color/colorPrimary</item>\n    <item name=\"colorSearchViewControl\">@color/colorGrayLight</item>\n\n    <item name=\"colorSearchLocalViewBackground\">@color/colorGrayDark</item>\n    <item name=\"colorSearchLocalViewControl\">@color/colorGrayLight</item>\n\n    <item name=\"colorPlaceholderBackground\">#222222</item>\n    <item name=\"colorPlaceholderStroke\">#6A6A6A</item>\n    <item name=\"colorPlaceholderIcon\">#6A6A6A</item>\n\n    <item name=\"colorSeparator\">@color/colorGrayDark</item>\n    <item name=\"colorCardBackground\">#282828</item>\n    <item name=\"colorBadgeBackground\">@color/colorGrayDark</item>\n    <item name=\"colorWidgetStatusBackground\">@color/colorBlackTranslucent</item>\n  </style>\n\n  <!--  Bottom Sheet Dialogs -->\n\n  <style name=\"CustomBottomSheetDialog\" parent=\"@style/ThemeOverlay.MaterialComponents.BottomSheetDialog\">\n    <item name=\"android:windowTranslucentStatus\">false</item>\n    <item name=\"bottomSheetStyle\">@style/CustomBottomSheet</item>\n  </style>\n\n  <style name=\"CustomBottomSheet\" parent=\"Widget.MaterialComponents.BottomSheet\">\n    <item name=\"backgroundTint\">#00000000</item>\n    <item name=\"shapeAppearanceOverlay\">@style/CustomShapeAppearanceBottomSheetDialog</item>\n  </style>\n\n  <style name=\"CustomShapeAppearanceBottomSheetDialog\" parent=\"\">\n    <item name=\"cornerFamily\">rounded</item>\n    <item name=\"cornerSizeTopRight\">16dp</item>\n    <item name=\"cornerSizeTopLeft\">16dp</item>\n    <item name=\"cornerSizeBottomRight\">0dp</item>\n    <item name=\"cornerSizeBottomLeft\">0dp</item>\n  </style>\n\n  <!-- Progress Bars -->\n\n  <style name=\"ProgressBar\">\n    <item name=\"android:indeterminateTintMode\">src_in</item>\n  </style>\n\n  <style name=\"ProgressBar.Dark\" parent=\"ProgressBar\">\n    <item name=\"android:indeterminateTint\">@color/colorGrayDark</item>\n  </style>\n\n  <style name=\"ProgressBar.Accent\" parent=\"ProgressBar\">\n    <item name=\"android:indeterminateTint\">@color/colorAccent</item>\n    <item name=\"colorAccent\">@color/colorAccent</item>\n  </style>\n\n  <style name=\"ContentProgressBar\" parent=\"Widget.AppCompat.ProgressBar\">\n    <item name=\"android:indeterminateTintMode\">src_in</item>\n  </style>\n\n  <style name=\"ContentProgressBar.Dark\" parent=\"ContentProgressBar\">\n    <item name=\"colorAccent\">@color/colorGrayDark</item>\n    <item name=\"android:indeterminateTint\">@color/colorGrayDark</item>\n  </style>\n\n  <style name=\"ContentProgressBar.Accent\" parent=\"ContentProgressBar\">\n    <item name=\"colorAccent\">@color/colorAccent</item>\n    <item name=\"android:indeterminateTint\">@color/colorAccent</item>\n  </style>\n\n  <!-- Scrollbars -->\n\n  <style name=\"ScrollbarsStyle\">\n    <item name=\"android:scrollbarDefaultDelayBeforeFade\">200</item>\n    <item name=\"android:scrollbarFadeDuration\">100</item>\n    <item name=\"android:scrollbarSize\">@dimen/scrollBarWidth</item>\n    <item name=\"android:scrollbarStyle\">outsideOverlay</item>\n    <item name=\"android:scrollbarThumbVertical\">@color/colorGrayDark</item>\n  </style>\n\n  <!-- Dialogs -->\n\n  <style name=\"AlertDialog\" parent=\"@style/Theme.Material3.DayNight.Dialog\">\n    <item name=\"colorAccent\">@color/colorAccent</item>\n    <item name=\"colorPrimary\">@color/colorAccent</item>\n    <item name=\"buttonBarNegativeButtonStyle\">@style/AlertDialog.NegativeButton</item>\n  </style>\n\n  <style name=\"AlertDialog.NegativeButton\" parent=\"Widget.Material3.Button.TextButton.Dialog\">\n    <item name=\"android:textColor\">@color/colorGrayLight</item>\n    <item name=\"cornerRadius\">100dp</item>\n  </style>\n\n  <!-- Toolbar -->\n\n  <style name=\"Toolbar.Button.Navigation.Tinted\" parent=\"Widget.AppCompat.Toolbar.Button.Navigation\">\n    <item name=\"tint\">?android:attr/textColorPrimary</item>\n  </style>\n\n  <style name=\"AlertDialog.SmallText\" parent=\"AlertDialog\">\n    <item name=\"android:textSize\">13sp</item>\n  </style>\n\n  <!-- Other  -->\n\n  <style name=\"ImageTitle\">\n    <item name=\"android:shadowColor\">@color/colorBlack</item>\n    <item name=\"android:shadowDx\">3</item>\n    <item name=\"android:shadowDy\">3</item>\n    <item name=\"android:shadowRadius\">1</item>\n    <item name=\"android:textColor\">?attr/textColorOnSurface</item>\n    <item name=\"android:ellipsize\">end</item>\n  </style>\n\n  <style name=\"ImageTitleGrid\" parent=\"ImageTitle\">\n    <item name=\"android:shadowColor\">@color/colorBlack</item>\n    <item name=\"android:textColor\">?attr/textColorGridTitle</item>\n    <item name=\"android:ellipsize\">end</item>\n  </style>\n\n  <style name=\"ScrollableTabsStyle\" parent=\"Widget.Design.TabLayout\">\n    <item name=\"tabMode\">scrollable</item>\n    <item name=\"tabIndicatorColor\">@color/colorAccent</item>\n    <item name=\"tabPaddingStart\">8dp</item>\n    <item name=\"tabPaddingEnd\">8dp</item>\n    <item name=\"tabTextAppearance\">@style/ScrollableTabTextStyle</item>\n    <item name=\"tabSelectedTextColor\">?attr/textColorTabSelected</item>\n    <item name=\"tabIndicatorFullWidth\">false</item>\n    <item name=\"tabRippleColor\">@android:color/transparent</item>\n    <item name=\"tabIndicatorAnimationDuration\">200</item>\n  </style>\n\n  <style name=\"ScrollableTabTextStyle\" parent=\"TextAppearance.Design.Tab\">\n    <item name=\"android:textSize\">15sp</item>\n    <item name=\"android:textColor\">?attr/textColorTab</item>\n    <item name=\"android:layout_gravity\">start</item>\n    <item name=\"textAllCaps\">false</item>\n  </style>\n\n  <style name=\"ScrollableTabsEpisodesStyle\" parent=\"Widget.Design.TabLayout\">\n    <item name=\"tabMode\">scrollable</item>\n    <item name=\"tabMinWidth\">0dp</item>\n    <item name=\"tabPaddingStart\">8dp</item>\n    <item name=\"tabPaddingEnd\">8dp</item>\n    <item name=\"tabIndicatorColor\">@color/colorAccent</item>\n    <item name=\"tabTextAppearance\">@style/ScrollableTabEpisodeTextStyle</item>\n    <item name=\"tabSelectedTextColor\">?attr/textColorTabSelected</item>\n    <item name=\"tabIndicatorFullWidth\">false</item>\n    <item name=\"tabRippleColor\">@android:color/transparent</item>\n    <item name=\"tabIndicatorAnimationDuration\">0</item>\n  </style>\n\n  <style name=\"ScrollableTabEpisodeTextStyle\" parent=\"TextAppearance.Design.Tab\">\n    <item name=\"android:textSize\">14sp</item>\n    <item name=\"android:textColor\">?attr/textColorTab</item>\n    <item name=\"android:layout_gravity\">start</item>\n    <item name=\"textAllCaps\">false</item>\n  </style>\n\n  <style name=\"Badge\">\n    <item name=\"android:layout_width\">wrap_content</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:layout_gravity\">top|end</item>\n    <item name=\"android:layout_marginEnd\">@dimen/spaceTiny</item>\n    <item name=\"android:layout_marginTop\">-2dp</item>\n    <item name=\"android:visibility\">gone</item>\n    <item name=\"android:elevation\">@dimen/elevationSmall</item>\n    <item name=\"android:tint\">@color/colorAccent</item>\n  </style>\n\n  <style name=\"Badge.Watchlist\" parent=\"Badge\">\n    <item name=\"android:tint\">@color/colorGrayLight</item>\n  </style>\n\n  <style name=\"CollectionItem\" />\n\n  <style name=\"CollectionItem.Header\" parent=\"CollectionItem\">\n    <item name=\"android:ellipsize\">end</item>\n    <item name=\"android:gravity\">start|center_vertical</item>\n    <item name=\"android:maxLines\">1</item>\n    <item name=\"android:textAlignment\">viewStart</item>\n    <item name=\"android:textColor\">?attr/colorAccent</item>\n    <item name=\"android:textSize\">12sp</item>\n  </style>\n\n  <style name=\"CollectionItem.Title\" parent=\"CollectionItem\">\n    <item name=\"android:ellipsize\">end</item>\n    <item name=\"android:fontFamily\">sans-serif-medium</item>\n    <item name=\"android:gravity\">start|center_vertical</item>\n    <item name=\"android:maxLines\">1</item>\n    <item name=\"android:textAlignment\">viewStart</item>\n    <item name=\"android:textColor\">?android:attr/textColorPrimary</item>\n    <item name=\"android:textSize\">18sp</item>\n  </style>\n\n  <style name=\"CollectionItem.Description\" parent=\"CollectionItem\">\n    <item name=\"android:ellipsize\">end</item>\n    <item name=\"android:gravity\">start|center_vertical</item>\n    <item name=\"android:maxLines\">2</item>\n    <item name=\"android:textAlignment\">viewStart</item>\n    <item name=\"android:textColor\">?android:attr/textColorSecondary</item>\n    <item name=\"android:textSize\">12sp</item>\n  </style>\n\n  <style name=\"ContextMenuItemButton.Move\" parent=\"RoundOutlinedButton\">\n    <item name=\"android:layout_width\">match_parent</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:gravity\">center</item>\n    <item name=\"android:includeFontPadding\">true</item>\n    <item name=\"android:textColor\">?android:attr/textColorPrimary</item>\n    <item name=\"iconGravity\">textStart</item>\n    <item name=\"iconPadding\">@dimen/spaceTiny</item>\n    <item name=\"iconTint\">?android:attr/textColorPrimary</item>\n    <item name=\"iconSize\">22dp</item>\n    <item name=\"rippleColor\">?android:attr/textColorPrimary</item>\n    <item name=\"strokeColor\">?android:attr/textColorPrimary</item>\n  </style>\n\n  <style name=\"ContextMenuItemButton.Remove\" parent=\"RoundTextButton\">\n    <item name=\"android:layout_width\">match_parent</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:gravity\">center</item>\n    <item name=\"android:includeFontPadding\">false</item>\n    <item name=\"android:textColor\">?android:attr/textColorPrimary</item>\n    <item name=\"icon\">@drawable/ic_remove_list</item>\n    <item name=\"iconGravity\">textStart</item>\n    <item name=\"iconSize\">22dp</item>\n    <item name=\"iconPadding\">@dimen/spaceTiny</item>\n    <item name=\"iconTint\">?android:attr/textColorPrimary</item>\n    <item name=\"rippleColor\">?android:attr/textColorPrimary</item>\n    <item name=\"strokeColor\">?android:attr/textColorPrimary</item>\n  </style>\n\n  <style name=\"RoundMaterialButton\" parent=\"Widget.Material3.Button\">\n    <item name=\"cornerRadius\">100dp</item>\n    <item name=\"android:textAllCaps\">true</item>\n    <item name=\"android:letterSpacing\">0.05</item>\n  </style>\n\n  <style name=\"RoundTextButton\" parent=\"Widget.Material3.Button.TextButton\">\n    <item name=\"cornerRadius\">100dp</item>\n    <item name=\"android:textAllCaps\">true</item>\n    <item name=\"android:letterSpacing\">0.05</item>\n  </style>\n\n  <style name=\"RoundOutlinedButton\" parent=\"Widget.Material3.Button.OutlinedButton\">\n    <item name=\"cornerRadius\">100dp</item>\n    <item name=\"android:textAllCaps\">true</item>\n    <item name=\"android:letterSpacing\">0.05</item>\n  </style>\n\n  <style name=\"DeclineOutlinedButton\" parent=\"RoundOutlinedButton\">\n    <item name=\"strokeColor\">@color/colorGrayLight</item>\n  </style>\n\n  <style name=\"ShowlyBottomBar\" parent=\"Widget.MaterialComponents.BottomNavigationView\">\n    <item name=\"itemTextAppearanceInactive\">@style/ShowlyBottomBar.Text</item>\n    <item name=\"itemTextAppearanceActive\">@style/ShowlyBottomBar.Text</item>\n  </style>\n\n  <style name=\"ShowlyBottomBar.Text\" parent=\"TextAppearance.Material3.LabelMedium\">\n    <item name=\"android:textSize\">12sp</item>\n  </style>\n\n  <style name=\"ShowlyBottomBar.Indicator\" parent=\"Widget.Material3.BottomNavigationView.ActiveIndicator\">\n    <item name=\"android:color\">?colorBottomMenuIndicator</item>\n  </style>\n\n  <style name=\"ShowlySwitch\" parent=\"Widget.Material3.CompoundButton.Switch\">\n    <item name=\"materialThemeOverlay\">@style/ShowlySwitch.Overlay</item>\n  </style>\n\n  <style name=\"ShowlySwitch.Overlay\" parent=\"\">\n    <item name=\"colorPrimary\">@color/colorAccent</item>\n    <item name=\"colorPrimaryContainer\">#4DF44336</item>\n  </style>\n\n  <style name=\"ShowlyChip\" parent=\"Widget.Material3.Chip.Suggestion\">\n    <item name=\"android:layout_width\">wrap_content</item>\n    <item name=\"android:layout_height\">30dp</item>\n    <item name=\"chipMinTouchTargetSize\">0dp</item>\n  </style>\n\n  <style name=\"ShowlyChip.Sort\" parent=\"ShowlyChip\">\n    <item name=\"android:textColor\">?android:textColorPrimary</item>\n    <item name=\"chipBackgroundColor\">?android:windowBackground</item>\n    <item name=\"chipStrokeColor\">?android:textColorSecondary</item>\n    <item name=\"closeIcon\">@drawable/ic_arrow_alt_up</item>\n    <item name=\"closeIconEndPadding\">0dp</item>\n    <item name=\"closeIconTint\">?android:textColorPrimary</item>\n    <item name=\"closeIconVisible\">true</item>\n    <item name=\"textStartPadding\">@dimen/spaceSmall</item>\n  </style>\n\n  <style name=\"ShowlyChip.Filter\" parent=\"ShowlyChip\">\n    <item name=\"android:textColor\">@color/selector_chip_text</item>\n    <item name=\"chipBackgroundColor\">@color/selector_chip_background</item>\n    <item name=\"chipStrokeColor\">@color/selector_chip_stroke</item>\n    <item name=\"closeIconVisible\">false</item>\n  </style>\n\n  <style name=\"ShowlyCheckbox\" parent=\"Widget.Material3.CompoundButton.CheckBox\">\n    <item name=\"buttonTint\">@color/selector_main_checkbox</item>\n    <item name=\"buttonIconTint\">@color/colorBackground</item>\n    <item name=\"android:textColor\">?android:textColorPrimary</item>\n  </style>\n\n  <style name=\"ShowlyCheckbox.Secondary\" parent=\"ShowlyCheckbox\">\n    <item name=\"buttonIconTint\">?attr/colorBottomMenuBackground</item>\n    <item name=\"android:textColor\">?android:textColorSecondary</item>\n  </style>\n\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-ar/dimens.xml",
    "content": "<resources>\n  <dimen name=\"searchViewDotTranslation\">23dp</dimen>\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textOk\">حسنًا</string>\n  <string name=\"textYes\">نعم</string>\n  <string name=\"textNo\">لا</string>\n  <string name=\"textApply\">تطبيق</string>\n  <string name=\"textCancel\">إلغاء</string>\n  <string name=\"textClose\">إغلاق</string>\n  <string name=\"textNotNow\">ليس الآن</string>\n  <string name=\"textNew\">جديد</string>\n  <string name=\"textSelect\">اختر</string>\n  <string name=\"textHot\">شائع</string>\n  <string name=\"textSubmit\">إرسال</string>\n  <string name=\"textHide\">إخفاء</string>\n  <string name=\"textRemove\">حذف التقييم</string>\n  <string name=\"textEmptyResults\">لا توجد نتائج…</string>\n  <string name=\"textSearchFor\">البحث</string>\n  <string name=\"textTip\">نصيحة:</string>\n  <string name=\"textSeason\">الموسم %1$d</string>\n  <string name=\"textSpecials\">الحلقات الخاصة</string>\n  <string name=\"textEpisode\">الحلقة %1$d</string>\n  <string name=\"textSeasonEpisode\">S.%02d E.%02d</string>\n  <string name=\"textSortBy\">ترتيب حسب:</string>\n  <string name=\"textSpoilersWarning\">هذا التعليق يحتوى على حرق للأحداث.\\nاضغط للقراءة.</string>\n  <string name=\"textCommentedOn\">تعليق بواسطة %s</string>\n  <string name=\"textNetwork\">(%2$s) %1$s</string>\n  <string name=\"textNetworks\" comment=\"TV network like HBO, AMC, Showtime, Disney etc.\">الشبكة التلفزيونية:</string>\n  <string name=\"textGenres\">التصنيفات:</string>\n  <string name=\"textShows\">مسلسلات</string>\n  <string name=\"textMovies\">أفلام</string>\n  <string name=\"textLists\">قوائم</string>\n  <string name=\"textMinutesShort\">دقائق</string>\n  <string name=\"textNoDescription\">لا يتوفر وصف.</string>\n  <string name=\"textPleaseWait\">الرجاء الانتظار…</string>\n  <string name=\"textRate\">قيم</string>\n  <string name=\"textLink\">إفتح في</string>\n  <string name=\"textDisabled\">تعطيل</string>\n  <string name=\"textRateSaved\">حُفظ تقييمك.</string>\n  <string name=\"textRateRemoved\">تم حذف تقييمك.</string>\n  <string name=\"textDays\">%1$d يوم</string>\n  <plurals name=\"textEpisodesLeft\" comment=\"Number of episodes left to see\">\n    <item quantity=\"zero\">بقي %d</item>\n    <item quantity=\"one\">بقي %d</item>\n    <item quantity=\"two\">بقي %d</item>\n    <item quantity=\"few\">بقي %d</item>\n    <item quantity=\"many\">بقي %d</item>\n    <item quantity=\"other\">بقي %d</item>\n  </plurals>\n  <string name=\"textPeople\">الأشخاص:</string>\n  <string name=\"textDirecting\">اخراج</string>\n  <string name=\"textDirector\">المخرج</string>\n  <string name=\"textWriting\">الكتابة</string>\n  <string name=\"textScreenplay\">النص السينمائي</string>\n  <string name=\"textSound\">الصوت</string>\n  <string name=\"textMusic\">موسيقى</string>\n  <string name=\"textActing\">التمثيل</string>\n  <string name=\"textSetCustomImages\">تخصيص الصور</string>\n  <string name=\"textSetCustomImagesDescription\">اِختر المُلصق والفان أرت الذان يُعجبانك لاِستخدامهما داخل التطبيق.</string>\n  <string name=\"textAiredAlready\">بُثت</string>\n  <string name=\"textAirsNow\">يُبث الآن</string>\n  <plurals name=\"textDaysToAir\">\n    <item quantity=\"zero\">سيُعرض بعد 0 يوم</item>\n    <item quantity=\"one\">سيُعرض غدًا</item>\n    <item quantity=\"two\">سيُعرض بعد يومين</item>\n    <item quantity=\"few\">سيُعرض بعد %d أيام</item>\n    <item quantity=\"many\">سيُعرض بعد %d أيام</item>\n    <item quantity=\"other\">سيُعرض بعد %d أيام</item>\n  </plurals>\n  <plurals name=\"textHoursToAir\">\n    <item quantity=\"zero\">سيُعرض بعد 0 ساعة</item>\n    <item quantity=\"one\">سيُعرض بعد ساعة</item>\n    <item quantity=\"two\">سيُعرض بعد ساعتين</item>\n    <item quantity=\"few\">سيُعرض بعد %d ساعات</item>\n    <item quantity=\"many\">سيُعرض بعد %d ساعة</item>\n    <item quantity=\"other\">تُعرض بعد %d ساعة</item>\n  </plurals>\n  <plurals name=\"textMinutesToAir\">\n    <item quantity=\"zero\">سيُعرض بعد 0 دقيقة</item>\n    <item quantity=\"one\">سيُعرض الآن</item>\n    <item quantity=\"two\">سيُعرض بعد دقيقتين</item>\n    <item quantity=\"few\">سيُعرض بعد %d دقائق</item>\n    <item quantity=\"many\">سيُعرض بعد %d دقيقة</item>\n    <item quantity=\"other\">سيُعرض بعد %d دقيقة</item>\n  </plurals>\n  <plurals name=\"textTraktQuickSyncComplete\">\n    <item quantity=\"zero\">لم تتم مزامنة أي عنصر.</item>\n    <item quantity=\"one\">عنصر واحد تمت مزامنته بنجاح.</item>\n    <item quantity=\"two\">تمت مزامنة %d عناصر بنجاح.</item>\n    <item quantity=\"few\">تمت مزامنة %d عناصر بنجاح.</item>\n    <item quantity=\"many\">تمت مزامنة %d عناصر بنجاح.</item>\n    <item quantity=\"other\">تمت مزامنة %d عناصر بنجاح.</item>\n  </plurals>\n  <string name=\"textToday\">اليوم</string>\n  <string name=\"textTomorrow\">غدًا</string>\n  <string name=\"textThisWeek\">هذا الأسبوع</string>\n  <string name=\"textNextWeek\">الأسبوع القادم</string>\n  <string name=\"textThisMonth\">هذا الشهر</string>\n  <string name=\"textNextMonth\">الشهر القادم</string>\n  <string name=\"textThisYear\">هذه السنة</string>\n  <string name=\"textNextYear\">السنة القادمة</string>\n  <string name=\"textLater\">لاحقًا</string>\n  <string name=\"textYesterday\">أمس</string>\n  <string name=\"textLast7Days\">آخر 7 أيام</string>\n  <string name=\"textLast30Days\">آخر 30 يوم</string>\n  <string name=\"textLast90Days\">آخر 90 يوم</string>\n  <string name=\"textNewEpisodeAvailable\">توفرت حلقة جديدة!</string>\n  <string name=\"textNewEpisodeAvailableSoon\">ستتوفر حلقة جديدة قريباً!</string>\n  <string name=\"textNewSeasonAvailable\">توفر موسم جديد!</string>\n  <string name=\"textNewSeasonAvailableSoon\">موسم جديد قادم قريباً!</string>\n  <string name=\"textNewMovieAvailable\">توفر الفيلم!</string>\n  <string name=\"textTraktSync\">مزامنة Trakt.tv</string>\n  <string name=\"textTraktSyncComplete\">إكتملت المزامنة بنجاح.</string>\n  <string name=\"textTraktSyncRunning\">بدء المزامنة…</string>\n  <string name=\"textTraktSyncError\">فشلت المزامنة مع Trakt.tv.</string>\n  <string name=\"textTraktSyncErrorFull\">فشلت المزامنة مع Trakt.tv. رجاءً تأكد من أنك مُتصل بالإنترنت ثم حاول مجدداً، وإذا إستمرت المشكلة بالحدوث، تواصل معنا.</string>\n  <string name=\"textTraktQuickSyncError\">فشلت المزامنة الفورية.</string>\n  <string name=\"textTraktQuickSyncErrorFull\">فشلت المزامنة الفورية. سيتم محاولة المزامنة الفورية بعد تشغيل التطبيق مرة أخرى، رجاءً تأكد من إتصالك بالإنترنت.</string>\n  <string name=\"textTraktNotificationsRationale\">الرجاء منح التطبيق الإذن لعرض معلومات حول تقدم المزامنة ونتائجها.\\n\\nهل ترغب في القيام بذلك الآن؟</string>\n  <string name=\"textRemoveFromTrakt\">حذف من Trakt.tv؟</string>\n  <string name=\"textRemoveFromTraktHidden\">أترغب في إزالته من قائمة \\\"المحتويات المخفية\\\" في حسابك على موقع Trakt.tv؟</string>\n  <string name=\"textRemoveFromTraktWatchlist\">أترغب في إزالته من \\\"قائمة المشاهدة\\\" في حسابك على موقع Trakt.tv؟</string>\n  <string name=\"textRemoveFromTraktProgress\">أترغب في إزالته من قائمة \\\"مستوى التقدم\\\" في حسابك على موقع Trakt.tv؟</string>\n  <string name=\"textAddToMyShows\">إضافة إلى مسلسلاتي</string>\n  <string name=\"textAddToMyMovies\">إضافة إلى أفلامي</string>\n  <string name=\"textAddToWatchlist\">إضافة إلى قائمة المشاهدة</string>\n  <string name=\"textAddToHidden\">إضافة إلى المخفية</string>\n  <string name=\"textMoveToMyShows\">نقل إلى مسلسلاتي</string>\n  <string name=\"textMoveToMyMovies\">نقل إلى أفلامي</string>\n  <string name=\"textMoveToWatchlist\">نقل إلى قائمة المشاهدة</string>\n  <string name=\"textMoveToHidden\">نقل إلى المخفية</string>\n  <string name=\"textRemoveFromMyShows\">إزالة من مسلسلاتي</string>\n  <string name=\"textRemoveFromMyMovies\">إزالة من أفلامي</string>\n  <string name=\"textRemoveFromWatchlist\">إزالة من قائمة المشاهدة</string>\n  <string name=\"textRemoveFromHidden\">إزالة من المخفية</string>\n  <string name=\"textWatchlist\">قائمة المشاهدة</string>\n  <string name=\"textWatchlistIncoming\">يُعرض قريباً</string>\n  <string name=\"textNewAlwaysAtTop\">الحلقات الجديدة دائماً أولاً</string>\n  <string name=\"textPremiumAd\">إدعم تطوير التطبيق واحصل على مميزات إضافية! انقر لرؤية المزيد.</string>\n  <string name=\"menuPin\">تثبيت في الأعلى</string>\n  <string name=\"menuUnpin\">إلغاء التثبيت من الأعلى</string>\n  <string name=\"menuAddOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">إضافة إلى \\\"المعلقة\\\"</string>\n  <string name=\"menuRemoveOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">إزالة من \\\"المعلقة\\\"</string>\n  <string name=\"errorGeneral\">عفواً… حدث خطأ ما.\\nإذا استمرت المشكلة بالحدوث، رجاءً تواصل معنا.</string>\n  <string name=\"errorAuthorization\">فشلت المصادقة. إذا استمرت المشكلة بالحدوث، رجاءً تواصل معنا.</string>\n  <string name=\"errorTraktAuthorization\">فشلت مصادقة Trakt.\\nقُم بتسجيل الدخول وحاول مرة أخرى.</string>\n  <string name=\"errorSeasonsNotLoaded\">رجاءً إنتظر قليلاً حتى يكتمل تحميل بيانات المواسم.</string>\n  <string name=\"errorEpisodeNotAired\">لم تُعرض الحلقة حتى الآن.</string>\n  <string name=\"errorCouldNotLoadDiscover\">لم نستطع تحميل صفحة إكتشف.\\nرجاءً تواصل معنا إذا استمرت هذه المشكلة بالحدوث.</string>\n  <string name=\"errorMalformedShow\">يبدو أن هذا المسلسل لم يعد موجودًا في قاعدة بيانات موقع Trakt.tv، أو أنه مُكرر.\\n\\nأنقر على زر \\\"موافق\\\" لِحذفه مِن التطبيق.</string>\n  <string name=\"errorMalformedMovie\">يبدو أن هذا الفيلم لم يعد موجودًا في قاعدة بيانات موقع Trakt.tv، أو أنه مُكرر.\\n\\nأنقر على زر \\\"موافق\\\" لِحذفه مِن التطبيق.</string>\n  <string name=\"errorCouldNotLoadShow\">لم نستطع تحميل معلومات هذا المسلسل.\\nرجاءً تواصل معنا إذا إستمرت هذه المشكلة بالحدوث</string>\n  <string name=\"errorCouldNotLoadMovie\">لم نستطع تحميل معلومات هذا الفيلم.\\nرجاءً تواصل معنا إذا إستمرت هذه المشكلة بالحدوث.</string>\n  <string name=\"errorCouldNotLoadSearchResults\">لم نستطع تحميل نتائج البحث.\\nرجاءً تواصل معنا إذا استمرت هذه المشكلة بالحدوث.</string>\n  <string name=\"errorNoInternetConnection\">لا يوجد إتصال بالإنترنت. رجاءً تحقق من إتصالك.</string>\n  <string name=\"errorTraktSyncGeneral\">فشلت مزامنة Trakt.tv.\\nرجاءً تحقق من إتصالك بالإنترنت ثم حاول مرة أخرى.</string>\n  <string name=\"errorTraktLocked\">حسابك في Trakt.tv مغلق. تواصل مع دعم Trakt.tv عبر الرابط https://support.trakt.tv/ لِفتح حسابك.</string>\n  <string name=\"errorSubscriptionsNotAvailable\">الدفع عبر Google Play غير متوفر على هذا الجهاز.</string>\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textOk\">OK</string>\n  <string name=\"textYes\">Ja</string>\n  <string name=\"textNo\">Nein</string>\n  <string name=\"textApply\">Anwenden</string>\n  <string name=\"textCancel\">Abbrechen</string>\n  <string name=\"textClose\">Schließen</string>\n  <string name=\"textNotNow\">Nicht jetzt</string>\n  <string name=\"textNew\">Neu</string>\n  <string name=\"textSelect\">Ausgewählt</string>\n  <string name=\"textHot\">Hot</string>\n  <string name=\"textSubmit\">Absenden</string>\n  <string name=\"textHide\">Verstecken</string>\n  <string name=\"textRemove\">Löschen</string>\n  <string name=\"textEmptyResults\">Keine Ergebnisse…</string>\n  <string name=\"textSearchFor\">Suche</string>\n  <string name=\"textTip\">Tipp:</string>\n  <string name=\"textSeason\">Staffel %1$d</string>\n  <string name=\"textSpecials\">Specials</string>\n  <string name=\"textEpisode\">Folge %1$d</string>\n  <string name=\"textSeasonEpisode\">S.%02d E.%02d</string>\n  <string name=\"textSortBy\">Sortiert nach:</string>\n  <string name=\"textSpoilersWarning\">Dieser Kommentar enthält Spoiler.\\nTippen um zu lesen.</string>\n  <string name=\"textCommentedOn\">Kommentiert von %s</string>\n  <string name=\"textNetwork\">%1$s (%2$s)</string>\n  <string name=\"textNetworks\" comment=\"TV network like HBO, AMC, Showtime, Disney etc.\">Fernseh-Network:</string>\n  <string name=\"textGenres\">Genres:</string>\n  <string name=\"textShows\">Serien</string>\n  <string name=\"textMovies\">Filme</string>\n  <string name=\"textLists\">Listen</string>\n  <string name=\"textMinutesShort\">min</string>\n  <string name=\"textNoDescription\">Übersicht nicht verfügbar.</string>\n  <string name=\"textPleaseWait\">Bitte warten…</string>\n  <string name=\"textRate\">Bewerten</string>\n  <string name=\"textLink\">Link</string>\n  <string name=\"textRateSaved\">Deine Bewertung wurde gespeichert.</string>\n  <string name=\"textRateRemoved\">Deine Bewertung wurde gelöscht.</string>\n  <plurals name=\"textEpisodesLeft\" comment=\"Number of episodes left to see\">\n    <item quantity=\"one\">%d übrig</item>\n    <item quantity=\"other\">%d übrig</item>\n  </plurals>\n  <string name=\"textPeople\">Personen:</string>\n  <string name=\"textDirecting\">Regie</string>\n  <string name=\"textDirector\">Regisseur</string>\n  <string name=\"textWriting\">Drehbuchautor</string>\n  <string name=\"textScreenplay\">Drehbuch</string>\n  <string name=\"textSound\">Ton</string>\n  <string name=\"textMusic\">Musik</string>\n  <string name=\"textActing\">Darsteller</string>\n  <string name=\"textSetCustomImages\">Benutzerdefinierte Bilder</string>\n  <string name=\"textSetCustomImagesDescription\">Wähle dein eigenes Poster und Fanart, diese werden in der gesamten App verwendet.</string>\n  <string name=\"textAiredAlready\">Veröffentlicht</string>\n  <string name=\"textAirsNow\">Jetzt live</string>\n  <plurals name=\"textDaysToAir\">\n    <item quantity=\"one\">Morgen verfügbar</item>\n    <item quantity=\"other\">Verfügbar in %d Tagen</item>\n  </plurals>\n  <plurals name=\"textHoursToAir\">\n    <item quantity=\"one\">Veröffentlichung in 1 Stunde</item>\n    <item quantity=\"other\">Verfügbar in %d Stunden</item>\n  </plurals>\n  <plurals name=\"textMinutesToAir\">\n    <item quantity=\"one\">Jetzt verfügbar</item>\n    <item quantity=\"other\">Verfügbar in %d Minuten</item>\n  </plurals>\n  <plurals name=\"textTraktQuickSyncComplete\">\n    <item quantity=\"one\">1 Element erfolgreich synchronisiert.</item>\n    <item quantity=\"other\">%d Items erfolgreich synchronisiert.  </item>\n  </plurals>\n  <string name=\"textToday\">Heute</string>\n  <string name=\"textTomorrow\">Morgen</string>\n  <string name=\"textThisWeek\">Diese Woche</string>\n  <string name=\"textNextWeek\">Nächste Woche</string>\n  <string name=\"textThisMonth\">Diesen Monat</string>\n  <string name=\"textNextMonth\">Nächsten Monat</string>\n  <string name=\"textThisYear\">Dieses Jahr</string>\n  <string name=\"textNextYear\">Nächstes Jahr</string>\n  <string name=\"textLater\">Später</string>\n  <string name=\"textYesterday\">Gestern</string>\n  <string name=\"textLast7Days\">Letzten 7 Tage</string>\n  <string name=\"textLast30Days\">Letzten 30 Tage</string>\n  <string name=\"textLast90Days\">Letzten 90 Tage</string>\n  <string name=\"textNewEpisodeAvailable\">Neue Folge jetzt verfügbar!</string>\n  <string name=\"textNewEpisodeAvailableSoon\">Neue Folge bald verfügbar!</string>\n  <string name=\"textNewSeasonAvailable\">Neue Staffel jetzt verfügbar!</string>\n  <string name=\"textNewSeasonAvailableSoon\">Neue Staffel bald verfügbar!</string>\n  <string name=\"textNewMovieAvailable\">Film wurde veroffentlicht!</string>\n  <string name=\"textTraktSync\">Trakt.tv Sync</string>\n  <string name=\"textTraktSyncComplete\">Synchronisation erfolgreich abgeschlossen!</string>\n  <string name=\"textTraktSyncRunning\">Läuft…</string>\n  <string name=\"textTraktSyncError\">Synchronisation mit Trakt.tv fehlgeschlagen.</string>\n  <string name=\"textTraktSyncErrorFull\">Trakt.tv Synchronisation fehlgeschlagen. Bitte überprüfe deine Internetverbindung und versuch es erneut oder kontaktiere uns, wenn der Fehler weiterhin besteht.</string>\n  <string name=\"textTraktQuickSyncError\">Sofortige Synchronisation fehlgeschlagen.</string>\n  <string name=\"textTraktQuickSyncErrorFull\">Sofortige Synchronisation fehlgeschlagen. Wir versuchen es erneut beim nächsten Öffnen der App. Bitte überprüfe deine Internetverbindung.</string>\n  <string name=\"textRemoveFromTrakt\">Von Trakt.tv entfernen?</string>\n  <string name=\"textRemoveFromTraktHidden\">Möchtest du dieses Element aus den \\'Versteckten Elementen\\' deines Trakt.tv-Accounts entfernen?</string>\n  <string name=\"textRemoveFromTraktWatchlist\">Möchtest du dieses Element aus der \\'Watchlist\\' deines Trakt.tv-Accounts entfernen?</string>\n  <string name=\"textRemoveFromTraktProgress\">Möchtest du dieses Element aus der Kategorie \\'Fortschritt\\' deines Trakt.tv-Accounts entfernen?</string>\n  <string name=\"textAddToMyShows\">Zu Meine Serien</string>\n  <string name=\"textAddToMyMovies\">Zu Meinen Filmen</string>\n  <string name=\"textAddToWatchlist\">Zu Watchlist</string>\n  <string name=\"textAddToHidden\">Zu Versteckt</string>\n  <string name=\"textMoveToMyShows\">Zu meinen Serien verschieben</string>\n  <string name=\"textMoveToMyMovies\">In meine Filme verschieben</string>\n  <string name=\"textMoveToWatchlist\">Zur Watchlist verschieben</string>\n  <string name=\"textMoveToHidden\">In Versteckt verschieben</string>\n  <string name=\"textRemoveFromMyShows\">Von meinen Serien entfernen</string>\n  <string name=\"textRemoveFromMyMovies\">Von meinen Filmen entfernen</string>\n  <string name=\"textRemoveFromWatchlist\">Von Watchlist entfernen</string>\n  <string name=\"textRemoveFromHidden\">Von Versteckt entfernen</string>\n  <string name=\"textWatchlist\">Watchlist</string>\n  <string name=\"textWatchlistIncoming\">Demnächst</string>\n  <string name=\"textNewAlwaysAtTop\">Neue Folgen immer zuerst</string>\n  <string name=\"textPremiumAd\">Werde Unterstützer und erhalte Zugang zu Bonusfunktionen! Klicke hier, um mehr zu sehen.</string>\n  <string name=\"menuPin\">Oben anpinnen</string>\n  <string name=\"menuUnpin\">Pin von Oben lösen</string>\n  <string name=\"menuAddOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Pin auf \\'Angehalten\\'</string>\n  <string name=\"menuRemoveOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Pin von \\'Angehalten\\' lösen</string>\n  <string name=\"errorGeneral\">Hoppla… Da ging etwas schief.\\nBitte kontaktiere uns, wenn dies weiterhin geschieht.</string>\n  <string name=\"errorAuthorization\">Authentifizierung fehlgeschlagen. Bitte kontaktiere uns, wenn dies weiterhin geschieht.</string>\n  <string name=\"errorTraktAuthorization\">Trakt.tv Authentifizierung fehlgeschlagen.\\nBitte einloggen und erneut versuchen.</string>\n  <string name=\"errorSeasonsNotLoaded\">Bitte warte bis die Staffelinformationen geladen sind.</string>\n  <string name=\"errorEpisodeNotAired\">Diese Folge wurde bisher noch nicht veröffentlicht.</string>\n  <string name=\"errorCouldNotLoadDiscover\">Wir können die Entdecken Seite aktuell nicht laden.\\nBitte kontaktiere uns, wenn dieser Fehler weiterhin besteht.</string>\n  <string name=\"errorMalformedShow\">Es sieht so aus als ob diese Sendung nicht länger in der Trakt.tv Datenbank existiert oder doppelt ist.\\n\\n Tippe auf \\\"OK\\\" um sie aus der App zu entfernen.</string>\n  <string name=\"errorMalformedMovie\">Es sieht so aus als ob dieser Film nicht länger in der Trakt.tv Datenbank existiert oder doppelt ist.\\n\\n Tippe auf \\\"OK\\\" um ihn auf der App zu entfernen.</string>\n  <string name=\"errorCouldNotLoadShow\">Wir können die Details zu dieser Serie aktuell nicht laden.\\nBitte kontaktiere uns, wenn dieser Fehler weiterhin passiert.</string>\n  <string name=\"errorCouldNotLoadMovie\">Wir können die Details zu diesem Filme aktuell nicht laden.\\nBitte kontaktiere uns, wenn dieser Fehler weiterhin passiert.</string>\n  <string name=\"errorCouldNotLoadSearchResults\">Wir können die Suchergebnisse aktuell nicht laden.\\nBitte schreib uns, wenn dieser Fehler weiterhin passiert.</string>\n  <string name=\"errorNoInternetConnection\">Kein Internet. Bitte überprüfe deine Verbindung.</string>\n  <string name=\"errorTraktSyncGeneral\">Synchronisierungsfehler mit Trakt.tv.\\nBitte prüfe deine Internetverbindung und versuch es erneut.</string>\n  <string name=\"errorTraktLocked\">Dein Trakt.tv-Account ist derzeit gesperrt.\\nWende dich an den Support unter https://support.trakt.tv/.</string>\n  <string name=\"errorSubscriptionsNotAvailable\">Google Play Abonnements sind auf diesem Gerät nicht verfügbar.</string>\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textOk\">OK</string>\n  <string name=\"textYes\">Si</string>\n  <string name=\"textNo\">No</string>\n  <string name=\"textApply\">Aceptar</string>\n  <string name=\"textCancel\">Cancelar</string>\n  <string name=\"textClose\">Cerrar</string>\n  <string name=\"textNotNow\">Ahora no</string>\n  <string name=\"textNew\">Nuevo</string>\n  <string name=\"textSelect\">Seleccionar</string>\n  <string name=\"textHot\">Tendencia</string>\n  <string name=\"textSubmit\">Enviar</string>\n  <string name=\"textHide\">Ocultar</string>\n  <string name=\"textRemove\">Eliminar</string>\n  <string name=\"textEmptyResults\">No hay resultados...</string>\n  <string name=\"textSearchFor\">Buscar</string>\n  <string name=\"textTip\">Consejo:</string>\n  <string name=\"textSeason\">Temporada %1$d</string>\n  <string name=\"textSpecials\">Especiales</string>\n  <string name=\"textEpisode\">Episodio %1$d</string>\n  <string name=\"textSeasonEpisode\">T.%02d E.%02d</string>\n  <string name=\"textSortBy\">Ordenar por:</string>\n  <string name=\"textSpoilersWarning\">Este comentario contiene spoilers.\\nTap para leer.</string>\n  <string name=\"textCommentedOn\">Comentado por %s</string>\n  <string name=\"textNetwork\">%1$s (%2$s)</string>\n  <string name=\"textNetworks\" comment=\"TV network like HBO, AMC, Showtime, Disney etc.\">Cadena:</string>\n  <string name=\"textGenres\">Géneros:</string>\n  <string name=\"textShows\">Series</string>\n  <string name=\"textMovies\">Películas</string>\n  <string name=\"textLists\">Listas</string>\n  <string name=\"textMinutesShort\">min</string>\n  <string name=\"textNoDescription\">Resumen no disponible.</string>\n  <string name=\"textPleaseWait\">Espera, por favor…</string>\n  <string name=\"textRate\">Calificar</string>\n  <string name=\"textLink\">Enlace</string>\n  <string name=\"textDisabled\">Deshabilitado</string>\n  <string name=\"textRateSaved\">Tu calificación se ha guardado.</string>\n  <string name=\"textRateRemoved\">Tu calificación ha sido eliminada.</string>\n  <string name=\"textDays\">%1$d días</string>\n  <plurals name=\"textEpisodesLeft\" comment=\"Number of episodes left to see\">\n    <item quantity=\"one\">%d restante</item>\n    <item quantity=\"other\">%d restantes</item>\n  </plurals>\n  <string name=\"textPeople\">Personas:</string>\n  <string name=\"textDirecting\">Dirección</string>\n  <string name=\"textDirector\">Director</string>\n  <string name=\"textWriting\">Escritores</string>\n  <string name=\"textScreenplay\">Guión</string>\n  <string name=\"textSound\">Sonido</string>\n  <string name=\"textMusic\">Música</string>\n  <string name=\"textActing\">Actuación</string>\n  <string name=\"textSetCustomImages\">Imágenes personalizadas</string>\n  <string name=\"textSetCustomImagesDescription\">Elige tu propio póster y fanart que se utilizará en toda la aplicación.</string>\n  <string name=\"textAiredAlready\">Emitido</string>\n  <string name=\"textAirsNow\">Se emite ahora</string>\n  <plurals name=\"textDaysToAir\">\n    <item quantity=\"one\">Se emite mañana</item>\n    <item quantity=\"other\">Se emite en %d días</item>\n  </plurals>\n  <plurals name=\"textHoursToAir\">\n    <item quantity=\"one\">Se emite en 1 hora</item>\n    <item quantity=\"other\">Se emite en %d horas</item>\n  </plurals>\n  <plurals name=\"textMinutesToAir\">\n    <item quantity=\"one\">Se emite ahora</item>\n    <item quantity=\"other\">Se emite en %d minutos</item>\n  </plurals>\n  <plurals name=\"textTraktQuickSyncComplete\">\n    <item quantity=\"one\">1 elemento sincronizado correctamente.</item>\n    <item quantity=\"other\">%d elementos sincronizados correctamente.</item>\n  </plurals>\n  <string name=\"textToday\">Hoy</string>\n  <string name=\"textTomorrow\">Mañana</string>\n  <string name=\"textThisWeek\">Esta Semana</string>\n  <string name=\"textNextWeek\">La Próxima Semana</string>\n  <string name=\"textThisMonth\">Este Mes</string>\n  <string name=\"textNextMonth\">El Próximo Mes</string>\n  <string name=\"textThisYear\">Este Año</string>\n  <string name=\"textNextYear\">El Próximo Año</string>\n  <string name=\"textLater\">Más Tarde</string>\n  <string name=\"textYesterday\">Ayer</string>\n  <string name=\"textLast7Days\">Últimos 7 Días</string>\n  <string name=\"textLast30Days\">Últimos 30 Días</string>\n  <string name=\"textLast90Days\">Últimos 90 Días</string>\n  <string name=\"textNewEpisodeAvailable\">Nuevo episodio disponible!</string>\n  <string name=\"textNewEpisodeAvailableSoon\">¡Nuevo episodio disponible próximamente!</string>\n  <string name=\"textNewSeasonAvailable\">Nueva temporada disponible!</string>\n  <string name=\"textNewSeasonAvailableSoon\">¡Nuevo temporada disponible próximamente!</string>\n  <string name=\"textNewMovieAvailable\">¡La película ha sido estrenada!</string>\n  <string name=\"textTraktSync\">Sincronizar Trakt.tv</string>\n  <string name=\"textTraktSyncComplete\">Sincronizar completado satisfactoriamente.</string>\n  <string name=\"textTraktSyncRunning\">En ejecución</string>\n  <string name=\"textTraktSyncError\">Sincronización Trakt.tv fallida.</string>\n  <string name=\"textTraktSyncErrorFull\">Sincronización Trakt.tv fallida. Por favor compruebe su conexión a Internet y pruebe de nuevo o contáctenos si sigue sucediendo.</string>\n  <string name=\"textTraktQuickSyncError\">Sincronización instantánea fallida.</string>\n  <string name=\"textTraktQuickSyncErrorFull\">Sincronización instantánea fallida. Lo intentaremos de nuevo la próxima vez que abras la aplicación. Por favor revisa tu conexión a internet.</string>\n  <string name=\"textTraktNotificationsRationale\">Por favor, concede a la aplicación permiso para mostrar información sobre el progreso y los resultados de la sincronización.\\n\\n¿Te gustaría hacerlo ahora?</string>\n  <string name=\"textRemoveFromTrakt\">¿Eliminar de Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktHidden\">¿Te gustaría eliminar este elemento de \\'Elementos Ocultos\\' de tu cuenta Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktWatchlist\">¿Te gustaría eliminar este elemento de \\'Pendientes\\' de tu cuenta Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktProgress\">¿Te gustaría eliminar este elemento de \\'Progreso\\' de tu cuenta Trakt.tv?</string>\n  <string name=\"textAddToMyShows\">Añadir a Mis Series</string>\n  <string name=\"textAddToMyMovies\">Añadir a Mis Películas</string>\n  <string name=\"textAddToWatchlist\">Añadir a Pendientes</string>\n  <string name=\"textAddToHidden\">Añadir a Ocultos</string>\n  <string name=\"textMoveToMyShows\">Mover a Mis Series</string>\n  <string name=\"textMoveToMyMovies\">Mover a Mis Películas</string>\n  <string name=\"textMoveToWatchlist\">Mover a Pendientes</string>\n  <string name=\"textMoveToHidden\">Mover a Ocultos</string>\n  <string name=\"textRemoveFromMyShows\">Eliminar de Mis Series</string>\n  <string name=\"textRemoveFromMyMovies\">Eliminar de Mis Películas</string>\n  <string name=\"textRemoveFromWatchlist\">Eliminar de Pendientes</string>\n  <string name=\"textRemoveFromHidden\">Eliminar de Ocultos</string>\n  <string name=\"textWatchlist\">Pendientes</string>\n  <string name=\"textWatchlistIncoming\">Próximamente</string>\n  <string name=\"textNewAlwaysAtTop\">Nuevos episodios siempre primero</string>\n  <string name=\"textPremiumAd\">¡Conviértete en un seguidor y consigue acceso a funciones extra! Haz clic para ver más.</string>\n  <string name=\"menuPin\">Anclar a la parte superior</string>\n  <string name=\"menuUnpin\">Desanclar de la parte superior</string>\n  <string name=\"menuAddOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Anclar a \\\"en espera\\\"</string>\n  <string name=\"menuRemoveOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Desanclar de \\\"en espera\\\"</string>\n  <string name=\"errorGeneral\">Ups… Algo salió mal.\\nPor favor, contáctanos si esto sigue sucediendo.</string>\n  <string name=\"errorAuthorization\">Autorización fallida. Por favor contáctenos si continúa sucediendo.</string>\n  <string name=\"errorTraktAuthorization\">Autorización de cuenta Trakt fallida.\\nPor favor ingrese e inténtelo de nuevo.</string>\n  <string name=\"errorSeasonsNotLoaded\">Por favor, espera a que se carguen los datos de las temporadas.</string>\n  <string name=\"errorEpisodeNotAired\">Este episodio aún no se ha emitido.</string>\n  <string name=\"errorCouldNotLoadDiscover\">No pudimos cargar los resultados de la página descubrir en este momento.\\nPor favor, contáctanos si esto sigue sucediendo.</string>\n  <string name=\"errorMalformedShow\">Parece que esta serie ya no existe en la base de datos de Trakt.tv o está duplicada.\\n\\nPulsa \\'OK\\' para eliminarla de la aplicación.</string>\n  <string name=\"errorMalformedMovie\">Parece que esta película ya no existe en la base de datos de Trakt.tv o está duplicada.\\n\\nPulsa \\'OK\\' para eliminarla de la aplicación.</string>\n  <string name=\"errorCouldNotLoadShow\">No pudimos cargar los detalles de esta serie en este momento.\\nPor favor, contáctanos si esto sigue sucediendo.</string>\n  <string name=\"errorCouldNotLoadMovie\">No pudimos cargar los detalles de esta película en este momento.\\nPor favor, contáctanos si esto sigue sucediendo.</string>\n  <string name=\"errorCouldNotLoadSearchResults\">No pudimos cargar los resultados de la búsqueda en este momento.\\nPor favor, contáctanos si esto sigue sucediendo.</string>\n  <string name=\"errorNoInternetConnection\">Sin Internet. Por favor compruebe su conexión.</string>\n  <string name=\"errorTraktSyncGeneral\">Error de sincronización de Trakt.tv.\\nPor favor comprueba tu conexión a Internet y prueba de nuevo o contáctanos si sigue sucediendo.</string>\n  <string name=\"errorTraktLocked\">Tu cuenta de Trakt.tv está bloqueada actualmente.\\nPor favor, ponte en contacto con el soporte de Trakt.tv en https://support.trakt.tv/ para desbloquear tu cuenta.</string>\n  <string name=\"errorSubscriptionsNotAvailable\">Las suscripciones de Google Play no están disponibles en este dispositivo.</string>\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textOk\">OK</string>\n  <string name=\"textYes\">Kyllä</string>\n  <string name=\"textNo\">Ei</string>\n  <string name=\"textApply\">Käytä</string>\n  <string name=\"textCancel\">Peruuta</string>\n  <string name=\"textClose\">Sulje</string>\n  <string name=\"textNotNow\">Ei nyt</string>\n  <string name=\"textNew\">Uusi</string>\n  <string name=\"textSelect\">Valitse</string>\n  <string name=\"textHot\">Kuuma</string>\n  <string name=\"textSubmit\">Lähetä</string>\n  <string name=\"textHide\">Piilota</string>\n  <string name=\"textRemove\">Poista</string>\n  <string name=\"textEmptyResults\">Ei tuloksia…</string>\n  <string name=\"textSearchFor\">Haku</string>\n  <string name=\"textTip\">Vinkki:</string>\n  <string name=\"textSeason\">Kausi %1$d</string>\n  <string name=\"textSpecials\">Erikoisjaksot</string>\n  <string name=\"textEpisode\">Jakso %1$d</string>\n  <string name=\"textSeasonEpisode\">K.%02d J.%02d</string>\n  <string name=\"textSortBy\">Lajittele:</string>\n  <string name=\"textSpoilersWarning\">Kommentti sisältää juonipaljastuksia.\\nLue napauttamalla.</string>\n  <string name=\"textCommentedOn\">Kirjoittanut %s</string>\n  <string name=\"textNetwork\">%1$s (%2$s)</string>\n  <string name=\"textNetworks\" comment=\"TV network like HBO, AMC, Showtime, Disney etc.\">Verkko:</string>\n  <string name=\"textGenres\">Tyylilajit:</string>\n  <string name=\"textShows\">Sarjat</string>\n  <string name=\"textMovies\">Elokuvat</string>\n  <string name=\"textLists\">Listat</string>\n  <string name=\"textMinutesShort\">min</string>\n  <string name=\"textNoDescription\">Yhteenvetoa ei ole saatavilla.</string>\n  <string name=\"textPleaseWait\">Odota…</string>\n  <string name=\"textRate\">Arvioi</string>\n  <string name=\"textLink\">Linkki</string>\n  <string name=\"textDisabled\">Ei käytössä</string>\n  <string name=\"textRateSaved\">Arviosi tallennettiin.</string>\n  <string name=\"textRateRemoved\">Arviosi poistettiin.</string>\n  <string name=\"textDays\">%1$d päivää</string>\n  <plurals name=\"textEpisodesLeft\" comment=\"Number of episodes left to see\">\n    <item quantity=\"one\">%d jäljellä</item>\n    <item quantity=\"other\">%d jäljellä</item>\n  </plurals>\n  <string name=\"textPeople\">Henkilöt:</string>\n  <string name=\"textDirecting\">Ohjaus</string>\n  <string name=\"textDirector\">Ohjaaja</string>\n  <string name=\"textWriting\">Käsikirjoitus</string>\n  <string name=\"textScreenplay\">Käsikirjoitus</string>\n  <string name=\"textSound\">Ääni</string>\n  <string name=\"textMusic\">Musiikki</string>\n  <string name=\"textActing\">Näyttely</string>\n  <string name=\"textSetCustomImages\">Omat kuvat</string>\n  <string name=\"textSetCustomImagesDescription\">Valitse oma juliste ja fanitaide, joita käytetään eri puolella sovellusta.</string>\n  <string name=\"textAiredAlready\">Esitetty</string>\n  <string name=\"textAirsNow\">Esitetään nyt</string>\n  <plurals name=\"textDaysToAir\">\n    <item quantity=\"one\">Esitetään huomenna</item>\n    <item quantity=\"other\">Esitetään %d päivän kuluttua</item>\n  </plurals>\n  <plurals name=\"textHoursToAir\">\n    <item quantity=\"one\">Esitetään tunnin kuluttua</item>\n    <item quantity=\"other\">Esitetään %d tunnin kuluttua</item>\n  </plurals>\n  <plurals name=\"textMinutesToAir\">\n    <item quantity=\"one\">Esitetään nyt</item>\n    <item quantity=\"other\">Esitetään %d minuutin kuluttua</item>\n  </plurals>\n  <plurals name=\"textTraktQuickSyncComplete\">\n    <item quantity=\"one\">1 kohde synkronoitiin.</item>\n    <item quantity=\"other\">%d kohdetta synkronoitiin.</item>\n  </plurals>\n  <string name=\"textToday\">Tänään</string>\n  <string name=\"textTomorrow\">Huomenna</string>\n  <string name=\"textThisWeek\">Tällä viikolla</string>\n  <string name=\"textNextWeek\">Ensi viikolla</string>\n  <string name=\"textThisMonth\">Tässä kuussa</string>\n  <string name=\"textNextMonth\">Ensi kuussa</string>\n  <string name=\"textThisYear\">Tänä vuonna</string>\n  <string name=\"textNextYear\">Ensi vuonna</string>\n  <string name=\"textLater\">Myöhemmin</string>\n  <string name=\"textYesterday\">Eilen</string>\n  <string name=\"textLast7Days\">Viimeisinä 7 päivänä</string>\n  <string name=\"textLast30Days\">Viimeisinä 30 päivänä</string>\n  <string name=\"textLast90Days\">Viimeisinä 90 päivänä</string>\n  <string name=\"textNewEpisodeAvailable\">Uusi jakso on julkaistu!</string>\n  <string name=\"textNewEpisodeAvailableSoon\">Uusi jakso julkaistaan pian!</string>\n  <string name=\"textNewSeasonAvailable\">Uusi tuotantokausi on julkaistu!</string>\n  <string name=\"textNewSeasonAvailableSoon\">Uusi tuotantokausi julkaistaan pian!</string>\n  <string name=\"textNewMovieAvailable\">Elokuva on julkaistu!</string>\n  <string name=\"textTraktSync\">Trakt.tv-synkronointi</string>\n  <string name=\"textTraktSyncComplete\">Synkronointi suoritettiin.</string>\n  <string name=\"textTraktSyncRunning\">Käynnissä…</string>\n  <string name=\"textTraktSyncError\">Trakt.tv-synkronointi epäonnistui.</string>\n  <string name=\"textTraktSyncErrorFull\">Trakt.tv-synkronointi epäonnistui. Tarkista Internet-yhteytesi ja yritä uudelleen tai ota meihin yhteyttä, jos tämä jatkuu.</string>\n  <string name=\"textTraktQuickSyncError\">Pikasynkronointi epäonnistui.</string>\n  <string name=\"textTraktQuickSyncErrorFull\">Pikasynkronointi epäonnistui. Yritämme uudelleen, kun avaat sovelluksen seuraavan kerran. Tarkista Internet-yhteytesi.</string>\n  <string name=\"textTraktNotificationsRationale\">Myönnä sovellukselle tietojen synkronoinnin edistymisen ja tulosten näyttöoikeus.\\n\\nHaluatko tehdä tämän nyt?</string>\n  <string name=\"textRemoveFromTrakt\">Poistetaanko Trakt.tv-kokoelmastasi?</string>\n  <string name=\"textRemoveFromTraktHidden\">Haluatko poistaa kohteen Trakt.tv-tilisi \\'Hidden Items\\' (Piilotetut kohteet) -osiosta?</string>\n  <string name=\"textRemoveFromTraktWatchlist\">Haluatko poistaa kohteen Trakt.tv-tilisi \\'Watchlist\\' (Katselulista) -osiosta?</string>\n  <string name=\"textRemoveFromTraktProgress\">Haluatko poistaa kohteen Trakt.tv-tilisi \\\"Progress\\\" (Katselutila) -osiosta?</string>\n  <string name=\"textAddToMyShows\">Lisää omiin sarjoihin</string>\n  <string name=\"textAddToMyMovies\">Lisää omiin elokuviin</string>\n  <string name=\"textAddToWatchlist\">Lisää katselulistalle</string>\n  <string name=\"textAddToHidden\">Lisää piilotettuihin</string>\n  <string name=\"textMoveToMyShows\">Siirrä omiin sarjoihin</string>\n  <string name=\"textMoveToMyMovies\">Siirrä omiin elokuviin</string>\n  <string name=\"textMoveToWatchlist\">Siirrä katselulistalle</string>\n  <string name=\"textMoveToHidden\">Siirrä piilotettuihin</string>\n  <string name=\"textRemoveFromMyShows\">Poista omista sarjoista</string>\n  <string name=\"textRemoveFromMyMovies\">Poista omista elokuvista</string>\n  <string name=\"textRemoveFromWatchlist\">Poista katselulistalta</string>\n  <string name=\"textRemoveFromHidden\">Poista piilotetuista</string>\n  <string name=\"textWatchlist\">Katselulista</string>\n  <string name=\"textWatchlistIncoming\">Tulossa</string>\n  <string name=\"textNewAlwaysAtTop\">Uudet jaksot aina ensin</string>\n  <string name=\"textPremiumAd\">Ryhdy tukijaksi, niin saat käyttöösi lisäominaisuuksia! Napauta nähdäksesi lisää.</string>\n  <string name=\"menuPin\">Kiinnitä listan ylälaitaan</string>\n  <string name=\"menuUnpin\">Irrota listan ylälaidasta</string>\n  <string name=\"menuAddOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Kiinnitä \\'pitoon\\'</string>\n  <string name=\"menuRemoveOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Irrota \\'pidosta\\'</string>\n  <string name=\"errorGeneral\">Hups… Jotain meni pieleen.\\nOta meihin yhteyttä, jos tämä jatkuu.</string>\n  <string name=\"errorAuthorization\">Valtuutus epäonnistui. Ota meihin yhteyttä, jos tämä jatkuu.</string>\n  <string name=\"errorTraktAuthorization\">Trakt.tv-tilin valtuutus epäonnistui.\\nKirjaudu sisään ja yritä uudelleen.</string>\n  <string name=\"errorSeasonsNotLoaded\">Odota kun tuotantokausien tiedot ladataan.</string>\n  <string name=\"errorEpisodeNotAired\">Jaksoa ei ole vielä esitetty.</string>\n  <string name=\"errorCouldNotLoadDiscover\">Etsintäosion lataus ei juuri nyt onnistunut.\\nOta yhteyttä, jos tämä jatkuu.</string>\n  <string name=\"errorMalformedShow\">Sarjaa ei näyttäisi enää olevan Trakt.tv:n tietokannassa tai se on kaksoiskappale.\\n\\nPoista se sovelluksesta napauttamalla \\'OK\\'.</string>\n  <string name=\"errorMalformedMovie\">Näyttäisi siltä, ettei elokuvaa enää ole Trakt.tv:n tietokannassa tai se on kaksoiskappale.\\n\\nPoista se sovelluksesta napauttamalla \\'OK\\'.</string>\n  <string name=\"errorCouldNotLoadShow\">Sarjan tietoja ei voitu ladata.\\nOta meihin yhteyttä, jos tämä jatkuu.</string>\n  <string name=\"errorCouldNotLoadMovie\">Elokuvan tietoja ei voitu ladata.\\nOta meihin yhteyttä, jos tämä jatkuu.</string>\n  <string name=\"errorCouldNotLoadSearchResults\">Hakutuloksia ei voitu ladata.\\nOta meihin yhteyttä, jos tämä jatkuu.</string>\n  <string name=\"errorNoInternetConnection\">Ei Internet-yhteyttä. Tarkista yhteys.</string>\n  <string name=\"errorTraktSyncGeneral\">Trakt.tv-synkronointivirhe.\\nTarkista Internet-yhteys ja yritä uudelleen.</string>\n  <string name=\"errorTraktLocked\">Trakt.tv-tilisi on lukittu.\\nOta yhteyttä Trakt.tv:n tukeen osoitteessa https://support.trakt.tv/ poistaaksesi lukituksen.</string>\n  <string name=\"errorSubscriptionsNotAvailable\">Google Play -tilaukset eivät ole käytettävissä tällä laitteella.</string>\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textOk\">OK</string>\n  <string name=\"textYes\">Oui</string>\n  <string name=\"textNo\">Non</string>\n  <string name=\"textApply\">Appliquer</string>\n  <string name=\"textCancel\">Annuler</string>\n  <string name=\"textClose\">Fermer</string>\n  <string name=\"textNotNow\">Pas maintenant</string>\n  <string name=\"textNew\">Nouveau</string>\n  <string name=\"textSelect\">Sélectionner</string>\n  <string name=\"textHot\">Tendance</string>\n  <string name=\"textSubmit\">Envoyer</string>\n  <string name=\"textHide\">Masquer</string>\n  <string name=\"textRemove\">Supprimer</string>\n  <string name=\"textEmptyResults\">Il n\\'y a aucun résultat…</string>\n  <string name=\"textSearchFor\">Recherche</string>\n  <string name=\"textTip\">Astuce :</string>\n  <string name=\"textSeason\">Saison %1$d</string>\n  <string name=\"textSpecials\">Hors-séries</string>\n  <string name=\"textEpisode\">Épisode %1$d</string>\n  <string name=\"textSeasonEpisode\">S.%02d E.%02d</string>\n  <string name=\"textSortBy\">Trier par :</string>\n  <string name=\"textSpoilersWarning\">Ce commentaire contient des spoilers.\\nAppuyer pour lire.</string>\n  <string name=\"textCommentedOn\">Commenté par %s</string>\n  <string name=\"textNetwork\">%1$s (%2$s)</string>\n  <string name=\"textNetworks\" comment=\"TV network like HBO, AMC, Showtime, Disney etc.\">Réseau :</string>\n  <string name=\"textGenres\">Genres :</string>\n  <string name=\"textShows\">Séries</string>\n  <string name=\"textMovies\">Films</string>\n  <string name=\"textLists\">Listes</string>\n  <string name=\"textMinutesShort\">min</string>\n  <string name=\"textNoDescription\">Aperçu non disponible.</string>\n  <string name=\"textPleaseWait\">Veuillez patienter…</string>\n  <string name=\"textRate\">Noter</string>\n  <string name=\"textLink\">Liens</string>\n  <string name=\"textDisabled\">Désactivé</string>\n  <string name=\"textRateSaved\">Votre note a été enregistrée.</string>\n  <string name=\"textRateRemoved\">Votre note a été supprimée.</string>\n  <string name=\"textDays\">%1$d jours</string>\n  <plurals name=\"textEpisodesLeft\" comment=\"Number of episodes left to see\">\n    <item quantity=\"one\">%d restant</item>\n    <item quantity=\"other\">%d restants</item>\n  </plurals>\n  <string name=\"textPeople\">Personnes :</string>\n  <string name=\"textDirecting\">Réalisation</string>\n  <string name=\"textDirector\">Réalisateur</string>\n  <string name=\"textWriting\">Écriture</string>\n  <string name=\"textScreenplay\">Scénario</string>\n  <string name=\"textSound\">Son</string>\n  <string name=\"textMusic\">Musique</string>\n  <string name=\"textActing\">Jeu d\\'acteur</string>\n  <string name=\"textSetCustomImages\">Images personnalisées</string>\n  <string name=\"textSetCustomImagesDescription\">Choisissez vous-même l\\'affiche et le fanart qui seront utilisés dans l\\'application.</string>\n  <string name=\"textAiredAlready\">Diffusé</string>\n  <string name=\"textAirsNow\">Diffusé en ce moment</string>\n  <plurals name=\"textDaysToAir\">\n    <item quantity=\"one\">Diffusé demain</item>\n    <item quantity=\"other\">Diffusé dans %d jours</item>\n  </plurals>\n  <plurals name=\"textHoursToAir\">\n    <item quantity=\"one\">Diffusé dans 1 heure</item>\n    <item quantity=\"other\">Diffusé dans %d heures</item>\n  </plurals>\n  <plurals name=\"textMinutesToAir\">\n    <item quantity=\"one\">Diffusé maintenant</item>\n    <item quantity=\"other\">Diffusé dans %d minutes</item>\n  </plurals>\n  <plurals name=\"textTraktQuickSyncComplete\">\n    <item quantity=\"one\">1 élément synchronisé avec succès.</item>\n    <item quantity=\"other\">%d éléments synchronisés avec succès.</item>\n  </plurals>\n  <string name=\"textToday\">Aujourd\\'hui</string>\n  <string name=\"textTomorrow\">Demain</string>\n  <string name=\"textThisWeek\">Cette Semaine</string>\n  <string name=\"textNextWeek\">La semaine prochaine</string>\n  <string name=\"textThisMonth\">Ce mois</string>\n  <string name=\"textNextMonth\">Le mois prochain</string>\n  <string name=\"textThisYear\">Cette année</string>\n  <string name=\"textNextYear\">L\\'Année Prochaine</string>\n  <string name=\"textLater\">Plus tard</string>\n  <string name=\"textYesterday\">Hier</string>\n  <string name=\"textLast7Days\">7 derniers jours</string>\n  <string name=\"textLast30Days\">30 derniers jours</string>\n  <string name=\"textLast90Days\">90 derniers jours</string>\n  <string name=\"textNewEpisodeAvailable\">Un nouvel épisode est disponible dès maintenant !</string>\n  <string name=\"textNewEpisodeAvailableSoon\">Un nouvel épisode sera bientôt disponible!</string>\n  <string name=\"textNewSeasonAvailable\">Une nouvelle saison est disponible dès maintenant !</string>\n  <string name=\"textNewSeasonAvailableSoon\">Une nouvelle saison sera bientôt disponible !</string>\n  <string name=\"textNewMovieAvailable\">Le film est sorti !</string>\n  <string name=\"textTraktSync\">Synchronisation Trakt.tv</string>\n  <string name=\"textTraktSyncComplete\">Synchronisation terminée avec succès.</string>\n  <string name=\"textTraktSyncRunning\">En cours d\\'exécution…</string>\n  <string name=\"textTraktSyncError\">La synchronisation de Trakt.tv a échoué.</string>\n  <string name=\"textTraktSyncErrorFull\">La synchronisation avec Trakt.tv a échoué. Veuillez vérifier votre connexion Internet et réessayer ou contactez-nous si cela se produit à nouveau.</string>\n  <string name=\"textTraktQuickSyncError\">La synchronisation instantanée a échoué.</string>\n  <string name=\"textTraktQuickSyncErrorFull\">La synchronisation instantanée a échoué. Nous allons réessayer la prochaine fois que vous ouvrirez l\\'application. Veuillez vérifier votre connexion Internet.</string>\n  <string name=\"textTraktNotificationsRationale\">Veuillez accorder à l\\'application la permission d\\'afficher des informations sur la progression et les résultats de la synchronisation.\\n\\nVoulez-vous le faire maintenant ?</string>\n  <string name=\"textRemoveFromTrakt\">Retirer de Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktHidden\">Voulez-vous retirer cet élément des \\'Éléments masqués\\' de votre compte Trakt.tv ?</string>\n  <string name=\"textRemoveFromTraktWatchlist\">Voulez-vous retirer cet élément de la liste \\'Watchlist\\' de votre compte Trakt.tv ?</string>\n  <string name=\"textRemoveFromTraktProgress\">Voulez-vous retirer cet élément de la liste \\'Progression\\' de votre compte Trakt.tv ?</string>\n  <string name=\"textAddToMyShows\">Ajouter à Mes Séries</string>\n  <string name=\"textAddToMyMovies\">Ajouter à Mes Films</string>\n  <string name=\"textAddToWatchlist\">Ajouter à la Watchlist</string>\n  <string name=\"textAddToHidden\">Ajouter aux éléments Masqués</string>\n  <string name=\"textMoveToMyShows\">Déplacer vers Mes Séries</string>\n  <string name=\"textMoveToMyMovies\">Déplacer vers Mes Films</string>\n  <string name=\"textMoveToWatchlist\">Déplacer vers la Watchlist</string>\n  <string name=\"textMoveToHidden\">Déplacer vers les éléments Masqués</string>\n  <string name=\"textRemoveFromMyShows\">Supprimer de Mes Séries</string>\n  <string name=\"textRemoveFromMyMovies\">Supprimer de Mes Films</string>\n  <string name=\"textRemoveFromWatchlist\">Supprimer de la Watchlist</string>\n  <string name=\"textRemoveFromHidden\">Supprimer des éléments Masqués</string>\n  <string name=\"textWatchlist\">Watchlist</string>\n  <string name=\"textWatchlistIncoming\">À venir</string>\n  <string name=\"textNewAlwaysAtTop\">Nouveaux épisodes en premier</string>\n  <string name=\"textPremiumAd\">Devenez un supporteur et accédez à des fonctionnalités bonus ! Cliquez pour en savoir plus.</string>\n  <string name=\"menuPin\">Épingler en haut</string>\n  <string name=\"menuUnpin\">Désépingler du haut</string>\n  <string name=\"menuAddOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Épingler vers \\\"En attente\\\"</string>\n  <string name=\"menuRemoveOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Désépingler de \\\"En attente\\\"</string>\n  <string name=\"errorGeneral\">Oups… Quelque chose s\\'est mal passé.\\nVeuillez nous contacter si cela continue de se produire.</string>\n  <string name=\"errorAuthorization\">Échec de l\\'autorisation. Veuillez nous contacter si cela se produit à nouveau.</string>\n  <string name=\"errorTraktAuthorization\">L\\'autorisation du compte Trakt a échoué.\\nVeuillez vous connecter et réessayer.</string>\n  <string name=\"errorSeasonsNotLoaded\">Veuillez attendre que les données des saisons soient chargées.</string>\n  <string name=\"errorEpisodeNotAired\">Cet épisode n\\'a pas encore été diffusé.</string>\n  <string name=\"errorCouldNotLoadDiscover\">Impossible de charger la page Découvrir pour le moment.\\nVeuillez nous contacter si cela persiste.</string>\n  <string name=\"errorMalformedShow\">On dirait que cette série n\\'existe plus dans la base de données Trakt.tv ou est un doublon.\\n\\nAppuyez sur \\'OK\\' pour la retirer de l\\'application.</string>\n  <string name=\"errorMalformedMovie\">On dirait que ce film n\\'existe plus dans la base de données Trakt.tv ou est un doublon.\\n\\nAppuyez sur \\'OK\\' pour le retirer de l\\'application.</string>\n  <string name=\"errorCouldNotLoadShow\">Nous n\\'avons pas pu charger les détails de cette série pour le moment.\\nVeuillez nous contacter si cela se produit à nouveau.</string>\n  <string name=\"errorCouldNotLoadMovie\">Nous n\\'avons pas pu charger les détails de ce film pour le moment.\\nVeuillez nous contacter si cela se produit à nouveau.</string>\n  <string name=\"errorCouldNotLoadSearchResults\">Nous n\\'avons pas pu charger les résultats de recherche pour le moment.\\nVeuillez nous contacter si cela se produit à nouveau.</string>\n  <string name=\"errorNoInternetConnection\">Pas de connexion Internet. Vérifiez votre connexion.</string>\n  <string name=\"errorTraktSyncGeneral\">Erreur de synchronisation Trakt.tv.\\nVeuillez vérifier votre connexion internet et réessayer.</string>\n  <string name=\"errorTraktLocked\">Votre compte Trakt.tv est actuellement verrouillé.\\nVeuillez contacter l\\'assistance Trakt.tv à https://support.trakt.tv/ pour déverrouiller votre compte.</string>\n  <string name=\"errorSubscriptionsNotAvailable\">Les abonnements via Google Play ne sont pas disponibles sur cet appareil.</string>\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textOk\">OK</string>\n  <string name=\"textYes\">Si</string>\n  <string name=\"textNo\">No</string>\n  <string name=\"textApply\">Applica</string>\n  <string name=\"textCancel\">Annulla</string>\n  <string name=\"textClose\">Chiudi</string>\n  <string name=\"textNotNow\">Non Ora</string>\n  <string name=\"textNew\">Nuovo</string>\n  <string name=\"textSelect\">Seleziona</string>\n  <string name=\"textHot\">Popolari</string>\n  <string name=\"textSubmit\">Invia</string>\n  <string name=\"textHide\">Nascondi</string>\n  <string name=\"textRemove\">Rimuovi</string>\n  <string name=\"textEmptyResults\">Non ci sono risultati...</string>\n  <string name=\"textSearchFor\">Cerca</string>\n  <string name=\"textTip\">Consiglio:</string>\n  <string name=\"textSeason\">Stagione %1$d</string>\n  <string name=\"textSpecials\">Speciali</string>\n  <string name=\"textEpisode\">Episodio %1$d</string>\n  <string name=\"textSeasonEpisode\">S.%02d E.%02d</string>\n  <string name=\"textSortBy\">Ordina:</string>\n  <string name=\"textSpoilersWarning\">Questo commento contiene spoiler.\\nTocca per leggerlo.</string>\n  <string name=\"textCommentedOn\">Commentato da %s</string>\n  <string name=\"textNetwork\">%1$s (%2$s)</string>\n  <string name=\"textNetworks\" comment=\"TV network like HBO, AMC, Showtime, Disney etc.\">Rete:</string>\n  <string name=\"textGenres\">Generi:</string>\n  <string name=\"textShows\">Show</string>\n  <string name=\"textMovies\">Film</string>\n  <string name=\"textLists\">Liste</string>\n  <string name=\"textMinutesShort\">min</string>\n  <string name=\"textNoDescription\">Panoramica non disponibile.</string>\n  <string name=\"textPleaseWait\">Attendere prego…</string>\n  <string name=\"textRate\">Valuta</string>\n  <string name=\"textLink\">Link</string>\n  <string name=\"textDisabled\">Disabilitato</string>\n  <string name=\"textRateSaved\">La tua valutazione è stata salvata.</string>\n  <string name=\"textRateRemoved\">La tua valutazione è stata rimossa.</string>\n  <string name=\"textDays\">%1$d giorni</string>\n  <plurals name=\"textEpisodesLeft\" comment=\"Number of episodes left to see\">\n    <item quantity=\"one\">Ancora %d</item>\n    <item quantity=\"other\">Ancora %d</item>\n  </plurals>\n  <string name=\"textPeople\">Persone:</string>\n  <string name=\"textDirecting\">Regia</string>\n  <string name=\"textDirector\">Regista</string>\n  <string name=\"textWriting\">Scrittura</string>\n  <string name=\"textScreenplay\">Sceneggiatura</string>\n  <string name=\"textSound\">Suono</string>\n  <string name=\"textMusic\">Musica</string>\n  <string name=\"textActing\">Recitazione</string>\n  <string name=\"textSetCustomImages\">Immagini personalizzate</string>\n  <string name=\"textSetCustomImagesDescription\">Scegli il tuo poster e la tua fanart che verranno utilizzati all\\'interno dell\\'app.</string>\n  <string name=\"textAiredAlready\">Trasmesso</string>\n  <string name=\"textAirsNow\">Trasmesso ora</string>\n  <plurals name=\"textDaysToAir\">\n    <item quantity=\"one\">Trasmesso domani</item>\n    <item quantity=\"other\">Trasmesso tra %d giorni</item>\n  </plurals>\n  <plurals name=\"textHoursToAir\">\n    <item quantity=\"one\">Trasmesso tra 1 ora</item>\n    <item quantity=\"other\">Trasmesso tra %d ore</item>\n  </plurals>\n  <plurals name=\"textMinutesToAir\">\n    <item quantity=\"one\">Trasmesso ora</item>\n    <item quantity=\"other\">Trasmesso tra %d minuti</item>\n  </plurals>\n  <plurals name=\"textTraktQuickSyncComplete\">\n    <item quantity=\"one\">1 elemento sincronizzato correttamente.</item>\n    <item quantity=\"other\">%d elementi sincronizzati correttamente.</item>\n  </plurals>\n  <string name=\"textToday\">Oggi</string>\n  <string name=\"textTomorrow\">Domani</string>\n  <string name=\"textThisWeek\">Questa settimana</string>\n  <string name=\"textNextWeek\">Settimana prossima</string>\n  <string name=\"textThisMonth\">Questo mese</string>\n  <string name=\"textNextMonth\">Il mese prossimo</string>\n  <string name=\"textThisYear\">Quest\\'anno</string>\n  <string name=\"textNextYear\">L\\'anno prossimo</string>\n  <string name=\"textLater\">Più avanti</string>\n  <string name=\"textYesterday\">Ieri</string>\n  <string name=\"textLast7Days\">Ultimi 7 giorni</string>\n  <string name=\"textLast30Days\">Ultimi 30 giorni</string>\n  <string name=\"textLast90Days\">Ultimi 90 giorni</string>\n  <string name=\"textNewEpisodeAvailable\">Nuovi episodi disponibili ora!</string>\n  <string name=\"textNewEpisodeAvailableSoon\">Il nuovo episodio sarà disponibile a breve!</string>\n  <string name=\"textNewSeasonAvailable\">Nuova stagione disponibile ora!</string>\n  <string name=\"textNewSeasonAvailableSoon\">La nuova stagione sarà disponibile a breve!</string>\n  <string name=\"textNewMovieAvailable\">Il film è uscito!</string>\n  <string name=\"textTraktSync\">Sincronizzazione Trakt.tv</string>\n  <string name=\"textTraktSyncComplete\">Sincronizzazione completata correttamente.</string>\n  <string name=\"textTraktSyncRunning\">Eseguo...</string>\n  <string name=\"textTraktSyncError\">Sincronizzazione Trakt.tv fallita.</string>\n  <string name=\"textTraktSyncErrorFull\">Sincronizzazione Trakt.tv fallita. Per favore controlla la tua connessione internet e riprova oppure contattaci se il problema persiste.</string>\n  <string name=\"textTraktQuickSyncError\">Sincronizzazione istantanea fallita.</string>\n  <string name=\"textTraktQuickSyncErrorFull\">Sincronizzazione istantanea fallita. Per favore controlla la tua connessione internet e riprova oppure contattaci se il problema persiste.</string>\n  <string name=\"textTraktNotificationsRationale\">È necessario concedere all\\'applicazione il permesso di visualizzare le informazioni sui progressi e i risultati della sincronizzazione.\\n\\nVuoi farlo ora?</string>\n  <string name=\"textRemoveFromTrakt\">Rimuovere da Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktHidden\">Vuoi rimuovere questo elemento dagli \\\"Elementi nascosti\\\" del tuo account Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktWatchlist\">Vuoi rimuovere questo elemento dalla lista \\\"Da vedere\\\" del tuo account Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktProgress\">Vuoi rimuovere questo elemento dai \\\"Progressi\\\" del tuo account Trakt.tv?</string>\n  <string name=\"textAddToMyShows\">Aggiungi a I miei show</string>\n  <string name=\"textAddToMyMovies\">Aggiungi a I miei film</string>\n  <string name=\"textAddToWatchlist\">Aggiungi alla lista Da vedere</string>\n  <string name=\"textAddToHidden\">Aggiungi a Nascosti</string>\n  <string name=\"textMoveToMyShows\">Sposta in I miei show</string>\n  <string name=\"textMoveToMyMovies\">Sposta in I miei film</string>\n  <string name=\"textMoveToWatchlist\">Sposta nella lista Da vedere</string>\n  <string name=\"textMoveToHidden\">Sposta in Nascosti</string>\n  <string name=\"textRemoveFromMyShows\">Rimuovi da I miei show</string>\n  <string name=\"textRemoveFromMyMovies\">Rimuovi da I miei film</string>\n  <string name=\"textRemoveFromWatchlist\">Rimuovi dalla lista Da vedere</string>\n  <string name=\"textRemoveFromHidden\">Rimuovi da Nascosti</string>\n  <string name=\"textWatchlist\">Da vedere</string>\n  <string name=\"textWatchlistIncoming\">In arrivo</string>\n  <string name=\"textNewAlwaysAtTop\">I nuovi episodi sempre per primi</string>\n  <string name=\"textPremiumAd\">Diventa un sostenitore e ottieni l\\'accesso alle funzionalità bonus! Clicca per saperne di più.</string>\n  <string name=\"menuPin\">Fissa in cima</string>\n  <string name=\"menuUnpin\">Rimuovi</string>\n  <string name=\"menuAddOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Aggiungi a \\'In pausa\\'</string>\n  <string name=\"menuRemoveOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Togli da \\'In pausa\\'</string>\n  <string name=\"errorGeneral\">Oops... Qualcosa è andato storto.\\nPer favore contattaci se il problema persiste.</string>\n  <string name=\"errorAuthorization\">Autorizzazione fallita. Per favore contattaci se il problema persiste.</string>\n  <string name=\"errorTraktAuthorization\">Autorizzazione account Trakt fallita.\\nPer favore accedi e prova di nuovo.</string>\n  <string name=\"errorSeasonsNotLoaded\">Per favore attendi che i dati delle stagione siano scaricati.</string>\n  <string name=\"errorEpisodeNotAired\">Questo episodio non è ancora stato trasmesso.</string>\n  <string name=\"errorCouldNotLoadDiscover\">Non siamo riusciti a caricare la pagina scopri.\\nPer favore contattaci se il problema persiste.</string>\n  <string name=\"errorMalformedShow\">Sembra che questo show non sia più presente nel database di Trakt.tv oppure è un duplicato.\\n\\nPremi \\\"OK\\\" per rimuoverlo dall\\'app.</string>\n  <string name=\"errorMalformedMovie\">Sembra che questo film non sia più presente nel database di Trakt.tv oppure è un duplicato.\\n\\nPremi \\\"OK\\\" per rimuoverlo dall\\'app.</string>\n  <string name=\"errorCouldNotLoadShow\">Non siamo riusciti a scaricare i dettagli di questo show.\\nPer favore contattaci se il problema persiste.</string>\n  <string name=\"errorCouldNotLoadMovie\">Non siamo riusciti a scaricare i dettagli del film.\\nPer favore contattaci se il problema persiste.</string>\n  <string name=\"errorCouldNotLoadSearchResults\">Non siamo riusciti a caricare i risultati di ricerca.\\nPer favore contattaci se il problema persiste.</string>\n  <string name=\"errorNoInternetConnection\">Nessuna connessione. Per favore controlla la tua connessione internet.</string>\n  <string name=\"errorTraktSyncGeneral\">Errore sincronizzazione Trakt.tv .\\nPer favore controlla la tua connessione internet e prova di nuovo.</string>\n  <string name=\"errorTraktLocked\">Il tuo account Trakt.tv è bloccato.\\nPer favore contatta il supporto di Trakt.tv al link https://support.trakt.tv/ per sbloccare il tuo account.</string>\n  <string name=\"errorSubscriptionsNotAvailable\">Gli abbonamenti tramite Google Play non sono disponibili su questo dispositivo.</string>\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-notnight/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <color name=\"colorBackground\">#F6F7F9</color>\n\n  <color name=\"colorPrimary\">#FDFDFD</color>\n  <color name=\"colorPrimaryDark\">#E1E6EC</color>\n  <color name=\"colorAccent\">#4B6383</color>\n</resources>"
  },
  {
    "path": "ui-base/src/main/res/values-notnight/dimens.xml",
    "content": "<resources>\n  <dimen name=\"elevationTiny\">2dp</dimen>\n  <dimen name=\"elevationSmall\">3dp</dimen>\n  <dimen name=\"elevationNormal\">6dp</dimen>\n  <dimen name=\"elevationSuggestions\">3dp</dimen>\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-notnight/misc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <item name=\"detailsImageRatio\" format=\"float\" type=\"string\">0.38</item>\n  <bool name=\"detailsImagePadded\">false</bool>\n</resources>"
  },
  {
    "path": "ui-base/src/main/res/values-notnight/styles.xml",
    "content": "<resources>\n\n  <!-- Base app theme -->\n  <style name=\"AppTheme\" parent=\"Theme.Material3.DayNight.NoActionBar\">\n    <item name=\"android:windowBackground\">@color/colorBackground</item>\n    <item name=\"android:navigationBarColor\">@color/colorBlack</item>\n    <item name=\"android:windowTranslucentStatus\">true</item>\n    <item name=\"toolbarNavigationButtonStyle\">@style/Toolbar.Button.Navigation.Tinted</item>\n    <item name=\"switchStyle\">@style/ShowlySwitch</item>\n\n    <item name=\"colorPrimary\">@color/colorWhite</item>\n    <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n    <item name=\"colorAccent\">@color/colorAccent</item>\n\n    <item name=\"android:textColorPrimary\">@color/colorBlue</item>\n    <item name=\"android:textColorSecondary\">@color/colorBlueLight</item>\n    <item name=\"textColorOnSurface\">@color/colorWhite</item>\n    <item name=\"textColorGridTitle\">@color/colorBlue</item>\n    <item name=\"textColorTab\">@color/colorBlueLight2x</item>\n    <item name=\"textColorTabSelected\">@color/colorBlue</item>\n    <item name=\"textColorChip\">?android:textColorPrimary</item>\n    <item name=\"textColorChipSelected\">?android:textColorPrimary</item>\n    <item name=\"colorBackgroundChipSelected\">#D9E2EC</item>\n    <item name=\"colorBackgroundChipSelectedLight\">#D9E2EC</item>\n    <item name=\"colorProgressTrack\">#D3D3D3</item>\n\n    <item name=\"colorInfoSnackbar\">@color/colorAccent</item>\n    <item name=\"textColorInfoSnackbar\">@color/colorWhite</item>\n    <item name=\"colorErrorSnackbar\">@color/colorError</item>\n    <item name=\"textColorErrorSnackbar\">@color/colorWhite</item>\n\n    <item name=\"colorBottomMenuBackground\">@color/colorPrimary</item>\n    <item name=\"colorBottomMenuItem\">@color/colorBlueLight2x</item>\n    <item name=\"colorBottomMenuItemChecked\">@color/colorBlue</item>\n    <item name=\"colorBottomMenuSeparator\">@color/colorPrimaryDark</item>\n    <item name=\"colorBottomMenuIndicator\">@color/colorPrimaryDark</item>\n\n    <item name=\"colorSearchViewBackground\">@color/colorWhite</item>\n    <item name=\"colorSearchViewControl\">@color/colorBlue</item>\n\n    <item name=\"colorSearchLocalViewBackground\">@color/colorPrimaryDark</item>\n    <item name=\"colorSearchLocalViewControl\">@color/colorBlue</item>\n\n    <item name=\"colorPlaceholderBackground\">@color/colorPrimaryDark</item>\n    <item name=\"colorPlaceholderStroke\">@color/colorAccent</item>\n    <item name=\"colorPlaceholderIcon\">@color/colorAccent</item>\n\n    <item name=\"colorSeparator\">#DCE1E6</item>\n    <item name=\"colorCardBackground\">@color/colorPrimaryDark</item>\n    <item name=\"colorBadgeBackground\">@color/colorPrimaryDark</item>\n    <item name=\"colorWidgetStatusBackground\">@color/colorBlackTranslucentLight</item>\n  </style>\n\n  <!-- Progress Bar -->\n\n  <style name=\"ProgressBar.Dark\" parent=\"ProgressBar\">\n    <item name=\"android:indeterminateTint\">@color/colorAccent</item>\n  </style>\n\n  <!-- Dialogs -->\n\n  <style name=\"AlertDialog\" parent=\"@style/Theme.Material3.DayNight.Dialog\">\n    <item name=\"android:textColor\">@color/colorBlue</item>\n    <item name=\"colorAccent\">@color/colorAccent</item>\n    <item name=\"colorPrimary\">@color/colorAccent</item>\n    <item name=\"android:textColorSecondary\">@color/colorBlueLight</item>\n    <item name=\"textColorAlertDialogListItem\">@color/colorBlue</item>\n    <item name=\"android:textColorPrimary\">@color/colorBlue</item>\n    <item name=\"buttonBarPositiveButtonStyle\">@style/AlertDialog.PositiveButton</item>\n    <item name=\"buttonBarNegativeButtonStyle\">@style/AlertDialog.NegativeButton</item>\n  </style>\n\n  <style name=\"AlertDialog.PositiveButton\" parent=\"Widget.MaterialComponents.Button.TextButton.Dialog\">\n    <item name=\"android:textColor\">@color/colorAccent</item>\n    <item name=\"cornerRadius\">100dp</item>\n  </style>\n\n  <style name=\"AlertDialog.NegativeButton\" parent=\"Widget.MaterialComponents.Button.TextButton.Dialog\">\n    <item name=\"android:textColor\">@color/colorGrayLight</item>\n    <item name=\"cornerRadius\">100dp</item>\n  </style>\n\n  <style name=\"ShowlySwitch.Overlay\" parent=\"\">\n    <item name=\"colorPrimary\">@color/colorAccent</item>\n    <item name=\"colorPrimaryContainer\">#4D4B6383</item>\n  </style>\n\n  <style name=\"ImageTitleGrid\" parent=\"ImageTitle\">\n    <item name=\"android:shadowColor\">@color/colorTransparent</item>\n    <item name=\"android:textColor\">?attr/textColorGridTitle</item>\n    <item name=\"fontFamily\">sans-serif-medium</item>\n  </style>\n\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-notnight-v27/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <style name=\"AppTheme\" parent=\"Theme.Material3.DayNight.NoActionBar\">\n    <item name=\"android:windowBackground\">@color/colorBackground</item>\n    <item name=\"android:navigationBarColor\">@color/colorPrimary</item>\n    <item name=\"android:windowLightNavigationBar\">true</item>\n    <item name=\"android:windowTranslucentStatus\">true</item>\n    <item name=\"toolbarNavigationButtonStyle\">@style/Toolbar.Button.Navigation.Tinted</item>\n    <item name=\"switchStyle\">@style/ShowlySwitch</item>\n\n    <item name=\"colorPrimary\">@color/colorWhite</item>\n    <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n    <item name=\"colorAccent\">@color/colorAccent</item>\n\n    <item name=\"android:textColorPrimary\">@color/colorBlue</item>\n    <item name=\"android:textColorSecondary\">@color/colorBlueLight</item>\n    <item name=\"textColorOnSurface\">@color/colorWhite</item>\n    <item name=\"textColorGridTitle\">@color/colorBlue</item>\n    <item name=\"textColorTab\">@color/colorBlueLight2x</item>\n    <item name=\"textColorTabSelected\">@color/colorBlue</item>\n    <item name=\"textColorChip\">?android:textColorPrimary</item>\n    <item name=\"textColorChipSelected\">?android:textColorPrimary</item>\n    <item name=\"colorBackgroundChipSelected\">#D9E2EC</item>\n    <item name=\"colorBackgroundChipSelectedLight\">#D9E2EC</item>\n    <item name=\"colorProgressTrack\">#D3D3D3</item>\n\n    <item name=\"colorInfoSnackbar\">@color/colorAccent</item>\n    <item name=\"textColorInfoSnackbar\">@color/colorWhite</item>\n    <item name=\"colorErrorSnackbar\">@color/colorError</item>\n    <item name=\"textColorErrorSnackbar\">@color/colorWhite</item>\n\n    <item name=\"colorBottomMenuBackground\">@color/colorPrimary</item>\n    <item name=\"colorBottomMenuItem\">@color/colorBlueLight2x</item>\n    <item name=\"colorBottomMenuItemChecked\">@color/colorBlue</item>\n    <item name=\"colorBottomMenuSeparator\">@color/colorPrimaryDark</item>\n    <item name=\"colorBottomMenuIndicator\">@color/colorPrimaryDark</item>\n\n    <item name=\"colorSearchViewBackground\">@color/colorWhite</item>\n    <item name=\"colorSearchViewControl\">@color/colorBlue</item>\n\n    <item name=\"colorSearchLocalViewBackground\">@color/colorPrimaryDark</item>\n    <item name=\"colorSearchLocalViewControl\">@color/colorBlue</item>\n\n    <item name=\"colorPlaceholderBackground\">@color/colorPrimaryDark</item>\n    <item name=\"colorPlaceholderStroke\">@color/colorAccent</item>\n    <item name=\"colorPlaceholderIcon\">@color/colorAccent</item>\n\n    <item name=\"colorSeparator\">#DCE1E6</item>\n    <item name=\"colorCardBackground\">@color/colorPrimaryDark</item>\n    <item name=\"colorBadgeBackground\">@color/colorPrimaryDark</item>\n    <item name=\"colorWidgetStatusBackground\">@color/colorBlackTranslucentLight</item>\n  </style>\n\n</resources>"
  },
  {
    "path": "ui-base/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textOk\">OK</string>\n  <string name=\"textYes\">Tak</string>\n  <string name=\"textNo\">Nie</string>\n  <string name=\"textApply\">Zastosuj</string>\n  <string name=\"textCancel\">Anuluj</string>\n  <string name=\"textClose\">Zamknij</string>\n  <string name=\"textNotNow\">Nie Teraz</string>\n  <string name=\"textNew\">Nowy</string>\n  <string name=\"textSelect\">Wybierz</string>\n  <string name=\"textHot\">Gorące</string>\n  <string name=\"textSubmit\">Wyślij</string>\n  <string name=\"textHide\">Ukryj</string>\n  <string name=\"textRemove\">Usuń</string>\n  <string name=\"textEmptyResults\">Brak wyników…</string>\n  <string name=\"textSearchFor\">Szukaj</string>\n  <string name=\"textTip\">Wskazówka:</string>\n  <string name=\"textSeason\">Sezon %1$d</string>\n  <string name=\"textSpecials\">Specjalne</string>\n  <string name=\"textEpisode\">Odcinek %1$d</string>\n  <string name=\"textSeasonEpisode\">S.%02d E.%02d</string>\n  <string name=\"textSortBy\">Sortuj:</string>\n  <string name=\"textSpoilersWarning\">Opinia zawiera spoilery.\\nKliknij aby przeczytać.</string>\n  <string name=\"textCommentedOn\">Komentarz od %s</string>\n  <string name=\"textNetwork\">%1$s (%2$s)</string>\n  <string name=\"textNetworks\" comment=\"TV network like HBO, AMC, Showtime, Disney etc.\">Sieć:</string>\n  <string name=\"textGenres\">Gatunki:</string>\n  <string name=\"textShows\">Seriale</string>\n  <string name=\"textMovies\">Filmy</string>\n  <string name=\"textLists\">Listy</string>\n  <string name=\"textMinutesShort\">min</string>\n  <string name=\"textNoDescription\">Opis niedostępny.</string>\n  <string name=\"textPleaseWait\">Proszę czekać…</string>\n  <string name=\"textRate\">Oceń</string>\n  <string name=\"textLink\">Link</string>\n  <string name=\"textDisabled\">Wyłączone</string>\n  <string name=\"textRateSaved\">Ocena została zapisana.</string>\n  <string name=\"textRateRemoved\">Ocena została usunięta.</string>\n  <string name=\"textDays\">%1$d dni</string>\n  <plurals name=\"textEpisodesLeft\" comment=\"Number of episodes left to see\">\n    <item quantity=\"one\">%d do obejrzenia</item>\n    <item quantity=\"few\">%d do obejrzenia</item>\n    <item quantity=\"many\">%d do obejrzenia</item>\n    <item quantity=\"other\">%d do obejrzenia</item>\n  </plurals>\n  <string name=\"textPeople\">Osoby:</string>\n  <string name=\"textDirecting\">Reżyseria</string>\n  <string name=\"textDirector\">Reżyseria</string>\n  <string name=\"textWriting\">Scenariusz</string>\n  <string name=\"textScreenplay\">Scenariusz</string>\n  <string name=\"textSound\">Dźwięk</string>\n  <string name=\"textMusic\">Muzyka</string>\n  <string name=\"textActing\">Aktorstwo</string>\n  <string name=\"textSetCustomImages\">Własne Grafiki</string>\n  <string name=\"textSetCustomImagesDescription\">Wybierz swój własny plakat i fanart, które będą używane w aplikacji.</string>\n  <string name=\"textAiredAlready\">Po premierze</string>\n  <string name=\"textAirsNow\">Premiera teraz</string>\n  <plurals name=\"textDaysToAir\">\n    <item quantity=\"one\">Premiera jutro</item>\n    <item quantity=\"few\">Premiera za %d dni</item>\n    <item quantity=\"many\">Premiera za %d dni</item>\n    <item quantity=\"other\">Premiera za %d dni</item>\n  </plurals>\n  <plurals name=\"textHoursToAir\">\n    <item quantity=\"one\">Premiera za godzinę</item>\n    <item quantity=\"few\">Premiera za %d godzin</item>\n    <item quantity=\"many\">Premiera za %d godzin</item>\n    <item quantity=\"other\">Premiera za %d godzin</item>\n  </plurals>\n  <plurals name=\"textMinutesToAir\">\n    <item quantity=\"one\">Premiera teraz</item>\n    <item quantity=\"few\">Premiera za %d minut</item>\n    <item quantity=\"many\">Premiera za %d minut</item>\n    <item quantity=\"other\">Premiera za %d minut</item>\n  </plurals>\n  <plurals name=\"textTraktQuickSyncComplete\">\n    <item quantity=\"one\">Zsynchronizowano 1 element.</item>\n    <item quantity=\"few\">Zsynchronizowano %d elementy.</item>\n    <item quantity=\"many\">Zsynchronizowano %d elementów.</item>\n    <item quantity=\"other\">Zsynchronizowano %d elementów.</item>\n  </plurals>\n  <string name=\"textToday\">Dzisiaj</string>\n  <string name=\"textTomorrow\">Jutro</string>\n  <string name=\"textThisWeek\">Ten Tydzień</string>\n  <string name=\"textNextWeek\">Następny Tydzień</string>\n  <string name=\"textThisMonth\">Ten Miesiąc</string>\n  <string name=\"textNextMonth\">Następny Miesiąc</string>\n  <string name=\"textThisYear\">Ten Rok</string>\n  <string name=\"textNextYear\">Następny Rok</string>\n  <string name=\"textLater\">Później</string>\n  <string name=\"textYesterday\">Wczoraj</string>\n  <string name=\"textLast7Days\">Poprzednie 7 Dni</string>\n  <string name=\"textLast30Days\">Poprzednie 30 Dni</string>\n  <string name=\"textLast90Days\">Poprzednie 90 Dni</string>\n  <string name=\"textNewEpisodeAvailable\">Nowy odcinek jest już dostępny!</string>\n  <string name=\"textNewEpisodeAvailableSoon\">Nowy odcinek będzie wkrótce dostępny!</string>\n  <string name=\"textNewSeasonAvailable\">Nowy sezon jest już dostępny!</string>\n  <string name=\"textNewSeasonAvailableSoon\">Nowy sezon będzie wkrótce dostępny!</string>\n  <string name=\"textNewMovieAvailable\">Nowy film jest już dostępny!</string>\n  <string name=\"textTraktSync\">Synchronizacja Trakt.tv</string>\n  <string name=\"textTraktSyncComplete\">Synchronizacja zakończona.</string>\n  <string name=\"textTraktSyncRunning\">Przetwarzanie…</string>\n  <string name=\"textTraktSyncError\">Błąd synchronizacji Trakt.tv.</string>\n  <string name=\"textTraktSyncErrorFull\">Błąd synchronizacji Trakt.tv. Sprawdź połączenie z internetem lub skontaktuj się z nami jeżeli błąd się powtarza.</string>\n  <string name=\"textTraktQuickSyncError\">Błąd szybkiej synchronizacji.</string>\n  <string name=\"textTraktQuickSyncErrorFull\">Błąd szybkiej synchronizacji. Spróbujemy ponownie przy następnym uruchomieniu aplikacji.</string>\n  <string name=\"textTraktNotificationsRationale\">Aby poprawnie wyświetlać informacje związane z synchronizacją aplikacja potrzebuje twojej zgody.\\n\\nCzy chcesz jej udzielić teraz?</string>\n  <string name=\"textRemoveFromTrakt\">Usunąć z Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktHidden\">Czy chcesz usunąć ten element z sekcji \\'Ukryte\\' na twoim koncie Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktWatchlist\">Czy chcesz usunąć ten element z sekcji \\'Na Później\\' na twoim koncie Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktProgress\">Czy chcesz usunąć ten element z sekcji \\'Postęp\\' na twoim koncie Trakt.tv?</string>\n  <string name=\"textAddToMyShows\">Dodaj do moich seriali</string>\n  <string name=\"textAddToMyMovies\">Dodaj do moich filmów</string>\n  <string name=\"textAddToWatchlist\">Dodaj do \\'Na Później\\'</string>\n  <string name=\"textAddToHidden\">Dodaj do ukrytych</string>\n  <string name=\"textMoveToMyShows\">Przenieś do moich seriali</string>\n  <string name=\"textMoveToMyMovies\">Przenieś do moich filmów</string>\n  <string name=\"textMoveToWatchlist\">Przenieś do \\'Na Później\\'</string>\n  <string name=\"textMoveToHidden\">Przenieś do ukrytych</string>\n  <string name=\"textRemoveFromMyShows\">Usuń z moich seriali</string>\n  <string name=\"textRemoveFromMyMovies\">Usuń z moich filmów</string>\n  <string name=\"textRemoveFromWatchlist\">Usuń z \\'Na Później\\'</string>\n  <string name=\"textRemoveFromHidden\">Usuń z ukrytych</string>\n  <string name=\"textWatchlist\">Na Później</string>\n  <string name=\"textWatchlistIncoming\">Nadchodzące</string>\n  <string name=\"textNewAlwaysAtTop\">Nowe odcinki zawsze u góry</string>\n  <string name=\"textPremiumAd\">Zostań subskrybentem i uzyskaj dostęp do dodatkowych funkcji! Kliknij, aby zobaczyć więcej.</string>\n  <string name=\"menuPin\">Przypnij u góry</string>\n  <string name=\"menuUnpin\">Odepnij z góry</string>\n  <string name=\"menuAddOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Przypnij do wstrzymanych</string>\n  <string name=\"menuRemoveOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Odepnij ze wstrzymanych</string>\n  <string name=\"errorGeneral\">Oj… Coś poszło nie tak.\\nSkontaktuj się z nami jeżeli błąd się powtarza.</string>\n  <string name=\"errorAuthorization\">Błąd autoryzacji.\\nSkontaktuj się z nami jeżeli błąd się powtarza.</string>\n  <string name=\"errorTraktAuthorization\">Błąd autoryzacji Trakt.tv.\\nZaloguj się i spróbuj ponownie.</string>\n  <string name=\"errorSeasonsNotLoaded\">Proszę poczekać na dane o sezonach.</string>\n  <string name=\"errorEpisodeNotAired\">Ten odcinek nie miał jeszcze premiery.</string>\n  <string name=\"errorCouldNotLoadDiscover\">W tej chwili nie można załadować świeżych danych.\\nSkontaktuj się z nami jeżeli błąd się powtarza.</string>\n  <string name=\"errorMalformedShow\">Wygląda na to, że ten program nie istnieje już w bazie danych Trakt.tv lub jest duplikatem.\\n\\nNaciśnij \\\"OK\\\", aby usunąć go z aplikacji.</string>\n  <string name=\"errorMalformedMovie\">Wygląda na to, że ten film nie istnieje już w bazie danych Trakt.tv lub jest duplikatem.\\n\\nNaciśnij \\\"OK\\\", aby usunąć go z aplikacji.</string>\n  <string name=\"errorCouldNotLoadShow\">W tej chwili nie można załadować informacji o serialu.\\nSkontaktuj się z nami jeżeli błąd się powtarza.</string>\n  <string name=\"errorCouldNotLoadMovie\">W tej chwili nie można załadować informacji o filmie.\\nSkontaktuj się z nami jeżeli błąd się powtarza.</string>\n  <string name=\"errorCouldNotLoadSearchResults\">W tej chwili nie można załadować wyników wyszukiwania.\\nSkontaktuj się z nami jeżeli błąd się powtarza.</string>\n  <string name=\"errorNoInternetConnection\">Brak Internetu. Sprawdź swoje połączenie.</string>\n  <string name=\"errorTraktSyncGeneral\">Błąd synchronizacji Trakt.tv\\nSprawdź połączenie z internetem i spróbuj ponownie.</string>\n  <string name=\"errorTraktLocked\">Twoje konto Trakt.tv jest obecnie zablokowane.\\nSkontaktuj się z obsługą Trakt.tv aby je odblokować: https://support.trakt.tv/</string>\n  <string name=\"errorSubscriptionsNotAvailable\">Subskrypcje Google Play nie są dostępne na tym urządzeniu.</string>\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textOk\">OK</string>\n  <string name=\"textYes\">Sim</string>\n  <string name=\"textNo\">Não</string>\n  <string name=\"textApply\">Aplicar</string>\n  <string name=\"textCancel\">Cancelar</string>\n  <string name=\"textClose\">Fechar</string>\n  <string name=\"textNotNow\">Agora não</string>\n  <string name=\"textNew\">Novo</string>\n  <string name=\"textSelect\">Selecionar</string>\n  <string name=\"textHot\">Em alta</string>\n  <string name=\"textSubmit\">Enviar</string>\n  <string name=\"textHide\">Esconder</string>\n  <string name=\"textRemove\">Remover</string>\n  <string name=\"textEmptyResults\">Não há resultados…</string>\n  <string name=\"textSearchFor\">Pesquisar</string>\n  <string name=\"textTip\">Dica:</string>\n  <string name=\"textSeason\">Temporada %1$d</string>\n  <string name=\"textSpecials\">Especiais</string>\n  <string name=\"textEpisode\">Episódio %1$d</string>\n  <string name=\"textSeasonEpisode\">S.%02d E.%02d</string>\n  <string name=\"textSortBy\">Ordenar por:</string>\n  <string name=\"textSpoilersWarning\">Este comentário contém spoilers.\\nToque para ler.</string>\n  <string name=\"textCommentedOn\">Comentado por %s</string>\n  <string name=\"textNetwork\">%1$s (%2$s)</string>\n  <string name=\"textNetworks\" comment=\"TV network like HBO, AMC, Showtime, Disney etc.\">Rede de televisão:</string>\n  <string name=\"textGenres\">Gêneros:</string>\n  <string name=\"textShows\">Séries</string>\n  <string name=\"textMovies\">Filmes</string>\n  <string name=\"textLists\">Listas</string>\n  <string name=\"textMinutesShort\">min</string>\n  <string name=\"textNoDescription\">Resumo não disponível.</string>\n  <string name=\"textPleaseWait\">Por favor, aguarde…</string>\n  <string name=\"textRate\">Avaliar</string>\n  <string name=\"textLink\">Link</string>\n  <string name=\"textDisabled\">Desativado</string>\n  <string name=\"textRateSaved\">Sua avaliação foi salva.</string>\n  <string name=\"textRateRemoved\">Sua avaliação foi removida.</string>\n  <string name=\"textDays\">%1$d dias</string>\n  <plurals name=\"textEpisodesLeft\" comment=\"Number of episodes left to see\">\n    <item quantity=\"one\">%d restante</item>\n    <item quantity=\"other\">%d restantes</item>\n  </plurals>\n  <string name=\"textPeople\">Pessoas:</string>\n  <string name=\"textDirecting\">Direcionando</string>\n  <string name=\"textDirector\">Diretor</string>\n  <string name=\"textWriting\">Escrita</string>\n  <string name=\"textScreenplay\">Screenplay</string>\n  <string name=\"textSound\">Som</string>\n  <string name=\"textMusic\">Música</string>\n  <string name=\"textActing\">Atuação</string>\n  <string name=\"textSetCustomImages\">Imagens personalizadas</string>\n  <string name=\"textSetCustomImagesDescription\">Escolha seu próprio pôster e fanart que serão usados em todo o aplicativo.</string>\n  <string name=\"textAiredAlready\">Exibido</string>\n  <string name=\"textAirsNow\">No ar agora</string>\n  <plurals name=\"textDaysToAir\">\n    <item quantity=\"one\">No ar amanhã</item>\n    <item quantity=\"other\">No ar em %d dias</item>\n  </plurals>\n  <plurals name=\"textHoursToAir\">\n    <item quantity=\"one\">No ar em 1 hora</item>\n    <item quantity=\"other\">No ar em %d horas</item>\n  </plurals>\n  <plurals name=\"textMinutesToAir\">\n    <item quantity=\"one\">No ar agora</item>\n    <item quantity=\"other\">No ar em %d minutos</item>\n  </plurals>\n  <plurals name=\"textTraktQuickSyncComplete\">\n    <item quantity=\"one\">1 item sincronizado com sucesso.</item>\n    <item quantity=\"other\">%d itens sincronizados com sucesso.</item>\n  </plurals>\n  <string name=\"textToday\">Hoje</string>\n  <string name=\"textTomorrow\">Amanhã</string>\n  <string name=\"textThisWeek\">Esta semana</string>\n  <string name=\"textNextWeek\">Próxima semana</string>\n  <string name=\"textThisMonth\">Este mês</string>\n  <string name=\"textNextMonth\">Próximo mês</string>\n  <string name=\"textThisYear\">Este ano</string>\n  <string name=\"textNextYear\">Próximo ano</string>\n  <string name=\"textLater\">Mais tarde</string>\n  <string name=\"textYesterday\">Ontem</string>\n  <string name=\"textLast7Days\">Últimos 7 dias</string>\n  <string name=\"textLast30Days\">Últimos 30 dias</string>\n  <string name=\"textLast90Days\">Últimos 90 dias</string>\n  <string name=\"textNewEpisodeAvailable\">Novo episódio está disponível agora!</string>\n  <string name=\"textNewEpisodeAvailableSoon\">Novo episódio estará disponível em breve!</string>\n  <string name=\"textNewSeasonAvailable\">Nova temporada está disponível agora!</string>\n  <string name=\"textNewSeasonAvailableSoon\">Nova temporada estará disponível em breve!</string>\n  <string name=\"textNewMovieAvailable\">O filme foi lançado!</string>\n  <string name=\"textTraktSync\">Sincronizar Trakt.tv</string>\n  <string name=\"textTraktSyncComplete\">Sincronização concluída com sucesso.</string>\n  <string name=\"textTraktSyncRunning\">Em andamento…</string>\n  <string name=\"textTraktSyncError\">A sincronização Trakt.tv falhou.</string>\n  <string name=\"textTraktSyncErrorFull\">A sincronização do Trakt.tv falhou. Por favor, verifique sua conexão com a internet e tente novamente ou entre em contato conosco se isso continuar acontecendo.</string>\n  <string name=\"textTraktQuickSyncError\">Sincronização instantânea falhou.</string>\n  <string name=\"textTraktQuickSyncErrorFull\">A sincronização instantânea falhou. Tentaremos de novo na próxima vez que você abrir o aplicativo. Por favor, verifique sua conexão com a internet.</string>\n  <string name=\"textTraktNotificationsRationale\">Por favor, conceda à aplicação permissão para exibir informações sobre o progresso e os resultados da sincronização.\\n\\nGostaria de fazê-lo agora?</string>\n  <string name=\"textRemoveFromTrakt\">Remover do Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktHidden\">Você gostaria de remover este item da sua conta Trakt.tv \\'Oculto\\'?</string>\n  <string name=\"textRemoveFromTraktWatchlist\">Você gostaria de remover este item da sua conta do Trakt.tv \\\"Watchlist\\\"?</string>\n  <string name=\"textRemoveFromTraktProgress\">Você gostaria de remover este item da sua conta do Trakt.tv \\\"Progresso\\\"?</string>\n  <string name=\"textAddToMyShows\">Adicionar às minhas séries</string>\n  <string name=\"textAddToMyMovies\">Adicionar aos meus filmes</string>\n  <string name=\"textAddToWatchlist\">Adicionar à Lista de Desejos</string>\n  <string name=\"textAddToHidden\">Adicionar ao Oculto</string>\n  <string name=\"textMoveToMyShows\">Mover para Minhas Séries</string>\n  <string name=\"textMoveToMyMovies\">Mover para Meus filmes</string>\n  <string name=\"textMoveToWatchlist\">Mover para a Lista de Desejos</string>\n  <string name=\"textMoveToHidden\">Mover para Oculto</string>\n  <string name=\"textRemoveFromMyShows\">Remover das Minhas Séries</string>\n  <string name=\"textRemoveFromMyMovies\">Remover dos Meus Filmes</string>\n  <string name=\"textRemoveFromWatchlist\">Remover da Lista de Desejos</string>\n  <string name=\"textRemoveFromHidden\">Remover do Oculto</string>\n  <string name=\"textWatchlist\">Interesses</string>\n  <string name=\"textWatchlistIncoming\">Em breve</string>\n  <string name=\"textNewAlwaysAtTop\">Novos episódios sempre primeiro</string>\n  <string name=\"textPremiumAd\">Torne-se um apoiador e tenha acesso a recursos premium! Clique para ver mais.</string>\n  <string name=\"menuPin\">Fixar no topo</string>\n  <string name=\"menuUnpin\">Desafixar do topo</string>\n  <string name=\"menuAddOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Fixar em \\'em espera\\'</string>\n  <string name=\"menuRemoveOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Desafixar de \\'em espera\\'</string>\n  <string name=\"errorGeneral\">Ops… Algo deu errado.\\nEntre em contato conosco se isto continuar acontecendo.</string>\n  <string name=\"errorAuthorization\">A autorização falhou. Entre em contato conosco se isso continuar acontecendo.</string>\n  <string name=\"errorTraktAuthorization\">Falha na autorização da conta Trakt.\\nPor favor, faça o login e tente novamente.</string>\n  <string name=\"errorSeasonsNotLoaded\">Por favor, aguarde até que os dados da temporada sejam carregados.</string>\n  <string name=\"errorEpisodeNotAired\">Este episódio ainda não foi exibido.</string>\n  <string name=\"errorCouldNotLoadDiscover\">Não foi possível carregar a página descoberta no momento.\\nEntre em contato conosco se isso continuar acontecendo.</string>\n  <string name=\"errorMalformedShow\">Parece que esta série não existe mais no banco de dados do Trakt.tv ou é uma duplicação.\\n\\nToque em \\'OK\\' para removê-la do aplicativo.</string>\n  <string name=\"errorMalformedMovie\">Parece que esse filme não existe mais no banco de dados do Trakt.tv ou é uma duplicação.\\n\\nToque em \\'OK\\' para removê-lo do aplicativo.</string>\n  <string name=\"errorCouldNotLoadShow\">Não foi possível carregar os detalhes desta série no momento.\\nPor favor, entre em contato conosco se isto continuar acontecendo.</string>\n  <string name=\"errorCouldNotLoadMovie\">Não foi possível carregar os detalhes deste filme no momento.\\nPor favor, entre em contato conosco se isto continuar acontecendo.</string>\n  <string name=\"errorCouldNotLoadSearchResults\">Não foi possível carregar os resultados da pesquisa no momento.\\nEntre em contato conosco se isso continuar acontecendo.</string>\n  <string name=\"errorNoInternetConnection\">Sem conexão com a internet. Por favor, verifique sua conexão.</string>\n  <string name=\"errorTraktSyncGeneral\">Erro de sincronização Trakt.tv.\\nPor favor, verifique sua conexão com a internet e tente novamente.</string>\n  <string name=\"errorTraktLocked\">Sua conta Trakt.tv está bloqueada no momento.\\nPor favor, entre em contato com o suporte de Trakt.tv em https://support.trakt.tv/ para desbloquear sua conta.</string>\n  <string name=\"errorSubscriptionsNotAvailable\">As assinaturas do Google Play não estão disponíveis neste dispositivo.</string>\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textOk\">ОК</string>\n  <string name=\"textYes\">Да</string>\n  <string name=\"textNo\">Нет</string>\n  <string name=\"textApply\">Применить</string>\n  <string name=\"textCancel\">Отменить</string>\n  <string name=\"textClose\">Закрыть</string>\n  <string name=\"textNotNow\">Позже</string>\n  <string name=\"textNew\">Новое</string>\n  <string name=\"textSelect\">Выбрать</string>\n  <string name=\"textHot\">Горячее</string>\n  <string name=\"textSubmit\">Отправить</string>\n  <string name=\"textHide\">Скрыть</string>\n  <string name=\"textRemove\">Удалить</string>\n  <string name=\"textEmptyResults\">Ничего не найдено…</string>\n  <string name=\"textSearchFor\">Поиск</string>\n  <string name=\"textTip\">Подсказка:</string>\n  <string name=\"textSeason\">Сезон %1$d</string>\n  <string name=\"textSpecials\">Спецвыпуски</string>\n  <string name=\"textEpisode\">Эпизод %1$d</string>\n  <string name=\"textSeasonEpisode\">S.%02d Е.%02d</string>\n  <string name=\"textSortBy\">Сортировать по:</string>\n  <string name=\"textSpoilersWarning\">Этот комментарий содержит спойлеры.\\nНажмите, чтобы прочитать.</string>\n  <string name=\"textCommentedOn\">Прокомментировал %s</string>\n  <string name=\"textNetwork\">%1$s (%2$s)</string>\n  <string name=\"textNetworks\" comment=\"TV network like HBO, AMC, Showtime, Disney etc.\">Студия:</string>\n  <string name=\"textGenres\">Жанры:</string>\n  <string name=\"textShows\">Сериалы</string>\n  <string name=\"textMovies\">Фильмы</string>\n  <string name=\"textLists\">Списки</string>\n  <string name=\"textMinutesShort\">мин</string>\n  <string name=\"textNoDescription\">Описание недоступно.</string>\n  <string name=\"textPleaseWait\">Подождите…</string>\n  <string name=\"textRate\">Оценить</string>\n  <string name=\"textLink\">Ссылка</string>\n  <string name=\"textRateSaved\">Ваша оценка была сохранена.</string>\n  <string name=\"textRateRemoved\">Ваша оценка была удалена.</string>\n  <plurals name=\"textEpisodesLeft\" comment=\"Number of episodes left to see\">\n    <item quantity=\"one\">%d осталось</item>\n    <item quantity=\"few\">%d осталось</item>\n    <item quantity=\"many\">%d осталось</item>\n    <item quantity=\"other\">%d осталось</item>\n  </plurals>\n  <string name=\"textPeople\">Люди:</string>\n  <string name=\"textDirecting\">Режиссура</string>\n  <string name=\"textDirector\">Режиссер</string>\n  <string name=\"textWriting\">Сценарий</string>\n  <string name=\"textScreenplay\">Сценарист</string>\n  <string name=\"textSound\">Звук</string>\n  <string name=\"textMusic\">Музыка</string>\n  <string name=\"textActing\">Фильмография</string>\n  <string name=\"textSetCustomImages\">Пользовательские изображения</string>\n  <string name=\"textSetCustomImagesDescription\">Выберите свой собственный постер и фанарт, который будет использоваться в приложении.</string>\n  <string name=\"textAiredAlready\">Вышло</string>\n  <string name=\"textAirsNow\">Уже вышло</string>\n  <plurals name=\"textDaysToAir\">\n    <item quantity=\"one\">Выходит через %d день</item>\n    <item quantity=\"few\">Выходит через %d дней</item>\n    <item quantity=\"many\">Выходит через %d дней</item>\n    <item quantity=\"other\">Выходит через %d дней</item>\n  </plurals>\n  <plurals name=\"textHoursToAir\">\n    <item quantity=\"one\">Выходит через 1 час</item>\n    <item quantity=\"few\">Выходит через %d часа</item>\n    <item quantity=\"many\">Выходит через %d часов</item>\n    <item quantity=\"other\">Выходит через %d часов</item>\n  </plurals>\n  <plurals name=\"textMinutesToAir\">\n    <item quantity=\"one\">Уже вышло</item>\n    <item quantity=\"few\">Выходит через %d минут</item>\n    <item quantity=\"many\">Выходит через %d минут</item>\n    <item quantity=\"other\">Выходит через %d минут</item>\n  </plurals>\n  <plurals name=\"textTraktQuickSyncComplete\">\n    <item quantity=\"one\">1 элемент успешно синхронизирован.</item>\n    <item quantity=\"few\">%d элементов успешно синхронизировано.</item>\n    <item quantity=\"many\">%d элементов успешно синхронизировано.</item>\n    <item quantity=\"other\">%d элементов успешно синхронизировано.</item>\n  </plurals>\n  <string name=\"textToday\">Сегодня</string>\n  <string name=\"textTomorrow\">Завтра</string>\n  <string name=\"textThisWeek\">На этой неделе</string>\n  <string name=\"textNextWeek\">Следующая неделя</string>\n  <string name=\"textThisMonth\">В этом месяце</string>\n  <string name=\"textNextMonth\">Следующий месяц</string>\n  <string name=\"textThisYear\">В этом году</string>\n  <string name=\"textNextYear\">В следующем году</string>\n  <string name=\"textLater\">Позже</string>\n  <string name=\"textYesterday\">Вчера</string>\n  <string name=\"textLast7Days\">Последние 7 дней</string>\n  <string name=\"textLast30Days\">Последние 30 дней</string>\n  <string name=\"textLast90Days\">Последние 90 дней</string>\n  <string name=\"textNewEpisodeAvailable\">Новый эпизод уже доступен!</string>\n  <string name=\"textNewEpisodeAvailableSoon\">Новый эпизод скоро будет доступен!</string>\n  <string name=\"textNewSeasonAvailable\">Новый сезон уже доступен!</string>\n  <string name=\"textNewSeasonAvailableSoon\">Новый сезон скоро будет доступен!</string>\n  <string name=\"textNewMovieAvailable\">Фильм был выпущен!</string>\n  <string name=\"textTraktSync\">Синхронизация Trakt.tv</string>\n  <string name=\"textTraktSyncComplete\">Синхронизация успешно завершена.</string>\n  <string name=\"textTraktSyncRunning\">Запуск…</string>\n  <string name=\"textTraktSyncError\">Сбой синхронизации Trakt.tv.</string>\n  <string name=\"textTraktSyncErrorFull\">Сбой синхронизации Trakt.tv. Пожалуйста, проверьте подключение к Интернету и повторите попытку или свяжитесь с нами, если это повторится.</string>\n  <string name=\"textTraktQuickSyncError\">Ошибка мгновенной синхронизации.</string>\n  <string name=\"textTraktQuickSyncErrorFull\">Ошибка мгновенной синхронизации. Попробуем снова при следующем запуске приложения. Пожалуйста, проверьте подключение к Интернету.</string>\n  <string name=\"textTraktNotificationsRationale\">Пожалуйста, предоставьте приложению разрешение на отображение информации о ходе и результатах синхронизации.\\n\\n Хотели бы вы сделать это сейчас?</string>\n  <string name=\"textRemoveFromTrakt\">Удалить из Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktHidden\">Вы хотите удалить этот предмет из вашего аккаунта Trakt.tv \\'Скрытое\\'?</string>\n  <string name=\"textRemoveFromTraktWatchlist\">Вы хотите удалить этот предмет из вашего аккаунта Trakt.tv \\'Буду смотреть\\'?</string>\n  <string name=\"textRemoveFromTraktProgress\">Вы хотите удалить этот предмет из вашего аккаунта Trakt.tv \\'Прогресс\\'?</string>\n  <string name=\"textAddToMyShows\">Добавить в Мои Сериалы</string>\n  <string name=\"textAddToMyMovies\">Добавить в Мои Фильмы</string>\n  <string name=\"textAddToWatchlist\">Добавить в Буду Смотреть</string>\n  <string name=\"textAddToHidden\">Добавить в Скрытое</string>\n  <string name=\"textMoveToMyShows\">Переместить в Мои Сериалы</string>\n  <string name=\"textMoveToMyMovies\">Переместить в Мои Фильмы</string>\n  <string name=\"textMoveToWatchlist\">Переместить в Буду Смотреть</string>\n  <string name=\"textMoveToHidden\">Переместить в Скрытое</string>\n  <string name=\"textRemoveFromMyShows\">Удалить из Моих Сериалов</string>\n  <string name=\"textRemoveFromMyMovies\">Удалить из Моих Фильмов</string>\n  <string name=\"textRemoveFromWatchlist\">Удалить из Буду Смотреть</string>\n  <string name=\"textRemoveFromHidden\">Удалить из Скрытое</string>\n  <string name=\"textWatchlist\">Буду смотреть</string>\n  <string name=\"textWatchlistIncoming\">Предстоящие</string>\n  <string name=\"textNewAlwaysAtTop\">Новые серии всегда первыми</string>\n  <string name=\"textPremiumAd\">Станьте спонсором и получите доступ к бонусным функциям! Нажмите, чтобы увидеть больше.</string>\n  <string name=\"menuPin\">Закрепить</string>\n  <string name=\"menuUnpin\">Открепить</string>\n  <string name=\"menuAddOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Закрепить в \\'На паузе\\'</string>\n  <string name=\"menuRemoveOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Открепить от \\'На паузе\\'</string>\n  <string name=\"errorGeneral\">Ой,… Что-то пошло не так.\\nПожалуйста, свяжитесь с нами, если это повторится.</string>\n  <string name=\"errorAuthorization\">Авторизация не удалась. Пожалуйста, свяжитесь с нами, если это произойдет снова.</string>\n  <string name=\"errorTraktAuthorization\">Ошибка авторизации учетной записи Trakt.\\nПожалуйста, войдите в систему и повторите попытку.</string>\n  <string name=\"errorSeasonsNotLoaded\">Пожалуйста, дождитесь загрузки данных сезонов.</string>\n  <string name=\"errorEpisodeNotAired\">Этот эпизод еще не вышел.</string>\n  <string name=\"errorCouldNotLoadDiscover\">Мы не смогли загрузить Открытия данный момент.\\nПожалуйста, свяжитесь с нами, если это произойдет снова.</string>\n  <string name=\"errorMalformedShow\">Похоже, это шоу больше не существует в базе данных Trakt.tv или является дубликатом.\\n\\nНажмите \\'OK\\' для его удаления из приложения.</string>\n  <string name=\"errorMalformedMovie\">Похоже, этот фильм больше не существует в базе данных Trakt.tv или является дубликатом.\\n\\nНажмите \\'OK\\' для его удаления из приложения.</string>\n  <string name=\"errorCouldNotLoadShow\">Мы не смогли загрузить детали этого сериала.\\nПожалуйста, свяжитесь с нами, если это произойдет снова.</string>\n  <string name=\"errorCouldNotLoadMovie\">Мы не смогли загрузить детали этого фильма.\\nПожалуйста, свяжитесь с нами, если это произойдет снова.</string>\n  <string name=\"errorCouldNotLoadSearchResults\">Мы не смогли загрузить результаты поиска.\\nПожалуйста, свяжитесь с нами, если это произойдет снова.</string>\n  <string name=\"errorNoInternetConnection\">Нет интернет соединения. Проверьте своё подключение.</string>\n  <string name=\"errorTraktSyncGeneral\">Ошибка синхронизации Trakt.tv. \\nПроверьте доступ в Интернет и повторите попытку.</string>\n  <string name=\"errorTraktLocked\">Ваша учетная запись Trakt.tv в настоящее время заблокирована.\\nПожалуйста, свяжитесь со службой поддержки Trakt.tv по адресу https://support.trakt.tv/ для разблокировки вашей учетной записи.</string>\n  <string name=\"errorSubscriptionsNotAvailable\">Сервисы Google Play недоступны на этом устройстве.</string>\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-sw600dp/bool.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <bool name=\"isTablet\">true</bool>\n</resources>"
  },
  {
    "path": "ui-base/src/main/res/values-sw600dp/dimens.xml",
    "content": "<resources>\n\n  <dimen name=\"gridSpacing\">4dp</dimen>\n  <dimen name=\"gridPadding\">20dp</dimen>\n\n  <dimen name=\"screenMarginHorizontal\">24dp</dimen>\n  <dimen name=\"mediaTileCorner\">6dp</dimen>\n  <dimen name=\"tutorialTipSize\">34dp</dimen>\n  <dimen name=\"tutorialTipMargin\">128dp</dimen>\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-sw600dp/styles.xml",
    "content": "<resources>\n\n  <style name=\"ScrollableTabsStyle\" parent=\"Widget.Design.TabLayout\">\n    <item name=\"tabMode\">fixed</item>\n    <item name=\"tabIndicatorColor\">@color/colorAccent</item>\n    <item name=\"tabPaddingStart\">8dp</item>\n    <item name=\"tabPaddingEnd\">8dp</item>\n    <item name=\"tabTextAppearance\">@style/ScrollableTabTextStyle</item>\n    <item name=\"tabSelectedTextColor\">?attr/textColorTabSelected</item>\n    <item name=\"tabIndicatorFullWidth\">false</item>\n    <item name=\"tabRippleColor\">@android:color/transparent</item>\n    <item name=\"tabIndicatorAnimationDuration\">200</item>\n  </style>\n\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textOk\">Tamam</string>\n  <string name=\"textYes\">Evet</string>\n  <string name=\"textNo\">Hayır</string>\n  <string name=\"textApply\">Uygula</string>\n  <string name=\"textCancel\">İptal</string>\n  <string name=\"textClose\">Kapat</string>\n  <string name=\"textNotNow\">Şimdi değil</string>\n  <string name=\"textNew\">Yeni</string>\n  <string name=\"textSelect\">Seç</string>\n  <string name=\"textHot\">Popüler</string>\n  <string name=\"textSubmit\">Gönder</string>\n  <string name=\"textHide\">Saklamak</string>\n  <string name=\"textRemove\">Kaldır</string>\n  <string name=\"textEmptyResults\">Hiçbir sonuç bulunamadı…</string>\n  <string name=\"textSearchFor\">Ara</string>\n  <string name=\"textTip\">İpucu:</string>\n  <string name=\"textSeason\">%1$d. Sezon</string>\n  <string name=\"textSpecials\">Özeller</string>\n  <string name=\"textEpisode\">%1$d. Bölüm</string>\n  <string name=\"textSeasonEpisode\">S.%02d B.%02d</string>\n  <string name=\"textSortBy\">Sırala:</string>\n  <string name=\"textSpoilersWarning\">Bu yorum sürpriz bozan içermektedir.\\nOkumak için dokunun.</string>\n  <string name=\"textCommentedOn\">Yorum yapan: %s</string>\n  <string name=\"textNetwork\">%1$s (%2$s)</string>\n  <string name=\"textNetworks\" comment=\"TV network like HBO, AMC, Showtime, Disney etc.\">Ağ:</string>\n  <string name=\"textGenres\">Türler:</string>\n  <string name=\"textShows\">Diziler</string>\n  <string name=\"textMovies\">Filmler</string>\n  <string name=\"textLists\">Listeler</string>\n  <string name=\"textMinutesShort\">dk</string>\n  <string name=\"textNoDescription\">Tanıtım mevcut değil.</string>\n  <string name=\"textPleaseWait\">Lütfen bekleyin…</string>\n  <string name=\"textRate\">Oy Ver</string>\n  <string name=\"textLink\">Link</string>\n  <string name=\"textDisabled\">Devre dışı</string>\n  <string name=\"textRateSaved\">Oyunuz kaydedildi.</string>\n  <string name=\"textRateRemoved\">Verdiğiniz oy kaldırıldı.</string>\n  <string name=\"textDays\">%1$d gün</string>\n  <plurals name=\"textEpisodesLeft\" comment=\"Number of episodes left to see\">\n    <item quantity=\"one\">%d kaldı</item>\n    <item quantity=\"other\">%d kaldı</item>\n  </plurals>\n  <string name=\"textPeople\">Kişiler:</string>\n  <string name=\"textDirecting\">Reji</string>\n  <string name=\"textDirector\">Yönetmen</string>\n  <string name=\"textWriting\">Yazarlık</string>\n  <string name=\"textScreenplay\">Senaryo</string>\n  <string name=\"textSound\">Ses</string>\n  <string name=\"textMusic\">Müzik</string>\n  <string name=\"textActing\">Oyunculuk</string>\n  <string name=\"textSetCustomImages\">Özel Fotoğraflar</string>\n  <string name=\"textSetCustomImagesDescription\">Uygulama genelinde kullanılacak kendi afişinizi ve hayran çalışmasını seçin.</string>\n  <string name=\"textAiredAlready\">Yayınlandı</string>\n  <string name=\"textAirsNow\">Şimdi Yayında</string>\n  <plurals name=\"textDaysToAir\">\n    <item quantity=\"one\">Yarın yayında</item>\n    <item quantity=\"other\">%d gün içinde yayınlanacak</item>\n  </plurals>\n  <plurals name=\"textHoursToAir\">\n    <item quantity=\"one\">1 saat içinde yayınlanacak</item>\n    <item quantity=\"other\">%d saat içinde yayınlanır</item>\n  </plurals>\n  <plurals name=\"textMinutesToAir\">\n    <item quantity=\"one\">Şimdi yayında</item>\n    <item quantity=\"other\">%d dakika içinde yayında</item>\n  </plurals>\n  <plurals name=\"textTraktQuickSyncComplete\">\n    <item quantity=\"one\">1 öge başarıyla eşitlendi.</item>\n    <item quantity=\"other\">%d öge başarıyla senkronize edildi.</item>\n  </plurals>\n  <string name=\"textToday\">Bugün</string>\n  <string name=\"textTomorrow\">Yarın</string>\n  <string name=\"textThisWeek\">Bu Hafta</string>\n  <string name=\"textNextWeek\">Gelecek Hafta</string>\n  <string name=\"textThisMonth\">Bu Ay</string>\n  <string name=\"textNextMonth\">Gelecek Ay</string>\n  <string name=\"textThisYear\">Bu Yıl</string>\n  <string name=\"textNextYear\">Gelecek Yıl</string>\n  <string name=\"textLater\">Daha sonra</string>\n  <string name=\"textYesterday\">Dün</string>\n  <string name=\"textLast7Days\">Son 7 Gün</string>\n  <string name=\"textLast30Days\">Son 30 Gün</string>\n  <string name=\"textLast90Days\">Son 90 Gün</string>\n  <string name=\"textNewEpisodeAvailable\">Yeni bölüm yayınlandı!</string>\n  <string name=\"textNewEpisodeAvailableSoon\">Yeni bölüm yakında yayınlanacak!</string>\n  <string name=\"textNewSeasonAvailable\">Yeni sezon yayınlandı!</string>\n  <string name=\"textNewSeasonAvailableSoon\">Yeni sezon yakında yayınlanacak!</string>\n  <string name=\"textNewMovieAvailable\">Film yayınlandı!</string>\n  <string name=\"textTraktSync\">Trakt.tv Eşitlemesi</string>\n  <string name=\"textTraktSyncComplete\">Eşitleme başarıyla tamamlandı.</string>\n  <string name=\"textTraktSyncRunning\">Çalışıyor…</string>\n  <string name=\"textTraktSyncError\">Trakt.tv eşitlemesi başarısız.</string>\n  <string name=\"textTraktSyncErrorFull\">Trakt.tv eşitlemesi başarısız oldu. Lütfen internet bağlantınızı kontrol edin ve tekrar deneyin veya bu sorun devam ederse bizimle iletişime geçin.</string>\n  <string name=\"textTraktQuickSyncError\">Anlık Eşitleme başarısız.</string>\n  <string name=\"textTraktQuickSyncErrorFull\">Anlık Eşitleme başarısız oldu. Uygulamayı bir sonraki başlattığınızda tekrar deneyeceğiz. Lütfen internet bağlantınızı kontrol edin.</string>\n  <string name=\"textTraktNotificationsRationale\">Lütfen uygulamaya senkronizasyon ilerlemesi ve sonuçları hakkında bilgiler göstermesi için izin verin\\n\\nBunu şimdi yapmak ister misiniz?</string>\n  <string name=\"textRemoveFromTrakt\">Trakt.tv\\'den silinsin mi?</string>\n  <string name=\"textRemoveFromTraktHidden\">Bu öğeyi Trakt.tv hesabınızdan \\'Gizli\\' kaldırmak ister misiniz?</string>\n  <string name=\"textRemoveFromTraktWatchlist\">Bu öğeyi Trakt.tv hesabınızdaki \\'Watchlist\\' kaldırmak ister misiniz?</string>\n  <string name=\"textRemoveFromTraktProgress\">Bu öğeyi Trakt.tv hesabınızdaki \\'İlerleme\\'den kaldırmak ister misiniz?</string>\n  <string name=\"textAddToMyShows\">Dizilerime Ekle</string>\n  <string name=\"textAddToMyMovies\">Filmlerime Ekle</string>\n  <string name=\"textAddToWatchlist\">İzleme Listesine Ekle</string>\n  <string name=\"textAddToHidden\">Gizlenene Ekle</string>\n  <string name=\"textMoveToMyShows\">Dizilerime Taşı</string>\n  <string name=\"textMoveToMyMovies\">Filmlerime Taşı</string>\n  <string name=\"textMoveToWatchlist\">İzleme Listesine Taşı</string>\n  <string name=\"textMoveToHidden\">Gizlenene Taşı</string>\n  <string name=\"textRemoveFromMyShows\">Dizilerimden Kaldır</string>\n  <string name=\"textRemoveFromMyMovies\">Filmlerimden Kaldır</string>\n  <string name=\"textRemoveFromWatchlist\">İzleme Listesinden Kaldır</string>\n  <string name=\"textRemoveFromHidden\">Gizlenenlerden Kaldır</string>\n  <string name=\"textWatchlist\">İstek Listesi</string>\n  <string name=\"textWatchlistIncoming\">Yakında</string>\n  <string name=\"textNewAlwaysAtTop\">Her zaman önce yeni bölümler</string>\n  <string name=\"textPremiumAd\">Destekçi olun ve bonus özelliklere erişin! Daha fazlasını görmek için dokunun.</string>\n  <string name=\"menuPin\">En üste sabitle</string>\n  <string name=\"menuUnpin\">Sabitlemeyi kaldır</string>\n  <string name=\"menuAddOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">\\\"Beklemeye\\\" sabitle</string>\n  <string name=\"menuRemoveOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">\\\"Beklemeye\\\" sabitlemeyi kaldır</string>\n  <string name=\"errorGeneral\">Hay aksi… Bir şeyler ters gitti.\\nBu sorun devam ederse lütfen bizimle iletişime geçin.</string>\n  <string name=\"errorAuthorization\">Yetkilendirme başarısız oldu. Bu durum devam ederse lütfen bizimle iletişime geçin.</string>\n  <string name=\"errorTraktAuthorization\">Trakt hesabı yetkilendirmesi başarısız oldu.\\nLütfen giriş yapın ve tekrar deneyin.</string>\n  <string name=\"errorSeasonsNotLoaded\">Lütfen sezon verilerinin yüklenmesini bekleyin.</string>\n  <string name=\"errorEpisodeNotAired\">Bu bölüm henüz yayınlanmadı.</string>\n  <string name=\"errorCouldNotLoadDiscover\">Şu anda keşfet sayfasını yükleyemiyoruz.\\nBu durum devam ederse lütfen bizimle iletişime geçin.</string>\n  <string name=\"errorMalformedShow\">Bu dizi artık Trakt.tv veritabanında yok veya çift girilmiş gibi görünüyor.\\n\\nUygulamadan da silmek için \\'Tamam\\'a dokunun.</string>\n  <string name=\"errorMalformedMovie\">Bu film artık Trakt.tv veritabanında yok veya çift girilmiş gibi görünüyor.\\n\\nUygulamadan da silmek için \\'Tamam\\'a dokunun.</string>\n  <string name=\"errorCouldNotLoadShow\">Bu dizinin ayrıntılarını şu anda yükleyemiyoruz.\\nBu durum devam ederse lütfen bizimle iletişime geçin.</string>\n  <string name=\"errorCouldNotLoadMovie\">Bu filmin ayrıntılarını şu anda yükleyemiyoruz.\\nBu durum devam ederse lütfen bizimle iletişime geçin.</string>\n  <string name=\"errorCouldNotLoadSearchResults\">Şu anda arama sonuçlarını yükleyemiyoruz.\\nBu durum devam ederse lütfen bizimle iletişime geçin.</string>\n  <string name=\"errorNoInternetConnection\">İnternet bağlantısı yok. Lütfen bağlantınızı kontrol edin.</string>\n  <string name=\"errorTraktSyncGeneral\">Trakt.tv eşitleme hatası.\\nLütfen internet bağlantınızı kontrol edin ve tekrar deneyin.</string>\n  <string name=\"errorTraktLocked\">Trakt.tv hesabınız şu anda kilitli.\\nHesabınızın kilidini açmak için lütfen https://support.trakt.tv/ adresinden Trakt.tv destek ekibi ile iletişime geçin.</string>\n  <string name=\"errorSubscriptionsNotAvailable\">Google Play Abonelikleri bu cihazda kullanılamaz.</string>\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textOk\">OK</string>\n  <string name=\"textYes\">Так</string>\n  <string name=\"textNo\">Ні</string>\n  <string name=\"textApply\">Застосувати</string>\n  <string name=\"textCancel\">Скасувати</string>\n  <string name=\"textClose\">Закрити</string>\n  <string name=\"textNotNow\">Не зараз</string>\n  <string name=\"textNew\">Нове</string>\n  <string name=\"textSelect\">Обрати</string>\n  <string name=\"textHot\">Популярне</string>\n  <string name=\"textSubmit\">Надіслати</string>\n  <string name=\"textHide\">Приховати</string>\n  <string name=\"textRemove\">Видалити</string>\n  <string name=\"textEmptyResults\">Результатів немає…</string>\n  <string name=\"textSearchFor\">Пошук</string>\n  <string name=\"textTip\">Порада:</string>\n  <string name=\"textSeason\">Сезон %1$d</string>\n  <string name=\"textSpecials\">Спецвипуски</string>\n  <string name=\"textEpisode\">Серія %1$d</string>\n  <string name=\"textSeasonEpisode\">S.%02d E.%02d</string>\n  <string name=\"textSortBy\">Сортувати:</string>\n  <string name=\"textSpoilersWarning\">Цей коментар містить спойлери.\\nТоркніться, щоб прочитати.</string>\n  <string name=\"textCommentedOn\">Коментар від %s</string>\n  <string name=\"textNetwork\">%1$s (%2$s)</string>\n  <string name=\"textNetworks\" comment=\"TV network like HBO, AMC, Showtime, Disney etc.\">Мережа:</string>\n  <string name=\"textGenres\">Жанри:</string>\n  <string name=\"textShows\">Серіали</string>\n  <string name=\"textMovies\">Фільми</string>\n  <string name=\"textLists\">Списки</string>\n  <string name=\"textMinutesShort\">хв</string>\n  <string name=\"textNoDescription\">Опис недоступний.</string>\n  <string name=\"textPleaseWait\">Будь ласка, зачекайте…</string>\n  <string name=\"textRate\">Оцінити</string>\n  <string name=\"textLink\">Посилання</string>\n  <string name=\"textDisabled\">Вимкнено</string>\n  <string name=\"textRateSaved\">Ваша оцінка була збережена.</string>\n  <string name=\"textRateRemoved\">Ваша оцінка була видалена.</string>\n  <string name=\"textDays\">%1$d днів</string>\n  <plurals name=\"textEpisodesLeft\" comment=\"Number of episodes left to see\">\n    <item quantity=\"one\">%d залишився</item>\n    <item quantity=\"few\">%d залишилось</item>\n    <item quantity=\"many\">%d залишилось</item>\n    <item quantity=\"other\">%d залишилось</item>\n  </plurals>\n  <string name=\"textPeople\">Люди:</string>\n  <string name=\"textDirecting\">Режисура</string>\n  <string name=\"textDirector\">Режисер</string>\n  <string name=\"textWriting\">Сценарій</string>\n  <string name=\"textScreenplay\">Сценарій</string>\n  <string name=\"textSound\">Звук</string>\n  <string name=\"textMusic\">Музика</string>\n  <string name=\"textActing\">Акторство</string>\n  <string name=\"textSetCustomImages\">Користувацькі зображення</string>\n  <string name=\"textSetCustomImagesDescription\">Оберіть свій власний постер і фанарт, які будуть використані в додатку.</string>\n  <string name=\"textAiredAlready\">Прем\\'єра відбулася</string>\n  <string name=\"textAirsNow\">Прем\\'єра зараз</string>\n  <plurals name=\"textDaysToAir\">\n    <item quantity=\"one\">Прем\\'єра за %d день</item>\n    <item quantity=\"few\">Прем\\'єра за %d дні</item>\n    <item quantity=\"many\">Прем\\'єра за %d днів</item>\n    <item quantity=\"other\">Прем\\'єра за %d днів</item>\n  </plurals>\n  <plurals name=\"textHoursToAir\">\n    <item quantity=\"one\">Прем\\'єра за годину</item>\n    <item quantity=\"few\">Прем\\'єра за %d години</item>\n    <item quantity=\"many\">Прем\\'єра за %d годин</item>\n    <item quantity=\"other\">Прем\\'єра за %d годин</item>\n  </plurals>\n  <plurals name=\"textMinutesToAir\">\n    <item quantity=\"one\">Прем\\'єра зараз</item>\n    <item quantity=\"few\">Прем\\'єра за %d хвилини</item>\n    <item quantity=\"many\">Прем\\'єра за %d хвилин</item>\n    <item quantity=\"other\">Прем\\'єра за %d хвилин</item>\n  </plurals>\n  <plurals name=\"textTraktQuickSyncComplete\">\n    <item quantity=\"one\">1 елемент успішно синхронізовано.</item>\n    <item quantity=\"few\">%d елементи успішно синхронізовано.</item>\n    <item quantity=\"many\">%d елементів успішно синхронізовано.</item>\n    <item quantity=\"other\">%d елементів успішно синхронізовано.</item>\n  </plurals>\n  <string name=\"textToday\">Сьогодні</string>\n  <string name=\"textTomorrow\">Завтра</string>\n  <string name=\"textThisWeek\">Цього тижня</string>\n  <string name=\"textNextWeek\">Наступного тижня</string>\n  <string name=\"textThisMonth\">Цього місяця</string>\n  <string name=\"textNextMonth\">Наступного місяця</string>\n  <string name=\"textThisYear\">Цього року</string>\n  <string name=\"textNextYear\">Наступного року</string>\n  <string name=\"textLater\">Пізніше</string>\n  <string name=\"textYesterday\">Вчора</string>\n  <string name=\"textLast7Days\">Останні 7 днів</string>\n  <string name=\"textLast30Days\">Останні 30 днів</string>\n  <string name=\"textLast90Days\">Останні 90 днів</string>\n  <string name=\"textNewEpisodeAvailable\">Нова серія вже доступна!</string>\n  <string name=\"textNewEpisodeAvailableSoon\">Нова серія буде доступна найближчим часом!</string>\n  <string name=\"textNewSeasonAvailable\">Новий сезон вже доступний!</string>\n  <string name=\"textNewSeasonAvailableSoon\">Новий сезон буде доступний найближчим часом!</string>\n  <string name=\"textNewMovieAvailable\">Фільм вийшов в прокат!</string>\n  <string name=\"textTraktSync\">Синхронізація Trakt.tv</string>\n  <string name=\"textTraktSyncComplete\">Синхронізацію успішно завершено.</string>\n  <string name=\"textTraktSyncRunning\">Виконується…</string>\n  <string name=\"textTraktSyncError\">Помилка синхронізації Trakt.tv.</string>\n  <string name=\"textTraktSyncErrorFull\">Помилка синхронізації Trakt.tv. Будь ласка, перевірте ваше з\\'єднання з інтернетом і спробуйте ще раз або зв\\'яжіться з нами, якщо помилка не зникає.</string>\n  <string name=\"textTraktQuickSyncError\">Помилка миттєвої синхронізації.</string>\n  <string name=\"textTraktQuickSyncErrorFull\">Помилка миттєвої синхронізації. Ми спробуємо наступного разу, коли ви відкриєте додаток. Будь ласка, перевірте підключення до Інтернету.</string>\n  <string name=\"textTraktNotificationsRationale\">Будь ласка, надайте додатку дозвіл на показ інформації про прогрес та результати синхронізації.\\n\\nВи хотіли б зробити це зараз?</string>\n  <string name=\"textRemoveFromTrakt\">Видалити з Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktHidden\">Бажаєте видалити цей елемент з розділу \\'Приховане\\' вашого облікового запису Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktWatchlist\">Бажаєте видалити цей елемент з розділу \\'На потім\\' вашого облікового запису Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktProgress\">Бажаєте видалити цей елемент з розділу \\'Прогрес\\' вашого облікового запису Trakt.tv?</string>\n  <string name=\"textAddToMyShows\">Додати до моїх серіалів</string>\n  <string name=\"textAddToMyMovies\">Додати до моїх фільмів</string>\n  <string name=\"textAddToWatchlist\">Додати в \\'На потім\\'</string>\n  <string name=\"textAddToHidden\">Додати в \\'Приховане\\'</string>\n  <string name=\"textMoveToMyShows\">Перемістити до моїх серіалів</string>\n  <string name=\"textMoveToMyMovies\">Перемістити до моїх фільмів</string>\n  <string name=\"textMoveToWatchlist\">Перемістити в \\'На потім\\'</string>\n  <string name=\"textMoveToHidden\">Перемістити в \\'Приховане\\'</string>\n  <string name=\"textRemoveFromMyShows\">Видалити з моїх серіалів</string>\n  <string name=\"textRemoveFromMyMovies\">Видалити з моїх фільмів</string>\n  <string name=\"textRemoveFromWatchlist\">Видалити з \\'На потім\\'</string>\n  <string name=\"textRemoveFromHidden\">Видалити з \\'Приховане\\'</string>\n  <string name=\"textWatchlist\">На потім</string>\n  <string name=\"textWatchlistIncoming\">Незабаром</string>\n  <string name=\"textNewAlwaysAtTop\">Спочатку нові серії</string>\n  <string name=\"textPremiumAd\">Станьте підписником та отримуйте доступ до бонусних функцій! Натисніть, щоб побачити більше.</string>\n  <string name=\"menuPin\">Закріпити</string>\n  <string name=\"menuUnpin\">Відкріпити</string>\n  <string name=\"menuAddOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Закріпити в \\'Призупинено\\'</string>\n  <string name=\"menuRemoveOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Відкріпити з \\'Призупинено\\'</string>\n  <string name=\"errorGeneral\">Ой… Щось пішло не так.\\nБудь ласка, зв\\'яжіться з нами, якщо помилка не зникає.</string>\n  <string name=\"errorAuthorization\">Не вдалося авторизуватись. Будь ласка, зв\\'яжіться з нами, якщо помилка не зникає.</string>\n  <string name=\"errorTraktAuthorization\">Помилка авторизації облікового запису Trakt.\\nБудь ласка, увійдіть і спробуйте знову.</string>\n  <string name=\"errorSeasonsNotLoaded\">Будь ласка, дочекайтеся завантаження даних про сезони.</string>\n  <string name=\"errorEpisodeNotAired\">Прем\\'єра цієї серії ще не відбулася.</string>\n  <string name=\"errorCouldNotLoadDiscover\">Нам не вдалося завантажити сторінку Огляд.\\nБудь ласка, зв\\'яжіться з нами, якщо помилка не зникає.</string>\n  <string name=\"errorMalformedShow\">Схоже, цього серіалу більше не існує в базі даних Trakt.tv або воно є дублюючим.\\n\\nНатисніть \\'ОК\\', щоб видалити його з додатка.</string>\n  <string name=\"errorMalformedMovie\">Схоже, цього фільму більше не існує в базі даних Trakt.tv або воно є дублюючим.\\n\\nНатисніть \\'ОК\\', щоб видалити його з додатка.</string>\n  <string name=\"errorCouldNotLoadShow\">Нам не вдалося завантажити інформацію про цей серіал.\\nБудь ласка, зв\\'яжіться з нами, якщо помилка не зникає.</string>\n  <string name=\"errorCouldNotLoadMovie\">Нам не вдалося завантажити інформацію про цей фільм.\\nБудь ласка, зв\\'яжіться з нами, якщо помилка не зникає.</string>\n  <string name=\"errorCouldNotLoadSearchResults\">Нам не вдалося завантажити результати пошуку.\\nБудь ласка, зв\\'яжіться з нами, якщо помилка не зникає.</string>\n  <string name=\"errorNoInternetConnection\">Немає доступу до інтернету. Перевірте підключення до мережі.</string>\n  <string name=\"errorTraktSyncGeneral\">Помилка синхронізації Trakt.tv.\\nБудь ласка, перевірте підключення до Інтернету і спробуйте ще раз.</string>\n  <string name=\"errorTraktLocked\">Ваш обліковий запис Trakt.tv заблоковано.\\nЗверніться до служби підтримки Trakt.tv за посиланням https://support.trakt.tv/ щоб розблокувати ваш обліковий запис.</string>\n  <string name=\"errorSubscriptionsNotAvailable\">Підписки Google Play недоступні на цьому пристрої.</string>\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textOk\">OK</string>\n  <string name=\"textYes\">Có</string>\n  <string name=\"textNo\">Không</string>\n  <string name=\"textApply\">Áp dụng</string>\n  <string name=\"textCancel\">Hủy</string>\n  <string name=\"textClose\">Đóng</string>\n  <string name=\"textNotNow\">Không phải bây giờ</string>\n  <string name=\"textNew\">Mới</string>\n  <string name=\"textSelect\">Chọn</string>\n  <string name=\"textHot\">Nóng</string>\n  <string name=\"textSubmit\">Gửi</string>\n  <string name=\"textHide\">Ẩn</string>\n  <string name=\"textRemove\">Loại bỏ</string>\n  <string name=\"textEmptyResults\">Không có kết quả nào…</string>\n  <string name=\"textSearchFor\">Tìm kiếm</string>\n  <string name=\"textTip\">Mẹo:</string>\n  <string name=\"textSeason\">Mùa %1$d</string>\n  <string name=\"textSpecials\">Đặc biệt</string>\n  <string name=\"textEpisode\">Tập %1$d</string>\n  <string name=\"textSeasonEpisode\">M.%02d T.%02d</string>\n  <string name=\"textSortBy\">Sắp xếp theo:</string>\n  <string name=\"textSpoilersWarning\">Nhận xét này có tiết lộ nội dung.\\nNhấn để đọc.</string>\n  <string name=\"textCommentedOn\">Được bình luận bởi %s</string>\n  <string name=\"textNetwork\">%1$s (%2$s)</string>\n  <string name=\"textNetworks\" comment=\"TV network like HBO, AMC, Showtime, Disney etc.\">Mạng:</string>\n  <string name=\"textGenres\">Thể loại:</string>\n  <string name=\"textTba\" translatable=\"false\">TBA</string>\n  <string name=\"textShows\">Chương trình</string>\n  <string name=\"textMovies\">Phim</string>\n  <string name=\"textLists\">Danh sách</string>\n  <string name=\"textMinutesShort\">phút</string>\n  <string name=\"textActorRole\" translatable=\"false\">%s\\n(%s)</string>\n  <string name=\"textNoDescription\">Tổng quan không có sẵn.</string>\n  <string name=\"textPleaseWait\">Xin vui lòng đợi…</string>\n  <string name=\"textRate\">Xếp hạng</string>\n  <string name=\"textLink\">Liên kết</string>\n  <string name=\"textDisabled\">Đã tắt</string>\n  <string name=\"textRateSaved\">Đánh giá của bạn đã được lưu.</string>\n  <string name=\"textRateRemoved\">Đánh giá của bạn đã bị xóa.</string>\n  <string name=\"textCopiedToClipboard\" translatable=\"false\">Đã sao chép vào clipboard</string>\n  <string name=\"textDays\">%1$d ngày</string>\n\n  <plurals name=\"textEpisodesLeft\" comment=\"Number of episodes left to see\">\n    <item quantity=\"one\">%d còn lại</item>\n    <item quantity=\"other\">%d còn lại</item>\n  </plurals>\n\n  <string name=\"textPeople\">Mọi người:</string>\n  <string name=\"textDirecting\">Chỉ đạo</string>\n  <string name=\"textDirector\">Đạo diễn</string>\n  <string name=\"textWriting\">Tác giả</string>\n  <string name=\"textScreenplay\">Kịch bản</string>\n  <string name=\"textSound\">Âm thanh</string>\n  <string name=\"textMusic\">Âm nhạc</string>\n  <string name=\"textActing\">Diễn xuất</string>\n\n  <string name=\"textSetCustomImages\">Hình ảnh tùy chỉnh</string>\n  <string name=\"textSetCustomImagesDescription\">Chọn áp phích và fanart của riêng bạn để sử dụng trên ứng dụng.</string>\n\n  <string name=\"textAiredAlready\">Đã phát sóng</string>\n  <string name=\"textAirsNow\">Đang phát sóng</string>\n\n  <plurals name=\"textDaysToAir\">\n    <item quantity=\"one\">Phát sóng vào ngày mai</item>\n    <item quantity=\"other\">Phát sóng sau %d ngày</item>\n  </plurals>\n\n  <plurals name=\"textHoursToAir\">\n    <item quantity=\"one\">Phát sau 1 giờ</item>\n    <item quantity=\"other\">Phát sóng sau %d giờ</item>\n  </plurals>\n\n  <plurals name=\"textMinutesToAir\">\n    <item quantity=\"one\">Đang phát sóng</item>\n    <item quantity=\"other\">Sẽ phát sóng sau %d phút</item>\n  </plurals>\n\n  <plurals name=\"textTraktQuickSyncComplete\">\n    <item quantity=\"one\">1 mục đã được đồng bộ hóa thành công .</item>\n    <item quantity=\"other\">%d mục đã được đồng bộ hóa thành công.</item>\n  </plurals>\n\n  <string name=\"textToday\">Hôm nay</string>\n  <string name=\"textTomorrow\">Ngày mai</string>\n  <string name=\"textThisWeek\">Tuần này</string>\n  <string name=\"textNextWeek\">Tuần tới</string>\n  <string name=\"textThisMonth\">Tháng này</string>\n  <string name=\"textNextMonth\">Tháng tới</string>\n  <string name=\"textThisYear\">Năm nay</string>\n  <string name=\"textNextYear\">Năm tới</string>\n  <string name=\"textLater\">Sau</string>\n  <string name=\"textYesterday\">Hôm qua</string>\n  <string name=\"textLast7Days\">7 ngày qua</string>\n  <string name=\"textLast30Days\">30 ngày qua</string>\n  <string name=\"textLast90Days\">90 ngày qua</string>\n\n  <string name=\"textNewEpisodeAvailable\">Hiện đã có tập mới!</string>\n  <string name=\"textNewEpisodeAvailableSoon\">Tập mới sẽ sớm có!</string>\n  <string name=\"textNewSeasonAvailable\">Hiện đã có mùa mới!</string>\n  <string name=\"textNewSeasonAvailableSoon\">Mùa giải mới sẽ sớm có!</string>\n  <string name=\"textNewMovieAvailable\">Phim đã được phát hành!</string>\n\n  <string name=\"textTraktSync\">Đồng bộ hóa Trakt.tv</string>\n  <string name=\"textTraktSyncComplete\">Hoàn tất đồng bộ hóa thành công.</string>\n  <string name=\"textTraktSyncRunning\">Đang chạy…</string>\n  <string name=\"textTraktSyncError\">Đồng bộ hóa Trakt.tv thất bại.</string>\n  <string name=\"textTraktSyncErrorFull\">Đồng bộ hóa Trakt.tv thất bại. Vui lòng kiểm tra kết nối Internet của bạn và thử lại hoặc liên hệ với chúng tôi nếu điều này vẫn tiếp diễn.</string>\n  <string name=\"textTraktQuickSyncError\">Đồng bộ hóa tức thì thất bại.</string>\n  <string name=\"textTraktQuickSyncErrorFull\">Đồng bộ hóa tức thì thất bại. Chúng tôi sẽ thử lại vào lần tới khi bạn mở ứng dụng. Vui lòng kiểm tra kết nối internet của bạn.</string>\n  <string name=\"textTraktNotificationsRationale\">Vui lòng cấp cho ứng dụng quyền hiển thị thông tin về tiến trình và kết quả đồng bộ hóa.\\n\\nBạn có muốn thực hiện việc đó ngay bây giờ không?</string>\n\n  <string name=\"textRemoveFromTrakt\">Loại khỏi Trakt.tv?</string>\n  <string name=\"textRemoveFromTraktHidden\">Bạn có muốn loại mục này khỏi tài khoản Trakt.tv \\'Mục ẩn\\' của mình không?</string>\n  <string name=\"textRemoveFromTraktWatchlist\">Bạn có muốn loại mục này khỏi tài khoản Trakt.tv \\'Danh sách theo dõi\\' của mình không?</string>\n  <string name=\"textRemoveFromTraktProgress\">Bạn có muốn loại mục này khỏi tài khoản Trakt.tv \\'Tiến độ\\' của mình không?</string>\n\n  <string name=\"textAddToMyShows\">Thêm vào chương trình của tôi</string>\n  <string name=\"textAddToMyMovies\">Thêm vào Phim của tôi</string>\n  <string name=\"textAddToWatchlist\">Thêm vào Danh sách theo dõi</string>\n  <string name=\"textAddToHidden\">Thêm vào Ẩn</string>\n  <string name=\"textMoveToMyShows\">Di chuyển tới Chương trình của tôi</string>\n  <string name=\"textMoveToMyMovies\">Di chuyển tới Phim của tôi</string>\n  <string name=\"textMoveToWatchlist\">Di chuyển đến Danh sách theo dõi</string>\n  <string name=\"textMoveToHidden\">Di chuyển đến Ẩn</string>\n  <string name=\"textRemoveFromMyShows\">Loại khỏi Chương trình của tôi</string>\n  <string name=\"textRemoveFromMyMovies\">Loại khỏi Phim của tôi</string>\n  <string name=\"textRemoveFromWatchlist\">Loại khỏi Danh sách theo dõi</string>\n  <string name=\"textRemoveFromHidden\">Loại khỏi Ẩn</string>\n  <string name=\"textWatchlist\">Danh sách theo dõi</string>\n  <string name=\"textWatchlistIncoming\">Sắp tới</string>\n  <string name=\"textNewAlwaysAtTop\">Các tập mới luôn trước tiên</string>\n\n  <string name=\"textPremium\" translatable=\"false\">Showly Premium</string>\n  <string name=\"textPremiumAd\">Trở thành người ủng hộ và có quyền truy cập vào các tính năng thưởng! Bấm vào để xem thêm.</string>\n\n  <string name=\"menuPin\">Ghim lên đầu</string>\n  <string name=\"menuUnpin\">Bỏ ghim khỏi đầu</string>\n  <string name=\"menuAddOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Ghim vào \\'đang chờ\\'</string>\n  <string name=\"menuRemoveOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">Bỏ ghim khỏi \\'đang chờ\\'</string>\n\n  <string name=\"errorGeneral\">Ôpss… Đã xảy ra lỗi.\\nVui lòng liên hệ với chúng tôi nếu điều này vẫn tiếp diễn.</string>\n  <string name=\"errorAuthorization\">Ủy quyển thất bại. Vui lòng liên hệ với chúng tôi nếu điều này vẫn tiếp diễn.</string>\n  <string name=\"errorTraktAuthorization\">Ủy quyền tài khoản Trakt thất bại.\\nVui lòng đăng nhập và thử lại.</string>\n  <string name=\"errorSeasonsNotLoaded\">Vui lòng đợi dữ liệu các mùa được tải.</string>\n  <string name=\"errorEpisodeNotAired\">Tập này chưa được phát sóng.</string>\n  <string name=\"errorCouldNotLoadDiscover\">Chúng tôi không thể tải trang khám phá vào lúc này.\\nVui lòng liên hệ với chúng tôi nếu điều này vẫn tiếp diễn.</string>\n  <string name=\"errorMalformedShow\">Có vẻ như chương trình này không còn tồn tại trong cơ sở dữ liệu Trakt.tv hoặc là một trùng lặp.\\n\\nNhấn \\'OK\\' để xóa nó khỏi ứng dụng.</string>\n  <string name=\"errorMalformedMovie\">Có vẻ như phim này không còn tồn tại trong cơ sở dữ liệu Trakt.tv hoặc là một trùng lặp.\\n\\nNhấn \\'OK\\' để xóa phim khỏi ứng dụng.</string>\n  <string name=\"errorCouldNotLoadShow\">Chúng tôi không thể tải thông tin chi tiết về chương trình này vào lúc này.\\nVui lòng liên hệ với chúng tôi nếu điều này vẫn tiếp diễn.</string>\n  <string name=\"errorCouldNotLoadMovie\">Chúng tôi không thể tải thông tin chi tiết về phim này vào lúc này.\\nVui lòng liên hệ với chúng tôi nếu sự cố này vẫn tiếp diễn.</string>\n  <string name=\"errorCouldNotLoadSearchResults\">Chúng tôi không thể tải kết quả tìm kiếm vào lúc này.\\nVui lòng liên hệ với chúng tôi nếu điều này vẫn tiếp diễn.</string>\n  <string name=\"errorNoInternetConnection\">Không có mạng. Vui lòng kiểm tra kết nối của bạn.</string>\n  <string name=\"errorTraktSyncGeneral\">Lỗi đồng bộ hóa Trakt.tv.\\nVui lòng kiểm tra kết nối Internet của bạn và thử lại.</string>\n  <string name=\"errorTraktLocked\">Tài khoản Trakt.tv của bạn hiện bị khóa.\\nVui lòng liên hệ với bộ phận hỗ trợ Trakt.tv tại https://support.trakt.tv/ để mở khóa tài khoản của bạn.</string>\n  <string name=\"errorBillingProductsNotAvailable\" translatable=\"false\">Chúng tôi không thể tải các tùy chọn mua hàng. Hãy đảm bảo ứng dụng Cửa hàng Google Play của bạn được cập nhật lên phiên bản mới nhất và thử lại.</string>\n  <string name=\"errorSubscriptionsNotAvailable\">Đăng ký Google Play không khả dụng trên thiết bị này.</string>\n  <string name=\"errorCouldNotFindApp\" translatable=\"false\">Rất tiếc, chúng tôi không thể tìm thấy ứng dụng trên thiết bị này có thể xử lý liên kết này.</string>\n  <string name=\"errorAccountListsLimitsReached\" translatable=\"false\">Rất tiếc, bạn đã đạt đến giới hạn tài khoản Trakt cho danh sách cá nhân!\\n\\nVui lòng liên hệ với chúng tôi nếu bạn cần thêm trợ giúp.</string>\n  <string name=\"errorTraktSyncListsLimitsReached\" translatable=\"false\">Bạn đã đạt đến giới hạn tài khoản Trakt cho danh sách cá nhân! Không phải tất cả danh sách cá nhân của bạn đều được đồng bộ hóa.</string>\n  <string name=\"errorTraktSyncWatchlistLimitsReached\" translatable=\"false\">Bạn đã đạt đến giới hạn tài khoản Trakt cho các mục trong danh sách theo dõi! Vui lòng liên hệ với chúng tôi nếu bạn cần thêm trợ giúp.</string>\n\n</resources>\n"
  },
  {
    "path": "ui-base/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textOk\">确定</string>\n  <string name=\"textYes\">确定</string>\n  <string name=\"textNo\">取消</string>\n  <string name=\"textApply\">应用</string>\n  <string name=\"textCancel\">取消</string>\n  <string name=\"textClose\">关闭</string>\n  <string name=\"textNotNow\">稍后​​​​​</string>\n  <string name=\"textNew\">新播</string>\n  <string name=\"textSelect\">选择</string>\n  <string name=\"textHot\">热门</string>\n  <string name=\"textSubmit\">提交</string>\n  <string name=\"textHide\">隐藏</string>\n  <string name=\"textRemove\">移除</string>\n  <string name=\"textEmptyResults\">暂无结果…</string>\n  <string name=\"textSearchFor\">搜索</string>\n  <string name=\"textTip\">提示：</string>\n  <string name=\"textSeason\">第 %1$d 季</string>\n  <string name=\"textSpecials\">特别集</string>\n  <string name=\"textEpisode\">第 %1$d 集</string>\n  <string name=\"textSeasonEpisode\">%02d 季 %02d 集</string>\n  <string name=\"textSortBy\">排序：</string>\n  <string name=\"textSpoilersWarning\">此评论包含剧透。\\n点后阅读。</string>\n  <string name=\"textCommentedOn\">%s 的评论</string>\n  <string name=\"textNetwork\">%1$s (%2$s)</string>\n  <string name=\"textNetworks\" comment=\"TV network like HBO, AMC, Showtime, Disney etc.\">流媒体：</string>\n  <string name=\"textGenres\">类型：</string>\n  <string name=\"textShows\">剧集</string>\n  <string name=\"textMovies\">电影</string>\n  <string name=\"textLists\">列表</string>\n  <string name=\"textMinutesShort\">分钟</string>\n  <string name=\"textNoDescription\">暂无概览信息。</string>\n  <string name=\"textPleaseWait\">请稍等…</string>\n  <string name=\"textRate\">评分</string>\n  <string name=\"textLink\">链接</string>\n  <string name=\"textRateSaved\">您的评分已发布。</string>\n  <string name=\"textRateRemoved\">您的评分已移除。</string>\n  <plurals name=\"textEpisodesLeft\" comment=\"Number of episodes left to see\">\n    <item quantity=\"other\">剩余 %d 集</item>\n  </plurals>\n  <string name=\"textPeople\">演职人员：</string>\n  <string name=\"textDirecting\">导演</string>\n  <string name=\"textDirector\">导演</string>\n  <string name=\"textWriting\">编剧</string>\n  <string name=\"textScreenplay\">剧本</string>\n  <string name=\"textSound\">声效</string>\n  <string name=\"textMusic\">音乐</string>\n  <string name=\"textActing\">出演</string>\n  <string name=\"textSetCustomImages\">自定义图片</string>\n  <string name=\"textSetCustomImagesDescription\">选择您自己喜欢的海报或粉丝作品（将全局生效）。</string>\n  <string name=\"textAiredAlready\">已播出</string>\n  <string name=\"textAirsNow\">现已开播</string>\n  <plurals name=\"textDaysToAir\">\n    <item quantity=\"other\">%d 天后开播</item>\n  </plurals>\n  <plurals name=\"textHoursToAir\">\n    <item quantity=\"other\">%d 小时后开播</item>\n  </plurals>\n  <plurals name=\"textMinutesToAir\">\n    <item quantity=\"other\">%d 分钟后开播</item>\n  </plurals>\n  <plurals name=\"textTraktQuickSyncComplete\">\n    <item quantity=\"other\">已成功同步 %d 条记录。</item>\n  </plurals>\n  <string name=\"textToday\">今日</string>\n  <string name=\"textTomorrow\">明日</string>\n  <string name=\"textThisWeek\">本周</string>\n  <string name=\"textNextWeek\">下周</string>\n  <string name=\"textThisMonth\">本月</string>\n  <string name=\"textNextMonth\">下月</string>\n  <string name=\"textThisYear\">今年</string>\n  <string name=\"textNextYear\">明年</string>\n  <string name=\"textLater\">随后</string>\n  <string name=\"textYesterday\">昨日</string>\n  <string name=\"textLast7Days\">最近 7 天</string>\n  <string name=\"textLast30Days\">最近 30 天</string>\n  <string name=\"textLast90Days\">最近 90 天</string>\n  <string name=\"textNewEpisodeAvailable\">新剧集现已更新！</string>\n  <string name=\"textNewEpisodeAvailableSoon\">新剧集即将更新！</string>\n  <string name=\"textNewSeasonAvailable\">新一季现已更新！</string>\n  <string name=\"textNewSeasonAvailableSoon\">新一季即将更新！</string>\n  <string name=\"textNewMovieAvailable\">电影已经上映！</string>\n  <string name=\"textTraktSync\">Trakt.tv 同步</string>\n  <string name=\"textTraktSyncComplete\">已成功同步。</string>\n  <string name=\"textTraktSyncRunning\">运行中...</string>\n  <string name=\"textTraktSyncError\">Trakt.tv 同步失败。</string>\n  <string name=\"textTraktSyncErrorFull\">Trakt.tv 同步失败。请检查您的网络连接并重试，若该问题持续存在，请联系我们。</string>\n  <string name=\"textTraktQuickSyncError\">即时同步失败。</string>\n  <string name=\"textTraktQuickSyncErrorFull\">即时同步失败。下次打开应用时会自动重试。请检查您的网络连接。</string>\n  <string name=\"textRemoveFromTrakt\">是否从 Trakt.tv 中移除？</string>\n  <string name=\"textRemoveFromTraktHidden\">是否想要从您的 Trakt.tv「隐藏项目」中移除此项？</string>\n  <string name=\"textRemoveFromTraktWatchlist\">是否想要从您的 Trakt.tv「观看列表」中移除此项？</string>\n  <string name=\"textRemoveFromTraktProgress\">是否想要从您的 Trakt.tv「进行中」中移除此项？</string>\n  <string name=\"textAddToMyShows\">加入我的剧集</string>\n  <string name=\"textAddToMyMovies\">加入我的电影</string>\n  <string name=\"textAddToWatchlist\">加入观看列表</string>\n  <string name=\"textAddToHidden\">加入隐藏项目</string>\n  <string name=\"textMoveToMyShows\">移至我的剧集</string>\n  <string name=\"textMoveToMyMovies\">移动至我的电影</string>\n  <string name=\"textMoveToWatchlist\">移至观看列表</string>\n  <string name=\"textMoveToHidden\">移至隐藏项目</string>\n  <string name=\"textRemoveFromMyShows\">从我的剧集移除</string>\n  <string name=\"textRemoveFromMyMovies\">从我的电影移除</string>\n  <string name=\"textRemoveFromWatchlist\">从观看列表中移除</string>\n  <string name=\"textRemoveFromHidden\">从隐藏项目中移除</string>\n  <string name=\"textWatchlist\">待看列表</string>\n  <string name=\"textWatchlistIncoming\">即将到来</string>\n  <string name=\"textNewAlwaysAtTop\">始终置顶新剧集</string>\n  <string name=\"textPremiumAd\">成为支持者并获得额外功能！点击查看更多信息。</string>\n  <string name=\"menuPin\">置顶</string>\n  <string name=\"menuUnpin\">取消置顶</string>\n  <string name=\"menuAddOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">设为「暂缓中」</string>\n  <string name=\"menuRemoveOnHold\" comment=\"This is a button for Progress subsection. It will contain pending shows marked by user.\">取消「暂缓中」</string>\n  <string name=\"errorGeneral\">哎呀～ 出错了。\\n如果这种情况持续发生，请联系我们。</string>\n  <string name=\"errorAuthorization\">授权失败。如果这种情况持续发生，请联系我们。</string>\n  <string name=\"errorTraktAuthorization\">Trakt 账号授权失败。\\n请登录并重试。</string>\n  <string name=\"errorSeasonsNotLoaded\">剧集数据加载中，请等待。</string>\n  <string name=\"errorEpisodeNotAired\">该集尚未播出。</string>\n  <string name=\"errorCouldNotLoadDiscover\">当前暂时无法加载发现页面。\\n如果这种情况持续发生，请联系我们。</string>\n  <string name=\"errorMalformedShow\">似乎 Trakt.tv 数据库中已不存在这部剧，或存在重复。\\n\\n点击 「确定」 将其移除。</string>\n  <string name=\"errorMalformedMovie\">似乎在 Trakt.tv 数据库中该电影已不存在，或存在重复。\\n\\n点击 「确定」 移除。</string>\n  <string name=\"errorCouldNotLoadShow\">当前暂时无法加载该剧详情。\\n如果这种情况持续发生，请联系我们。</string>\n  <string name=\"errorCouldNotLoadMovie\">当前暂时无法加载该电影详情。\\n如果这种情况持续发生，请联系我们。</string>\n  <string name=\"errorCouldNotLoadSearchResults\">当前暂时无法加载搜索结果。\\n如果这种情况持续发生，请联系我们。</string>\n  <string name=\"errorNoInternetConnection\">无互联网连接。请检查您的网络。</string>\n  <string name=\"errorTraktSyncGeneral\">Trakt.tv 同步错误。\\n请检查您的网络连接后重试。</string>\n  <string name=\"errorTraktLocked\">您的 Trakt.tv 账号目前已被锁定。\\n请在 https://support.trakt.tv/ 联系 Trakt.tv 客服解锁。</string>\n  <string name=\"errorSubscriptionsNotAvailable\">Google Play 服务在此设备上无法使用。</string>\n</resources>\n"
  },
  {
    "path": "ui-comments/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-comments/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-parcelize'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_comments'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-remote')\n  implementation project(':ui-base')\n  implementation project(':ui-model')\n  implementation project(':repository')\n  implementation project(':ui-navigation')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-comments/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest />\n"
  },
  {
    "path": "ui-comments/src/main/java/com/michaldrabik/ui_comments/CommentItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_comments\n\nimport androidx.recyclerview.widget.DiffUtil\nimport com.michaldrabik.ui_model.Comment\n\nclass CommentItemDiffCallback : DiffUtil.ItemCallback<Comment>() {\n\n  override fun areItemsTheSame(oldItem: Comment, newItem: Comment) =\n    oldItem.id == newItem.id\n\n  override fun areContentsTheSame(oldItem: Comment, newItem: Comment) =\n    oldItem == newItem\n}\n"
  },
  {
    "path": "ui-comments/src/main/java/com/michaldrabik/ui_comments/CommentView.kt",
    "content": "package com.michaldrabik.ui_comments\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.Typeface\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CircleCrop\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.expandTouch\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_comments.databinding.ViewCommentBinding\nimport com.michaldrabik.ui_comments.utilities.refreshTextSelection\nimport com.michaldrabik.ui_model.Comment\nimport java.time.format.DateTimeFormatter\nimport java.util.Locale\n\nclass CommentView : ConstraintLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCommentBinding.inflate(LayoutInflater.from(context), this)\n\n  private val colorTextPrimary by lazy { context.colorFromAttr(android.R.attr.textColorPrimary) }\n  private val colorTextSecondary by lazy { context.colorFromAttr(android.R.attr.textColorSecondary) }\n  private val colorTextAccent by lazy { context.colorFromAttr(android.R.attr.colorAccent) }\n  private val commentSpace by lazy { context.dimenToPx(R.dimen.commentViewSpace) }\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    with(binding) {\n      arrayOf(commentReplies, commentRepliesCount).forEach {\n        with(it) {\n          expandTouch()\n          onClick {\n            if (!comment.isLoading) {\n              onRepliesClickListener?.invoke(comment)\n            }\n          }\n        }\n      }\n      commentReply.onClick { onReplyClickListener?.invoke(comment) }\n      commentDelete.onClick { onDeleteClickListener?.invoke(comment) }\n    }\n  }\n\n  var onRepliesClickListener: ((Comment) -> Unit)? = null\n  var onReplyClickListener: ((Comment) -> Unit)? = null\n  var onDeleteClickListener: ((Comment) -> Unit)? = null\n\n  private lateinit var comment: Comment\n\n  @SuppressLint(\"SetTextI18n\", \"DefaultLocale\")\n  fun bind(comment: Comment, dateFormat: DateTimeFormatter?) {\n    clear()\n    this.comment = comment\n\n    with(binding) {\n      commentSpacer.setGuidelineBegin(if (comment.isReply()) commentSpace else 0)\n      commentHeader.text = context.getString(R.string.textCommentedOn, comment.user.username)\n      commentDate.text = comment.updatedAt?.toLocalZone()?.let { dateFormat?.format(it) }\n\n      if (comment.isMe) {\n        commentDate.setTextColor(colorTextAccent)\n        commentHeader.setTextColor(colorTextAccent)\n      }\n\n      commentRating.visibleIf(comment.userRating > 0)\n      commentRating.text = String.format(Locale.ENGLISH, \"%d\", comment.userRating)\n      commentReplies.visibleIf(comment.replies > 0 && !comment.isLoading && !comment.hasRepliesLoaded)\n      commentRepliesCount.visibleIf(comment.replies > 0 && !comment.isLoading && !comment.hasRepliesLoaded)\n      commentRepliesCount.text = comment.replies.toString()\n      commentProgress.visibleIf(comment.isLoading || comment.isLoading)\n      commentSpacerLine.visibleIf(comment.isReply())\n      commentReply.visibleIf(comment.isSignedIn && !comment.isLoading)\n      commentDelete.visibleIf(comment.isSignedIn && comment.isMe && comment.replies == 0L && !comment.isLoading)\n\n      if (comment.hasSpoilers()) {\n        with(commentText) {\n          text = context.getString(R.string.textSpoilersWarning)\n          commentText.setTypeface(null, Typeface.BOLD_ITALIC)\n          commentText.setTextColor(colorTextSecondary)\n          onClick {\n            text = comment.comment\n            commentText.setTypeface(null, Typeface.NORMAL)\n            commentText.setTextColor(colorTextPrimary)\n          }\n        }\n      } else {\n        commentText.text = comment.comment\n      }\n\n      if (comment.user.avatarUrl.isNotEmpty()) {\n        Glide.with(this@CommentView)\n          .load(comment.user.avatarUrl)\n          .placeholder(R.drawable.ic_person_placeholder)\n          .transform(CircleCrop())\n          .into(commentImage)\n      }\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      commentText.refreshTextSelection()\n      commentText.setTypeface(null, Typeface.NORMAL)\n      commentText.setTextColor(colorTextPrimary)\n      commentDate.setTextColor(colorTextSecondary)\n      commentHeader.setTextColor(colorTextSecondary)\n      Glide.with(this@CommentView).clear(commentImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-comments/src/main/java/com/michaldrabik/ui_comments/CommentsAdapter.kt",
    "content": "package com.michaldrabik.ui_comments\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_model.Comment\nimport java.time.format.DateTimeFormatter\n\nclass CommentsAdapter(\n  val onDeleteClickListener: ((Comment) -> Unit)? = null,\n  val onReplyClickListener: ((Comment) -> Unit)? = null,\n  val onRepliesClickListener: ((Comment) -> Unit)? = null\n) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n  private val asyncDiffer = AsyncListDiffer(this, CommentItemDiffCallback())\n  private var dateFormat: DateTimeFormatter? = null\n\n  fun setItems(newItems: List<Comment>, dateFormat: DateTimeFormatter?) {\n    this.dateFormat = dateFormat\n    asyncDiffer.submitList(newItems)\n  }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    ViewHolderShow(\n      CommentView(parent.context).apply {\n        onRepliesClickListener = this@CommentsAdapter.onRepliesClickListener\n        onReplyClickListener = this@CommentsAdapter.onReplyClickListener\n        onDeleteClickListener = this@CommentsAdapter.onDeleteClickListener\n      }\n    )\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    (holder.itemView as CommentView).bind(item, dateFormat)\n  }\n\n  override fun getItemCount() = asyncDiffer.currentList.size\n\n  class ViewHolderShow(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-comments/src/main/java/com/michaldrabik/ui_comments/fragment/CommentsFragment.kt",
    "content": "package com.michaldrabik.ui_comments.fragment\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.os.Parcelable\nimport android.view.View\nimport androidx.core.content.ContextCompat\nimport androidx.core.os.bundleOf\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.addDivider\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.requireParcelable\nimport com.michaldrabik.ui_base.utilities.extensions.updateTopMargin\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_comments.CommentsAdapter\nimport com.michaldrabik.ui_comments.R\nimport com.michaldrabik.ui_comments.databinding.FragmentCommentsBinding\nimport com.michaldrabik.ui_model.Comment\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ACTION_NEW_COMMENT\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_COMMENT\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_COMMENT_ACTION\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_COMMENT_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_MOVIE_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_OPTIONS\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_REPLY_USER\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SHOW_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_COMMENT\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.parcelize.Parcelize\n\n@SuppressLint(\"SetTextI18n\", \"DefaultLocale\", \"SourceLockedOrientationActivity\")\n@AndroidEntryPoint\nclass CommentsFragment : BaseFragment<CommentsViewModel>(R.layout.fragment_comments) {\n\n  companion object {\n    const val BACK_UP_BUTTON_THRESHOLD = 25\n\n    fun createBundle(movie: Movie): Bundle =\n      bundleOf(ARG_OPTIONS to Options(movie.ids.trakt, Mode.MOVIES))\n\n    fun createBundle(show: Show): Bundle =\n      bundleOf(ARG_OPTIONS to Options(show.ids.trakt, Mode.SHOWS))\n  }\n\n  override val navigationId = R.id.commentsFragment\n  override val viewModel by viewModels<CommentsViewModel>()\n  private val binding by viewBinding(FragmentCommentsBinding::bind)\n\n  private var commentsAdapter: CommentsAdapter? = null\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupRecycler()\n    setupStatusBar()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.messageFlow.collect { showSnack(it) } }\n    )\n  }\n\n  private fun setupView() {\n    hideNavigation()\n    with(binding) {\n      commentsBackArrow.onClick { requireActivity().onBackPressed() }\n      commentsPostButton.onClick { openPostCommentSheet() }\n      commentsUpButton.onClick {\n        commentsUpButton.fadeOut(150)\n        resetScroll()\n      }\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      commentsRecycler.doOnApplyWindowInsets { _, insets, padding, _ ->\n        val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top\n        commentsRecycler.updatePadding(top = padding.top + inset)\n        commentsTitle.updateTopMargin(inset)\n        commentsBackArrow.updateTopMargin(inset)\n      }\n    }\n  }\n\n  private fun setupRecycler() {\n    commentsAdapter = CommentsAdapter(\n      onDeleteClickListener = { openDeleteCommentDialog(it) },\n      onReplyClickListener = { openPostCommentSheet(it) },\n      onRepliesClickListener = { viewModel.loadCommentReplies(it) }\n    )\n    binding.commentsRecycler.apply {\n      setHasFixedSize(true)\n      adapter = commentsAdapter\n      layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)\n      itemAnimator = null\n      addDivider(R.drawable.divider_comments_list)\n      addOnScrollListener(recyclerScrollListener)\n    }\n  }\n\n  private fun openPostCommentSheet(comment: Comment? = null) {\n    setFragmentResultListener(REQUEST_COMMENT) { _, bundle ->\n      showSnack(MessageEvent.Info(R.string.textCommentPosted))\n      when (bundle.getString(ARG_COMMENT_ACTION)) {\n        ACTION_NEW_COMMENT -> {\n          val newComment = bundle.getParcelable<Comment>(ARG_COMMENT)\n          newComment?.let { viewModel.addNewComment(newComment) }\n          if (comment == null) {\n            binding.commentsRecycler.smoothScrollToPosition(0)\n          }\n        }\n      }\n    }\n\n    val bundle = when {\n      comment != null -> bundleOf(\n        ARG_COMMENT_ID to comment.getReplyId(),\n        ARG_REPLY_USER to comment.user.username\n      )\n      else -> {\n        val (id, mode) = requireParcelable<Options>(ARG_OPTIONS)\n        when (mode) {\n          Mode.SHOWS -> bundleOf(ARG_SHOW_ID to id.id)\n          Mode.MOVIES -> bundleOf(ARG_MOVIE_ID to id.id)\n        }\n      }\n    }\n\n    navigateToSafe(R.id.actionCommentsFragmentToPostComment, bundle)\n  }\n\n  private fun openDeleteCommentDialog(comment: Comment) {\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setTitle(R.string.textCommentConfirmDeleteTitle)\n      .setMessage(R.string.textCommentConfirmDelete)\n      .setPositiveButton(R.string.textYes) { _, _ -> viewModel.deleteComment(comment) }\n      .setNegativeButton(R.string.textNo) { _, _ -> }\n      .show()\n  }\n\n  private fun resetScroll() {\n    with(binding) {\n      commentsRecycler.smoothScrollToPosition(0)\n      commentsBackArrow.animate().translationY(0F).start()\n      commentsTitle.animate().translationY(0F).start()\n    }\n  }\n\n  private fun render(uiState: CommentsUiState) {\n    with(uiState) {\n      comments?.let {\n        commentsAdapter?.setItems(comments, dateFormat)\n        with(binding) {\n          commentsProgress.gone()\n          commentsEmpty.visibleIf(comments.isEmpty())\n          commentsPostButton.fadeIf(isSignedIn, duration = 200, startDelay = 150)\n        }\n      }\n    }\n  }\n\n  override fun onDestroyView() {\n    commentsAdapter = null\n    super.onDestroyView()\n  }\n\n  private val recyclerScrollListener = object : RecyclerView.OnScrollListener() {\n    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {\n      if (newState != RecyclerView.SCROLL_STATE_IDLE) {\n        return\n      }\n      val layoutManager = (binding.commentsRecycler.layoutManager as? LinearLayoutManager)\n      if ((layoutManager?.findFirstVisibleItemPosition() ?: 0) >= BACK_UP_BUTTON_THRESHOLD) {\n        binding.commentsUpButton.fadeIn(150)\n      } else {\n        binding.commentsUpButton.fadeOut(150)\n      }\n    }\n  }\n\n  @Parcelize\n  data class Options(val id: IdTrakt, val mode: Mode) : Parcelable\n}\n"
  },
  {
    "path": "ui-comments/src/main/java/com/michaldrabik/ui_comments/fragment/CommentsUiState.kt",
    "content": "package com.michaldrabik.ui_comments.fragment\n\nimport com.michaldrabik.ui_model.Comment\nimport java.time.format.DateTimeFormatter\n\ndata class CommentsUiState(\n  val comments: List<Comment>? = null,\n  val dateFormat: DateTimeFormatter? = null,\n  val isLoading: Boolean = false,\n  val isSignedIn: Boolean = false\n)\n"
  },
  {
    "path": "ui-comments/src/main/java/com/michaldrabik/ui_comments/fragment/CommentsViewModel.kt",
    "content": "package com.michaldrabik.ui_comments.fragment\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_comments.R\nimport com.michaldrabik.ui_comments.fragment.CommentsFragment.Options\nimport com.michaldrabik.ui_comments.fragment.cases.DeleteCommentCase\nimport com.michaldrabik.ui_comments.fragment.cases.LoadCommentsCase\nimport com.michaldrabik.ui_comments.fragment.cases.LoadRepliesCase\nimport com.michaldrabik.ui_model.Comment\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_OPTIONS\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport java.time.format.DateTimeFormatter\nimport javax.inject.Inject\n\n@HiltViewModel\nclass CommentsViewModel @Inject constructor(\n  savedStateHandle: SavedStateHandle,\n  private val commentsCase: LoadCommentsCase,\n  private val repliesCase: LoadRepliesCase,\n  private val deleteCase: DeleteCommentCase,\n  private val userManager: UserTraktManager,\n  private val dateFormatProvider: DateFormatProvider\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val commentsState = MutableStateFlow<List<Comment>?>(null)\n  private val loadingState = MutableStateFlow(false)\n  private val signedInState = MutableStateFlow(false)\n  private val dateFormatState = MutableStateFlow<DateTimeFormatter?>(null)\n\n  init {\n    loadInitialState()\n    savedStateHandle.get<Options>(ARG_OPTIONS)?.let {\n      loadComments(it.id, it.mode)\n    }\n  }\n\n  private fun loadInitialState() {\n    viewModelScope.launch {\n      signedInState.update { userManager.isAuthorized() }\n      dateFormatState.update { dateFormatProvider.loadFullHourFormat() }\n    }\n  }\n\n  private fun loadComments(id: IdTrakt, mode: Mode) {\n    viewModelScope.launch {\n      try {\n        val comments = commentsCase.loadComments(id, mode)\n        commentsState.update { comments }\n      } catch (error: Throwable) {\n        commentsState.update { emptyList() }\n        Timber.e(error)\n      }\n    }\n  }\n\n  fun loadCommentReplies(comment: Comment) {\n    var currentComments = uiState.value.comments?.toMutableList() ?: mutableListOf()\n    if (currentComments.any { it.parentId == comment.id }) {\n      return\n    }\n    viewModelScope.launch {\n      try {\n        val parent = currentComments.find { it.id == comment.id }\n        parent?.let { p ->\n          val copy = p.copy(isLoading = true)\n          currentComments.findReplace(copy) { it.id == p.id }\n          commentsState.value = currentComments\n        }\n\n        val replies = repliesCase.loadReplies(comment)\n\n        currentComments = uiState.value.comments?.toMutableList() ?: mutableListOf()\n        val parentIndex = currentComments.indexOfFirst { it.id == comment.id }\n        if (parentIndex > -1) currentComments.addAll(parentIndex + 1, replies)\n        parent?.let {\n          currentComments.findReplace(parent.copy(isLoading = false, hasRepliesLoaded = true)) { it.id == comment.id }\n        }\n\n        commentsState.value = currentComments\n      } catch (t: Throwable) {\n        commentsState.value = currentComments\n        messageChannel.send(MessageEvent.Error(R.string.errorGeneral))\n      }\n    }\n  }\n\n  fun addNewComment(comment: Comment) {\n    val currentComments = uiState.value.comments?.toMutableList() ?: mutableListOf()\n    if (!comment.isReply()) {\n      currentComments.add(0, comment)\n    } else {\n      val parentIndex = currentComments.indexOfLast { it.id == comment.parentId }\n      if (parentIndex > -1) {\n        val parent = currentComments[parentIndex]\n        currentComments.add(parentIndex + 1, comment)\n        val repliesCount = currentComments.count { it.parentId == parent.id }.toLong()\n        currentComments.findReplace(parent.copy(replies = repliesCount)) {\n          it.id == comment.parentId\n        }\n      }\n    }\n    commentsState.update { currentComments }\n  }\n\n  fun deleteComment(comment: Comment) {\n    var currentComments = uiState.value.comments?.toMutableList() ?: mutableListOf()\n    val target = currentComments.find { it.id == comment.id } ?: return\n\n    viewModelScope.launch {\n      try {\n        val copy = target.copy(isLoading = true)\n        currentComments.findReplace(copy) { it.id == target.id }\n        commentsState.value = currentComments\n\n        deleteCase.delete(target)\n\n        currentComments = uiState.value.comments?.toMutableList() ?: mutableListOf()\n        val targetIndex = currentComments.indexOfFirst { it.id == target.id }\n        if (targetIndex > -1) {\n          currentComments.removeAt(targetIndex)\n          if (target.isReply()) {\n            val parent = currentComments.first { it.id == target.parentId }\n            val repliesCount = currentComments.count { it.parentId == parent.id }.toLong()\n            currentComments.findReplace(parent.copy(replies = repliesCount)) { it.id == target.parentId }\n          }\n        }\n\n        commentsState.value = currentComments\n        messageChannel.send(MessageEvent.Info(R.string.textCommentDeleted))\n      } catch (t: Throwable) {\n        when (ErrorHelper.parse(t)) {\n          is ShowlyError.CoroutineCancellation -> rethrowCancellation(t)\n          is ShowlyError.ResourceConflictError -> messageChannel.send(MessageEvent.Error(R.string.errorCommentDelete))\n          else -> messageChannel.send(MessageEvent.Error(R.string.errorGeneral))\n        }\n      }\n    }\n  }\n\n  val uiState = combine(\n    commentsState,\n    loadingState,\n    signedInState,\n    dateFormatState\n  ) { s1, s2, s3, s4 ->\n    CommentsUiState(\n      comments = s1,\n      isLoading = s2,\n      isSignedIn = s3,\n      dateFormat = s4\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = CommentsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-comments/src/main/java/com/michaldrabik/ui_comments/fragment/cases/DeleteCommentCase.kt",
    "content": "package com.michaldrabik.ui_comments.fragment.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.repository.CommentsRepository\nimport com.michaldrabik.ui_model.Comment\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport okhttp3.internal.EMPTY_RESPONSE\nimport retrofit2.HttpException\nimport retrofit2.Response\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass DeleteCommentCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val commentsRepository: CommentsRepository\n) {\n\n  suspend fun delete(comment: Comment) {\n    val dateMillis = comment.createdAt?.toMillis()\n    dateMillis?.let {\n      if (nowUtcMillis() - it >= TimeUnit.DAYS.toMillis(13)) {\n        throw HttpException(Response.error<Any>(409, EMPTY_RESPONSE))\n      }\n    }\n    withContext(dispatchers.IO) {\n      commentsRepository.deleteComment(comment.id)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-comments/src/main/java/com/michaldrabik/ui_comments/fragment/cases/LoadCommentsCase.kt",
    "content": "package com.michaldrabik.ui_comments.fragment.cases\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.CommentsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_model.Comment\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass LoadCommentsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val commentsRepository: CommentsRepository,\n  private val userManager: UserTraktManager\n) {\n\n  suspend fun loadComments(id: IdTrakt, mode: Mode): List<Comment> =\n    withContext(dispatchers.IO) {\n      val isSignedIn = userManager.isAuthorized()\n      val username = userManager.getUsername()\n      val comments = commentsRepository.loadComments(id, mode)\n        .map {\n          it.copy(\n            isSignedIn = isSignedIn,\n            isMe = it.user.username == username\n          )\n        }\n        .partition { it.isMe }\n\n      comments.first + comments.second\n    }\n}\n"
  },
  {
    "path": "ui-comments/src/main/java/com/michaldrabik/ui_comments/fragment/cases/LoadRepliesCase.kt",
    "content": "package com.michaldrabik.ui_comments.fragment.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.CommentsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_model.Comment\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass LoadRepliesCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val commentsRepository: CommentsRepository,\n  private val userManager: UserTraktManager\n) {\n\n  suspend fun loadReplies(comment: Comment): List<Comment> = withContext(dispatchers.IO) {\n    val isSignedIn = userManager.isAuthorized()\n    val username = userManager.getUsername()\n\n    commentsRepository.loadReplies(comment.id)\n      .map {\n        it.copy(\n          isSignedIn = isSignedIn,\n          isMe = it.user.username == username\n        )\n      }\n  }\n}\n"
  },
  {
    "path": "ui-comments/src/main/java/com/michaldrabik/ui_comments/post/PostCommentBottomSheet.kt",
    "content": "package com.michaldrabik.ui_comments.post\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.core.widget.doOnTextChanged\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.requireLong\nimport com.michaldrabik.ui_base.utilities.extensions.requireString\nimport com.michaldrabik.ui_base.utilities.extensions.showErrorSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_comments.R\nimport com.michaldrabik.ui_comments.databinding.ViewPostCommentBinding\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_COMMENT\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_COMMENT_ACTION\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_COMMENT_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_EPISODE_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_MOVIE_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_REPLY_USER\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SHOW_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_COMMENT\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass PostCommentBottomSheet : BaseBottomSheetFragment(R.layout.view_post_comment) {\n\n  private val showTraktId by lazy { IdTrakt(requireLong(ARG_SHOW_ID)) }\n  private val movieTraktId by lazy { IdTrakt(requireLong(ARG_MOVIE_ID)) }\n  private val episodeTraktId by lazy { IdTrakt(requireLong(ARG_EPISODE_ID)) }\n  private val replyCommentId by lazy { IdTrakt(requireLong(ARG_COMMENT_ID)) }\n  private val replyUser by lazy { requireString(ARG_REPLY_USER, default = \"\") }\n\n  private val binding by viewBinding(ViewPostCommentBinding::bind)\n  private val viewModel by viewModels<PostCommentViewModel>()\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.messageFlow.collect { renderSnackbar(it) } },\n    )\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun setupView() {\n    with(binding) {\n      viewPostCommentInputValue.doOnTextChanged { text, _, _, _ ->\n        val isValid =\n          !text?.trim().isNullOrEmpty() &&\n            (text?.trim()?.split(\" \")?.count { it.length > 1 } ?: 0) >= 5\n        viewPostCommentButton.isEnabled = isValid\n      }\n      viewPostCommentButton.onClick {\n        val commentText = viewPostCommentInputValue.text.toString()\n        val isSpoiler = viewPostCommentSpoilersCheck.isChecked\n        when {\n          replyCommentId.id > 0 -> viewModel.postReply(replyCommentId, commentText, isSpoiler)\n          showTraktId.id > 0 -> viewModel.postShowComment(showTraktId, commentText, isSpoiler)\n          movieTraktId.id > 0 -> viewModel.postMovieComment(movieTraktId, commentText, isSpoiler)\n          episodeTraktId.id > 0 -> viewModel.postEpisodeComment(episodeTraktId, commentText, isSpoiler)\n          else -> error(\"Invalid comment target.\")\n        }\n      }\n      if (replyUser.isNotEmpty() && replyCommentId.id != 0L) {\n        viewPostCommentInputValue.setText(\"@$replyUser \")\n      }\n    }\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun render(uiState: PostCommentUiState) {\n    uiState.run {\n      isLoading.let {\n        with(binding) {\n          viewPostCommentInput.isEnabled = !it\n          viewPostCommentInputValue.isEnabled = !it\n          viewPostCommentSpoilersCheck.isEnabled = !it\n          viewPostCommentProgress.visibleIf(it)\n          val commentText = viewPostCommentInputValue.text.toString()\n          viewPostCommentButton.isEnabled = !it && isCommentValid(commentText)\n          viewPostCommentButton.visibleIf(!it, gone = false)\n        }\n      }\n      isSuccess?.let {\n        it.consume()?.let { commentBundle ->\n          setFragmentResult(\n            REQUEST_COMMENT,\n            bundleOf(\n              ARG_COMMENT_ACTION to commentBundle.first,\n              ARG_COMMENT to commentBundle.second\n            )\n          )\n          closeSheet()\n        }\n      }\n    }\n  }\n\n  private fun renderSnackbar(message: MessageEvent) {\n    when (message) {\n      is MessageEvent.Info -> binding.viewPostCommentSnackHost.showInfoSnackbar(getString(message.textRestId))\n      is MessageEvent.Error -> binding.viewPostCommentSnackHost.showErrorSnackbar(getString(message.textRestId))\n    }\n  }\n\n  private fun isCommentValid(text: String) =\n    text.trim().isNotEmpty() && text.trim().split(\" \").count { it.length > 1 } >= 5\n}\n"
  },
  {
    "path": "ui-comments/src/main/java/com/michaldrabik/ui_comments/post/PostCommentUiState.kt",
    "content": "package com.michaldrabik.ui_comments.post\n\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.Comment\n\ndata class PostCommentUiState(\n  val isLoading: Boolean = false,\n  val isSuccess: Event<Pair<String, Comment>>? = null,\n)\n"
  },
  {
    "path": "ui-comments/src/main/java/com/michaldrabik/ui_comments/post/PostCommentViewModel.kt",
    "content": "package com.michaldrabik.ui_comments.post\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError.CoroutineCancellation\nimport com.michaldrabik.common.errors.ShowlyError.UnauthorizedError\nimport com.michaldrabik.common.errors.ShowlyError.ValidationError\nimport com.michaldrabik.repository.CommentsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_comments.R\nimport com.michaldrabik.ui_model.Comment\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ACTION_NEW_COMMENT\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass PostCommentViewModel @Inject constructor(\n  private val commentsRepository: CommentsRepository,\n  private val userTraktManager: UserTraktManager,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val loadingState = MutableStateFlow(false)\n  private val successState = MutableStateFlow<Event<Pair<String, Comment>>?>(null)\n\n  fun postShowComment(showId: IdTrakt, commentText: String, isSpoiler: Boolean) {\n    if (!isValid(commentText)) return\n    viewModelScope.launch {\n      try {\n        loadingState.value = true\n        val show = Show.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = showId))\n        val comment = commentsRepository\n          .postComment(show, commentText, isSpoiler)\n          .copy(isMe = true, isSignedIn = true)\n        successState.value = Event(Pair(ACTION_NEW_COMMENT, comment))\n      } catch (error: Throwable) {\n        handleError(error)\n      }\n    }\n  }\n\n  fun postMovieComment(movieId: IdTrakt, commentText: String, isSpoiler: Boolean) {\n    if (!isValid(commentText)) return\n    viewModelScope.launch {\n      try {\n        loadingState.value = true\n        val movie = Movie.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = movieId))\n        val comment = commentsRepository\n          .postComment(movie, commentText, isSpoiler)\n          .copy(isMe = true, isSignedIn = true)\n        successState.value = Event(Pair(ACTION_NEW_COMMENT, comment))\n      } catch (error: Throwable) {\n        handleError(error)\n      }\n    }\n  }\n\n  fun postEpisodeComment(episodeId: IdTrakt, commentText: String, isSpoiler: Boolean) {\n    if (!isValid(commentText)) return\n    viewModelScope.launch {\n      try {\n        loadingState.value = true\n        val episode = Episode.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = episodeId))\n        val comment = commentsRepository\n          .postComment(episode, commentText, isSpoiler)\n          .copy(isMe = true, isSignedIn = true)\n        successState.value = Event(Pair(ACTION_NEW_COMMENT, comment))\n      } catch (error: Throwable) {\n        handleError(error)\n      }\n    }\n  }\n\n  fun postReply(commentId: IdTrakt, commentText: String, isSpoiler: Boolean) {\n    if (!isValid(commentText)) return\n    viewModelScope.launch {\n      try {\n        loadingState.value = true\n        val comment = commentsRepository\n          .postReply(commentId.id, commentText, isSpoiler)\n          .copy(isMe = true, isSignedIn = true)\n        successState.value = Event(Pair(ACTION_NEW_COMMENT, comment))\n      } catch (error: Throwable) {\n        handleError(error)\n      }\n    }\n  }\n\n  private fun isValid(commentText: String) = commentText\n    .trim().split(\" \")\n    .filter { !it.startsWith(\"@\") }\n    .count { it.length > 1 } >= 5\n\n  private suspend fun handleError(error: Throwable) {\n    loadingState.value = false\n    when (ErrorHelper.parse(error)) {\n      is CoroutineCancellation -> rethrowCancellation(error)\n      is ValidationError -> messageChannel.send(MessageEvent.Error(R.string.errorCommentFormat))\n      is UnauthorizedError -> {\n        messageChannel.send(MessageEvent.Error(R.string.errorTraktAuthorization))\n        userTraktManager.revokeToken()\n      }\n      else -> messageChannel.send(MessageEvent.Error(R.string.errorGeneral))\n    }\n  }\n\n  val uiState = combine(\n    loadingState,\n    successState\n  ) { loadingState, successState ->\n    PostCommentUiState(\n      isLoading = loadingState,\n      isSuccess = successState\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = PostCommentUiState()\n  )\n}\n"
  },
  {
    "path": "ui-comments/src/main/java/com/michaldrabik/ui_comments/utilities/Extensions.kt",
    "content": "package com.michaldrabik.ui_comments.utilities\n\nimport android.widget.TextView\n\nfun TextView.refreshTextSelection() {\n  setTextIsSelectable(false)\n  post { setTextIsSelectable(true) }\n}\n"
  },
  {
    "path": "ui-comments/src/main/res/color/selector_comment_button.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"@color/colorGrayLight\" android:state_enabled=\"false\" />\n  <item android:color=\"?attr/colorAccent\" />\n</selector>"
  },
  {
    "path": "ui-comments/src/main/res/color/selector_comment_input.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?android:attr/textColorPrimary\" android:state_focused=\"true\" />\n  <item android:color=\"?android:attr/textColorSecondary\" />\n</selector>"
  },
  {
    "path": "ui-comments/src/main/res/drawable/bg_comment_rating.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"100dp\" />\n  <solid android:color=\"?attr/colorAccent\" />\n</shape>"
  },
  {
    "path": "ui-comments/src/main/res/drawable/divider_comments_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <size\n    android:width=\"32dp\"\n    android:height=\"32dp\"\n    />\n</shape>"
  },
  {
    "path": "ui-comments/src/main/res/drawable/ic_add_comment.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M21.99,4c0,-1.1 -0.89,-2 -1.99,-2H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14l4,4 -0.01,-18zM17,11h-4v4h-2v-4H7V9h4V5h2v4h4v2z\"/>\n</vector>\n"
  },
  {
    "path": "ui-comments/src/main/res/drawable/ic_delete.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8.46,11.88l1.41,-1.41L12,12.59l2.12,-2.12 1.41,1.41L13.41,14l2.12,2.12 -1.41,1.41L12,15.41l-2.12,2.12 -1.41,-1.41L10.59,14l-2.13,-2.12zM15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4z\"/>\n</vector>\n"
  },
  {
    "path": "ui-comments/src/main/res/drawable/ic_reply.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z\"/>\n</vector>\n"
  },
  {
    "path": "ui-comments/src/main/res/layout/fragment_comments.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  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/commentsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:background=\"?android:windowBackground\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/commentsRecycler\"\n    style=\"@style/ScrollbarsStyle\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/recyclerPaddingHorizontal\"\n    android:paddingTop=\"@dimen/backArrowSize\"\n    android:paddingEnd=\"@dimen/recyclerPaddingHorizontal\"\n    android:paddingBottom=\"80dp\"\n    android:scrollbars=\"vertical\"\n    app:layout_anchor=\"@+id/commentsEmpty\"\n    app:layout_anchorGravity=\"top|center\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/commentsProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    />\n\n  <TextView\n    android:id=\"@+id/commentsEmpty\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    android:gravity=\"center\"\n    android:text=\"@string/textNoComments\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.google.android.material.floatingactionbutton.FloatingActionButton\n    android:id=\"@+id/commentsPostButton\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"bottom|end\"\n    android:layout_margin=\"@dimen/fabButtonPadding\"\n    android:contentDescription=\"@string/textComment\"\n    android:visibility=\"gone\"\n    app:backgroundTint=\"?attr/colorAccent\"\n    app:srcCompat=\"@drawable/ic_add_comment\"\n    app:tint=\"?attr/textColorOnSurface\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.google.android.material.floatingactionbutton.FloatingActionButton\n    android:id=\"@+id/commentsUpButton\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"bottom|end\"\n    android:layout_marginEnd=\"@dimen/fabButtonCommentsPadding\"\n    android:layout_marginBottom=\"@dimen/fabButtonPadding\"\n    android:rotation=\"270\"\n    android:visibility=\"gone\"\n    app:backgroundTint=\"?attr/colorAccent\"\n    app:fabSize=\"mini\"\n    app:layout_behavior=\"com.google.android.material.floatingactionbutton.FloatingActionButton$Behavior\"\n    app:maxImageSize=\"30dp\"\n    app:srcCompat=\"@drawable/ic_arrow_right\"\n    app:tint=\"?attr/textColorOnSurface\"\n    tools:ignore=\"ContentDescription\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/commentsTitle\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"@dimen/backArrowSize\"\n    android:layout_marginStart=\"@dimen/backArrowSize\"\n    android:gravity=\"center_vertical\"\n    android:text=\"@string/textComments2\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"18sp\"\n    android:textStyle=\"bold\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    />\n\n  <ImageView\n    android:id=\"@+id/commentsBackArrow\"\n    android:layout_width=\"@dimen/backArrowSize\"\n    android:layout_height=\"@dimen/backArrowSize\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:padding=\"@dimen/backArrowPadding\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    app:srcCompat=\"@drawable/ic_arrow_back\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "ui-comments/src/main/res/layout/view_comment.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <androidx.constraintlayout.widget.Guideline\n    android:id=\"@+id/commentSpacer\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    app:layout_constraintGuide_begin=\"@dimen/commentViewSpace\"\n    />\n\n  <View\n    android:id=\"@+id/commentSpacerLine\"\n    android:layout_width=\"1dp\"\n    android:layout_height=\"0dp\"\n    android:layout_marginTop=\"3dp\"\n    android:background=\"?attr/colorSeparator\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"@id/commentSpacer\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <ImageView\n    android:id=\"@+id/commentImage\"\n    android:layout_width=\"36dp\"\n    android:layout_height=\"36dp\"\n    android:layout_marginTop=\"3dp\"\n    app:layout_constraintEnd_toStartOf=\"@id/commentHeader\"\n    app:layout_constraintStart_toStartOf=\"@id/commentSpacer\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_person_placeholder\"\n    />\n\n  <TextView\n    android:id=\"@+id/commentHeader\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:ellipsize=\"end\"\n    android:gravity=\"start\"\n    android:maxLines=\"1\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintBottom_toBottomOf=\"@id/commentImage\"\n    app:layout_constraintEnd_toStartOf=\"@id/commentReplies\"\n    app:layout_constraintStart_toEndOf=\"@id/commentImage\"\n    app:layout_constraintTop_toBottomOf=\"@+id/commentDate\"\n    app:layout_constraintVertical_chainStyle=\"packed\"\n    tools:ignore=\"SmallSp\"\n    tools:text=\"Commented by username\"\n    />\n\n  <TextView\n    android:id=\"@+id/commentDate\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:ellipsize=\"end\"\n    android:gravity=\"start\"\n    android:maxLines=\"1\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    app:layout_constraintBottom_toTopOf=\"@+id/commentHeader\"\n    app:layout_constraintEnd_toStartOf=\"@id/commentReplies\"\n    app:layout_constraintStart_toEndOf=\"@id/commentImage\"\n    app:layout_constraintTop_toTopOf=\"@id/commentImage\"\n    tools:ignore=\"SmallSp\"\n    tools:text=\"Dec 13, 2017 11:52 PM\"\n    />\n\n  <TextView\n    android:id=\"@+id/commentText\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:ellipsize=\"end\"\n    android:gravity=\"center_vertical|start\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textIsSelectable=\"true\"\n    android:textSize=\"14sp\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"@id/commentSpacer\"\n    app:layout_constraintTop_toBottomOf=\"@id/commentImage\"\n    app:layout_constraintVertical_bias=\"0\"\n    tools:targetApi=\"o\"\n    tools:text=\"@tools:sample/lorem\"\n    />\n\n  <TextView\n    android:id=\"@+id/commentRating\"\n    android:layout_width=\"18dp\"\n    android:layout_height=\"18dp\"\n    android:background=\"@drawable/bg_comment_rating\"\n    android:gravity=\"center\"\n    android:includeFontPadding=\"false\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    android:textSize=\"10sp\"\n    android:textStyle=\"bold\"\n    android:translationY=\"-14dp\"\n    android:visibility=\"visible\"\n    app:layout_constraintEnd_toEndOf=\"@id/commentImage\"\n    app:layout_constraintTop_toBottomOf=\"@id/commentImage\"\n    tools:ignore=\"SmallSp\"\n    tools:text=\"10\"\n    />\n\n  <ImageView\n    android:id=\"@+id/commentReplies\"\n    android:layout_width=\"20dp\"\n    android:layout_height=\"20dp\"\n    android:layout_marginEnd=\"@dimen/spaceTiny\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/commentImage\"\n    app:layout_constraintEnd_toStartOf=\"@id/commentRepliesCount\"\n    app:layout_constraintStart_toEndOf=\"@id/commentHeader\"\n    app:layout_constraintTop_toTopOf=\"@id/commentImage\"\n    app:srcCompat=\"@drawable/ic_comment\"\n    app:tint=\"?android:attr/textColorSecondary\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/commentRepliesCount\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    android:textStyle=\"bold\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/commentImage\"\n    app:layout_constraintEnd_toStartOf=\"@id/commentReply\"\n    app:layout_constraintStart_toEndOf=\"@id/commentReplies\"\n    app:layout_constraintTop_toTopOf=\"@id/commentImage\"\n    app:layout_goneMarginEnd=\"0dp\"\n    tools:ignore=\"SmallSp\"\n    tools:text=\"200\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/commentReply\"\n    android:layout_width=\"22dp\"\n    android:layout_height=\"22dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/commentImage\"\n    app:layout_constraintEnd_toStartOf=\"@id/commentDelete\"\n    app:layout_constraintStart_toEndOf=\"@id/commentRepliesCount\"\n    app:layout_constraintTop_toTopOf=\"@id/commentImage\"\n    app:layout_goneMarginEnd=\"0dp\"\n    app:srcCompat=\"@drawable/ic_reply\"\n    app:tint=\"?android:attr/textColorSecondary\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/commentDelete\"\n    android:layout_width=\"22dp\"\n    android:layout_height=\"22dp\"\n    android:layout_marginStart=\"@dimen/spaceMedium\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/commentImage\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/commentReply\"\n    app:layout_constraintTop_toTopOf=\"@id/commentImage\"\n    app:srcCompat=\"@drawable/ic_delete\"\n    app:tint=\"?android:attr/textColorSecondary\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/commentProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"28dp\"\n    android:layout_height=\"28dp\"\n    android:layout_gravity=\"center\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginBottom=\"@dimen/spaceMicro\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"@id/commentSpacer\"\n    app:layout_constraintTop_toBottomOf=\"@id/commentText\"\n    tools:visibility=\"visible\"\n    />\n\n</merge>\n"
  },
  {
    "path": "ui-comments/src/main/res/layout/view_post_comment.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/viewPostCommentRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  android:paddingStart=\"@dimen/spaceNormal\"\n  android:paddingTop=\"@dimen/spaceMedium\"\n  android:paddingEnd=\"@dimen/spaceNormal\"\n  android:paddingBottom=\"@dimen/spaceNormal\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <TextView\n    android:id=\"@+id/viewPostCommentTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"start\"\n    android:text=\"@string/textComment\"\n    android:textAlignment=\"viewStart\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewPostCommentSubTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"start\"\n    android:text=\"@string/textCommentDisclaimer\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPostCommentTitle\"\n    />\n\n  <com.google.android.material.textfield.TextInputLayout\n    android:id=\"@+id/viewPostCommentInput\"\n    style=\"@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    app:boxStrokeColor=\"@color/selector_comment_input\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewPostCommentSpoilersCheck\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPostCommentSubTitle\"\n    >\n\n    <com.google.android.material.textfield.TextInputEditText\n      android:id=\"@+id/viewPostCommentInputValue\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"start\"\n      android:inputType=\"textMultiLine\"\n      android:maxLines=\"20\"\n      android:minLines=\"6\"\n      android:textAlignment=\"viewStart\"\n      android:textCursorDrawable=\"@null\"\n      android:textSize=\"14sp\"\n      />\n\n  </com.google.android.material.textfield.TextInputLayout>\n\n  <com.google.android.material.checkbox.MaterialCheckBox\n    android:id=\"@+id/viewPostCommentSpoilersCheck\"\n    style=\"@style/ShowlyCheckbox\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:checked=\"false\"\n    android:gravity=\"center\"\n    android:text=\"@string/textSpoilers\"\n    android:translationX=\"-6dp\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewPostCommentButton\"\n    app:layout_constraintHorizontal_bias=\"0\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPostCommentInput\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewPostCommentButton\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:backgroundTint=\"@color/selector_comment_button\"\n    android:enabled=\"false\"\n    android:gravity=\"center\"\n    android:text=\"@string/textSubmit\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPostCommentSpoilersCheck\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPostCommentInput\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/viewPostCommentProgress\"\n    style=\"@style/ProgressBar.Accent\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    android:layout_gravity=\"center\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/viewPostCommentButton\"\n    app:layout_constraintEnd_toEndOf=\"@id/viewPostCommentButton\"\n    app:layout_constraintStart_toStartOf=\"@id/viewPostCommentButton\"\n    app:layout_constraintTop_toTopOf=\"@id/viewPostCommentButton\"\n    tools:visibility=\"visible\"\n    />\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/viewPostCommentSnackHost\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:translationZ=\"10dp\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-comments/src/main/res/layout-sw600dp/view_comment.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <androidx.constraintlayout.widget.Guideline\n    android:id=\"@+id/commentSpacer\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    app:layout_constraintGuide_begin=\"@dimen/commentViewSpace\"\n    />\n\n  <View\n    android:id=\"@+id/commentSpacerLine\"\n    android:layout_width=\"1dp\"\n    android:layout_height=\"0dp\"\n    android:layout_marginTop=\"3dp\"\n    android:background=\"?attr/colorSeparator\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"@id/commentSpacer\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <ImageView\n    android:id=\"@+id/commentImage\"\n    android:layout_width=\"46dp\"\n    android:layout_height=\"46dp\"\n    android:layout_marginTop=\"3dp\"\n    app:layout_constraintEnd_toStartOf=\"@id/commentHeader\"\n    app:layout_constraintStart_toStartOf=\"@id/commentSpacer\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_person_placeholder\"\n    />\n\n  <TextView\n    android:id=\"@+id/commentDate\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:ellipsize=\"end\"\n    android:gravity=\"start\"\n    android:maxLines=\"1\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    app:layout_constraintBottom_toTopOf=\"@+id/commentHeader\"\n    app:layout_constraintEnd_toStartOf=\"@id/commentReplies\"\n    app:layout_constraintStart_toEndOf=\"@id/commentImage\"\n    app:layout_constraintTop_toTopOf=\"@id/commentImage\"\n    app:layout_constraintVertical_chainStyle=\"packed\"\n    tools:ignore=\"SmallSp\"\n    tools:text=\"Dec 13, 2017 11:52 PM\"\n    />\n\n  <TextView\n    android:id=\"@+id/commentHeader\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:ellipsize=\"end\"\n    android:gravity=\"start\"\n    android:maxLines=\"1\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintBottom_toBottomOf=\"@id/commentImage\"\n    app:layout_constraintEnd_toStartOf=\"@id/commentReplies\"\n    app:layout_constraintStart_toEndOf=\"@id/commentImage\"\n    app:layout_constraintTop_toBottomOf=\"@+id/commentDate\"\n    tools:ignore=\"SmallSp\"\n    tools:text=\"Commented by username\"\n    />\n\n  <TextView\n    android:id=\"@+id/commentText\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:ellipsize=\"end\"\n    android:gravity=\"center_vertical|start\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textIsSelectable=\"true\"\n    android:textSize=\"14sp\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"@id/commentSpacer\"\n    app:layout_constraintTop_toBottomOf=\"@id/commentImage\"\n    app:layout_constraintVertical_bias=\"0\"\n    tools:targetApi=\"o\"\n    tools:text=\"@tools:sample/lorem\"\n    />\n\n  <TextView\n    android:id=\"@+id/commentRating\"\n    android:layout_width=\"22dp\"\n    android:layout_height=\"22dp\"\n    android:background=\"@drawable/bg_comment_rating\"\n    android:gravity=\"center\"\n    android:includeFontPadding=\"false\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    android:textSize=\"11sp\"\n    android:textStyle=\"bold\"\n    android:translationY=\"-14dp\"\n    android:visibility=\"visible\"\n    app:layout_constraintEnd_toEndOf=\"@id/commentImage\"\n    app:layout_constraintTop_toBottomOf=\"@id/commentImage\"\n    tools:ignore=\"SmallSp\"\n    tools:text=\"10\"\n    />\n\n  <ImageView\n    android:id=\"@+id/commentReplies\"\n    android:layout_width=\"20dp\"\n    android:layout_height=\"20dp\"\n    android:layout_marginEnd=\"@dimen/spaceTiny\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/commentImage\"\n    app:layout_constraintEnd_toStartOf=\"@id/commentRepliesCount\"\n    app:layout_constraintStart_toEndOf=\"@id/commentHeader\"\n    app:layout_constraintTop_toTopOf=\"@id/commentImage\"\n    app:srcCompat=\"@drawable/ic_comment\"\n    app:tint=\"?android:attr/textColorSecondary\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/commentRepliesCount\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    android:textStyle=\"bold\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/commentImage\"\n    app:layout_constraintEnd_toStartOf=\"@id/commentReply\"\n    app:layout_constraintStart_toEndOf=\"@id/commentReplies\"\n    app:layout_constraintTop_toTopOf=\"@id/commentImage\"\n    app:layout_goneMarginEnd=\"0dp\"\n    tools:ignore=\"SmallSp\"\n    tools:text=\"200\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/commentReply\"\n    android:layout_width=\"22dp\"\n    android:layout_height=\"22dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/commentImage\"\n    app:layout_constraintEnd_toStartOf=\"@id/commentDelete\"\n    app:layout_constraintStart_toEndOf=\"@id/commentRepliesCount\"\n    app:layout_constraintTop_toTopOf=\"@id/commentImage\"\n    app:layout_goneMarginEnd=\"0dp\"\n    app:srcCompat=\"@drawable/ic_reply\"\n    app:tint=\"?android:attr/textColorSecondary\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/commentDelete\"\n    android:layout_width=\"22dp\"\n    android:layout_height=\"22dp\"\n    android:layout_marginStart=\"@dimen/spaceMedium\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/commentImage\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/commentReply\"\n    app:layout_constraintTop_toTopOf=\"@id/commentImage\"\n    app:srcCompat=\"@drawable/ic_delete\"\n    app:tint=\"?android:attr/textColorSecondary\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/commentProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"28dp\"\n    android:layout_height=\"28dp\"\n    android:layout_gravity=\"center\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginBottom=\"@dimen/spaceMicro\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"@id/commentSpacer\"\n    app:layout_constraintTop_toBottomOf=\"@id/commentText\"\n    tools:visibility=\"visible\"\n    />\n\n</merge>\n"
  },
  {
    "path": "ui-comments/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"recyclerPaddingHorizontal\">16dp</dimen>\n  <dimen name=\"fabButtonPadding\">16dp</dimen>\n  <dimen name=\"fabButtonCommentsPadding\">88dp</dimen>\n</resources>"
  },
  {
    "path": "ui-comments/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textComment\">Comment</string>\n  <string name=\"textComments\">Comments:</string>\n  <string name=\"textComments2\">Comments</string>\n  <string name=\"textNoComments\">No comments.</string>\n  <string name=\"textCommentDisclaimer\">English only, 5+ words, be respectful, mark spoilers!</string>\n  <string name=\"textSpoilers\">Spoilers</string>\n  <string name=\"textCommentPosted\">Your comment has been submitted.</string>\n  <string name=\"textCommentDeleted\">Your comment has been deleted.</string>\n  <string name=\"textCommentConfirmDeleteTitle\">Delete Comment</string>\n  <string name=\"textCommentConfirmDelete\">Are you sure you want to delete this comment?</string>\n\n  <string name=\"textSignBeforeRate\">Please sign in before rating a show.</string>\n  <string name=\"textSignBeforeRateMovie\">Please sign in before rating a movie.</string>\n\n  <string name=\"errorCommentFormat\">Your comment must be in English and have 5 words or more.</string>\n  <string name=\"errorCommentDelete\">This comment can\\'t be deleted anymore.</string>\n</resources>"
  },
  {
    "path": "ui-comments/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textComment\">تعليق</string>\n  <string name=\"textComments\">التعليقات:</string>\n  <string name=\"textComments2\">التعليقات</string>\n  <string name=\"textNoComments\">لا توجد تعليقات.</string>\n  <string name=\"textCommentDisclaimer\">يجب أن يكون التعليق بالإنجليزية، وأكثر من 5 كلمات، وعند الحرق حدد أنه حرق!</string>\n  <string name=\"textSpoilers\">حرق</string>\n  <string name=\"textCommentPosted\">تم إرسال تعليقك.</string>\n  <string name=\"textCommentDeleted\">تم حذف تعليقك.</string>\n  <string name=\"textCommentConfirmDeleteTitle\">حذف التعليق</string>\n  <string name=\"textCommentConfirmDelete\">أمتأكد أنك تريد حذف هذا التعليق؟</string>\n  <string name=\"textSignBeforeRate\">رجاءً قُم بتسجيل الدخول قبل تقييم المسلسل.</string>\n  <string name=\"textSignBeforeRateMovie\">رجاءً قُم بتسجيل الدخول قبل تقييم الفيلم.</string>\n  <string name=\"errorCommentFormat\">يجب أن يكون التعليق بالإنجليزية، وأكثر من 5 كلمات.</string>\n  <string name=\"errorCommentDelete\">لا يُمكن حذف التعليق.</string>\n</resources>\n"
  },
  {
    "path": "ui-comments/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textComment\">Kommentar</string>\n  <string name=\"textComments\">Kommentare:</string>\n  <string name=\"textComments2\">Kommentare</string>\n  <string name=\"textNoComments\">Keine Kommentare.</string>\n  <string name=\"textCommentDisclaimer\">Nur Englisch, 5+ Wörter, sei respektvoll, markiere Spoiler!</string>\n  <string name=\"textSpoilers\">Spoiler</string>\n  <string name=\"textCommentPosted\">Ihr Kommentar wurde abgeschickt.</string>\n  <string name=\"textCommentDeleted\">Dein Kommentar wurde gelöscht.</string>\n  <string name=\"textCommentConfirmDeleteTitle\">Kommentar löschen</string>\n  <string name=\"textCommentConfirmDelete\">Möchtest du diesen Kommentar wirklich löschen?</string>\n  <string name=\"textSignBeforeRate\">Bitte melde dich an, um eine Serie zu bewerten.</string>\n  <string name=\"textSignBeforeRateMovie\">Bitte melde dich an bevor du einen Film bewerten kannst.</string>\n  <string name=\"errorCommentFormat\">Dein Kommentar muss auf Englisch sein und mindestens 5 Wörter haben.</string>\n  <string name=\"errorCommentDelete\">Dieser Kommentar kann nicht mehr gelöscht werden.</string>\n</resources>\n"
  },
  {
    "path": "ui-comments/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textComment\">Comentar</string>\n  <string name=\"textComments\">Comentarios:</string>\n  <string name=\"textComments2\">Comentarios</string>\n  <string name=\"textNoComments\">Sin comentarios.</string>\n  <string name=\"textCommentDisclaimer\">¡Sólo en inglés, más de 5 palabras, sé respetuoso/a, marca spoilers!</string>\n  <string name=\"textSpoilers\">Spoilers</string>\n  <string name=\"textCommentPosted\">Tu comentario ha sido enviado.</string>\n  <string name=\"textCommentDeleted\">Tu comentario ha sido borrado.</string>\n  <string name=\"textCommentConfirmDeleteTitle\">Borrar Comentario</string>\n  <string name=\"textCommentConfirmDelete\">¿Estás seguro de que quieres eliminar este comentario?</string>\n  <string name=\"textSignBeforeRate\">Por favor inicia sesión antes de calificar una serie.</string>\n  <string name=\"textSignBeforeRateMovie\">Por favor inicia sesión antes de calificar una película.</string>\n  <string name=\"errorCommentFormat\">Tu comentario debe estar en inglés y tener 5 palabras o más.</string>\n  <string name=\"errorCommentDelete\">Este comentario ya no puede ser eliminado.</string>\n</resources>\n"
  },
  {
    "path": "ui-comments/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textComment\">Kommentoi</string>\n  <string name=\"textComments\">Kommentit:</string>\n  <string name=\"textComments2\">Kommentit</string>\n  <string name=\"textNoComments\">Ei kommentteja.</string>\n  <string name=\"textCommentDisclaimer\">Vain englanniksi, 5+ sanaa, kunnioita muita, merkitse juonipaljastukset!</string>\n  <string name=\"textSpoilers\">Juonipaljastukset</string>\n  <string name=\"textCommentPosted\">Kommenttisi julkaistiin.</string>\n  <string name=\"textCommentDeleted\">Kommenttisi poistettiin.</string>\n  <string name=\"textCommentConfirmDeleteTitle\">Poista kommentti</string>\n  <string name=\"textCommentConfirmDelete\">Haluatko varmasti poistaa kommentin?</string>\n  <string name=\"textSignBeforeRate\">Kirjaudu sisään ennen sarjojen arviointia.</string>\n  <string name=\"textSignBeforeRateMovie\">Kirjaudu sisään ennen elokuvien arviointia.</string>\n  <string name=\"errorCommentFormat\">Kommenttisi on oltava englanninkielinen ja sisältää vähintään 5 sanaa.</string>\n  <string name=\"errorCommentDelete\">Kommentin poisto ei ole enää mahdollista.</string>\n</resources>\n"
  },
  {
    "path": "ui-comments/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textComment\">Commentaire</string>\n  <string name=\"textComments\">Commentaires :</string>\n  <string name=\"textComments2\">Commentaires</string>\n  <string name=\"textNoComments\">Aucun commentaire.</string>\n  <string name=\"textCommentDisclaimer\">En anglais uniquement, plus de 5 mots, soyez respectueux, indiquez les spoilers !</string>\n  <string name=\"textSpoilers\">Spoilers</string>\n  <string name=\"textCommentPosted\">Votre commentaire a été soumis.</string>\n  <string name=\"textCommentDeleted\">Votre commentaire a été supprimé.</string>\n  <string name=\"textCommentConfirmDeleteTitle\">Supprimer le commentaire</string>\n  <string name=\"textCommentConfirmDelete\">Voulez-vous vraiment supprimer ce commentaire ?</string>\n  <string name=\"textSignBeforeRate\">Veuillez vous connecter avant de noter une série.</string>\n  <string name=\"textSignBeforeRateMovie\">Veuillez vous connecter avant de noter un film.</string>\n  <string name=\"errorCommentFormat\">Votre commentaire doit être en anglais et avoir 5 mots ou plus.</string>\n  <string name=\"errorCommentDelete\">Ce commentaire ne peut plus être supprimé.</string>\n</resources>\n"
  },
  {
    "path": "ui-comments/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textComment\">Commenta</string>\n  <string name=\"textComments\">Commenti:</string>\n  <string name=\"textComments2\">Commenti</string>\n  <string name=\"textNoComments\">Nessun commento.</string>\n  <string name=\"textCommentDisclaimer\">Solo inglese, 5+ parole, sii rispettoso, segnala gli spoiler!</string>\n  <string name=\"textSpoilers\">Spoiler</string>\n  <string name=\"textCommentPosted\">Il tuo commento è stato inviato.</string>\n  <string name=\"textCommentDeleted\">Il tuo commento è stato eliminato.</string>\n  <string name=\"textCommentConfirmDeleteTitle\">Elimina commento</string>\n  <string name=\"textCommentConfirmDelete\">Sei sicuro di voler eliminare questo commento?</string>\n  <string name=\"textSignBeforeRate\">Per favore accedi prima di valutare uno show.</string>\n  <string name=\"textSignBeforeRateMovie\">Per favore accedi prima di valutare un film.</string>\n  <string name=\"errorCommentFormat\">Il tuo commento deve essere in inglese e avere 5 o più parole.</string>\n  <string name=\"errorCommentDelete\">Questo commento non può più essere eliminato.</string>\n</resources>\n"
  },
  {
    "path": "ui-comments/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textComment\">Komentarz</string>\n  <string name=\"textComments\">Opinie:</string>\n  <string name=\"textComments2\">Opinie</string>\n  <string name=\"textNoComments\">Brak opinii.</string>\n  <string name=\"textCommentDisclaimer\">Tylko po angielsku, minimum 5 słów, szanuj innych i oznaczaj spoilery!</string>\n  <string name=\"textSpoilers\">Spoilery</string>\n  <string name=\"textCommentPosted\">Twój komentarz został zapisany.</string>\n  <string name=\"textCommentDeleted\">Twój komentarz został usunięty.</string>\n  <string name=\"textCommentConfirmDeleteTitle\">Usuń Komentarz</string>\n  <string name=\"textCommentConfirmDelete\">Czy na pewno chcesz usunąć ten komentarz?</string>\n  <string name=\"textSignBeforeRate\">Zaloguj się aby móc ocenić.</string>\n  <string name=\"textSignBeforeRateMovie\">Zaloguj się aby móc ocenić.</string>\n  <string name=\"errorCommentFormat\">Twój komentarz musi być w języku angielskim i mieć 5 lub więcej słów.</string>\n  <string name=\"errorCommentDelete\">Ten komentarz nie może być już usunięty.</string>\n</resources>\n"
  },
  {
    "path": "ui-comments/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textComment\">Comente</string>\n  <string name=\"textComments\">Comentários:</string>\n  <string name=\"textComments2\">Comentários</string>\n  <string name=\"textNoComments\">Sem comentários.</string>\n  <string name=\"textCommentDisclaimer\">Apenas inglês, mais de 5 palavras, sejam respeitosas, marquem spoilers!</string>\n  <string name=\"textSpoilers\">Spoilers</string>\n  <string name=\"textCommentPosted\">O seu comentário foi publicado.</string>\n  <string name=\"textCommentDeleted\">Seu comentário foi excluído.</string>\n  <string name=\"textCommentConfirmDeleteTitle\">Excluir comentário</string>\n  <string name=\"textCommentConfirmDelete\">Você tem certeza de que deseja excluir este comentário?</string>\n  <string name=\"textSignBeforeRate\">Por favor, inicie a sessão antes de avaliar uma série.</string>\n  <string name=\"textSignBeforeRateMovie\">Por favor, inicie a sessão antes de avaliar um filme.</string>\n  <string name=\"errorCommentFormat\">O seu comentário deve ser em inglês e ter 5 palavras ou mais.</string>\n  <string name=\"errorCommentDelete\">Este comentário não pode ser mais excluído.</string>\n</resources>\n"
  },
  {
    "path": "ui-comments/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textComment\">Комментировать</string>\n  <string name=\"textComments\">Мнения:</string>\n  <string name=\"textComments2\">Мнения</string>\n  <string name=\"textNoComments\">Нет комментариев.</string>\n  <string name=\"textCommentDisclaimer\">Только английский, 5+ слов, будьте уважительны, помечайте спойлеры!</string>\n  <string name=\"textSpoilers\">Спойлеры</string>\n  <string name=\"textCommentPosted\">Ваш комментарий был отправлен.</string>\n  <string name=\"textCommentDeleted\">Ваш комментарий был удалён.</string>\n  <string name=\"textCommentConfirmDeleteTitle\">Удалить комментарий</string>\n  <string name=\"textCommentConfirmDelete\">Вы уверены, что хотите удалить этот комментарий?</string>\n  <string name=\"textSignBeforeRate\">Пожалуйста, войдите перед оценкой сериала.</string>\n  <string name=\"textSignBeforeRateMovie\">Пожалуйста, войдите перед оценкой фильма.</string>\n  <string name=\"errorCommentFormat\">Ваш комментарий должен быть на английском языке и иметь более 5 слов.</string>\n  <string name=\"errorCommentDelete\">Этот комментарий больше не может быть удален.</string>\n</resources>\n"
  },
  {
    "path": "ui-comments/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"recyclerPaddingHorizontal\">24dp</dimen>\n  <dimen name=\"fabButtonPadding\">24dp</dimen>\n  <dimen name=\"fabButtonCommentsPadding\">96dp</dimen>\n</resources>"
  },
  {
    "path": "ui-comments/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textComment\">Yorum</string>\n  <string name=\"textComments\">Yorumlar:</string>\n  <string name=\"textComments2\">Yorumlar</string>\n  <string name=\"textNoComments\">Hiçbir yorum yok.</string>\n  <string name=\"textCommentDisclaimer\">Saygılı bir şekilde İngilizce dilinde en az 5 kelimeyle yazın ve spoilerları belirtin!</string>\n  <string name=\"textSpoilers\">Spoiler</string>\n  <string name=\"textCommentPosted\">Yorumunuz gönderildi.</string>\n  <string name=\"textCommentDeleted\">Yorumunuz silindi.</string>\n  <string name=\"textCommentConfirmDeleteTitle\">Yorumu Sil</string>\n  <string name=\"textCommentConfirmDelete\">Bu yorumu silmek istediğinizden emin misiniz?</string>\n  <string name=\"textSignBeforeRate\">Bir diziye oy vermeden önce lütfen oturum açın.</string>\n  <string name=\"textSignBeforeRateMovie\">Bir filme oy vermeden önce lütfen oturum açın.</string>\n  <string name=\"errorCommentFormat\">Yorumunuz İngilizce dilinde olmalı ve en az 5 kelime içermelidir.</string>\n  <string name=\"errorCommentDelete\">Bu yorum artık silinemez.</string>\n</resources>\n"
  },
  {
    "path": "ui-comments/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textComment\">Коментувати</string>\n  <string name=\"textComments\">Коментарі:</string>\n  <string name=\"textComments2\">Коментарі</string>\n  <string name=\"textNoComments\">Коментарі відсутні.</string>\n  <string name=\"textCommentDisclaimer\">Тільки англійською, не менше 5 слів, поважайте інших та позначайте спойлери!</string>\n  <string name=\"textSpoilers\">Спойлери</string>\n  <string name=\"textCommentPosted\">Ваш коментар відправлено.</string>\n  <string name=\"textCommentDeleted\">Ваш коментар видалено.</string>\n  <string name=\"textCommentConfirmDeleteTitle\">Видалити коментар</string>\n  <string name=\"textCommentConfirmDelete\">Ви дійсно бажаєте видалити цей коментар?</string>\n  <string name=\"textSignBeforeRate\">Увійдіть, перш ніж оцінювати.</string>\n  <string name=\"textSignBeforeRateMovie\">Увійдіть, перш ніж оцінювати.</string>\n  <string name=\"errorCommentFormat\">Ваш коментар повинен бути англійською і містити 5 слів або більше.</string>\n  <string name=\"errorCommentDelete\">Цей коментар більше не можна видалити.</string>\n</resources>\n"
  },
  {
    "path": "ui-comments/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textComment\">Bình luận</string>\n  <string name=\"textComments\">Bình luận:</string>\n  <string name=\"textComments2\">Bình luận</string>\n  <string name=\"textNoComments\">Không có bình luận nào.</string>\n  <string name=\"textCommentDisclaimer\">Chỉ tiếng Anh, 5 từ trở lên, hãy tôn trọng, đánh dấu phần tiết lộ nội dung!</string>\n  <string name=\"textSpoilers\">Tiết lộ nội dung</string>\n  <string name=\"textCommentPosted\">Bình luận của bạn đã được gửi.</string>\n  <string name=\"textCommentDeleted\">Bình luận của bạn đã bị xóa.</string>\n  <string name=\"textCommentConfirmDeleteTitle\">Xóa bình luận</string>\n  <string name=\"textCommentConfirmDelete\">Bạn có chắc chắn muốn xóa bình luận này?</string>\n\n  <string name=\"textSignBeforeRate\">Vui lòng đăng nhập trước khi xếp hạng một chương trình.</string>\n  <string name=\"textSignBeforeRateMovie\">Vui lòng đăng nhập trước khi xếp hạng phim.</string>\n\n  <string name=\"errorCommentFormat\">Bình luận của bạn phải bằng tiếng Anh và có 5 từ trở lên.</string>\n  <string name=\"errorCommentDelete\">Bình luận này không thể bị xóa được nữa.</string>\n</resources>\n"
  },
  {
    "path": "ui-comments/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textComment\">评论</string>\n  <string name=\"textComments\">评论：</string>\n  <string name=\"textComments2\">评论</string>\n  <string name=\"textNoComments\">暂无评论。</string>\n  <string name=\"textCommentDisclaimer\">只能使用英文，5个单词以上，尊重他人，标记剧透！</string>\n  <string name=\"textSpoilers\">剧透</string>\n  <string name=\"textCommentPosted\">您的评论已提交。</string>\n  <string name=\"textCommentDeleted\">您的评论已删除。</string>\n  <string name=\"textCommentConfirmDeleteTitle\">删除评论</string>\n  <string name=\"textCommentConfirmDelete\">确实是否删除此评论？</string>\n  <string name=\"textSignBeforeRate\">给剧集评分前请先登录。</string>\n  <string name=\"textSignBeforeRateMovie\">给电影评分前请先登录。</string>\n  <string name=\"errorCommentFormat\">您的评论必须是英文且至少 5 个单词。</string>\n  <string name=\"errorCommentDelete\">此评论不能再被删除。</string>\n</resources>\n"
  },
  {
    "path": "ui-discover/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-discover/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_discover'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-local')\n  implementation project(':ui-base')\n  implementation project(':repository')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  testImplementation project(':common-test')\n  testImplementation libs.bundles.testing\n  androidTestImplementation libs.android.test.runner\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-discover/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/DiscoverFragment.kt",
    "content": "package com.michaldrabik.ui_discover\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup.MarginLayoutParams\nimport androidx.activity.addCallback\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateMargins\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.clearFragmentResultListener\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.OnTabReselectedListener\nimport com.michaldrabik.ui_base.common.sheets.context_menu.ContextMenuBottomSheet\nimport com.michaldrabik.ui_base.utilities.extensions.add\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.disableUi\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.enableUi\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.openWebUrl\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withSpanSizeLookup\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_discover.databinding.FragmentDiscoverBinding\nimport com.michaldrabik.ui_discover.helpers.DiscoverLayoutManagerProvider\nimport com.michaldrabik.ui_discover.recycler.DiscoverAdapter\nimport com.michaldrabik.ui_discover.recycler.DiscoverListItem\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SHOW_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_ITEM_MENU\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlin.random.Random\n\n@AndroidEntryPoint\ninternal class DiscoverFragment :\n  BaseFragment<DiscoverViewModel>(R.layout.fragment_discover),\n  OnTabReselectedListener {\n\n  companion object {\n    const val REQUEST_DISCOVER_FILTERS = \"REQUEST_DISCOVER_FILTERS\"\n  }\n\n  override val navigationId = R.id.discoverFragment\n\n  override val viewModel by viewModels<DiscoverViewModel>()\n  private val binding by viewBinding(FragmentDiscoverBinding::bind)\n\n  private val swipeRefreshStartOffset by lazy { requireContext().dimenToPx(R.dimen.swipeRefreshStartOffset) }\n  private val swipeRefreshEndOffset by lazy { requireContext().dimenToPx(R.dimen.swipeRefreshEndOffset) }\n\n  private var adapter: DiscoverAdapter? = null\n  private var layoutManager: GridLayoutManager? = null\n\n  private var searchViewPosition = 0F\n  private var tabsViewPosition = 0F\n  private var filtersViewPosition = 0F\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    savedInstanceState?.let {\n      searchViewPosition = it.getFloat(\"ARG_SEARCH_POS\", 0F)\n      tabsViewPosition = it.getFloat(\"ARG_TABS_POS\", 0F)\n      filtersViewPosition = it.getFloat(\"ARG_FILTERS_POS\", 0F)\n    }\n  }\n\n  override fun onSaveInstanceState(outState: Bundle) {\n    super.onSaveInstanceState(outState)\n    outState.putFloat(\"ARG_SEARCH_POS\", searchViewPosition)\n    outState.putFloat(\"ARG_TABS_POS\", tabsViewPosition)\n    outState.putFloat(\"ARG_FILTERS_POS\", filtersViewPosition)\n  }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupRecycler()\n    setupSwipeRefresh()\n    setupStatusBar()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.messageFlow.collect { showSnack(it) } },\n      doAfterLaunch = { viewModel.loadShows() }\n    )\n\n    setFragmentResultListener(REQUEST_DISCOVER_FILTERS) { _, _ ->\n      viewModel.loadShows(scrollToTop = true, skipCache = true, instantProgress = true)\n    }\n  }\n\n  override fun onResume() {\n    super.onResume()\n    showNavigation()\n  }\n\n  override fun onPause() {\n    enableUi()\n    with(binding) {\n      searchViewPosition = discoverSearchView.translationY\n      tabsViewPosition = discoverModeTabsView.translationY\n      filtersViewPosition = discoverFiltersView.translationY\n    }\n    super.onPause()\n  }\n\n  override fun onDestroyView() {\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n\n  private fun setupView() {\n    with(binding) {\n      discoverSearchView.run {\n        settingsIconVisible = true\n        isEnabled = false\n        onClick { openSearch() }\n        onSettingsClickListener = {\n          hideNavigation()\n          navigateToSafe(R.id.actionDiscoverFragmentToSettingsFragment)\n        }\n        translationY = searchViewPosition\n      }\n      discoverModeTabsView.run {\n        visibleIf(moviesEnabled)\n        translationY = tabsViewPosition\n        onModeSelected = { mode = it }\n        selectShows()\n      }\n      discoverFiltersView.run {\n        translationY = filtersViewPosition\n        onGenresChipClick = { navigateToSafe(R.id.actionDiscoverFragmentToFiltersGenres) }\n        onNetworksChipClick = { navigateToSafe(R.id.actionDiscoverFragmentToFiltersNetworks) }\n        onFeedChipClick = { navigateToSafe(R.id.actionDiscoverFragmentToFiltersFeed) }\n        onHideAnticipatedChipClick = { viewModel.toggleAnticipated() }\n        onHideCollectionChipClick = { viewModel.toggleCollection() }\n      }\n    }\n  }\n\n  private fun setupRecycler() {\n    layoutManager = DiscoverLayoutManagerProvider.provideLayoutManager(requireContext())\n    adapter = DiscoverAdapter(\n      itemClickListener = {\n        /*when (it.image.type) {\n          ImageType.TWITTER -> openWebUrl(Config.TWITTER_URL)\n          ImageType.PREMIUM -> openPremium()\n          else -> openDetails(it)\n        }*/\n        openDetails(it)\n      },\n      itemLongClickListener = { item -> openShowMenu(item.show) },\n      missingImageListener = { ids, force -> viewModel.loadMissingImage(ids, force) },\n      listChangeListener = { binding.discoverRecycler.scrollToPosition(0) },\n      twitterCancelClickListener = { viewModel.cancelTwitterAd() }\n    ).apply {\n      stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY\n    }\n    binding.discoverRecycler.apply {\n      adapter = this@DiscoverFragment.adapter\n      layoutManager = this@DiscoverFragment.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      setHasFixedSize(true)\n    }\n  }\n\n  private fun setupSwipeRefresh() {\n    binding.discoverSwipeRefresh.apply {\n      val color = requireContext().colorFromAttr(R.attr.colorAccent)\n      setProgressBackgroundColorSchemeColor(requireContext().colorFromAttr(R.attr.colorSearchViewBackground))\n      setColorSchemeColors(color, color, color)\n      setOnRefreshListener {\n        searchViewPosition = 0F\n        tabsViewPosition = 0F\n        viewModel.loadShows(pullToRefresh = true)\n      }\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      discoverRoot.doOnApplyWindowInsets { _, insets, _, _ ->\n        val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n        val statusBarSize = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n\n        val recyclerPadding =\n          if (moviesEnabled) R.dimen.discoverRecyclerPadding\n          else R.dimen.discoverRecyclerPaddingNoTabs\n\n        val filtersPadding =\n          if (moviesEnabled) R.dimen.collectionFiltersMargin\n          else R.dimen.collectionFiltersMarginNoTabs\n\n        discoverRecycler\n          .updatePadding(top = statusBarSize + dimenToPx(recyclerPadding))\n        (discoverSearchView.layoutParams as MarginLayoutParams)\n          .updateMargins(top = statusBarSize + dimenToPx(R.dimen.spaceMedium))\n        (discoverModeTabsView.layoutParams as MarginLayoutParams)\n          .updateMargins(top = statusBarSize + dimenToPx(R.dimen.collectionTabsMargin))\n        (discoverFiltersView.layoutParams as MarginLayoutParams)\n          .updateMargins(top = statusBarSize + dimenToPx(filtersPadding))\n        discoverSwipeRefresh.setProgressViewOffset(\n          true,\n          swipeRefreshStartOffset + statusBarSize,\n          swipeRefreshEndOffset\n        )\n      }\n    }\n  }\n\n  override fun setupBackPressed() {\n    val dispatcher = requireActivity().onBackPressedDispatcher\n    dispatcher.addCallback(viewLifecycleOwner) {\n      isEnabled = false\n      activity?.onBackPressed()\n    }\n  }\n\n  private fun openSearch() {\n    disableUi()\n    hideNavigation()\n    with(binding) {\n      discoverModeTabsView.fadeOut(duration = 200).add(animations)\n      discoverFiltersView.fadeOut(duration = 200).add(animations)\n      discoverRecycler.fadeOut(duration = 200) {\n        navigateToSafe(R.id.actionDiscoverFragmentToSearchFragment)\n      }.add(animations)\n    }\n  }\n\n  private fun openDetails(item: DiscoverListItem) {\n    if (!binding.discoverRecycler.isEnabled) return\n    disableUi()\n    hideNavigation()\n    animateItemsExit(item)\n  }\n\n  private fun openPremium() {\n    if (!binding.discoverRecycler.isEnabled) return\n    disableUi()\n    hideNavigation()\n    navigateToSafe(R.id.actionDiscoverFragmentToPremium, Bundle.EMPTY)\n  }\n\n  private fun openShowMenu(show: Show) {\n    if (!binding.discoverRecycler.isEnabled) return\n    setFragmentResultListener(REQUEST_ITEM_MENU) { requestKey, _ ->\n      if (requestKey == REQUEST_ITEM_MENU) {\n        viewModel.loadShows()\n      }\n      clearFragmentResultListener(REQUEST_ITEM_MENU)\n    }\n    val bundle = ContextMenuBottomSheet.createBundle(show.ids.trakt)\n    navigateToSafe(R.id.actionDiscoverFragmentToItemMenu, bundle)\n  }\n\n  private fun animateItemsExit(item: DiscoverListItem) {\n    with(binding) {\n      discoverSearchView.fadeOut().add(animations)\n      discoverModeTabsView.fadeOut().add(animations)\n      discoverFiltersView.fadeOut().add(animations)\n\n      val clickedIndex = adapter?.indexOf(item) ?: 0\n      val itemsCount = adapter?.itemCount ?: 0\n      (0..itemsCount).forEach {\n        if (it != clickedIndex) {\n          val view = discoverRecycler.findViewHolderForAdapterPosition(it)\n          view?.let { v ->\n            val randomDelay = Random.nextLong(50, 200)\n            v.itemView.fadeOut(duration = 150, startDelay = randomDelay).add(animations)\n          }\n        }\n      }\n\n      val clickedView = discoverRecycler.findViewHolderForAdapterPosition(clickedIndex)\n      clickedView?.itemView?.fadeOut(\n        duration = 150, startDelay = 350,\n        endAction = {\n          if (!isResumed) return@fadeOut\n          val bundle = Bundle().apply { putLong(ARG_SHOW_ID, item.show.traktId) }\n          navigateToSafe(R.id.actionDiscoverFragmentToShowDetailsFragment, bundle)\n        }\n      ).add(animations)\n    }\n  }\n\n  private fun render(uiState: DiscoverUiState) {\n    uiState.run {\n      with(binding) {\n        items?.let {\n          val resetScroll = resetScroll?.consume() == true\n          adapter?.setItems(it, resetScroll)\n          layoutManager?.withSpanSizeLookup { pos ->\n            adapter?.getItems()?.get(pos)?.image?.type?.getSpan(isTablet)!!\n          }\n          discoverRecycler.fadeIn(200, withHardware = true)\n        }\n        isSyncing?.let {\n          discoverSearchView.setTraktProgress(it)\n          discoverSearchView.isEnabled = !it\n        }\n        isLoading?.let {\n          discoverSearchView.isEnabled = !it\n          discoverSwipeRefresh.isRefreshing = it\n          discoverModeTabsView.isEnabled = !it\n          discoverFiltersView.isEnabled = !it\n          discoverRecycler.isEnabled = !it\n        }\n        filters?.let {\n          if (discoverFiltersView.visibility != View.VISIBLE) {\n            discoverFiltersView.visible()\n          }\n          discoverFiltersView.bind(it)\n        }\n      }\n    }\n  }\n\n  override fun onTabReselected() = openSearch()\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/DiscoverUiState.kt",
    "content": "package com.michaldrabik.ui_discover\n\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_discover.recycler.DiscoverListItem\nimport com.michaldrabik.ui_model.DiscoverFilters\n\ndata class DiscoverUiState(\n  val items: List<DiscoverListItem>? = null,\n  val isLoading: Boolean? = null,\n  val isSyncing: Boolean? = null,\n  var filters: DiscoverFilters? = null,\n  var resetScroll: Event<Boolean>? = null,\n)\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/DiscoverViewModel.kt",
    "content": "package com.michaldrabik.ui_discover\n\nimport androidx.annotation.VisibleForTesting\nimport androidx.annotation.VisibleForTesting.Companion.PRIVATE\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkInfo\nimport androidx.work.WorkManager\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.ui_base.trakt.TraktSyncWorker\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_discover.cases.DiscoverFiltersCase\nimport com.michaldrabik.ui_discover.cases.DiscoverShowsCase\nimport com.michaldrabik.ui_discover.cases.DiscoverTwitterCase\nimport com.michaldrabik.ui_discover.recycler.DiscoverListItem\nimport com.michaldrabik.ui_model.DiscoverFilters\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageFamily.MOVIE\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@HiltViewModel\ninternal class DiscoverViewModel @Inject constructor(\n  private val showsCase: DiscoverShowsCase,\n  private val filtersCase: DiscoverFiltersCase,\n  private val twitterCase: DiscoverTwitterCase,\n  private val imagesProvider: ShowImagesProvider,\n  workManager: WorkManager,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val itemsState = MutableStateFlow<List<DiscoverListItem>?>(null)\n  private val loadingState = MutableStateFlow(false)\n  private val syncingState = MutableStateFlow(false)\n  private val filtersState = MutableStateFlow<DiscoverFilters?>(null)\n  private val scrollState = MutableStateFlow(Event(false))\n\n  @VisibleForTesting(otherwise = PRIVATE) var lastPullToRefreshMs = 0L\n  private var initialFilters: DiscoverFilters? = null\n\n  init {\n    workManager.getWorkInfosByTagLiveData(TraktSyncWorker.TAG_ID).observeForever { work ->\n      syncingState.value = work.any { it.state == WorkInfo.State.RUNNING }\n    }\n    viewModelScope.launch {\n      initialFilters = filtersCase.loadFilters()\n    }\n  }\n\n  fun loadShows(\n    pullToRefresh: Boolean = false,\n    scrollToTop: Boolean = false,\n    skipCache: Boolean = false,\n    instantProgress: Boolean = false,\n  ) {\n    loadingState.value = true\n\n    if (pullToRefresh && nowUtcMillis() - lastPullToRefreshMs < Config.PULL_TO_REFRESH_COOLDOWN_MS) {\n      loadingState.value = false\n      return\n    }\n\n    loadingState.value = pullToRefresh\n\n    viewModelScope.launch {\n      val progressJob = launch {\n        delay(if (pullToRefresh || instantProgress) 0 else 750)\n        loadingState.value = true\n      }\n\n      try {\n        val filters = filtersCase.loadFilters()\n        filtersState.value = filters\n\n        if (!pullToRefresh && !skipCache) {\n          val shows = showsCase.loadCachedShows(filters)\n          itemsState.value = shows\n          scrollState.value = Event(scrollToTop)\n        }\n\n        if (pullToRefresh || skipCache || !showsCase.isCacheValid()) {\n          val shows = showsCase.loadRemoteShows(filters)\n          itemsState.value = shows\n          scrollState.value = Event(scrollToTop)\n          initialFilters = filters\n        }\n\n        if (pullToRefresh) {\n          lastPullToRefreshMs = nowUtcMillis()\n        }\n      } catch (error: Throwable) {\n        onError(error)\n      } finally {\n        loadingState.value = false\n        progressJob.cancel()\n      }\n    }\n  }\n\n  fun loadMissingImage(item: DiscoverListItem, force: Boolean) {\n\n    fun updateItem(newItem: DiscoverListItem) {\n      val currentItems = uiState.value.items?.toMutableList()\n      currentItems?.findReplace(newItem) { it.isSameAs(newItem) }\n      itemsState.value = currentItems\n      scrollState.value = Event(false)\n    }\n\n    viewModelScope.launch {\n      val loadingJob = launch {\n        delay(750)\n        updateItem(item.copy(isLoading = true))\n      }\n      try {\n        val image = imagesProvider.loadRemoteImage(item.show, item.image.type, force)\n        updateItem(item.copy(isLoading = false, image = image))\n      } catch (t: Throwable) {\n        updateItem(item.copy(isLoading = false, image = Image.createUnavailable(item.image.type, MOVIE)))\n        rethrowCancellation(t)\n      } finally {\n        loadingJob.cancel()\n      }\n    }\n  }\n\n  fun cancelTwitterAd() {\n    twitterCase.cancelTwitterAd()\n    loadShows()\n  }\n\n  fun toggleAnticipated() {\n    viewModelScope.launch {\n      filtersCase.toggleAnticipated()\n      loadShows(scrollToTop = true, skipCache = true, instantProgress = true)\n    }\n  }\n\n  fun toggleCollection() {\n    viewModelScope.launch {\n      filtersCase.toggleCollection()\n      loadShows(scrollToTop = true, skipCache = true, instantProgress = true)\n    }\n  }\n\n  private suspend fun onError(error: Throwable) {\n    if (error !is CancellationException) {\n      messageChannel.send(MessageEvent.Error(R.string.errorCouldNotLoadDiscover))\n      Timber.e(error)\n    }\n    rethrowCancellation(error)\n  }\n\n  override fun onCleared() {\n    filtersCase.revertFilters(\n      initialFilters = initialFilters,\n      currentFilters = filtersState.value\n    )\n    super.onCleared()\n  }\n\n  val uiState = combine(\n    itemsState,\n    loadingState,\n    syncingState,\n    filtersState,\n    scrollState\n  ) { s1, s2, s3, s4, s5 ->\n    DiscoverUiState(\n      items = s1,\n      isLoading = s2,\n      isSyncing = s3,\n      filters = s4,\n      resetScroll = s5\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = DiscoverUiState()\n  )\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/cases/DiscoverFiltersCase.kt",
    "content": "package com.michaldrabik.ui_discover.cases\n\nimport android.content.Context\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.AppScopeProvider\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_model.DiscoverFilters\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass DiscoverFiltersCase @Inject constructor(\n  @ApplicationContext private val context: Context,\n  private val dispatchers: CoroutineDispatchers,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun loadFilters(): DiscoverFilters =\n    withContext(dispatchers.IO) {\n      val settings = settingsRepository.load()\n      DiscoverFilters(\n        feedOrder = settings.discoverFilterFeed,\n        hideAnticipated = !settings.showAnticipatedShows,\n        hideCollection = !settings.showCollectionShows,\n        genres = settings.discoverFilterGenres.toList(),\n        networks = settings.discoverFilterNetworks.toList()\n      )\n    }\n\n  fun revertFilters(\n    initialFilters: DiscoverFilters?,\n    currentFilters: DiscoverFilters?,\n  ) {\n    (context as AppScopeProvider).appScope.launch {\n      try {\n        if (initialFilters != currentFilters) {\n          initialFilters?.let { initial ->\n            val settings = settingsRepository.load()\n            settingsRepository.update(\n              settings.copy(\n                discoverFilterFeed = initial.feedOrder,\n                discoverFilterGenres = initial.genres,\n                discoverFilterNetworks = initial.networks,\n                showAnticipatedShows = !initial.hideAnticipated,\n                showCollectionShows = !initial.hideCollection\n              )\n            )\n          }\n        }\n      } catch (error: Throwable) {\n        rethrowCancellation(error)\n      }\n    }\n  }\n\n  suspend fun toggleAnticipated() {\n    withContext(dispatchers.IO) {\n      val settings = settingsRepository.load()\n      settingsRepository.update(\n        settings.copy(showAnticipatedShows = !settings.showAnticipatedShows)\n      )\n    }\n  }\n\n  suspend fun toggleCollection() {\n    withContext(dispatchers.IO) {\n      val settings = settingsRepository.load()\n      settingsRepository.update(\n        settings.copy(showCollectionShows = !settings.showCollectionShows)\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/cases/DiscoverShowsCase.kt",
    "content": "package com.michaldrabik.ui_discover.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.ConfigVariant\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_discover.helpers.itemtype.ImageTypeProvider\nimport com.michaldrabik.ui_discover.recycler.DiscoverListItem\nimport com.michaldrabik.ui_model.DiscoverFilters\nimport com.michaldrabik.ui_model.DiscoverSortOrder\nimport com.michaldrabik.ui_model.DiscoverSortOrder.HOT\nimport com.michaldrabik.ui_model.DiscoverSortOrder.NEWEST\nimport com.michaldrabik.ui_model.DiscoverSortOrder.RATING\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\ninternal class DiscoverShowsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val showsRepository: ShowsRepository,\n  private val imageTypeProvider: ImageTypeProvider,\n  private val imagesProvider: ShowImagesProvider,\n  private val translationsRepository: TranslationsRepository,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun isCacheValid() =\n    withContext(dispatchers.IO) {\n      showsRepository.discoverShows.isCacheValid()\n    }\n\n  suspend fun loadCachedShows(filters: DiscoverFilters) =\n    withContext(dispatchers.IO) {\n      val myShowsIds = async { showsRepository.myShows.loadAllIds() }\n      val watchlistShowsIds = async { showsRepository.watchlistShows.loadAllIds() }\n      val archiveShowsIds = async { showsRepository.hiddenShows.loadAllIds() }\n      val cachedShows = async { showsRepository.discoverShows.loadAllCached() }\n\n      prepareItems(\n        shows = cachedShows.await(),\n        myShowsIds = myShowsIds.await(),\n        watchlistShowsIds = watchlistShowsIds.await(),\n        hiddenShowsIds = archiveShowsIds.await(),\n        filters = filters\n      )\n    }\n\n  suspend fun loadRemoteShows(filters: DiscoverFilters) = withContext(dispatchers.IO) {\n    val showAnticipated = !filters.hideAnticipated\n    val showCollection = !filters.hideCollection\n    val genres = filters.genres.toList()\n    val networks = filters.networks.toList()\n\n    val myAsync = async { showsRepository.myShows.loadAllIds() }\n    val watchlistSync = async { showsRepository.watchlistShows.loadAllIds() }\n    val archiveAsync = async { showsRepository.hiddenShows.loadAllIds() }\n    val (myIds, watchlistIds, hiddenIds) = awaitAll(myAsync, watchlistSync, archiveAsync)\n    val collectionSize = myIds.size + watchlistIds.size + hiddenIds.size\n\n    val remoteShows = showsRepository.discoverShows.loadAllRemote(\n      showAnticipated,\n      showCollection,\n      collectionSize,\n      genres,\n      networks\n    )\n\n    showsRepository.discoverShows.cacheDiscoverShows(remoteShows)\n\n    prepareItems(\n      shows = remoteShows,\n      myShowsIds = myIds,\n      watchlistShowsIds = watchlistIds,\n      hiddenShowsIds = hiddenIds,\n      filters = filters\n    )\n  }\n\n  private suspend fun prepareItems(\n    shows: List<Show>,\n    myShowsIds: List<Long>,\n    watchlistShowsIds: List<Long>,\n    hiddenShowsIds: List<Long>,\n    filters: DiscoverFilters?,\n  ) = coroutineScope {\n    val language = translationsRepository.getLanguage()\n    val collectionIds = myShowsIds + watchlistShowsIds + hiddenShowsIds\n    shows\n      .filter { it.traktId !in hiddenShowsIds }\n      .filter {\n        if (filters?.hideCollection == false) true\n        else it.traktId !in collectionIds\n      }\n      .sortedBy(filters?.feedOrder ?: HOT)\n      .mapIndexed { index, show ->\n        async {\n          val itemType = imageTypeProvider.getImageType(index)\n          val image = imagesProvider.findCachedImage(show, itemType)\n          val translation = loadTranslation(language, itemType, show)\n          DiscoverListItem(\n            show = show,\n            image = image,\n            isFollowed = show.traktId in myShowsIds,\n            isWatchlist = show.traktId in watchlistShowsIds,\n            translation = translation\n          )\n        }\n      }.awaitAll()\n      .toMutableList()\n      //.apply { insertTwitterAdItem(this) }\n      //.apply { insertPremiumAdItem(this) }\n      .toList()\n  }\n\n  private fun insertTwitterAdItem(items: MutableList<DiscoverListItem>) {\n    val isEnabled = settingsRepository.isTwitterAdEnabled\n    val isTimePassed = (nowUtcMillis() - settingsRepository.installTimestamp) > ConfigVariant.TWITTER_AD_DELAY\n    if (!isEnabled || !isTimePassed) return\n\n    val twitterAd = DiscoverListItem(Show.EMPTY, Image.createUnknown(ImageType.TWITTER))\n    if (items.size >= imageTypeProvider.twitterAdPosition) {\n      items.add(imageTypeProvider.twitterAdPosition, twitterAd)\n    } else {\n      items.add(twitterAd)\n    }\n  }\n\n  private fun insertPremiumAdItem(items: MutableList<DiscoverListItem>) {\n    val isPremium = settingsRepository.isPremium\n    val isTimePassed = (nowUtcMillis() - settingsRepository.installTimestamp) > ConfigVariant.PREMIUM_AD_DELAY\n    if (isPremium || !isTimePassed) return\n\n    val premiumAd = DiscoverListItem(Show.EMPTY, Image.createUnknown(ImageType.PREMIUM))\n    var position = imageTypeProvider.premiumAdPosition\n    if (items.size >= position) {\n      if (items.any { it.image.type == ImageType.TWITTER }) {\n        position++\n      }\n      items.add(position, premiumAd)\n    } else if (items.isNotEmpty()) {\n      items.add(premiumAd)\n    }\n  }\n\n  private suspend fun loadTranslation(language: String, itemType: ImageType, show: Show) =\n    if (language == Config.DEFAULT_LANGUAGE || itemType == ImageType.POSTER) null\n    else translationsRepository.loadTranslation(show, language, true)\n\n  private fun List<Show>.sortedBy(order: DiscoverSortOrder) = when (order) {\n    HOT -> this\n    RATING -> this.sortedWith(compareByDescending<Show> { it.votes }.thenBy { it.rating })\n    NEWEST -> this.sortedByDescending { it.year }\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/cases/DiscoverTwitterCase.kt",
    "content": "package com.michaldrabik.ui_discover.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass DiscoverTwitterCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun cancelTwitterAd() {\n    settingsRepository.isTwitterAdEnabled = false\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/di/DiscoverModule.kt",
    "content": "package com.michaldrabik.ui_discover.di\n\nimport android.content.Context\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_discover.helpers.itemtype.ImageTypeProvider\nimport com.michaldrabik.ui_discover.helpers.itemtype.PhoneImageTypeProvider\nimport com.michaldrabik.ui_discover.helpers.itemtype.TabletImageTypeProvider\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.components.SingletonComponent\n\n@Module\n@InstallIn(SingletonComponent::class)\nclass DiscoverModule {\n\n  @Provides\n  internal fun providesItemTypeProvider(@ApplicationContext context: Context): ImageTypeProvider {\n    return if (context.isTablet()) {\n      TabletImageTypeProvider()\n    } else {\n      PhoneImageTypeProvider()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/filters/feed/DiscoverFiltersFeedBottomSheet.kt",
    "content": "package com.michaldrabik.ui_discover.filters.feed\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetDialog\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenHeight\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_discover.DiscoverFragment.Companion.REQUEST_DISCOVER_FILTERS\nimport com.michaldrabik.ui_discover.R\nimport com.michaldrabik.ui_discover.databinding.ViewDiscoverFiltersFeedBinding\nimport com.michaldrabik.ui_discover.filters.feed.DiscoverFiltersFeedUiEvent.ApplyFilters\nimport com.michaldrabik.ui_discover.filters.feed.DiscoverFiltersFeedUiEvent.CloseFilters\nimport com.michaldrabik.ui_model.DiscoverSortOrder\nimport com.michaldrabik.ui_model.DiscoverSortOrder.HOT\nimport com.michaldrabik.ui_model.DiscoverSortOrder.NEWEST\nimport com.michaldrabik.ui_model.DiscoverSortOrder.RATING\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\ninternal class DiscoverFiltersFeedBottomSheet : BaseBottomSheetFragment(R.layout.view_discover_filters_feed) {\n\n  private val viewModel by viewModels<DiscoverFiltersFeedViewModel>()\n  private val binding by viewBinding(ViewDiscoverFiltersFeedBinding::bind)\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } }\n    )\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun setupView() {\n    val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior\n    behavior.skipCollapsed = true\n    behavior.maxHeight = (screenHeight() * 0.9).toInt()\n\n    with(binding) {\n      applyButton.onClick { saveFeedOrder() }\n    }\n  }\n\n  private fun saveFeedOrder() {\n    with(binding) {\n      val feedOrder = when {\n        feedChipHot.isChecked -> HOT\n        feedChipTopRated.isChecked -> RATING\n        feedChipRecent.isChecked -> NEWEST\n        else -> throw IllegalStateException()\n      }\n      viewModel.saveFeedOrder(feedOrder)\n    }\n  }\n\n  private fun render(uiState: DiscoverFiltersFeedUiState) {\n    with(uiState) {\n      feedOrder?.let { renderFilters(it) }\n    }\n  }\n\n  private fun renderFilters(feedOrder: DiscoverSortOrder) {\n    with(binding) {\n      feedChipHot.isChecked = feedOrder == HOT\n      feedChipTopRated.isChecked = feedOrder == RATING\n      feedChipRecent.isChecked = feedOrder == NEWEST\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is ApplyFilters -> {\n        setFragmentResult(REQUEST_DISCOVER_FILTERS, Bundle.EMPTY)\n        closeSheet()\n      }\n      is CloseFilters -> closeSheet()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/filters/feed/DiscoverFiltersFeedUiEvent.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_discover.filters.feed\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\ninternal sealed class DiscoverFiltersFeedUiEvent<T>(action: T) : Event<T>(action) {\n  object ApplyFilters : DiscoverFiltersFeedUiEvent<Unit>(Unit)\n  object CloseFilters : DiscoverFiltersFeedUiEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/filters/feed/DiscoverFiltersFeedUiState.kt",
    "content": "package com.michaldrabik.ui_discover.filters.feed\n\nimport com.michaldrabik.ui_model.DiscoverSortOrder\n\ninternal data class DiscoverFiltersFeedUiState(\n  val feedOrder: DiscoverSortOrder? = null,\n  val isLoading: Boolean? = null,\n)\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/filters/feed/DiscoverFiltersFeedViewModel.kt",
    "content": "package com.michaldrabik.ui_discover.filters.feed\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_discover.filters.feed.DiscoverFiltersFeedUiEvent.ApplyFilters\nimport com.michaldrabik.ui_discover.filters.feed.DiscoverFiltersFeedUiEvent.CloseFilters\nimport com.michaldrabik.ui_model.DiscoverSortOrder\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\ninternal class DiscoverFiltersFeedViewModel @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val feedOrderState = MutableStateFlow<DiscoverSortOrder?>(null)\n  private val loadingState = MutableStateFlow(false)\n\n  init {\n    loadFilters()\n  }\n\n  private fun loadFilters() {\n    viewModelScope.launch {\n      val settings = settingsRepository.load()\n      feedOrderState.value = settings.discoverFilterFeed\n    }\n  }\n\n  fun saveFeedOrder(feedOrder: DiscoverSortOrder) {\n    viewModelScope.launch {\n      if (feedOrder == feedOrderState.value) {\n        eventChannel.send(CloseFilters)\n        return@launch\n      }\n      val settings = settingsRepository.load()\n      settingsRepository.update(\n        settings.copy(discoverFilterFeed = feedOrder)\n      )\n      eventChannel.send(ApplyFilters)\n    }\n  }\n\n  val uiState = combine(\n    feedOrderState,\n    loadingState,\n  ) { s1, s2 ->\n    DiscoverFiltersFeedUiState(\n      feedOrder = s1,\n      isLoading = s2,\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = DiscoverFiltersFeedUiState()\n  )\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/filters/genres/DiscoverFiltersGenresBottomSheet.kt",
    "content": "package com.michaldrabik.ui_discover.filters.genres\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.forEach\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetDialog\nimport com.google.android.material.chip.Chip\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenHeight\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_discover.DiscoverFragment.Companion.REQUEST_DISCOVER_FILTERS\nimport com.michaldrabik.ui_discover.R\nimport com.michaldrabik.ui_discover.databinding.ViewDiscoverFiltersGenresBinding\nimport com.michaldrabik.ui_discover.filters.genres.DiscoverFiltersGenresUiEvent.ApplyFilters\nimport com.michaldrabik.ui_discover.filters.genres.DiscoverFiltersGenresUiEvent.CloseFilters\nimport com.michaldrabik.ui_model.Genre\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\ninternal class DiscoverFiltersGenresBottomSheet : BaseBottomSheetFragment(R.layout.view_discover_filters_genres) {\n\n  private val viewModel by viewModels<DiscoverFiltersGenresViewModel>()\n  private val binding by viewBinding(ViewDiscoverFiltersGenresBinding::bind)\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } }\n    )\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun setupView() {\n    val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior\n    behavior.skipCollapsed = true\n    behavior.maxHeight = (screenHeight() * 0.9).toInt()\n\n    with(binding) {\n      applyButton.onClick { saveGenres() }\n      clearButton.onClick { renderGenres(emptyList()) }\n    }\n  }\n\n  private fun saveGenres() {\n    with(binding) {\n      val genres = mutableListOf<Genre>().apply {\n        genresChipGroup.forEach { chip ->\n          if ((chip as Chip).isChecked) {\n            add(Genre.valueOf(chip.tag.toString()))\n          }\n        }\n      }\n      viewModel.saveGenres(genres)\n    }\n  }\n\n  private fun render(uiState: DiscoverFiltersGenresUiState) {\n    with(uiState) {\n      genres?.let { renderGenres(it) }\n    }\n  }\n\n  private fun renderGenres(genres: List<Genre>) {\n    binding.genresChipGroup.removeAllViews()\n    binding.clearButton.visibleIf(genres.isNotEmpty())\n\n    val genresNames = genres.map { it.name }\n    Genre.values()\n      .sortedBy { requireContext().getString(it.displayName) }\n      .forEach { genre ->\n        val chip = Chip(requireContext()).apply {\n          tag = genre.name\n          text = requireContext().getString(genre.displayName)\n          isCheckable = true\n          isCheckedIconVisible = false\n          setEnsureMinTouchTargetSize(false)\n          shapeAppearanceModel = shapeAppearanceModel.toBuilder()\n            .setAllCornerSizes(100f)\n            .build()\n          chipBackgroundColor = ContextCompat.getColorStateList(context, R.color.selector_discover_chip_background)\n          setChipStrokeColorResource(R.color.selector_discover_chip_text)\n          setChipStrokeWidthResource(R.dimen.discoverFilterChipStroke)\n          setTextColor(ContextCompat.getColorStateList(context, R.color.selector_discover_chip_text))\n          isChecked = genre.name in genresNames\n        }\n        binding.genresChipGroup.addView(chip)\n      }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is ApplyFilters -> {\n        setFragmentResult(REQUEST_DISCOVER_FILTERS, Bundle.EMPTY)\n        closeSheet()\n      }\n      is CloseFilters -> closeSheet()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/filters/genres/DiscoverFiltersGenresUiEvent.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_discover.filters.genres\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\ninternal sealed class DiscoverFiltersGenresUiEvent<T>(action: T) : Event<T>(action) {\n  object ApplyFilters : DiscoverFiltersGenresUiEvent<Unit>(Unit)\n  object CloseFilters : DiscoverFiltersGenresUiEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/filters/genres/DiscoverFiltersGenresUiState.kt",
    "content": "package com.michaldrabik.ui_discover.filters.genres\n\nimport com.michaldrabik.ui_model.Genre\n\ninternal data class DiscoverFiltersGenresUiState(\n  val genres: List<Genre>? = null,\n  val isLoading: Boolean? = null,\n)\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/filters/genres/DiscoverFiltersGenresViewModel.kt",
    "content": "package com.michaldrabik.ui_discover.filters.genres\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_discover.filters.genres.DiscoverFiltersGenresUiEvent.ApplyFilters\nimport com.michaldrabik.ui_discover.filters.genres.DiscoverFiltersGenresUiEvent.CloseFilters\nimport com.michaldrabik.ui_model.Genre\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\ninternal class DiscoverFiltersGenresViewModel @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val genresState = MutableStateFlow<List<Genre>?>(null)\n  private val loadingState = MutableStateFlow(false)\n\n  init {\n    loadGenres()\n  }\n\n  private fun loadGenres() {\n    viewModelScope.launch {\n      val settings = settingsRepository.load()\n      genresState.value = settings.discoverFilterGenres.toList()\n    }\n  }\n\n  fun saveGenres(genres: List<Genre>) {\n    viewModelScope.launch {\n      if (genres == genresState.value) {\n        eventChannel.send(CloseFilters)\n        return@launch\n      }\n      val settings = settingsRepository.load()\n      settingsRepository.update(\n        settings.copy(\n          discoverFilterGenres = genres.toList(),\n        )\n      )\n      eventChannel.send(ApplyFilters)\n    }\n  }\n\n  val uiState = combine(\n    genresState,\n    loadingState,\n  ) { s1, s2 ->\n    DiscoverFiltersGenresUiState(\n      genres = s1,\n      isLoading = s2,\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = DiscoverFiltersGenresUiState()\n  )\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/filters/networks/DiscoverFiltersNetworksBottomSheet.kt",
    "content": "package com.michaldrabik.ui_discover.filters.networks\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.forEach\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetDialog\nimport com.google.android.material.chip.Chip\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.NetworkIconProvider\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenHeight\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_discover.DiscoverFragment.Companion.REQUEST_DISCOVER_FILTERS\nimport com.michaldrabik.ui_discover.R\nimport com.michaldrabik.ui_discover.databinding.ViewDiscoverFiltersNetworksBinding\nimport com.michaldrabik.ui_discover.filters.networks.DiscoverFiltersNetworksUiEvent.ApplyFilters\nimport com.michaldrabik.ui_discover.filters.networks.DiscoverFiltersNetworksUiEvent.CloseFilters\nimport com.michaldrabik.ui_model.Network\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\ninternal class DiscoverFiltersNetworksBottomSheet : BaseBottomSheetFragment(R.layout.view_discover_filters_networks) {\n\n  private val viewModel by viewModels<DiscoverFiltersNetworksViewModel>()\n  private val binding by viewBinding(ViewDiscoverFiltersNetworksBinding::bind)\n\n  @Inject\n  lateinit var networkIconProvider: NetworkIconProvider\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } }\n    )\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun setupView() {\n    val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior\n    behavior.skipCollapsed = true\n    behavior.maxHeight = (screenHeight() * 0.9).toInt()\n\n    with(binding) {\n      applyButton.onClick { saveNetworks() }\n      clearButton.onClick { renderNetworks(emptyList()) }\n    }\n  }\n\n  private fun saveNetworks() {\n    with(binding) {\n      val networks = mutableListOf<Network>().apply {\n        networksChipGroup.forEach { chip ->\n          if ((chip as Chip).isChecked) {\n            add(Network.valueOf(chip.tag.toString()))\n          }\n        }\n      }\n      viewModel.saveNetworks(networks)\n    }\n  }\n\n  private fun render(uiState: DiscoverFiltersNetworksUiState) {\n    with(uiState) {\n      networks?.let { renderNetworks(it) }\n    }\n  }\n\n  private fun renderNetworks(networks: List<Network>) {\n    binding.networksChipGroup.removeAllViews()\n    binding.clearButton.visibleIf(networks.isNotEmpty())\n\n    val networksNames = networks.map { it.name }\n    Network.values()\n      .sortedBy { it.name }\n      .forEach { network ->\n        val icon = networkIconProvider.getIcon(network)\n        val chip = Chip(requireContext()).apply {\n          tag = network.name\n          text = network.channels.first()\n          isCheckable = true\n          isCheckedIconVisible = false\n          shapeAppearanceModel = shapeAppearanceModel.toBuilder()\n            .setAllCornerSizes(100f)\n            .build()\n          setEnsureMinTouchTargetSize(false)\n          setChipIconResource(icon)\n          chipBackgroundColor = ContextCompat.getColorStateList(requireContext(), R.color.selector_discover_chip_background)\n          setChipStrokeColorResource(R.color.selector_discover_chip_text)\n          setChipStrokeWidthResource(R.dimen.discoverFilterChipStroke)\n          setTextColor(ContextCompat.getColorStateList(requireContext(), R.color.selector_discover_chip_text))\n          isChecked = network.name in networksNames\n        }\n        binding.networksChipGroup.addView(chip)\n      }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is ApplyFilters -> {\n        setFragmentResult(REQUEST_DISCOVER_FILTERS, Bundle.EMPTY)\n        closeSheet()\n      }\n      is CloseFilters -> closeSheet()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/filters/networks/DiscoverFiltersNetworksUiEvent.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_discover.filters.networks\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\ninternal sealed class DiscoverFiltersNetworksUiEvent<T>(action: T) : Event<T>(action) {\n  object ApplyFilters : DiscoverFiltersNetworksUiEvent<Unit>(Unit)\n  object CloseFilters : DiscoverFiltersNetworksUiEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/filters/networks/DiscoverFiltersNetworksUiState.kt",
    "content": "package com.michaldrabik.ui_discover.filters.networks\n\nimport com.michaldrabik.ui_model.Network\n\ninternal data class DiscoverFiltersNetworksUiState(\n  val networks: List<Network>? = null,\n  val isLoading: Boolean? = null,\n)\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/filters/networks/DiscoverFiltersNetworksViewModel.kt",
    "content": "package com.michaldrabik.ui_discover.filters.networks\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_discover.filters.networks.DiscoverFiltersNetworksUiEvent.ApplyFilters\nimport com.michaldrabik.ui_discover.filters.networks.DiscoverFiltersNetworksUiEvent.CloseFilters\nimport com.michaldrabik.ui_model.Network\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\ninternal class DiscoverFiltersNetworksViewModel @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val networksState = MutableStateFlow<List<Network>?>(null)\n  private val loadingState = MutableStateFlow(false)\n\n  init {\n    loadNetworks()\n  }\n\n  private fun loadNetworks() {\n    viewModelScope.launch {\n      val settings = settingsRepository.load()\n      networksState.value = settings.discoverFilterNetworks.toList()\n    }\n  }\n\n  fun saveNetworks(networks: List<Network>) {\n    viewModelScope.launch {\n      if (networks == networksState.value) {\n        eventChannel.send(CloseFilters)\n        return@launch\n      }\n      val settings = settingsRepository.load()\n      settingsRepository.update(\n        settings.copy(\n          discoverFilterNetworks = networks.toList(),\n        )\n      )\n      eventChannel.send(ApplyFilters)\n    }\n  }\n\n  val uiState = combine(\n    networksState,\n    loadingState,\n  ) { s1, s2 ->\n    DiscoverFiltersNetworksUiState(\n      networks = s1,\n      isLoading = s2,\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = DiscoverFiltersNetworksUiState()\n  )\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/filters/views/DiscoverFiltersView.kt",
    "content": "package com.michaldrabik.ui_discover.filters.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport androidx.core.view.children\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_discover.R\nimport com.michaldrabik.ui_discover.databinding.ViewDiscoverFiltersBinding\nimport com.michaldrabik.ui_model.DiscoverFilters\nimport com.michaldrabik.ui_model.DiscoverSortOrder\nimport com.michaldrabik.ui_model.DiscoverSortOrder.HOT\nimport com.michaldrabik.ui_model.DiscoverSortOrder.NEWEST\nimport com.michaldrabik.ui_model.DiscoverSortOrder.RATING\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.Network\n\nclass DiscoverFiltersView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewDiscoverFiltersBinding.inflate(LayoutInflater.from(context), this)\n\n  var onFeedChipClick: (() -> Unit)? = null\n  var onGenresChipClick: (() -> Unit)? = null\n  var onNetworksChipClick: (() -> Unit)? = null\n  var onHideCollectionChipClick: (() -> Unit)? = null\n  var onHideAnticipatedChipClick: (() -> Unit)? = null\n\n  private lateinit var filters: DiscoverFilters\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    with(binding) {\n      discoverGenresChip.text = discoverGenresChip.text.toString().filter { it.isLetter() }\n      discoverGenresChip.onClick { onGenresChipClick?.invoke() }\n      discoverNetworksChip.text = discoverNetworksChip.text.toString().filter { it.isLetter() }\n      discoverNetworksChip.onClick { onNetworksChipClick?.invoke() }\n      discoverFeedChip.isSelected = true\n      discoverFeedChip.onClick { onFeedChipClick?.invoke() }\n      discoverCollectionChip.onClick { onHideCollectionChipClick?.invoke() }\n      discoverAnticipatedChip.onClick { onHideAnticipatedChipClick?.invoke() }\n    }\n  }\n\n  fun bind(filters: DiscoverFilters) {\n    this.filters = filters\n    bindFeed(filters.feedOrder)\n    bindGenres(filters.genres)\n    bindNetworks(filters.networks)\n    with(binding) {\n      discoverCollectionChip.isChecked = filters.hideCollection\n      discoverAnticipatedChip.isChecked = filters.hideAnticipated\n    }\n  }\n\n  private fun bindFeed(feed: DiscoverSortOrder) {\n    with(binding) {\n      discoverFeedChip.text = when (feed) {\n        HOT -> context.getString(R.string.textHot)\n        RATING -> context.getString(R.string.textSortRated)\n        NEWEST -> context.getString(R.string.textSortNewest)\n      }\n    }\n  }\n\n  private fun bindGenres(genres: List<Genre>) {\n    with(binding) {\n      discoverGenresChip.isSelected = genres.isNotEmpty()\n      discoverGenresChip.text = when {\n        genres.isEmpty() -> context.getString(R.string.textGenres).filter { it.isLetter() }\n        genres.size == 1 -> context.getString(genres.first().displayName)\n        genres.size == 2 -> \"${context.getString(genres[0].displayName)}, ${context.getString(genres[1].displayName)}\"\n        else -> \"${context.getString(genres[0].displayName)}, ${context.getString(genres[1].displayName)} + ${genres.size - 2}\"\n      }\n    }\n  }\n\n  private fun bindNetworks(networks: List<Network>) {\n    with(binding) {\n      discoverNetworksChip.isSelected = networks.isNotEmpty()\n      discoverNetworksChip.text = when {\n        networks.isEmpty() -> context.getString(R.string.textNetworks).filter { it.isLetter() }\n        networks.size == 1 -> networks[0].channels.first()\n        else -> throw IllegalStateException()\n      }\n    }\n  }\n\n  override fun setEnabled(enabled: Boolean) {\n    binding.discoverChips.children.forEach { it.isEnabled = enabled }\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/helpers/DiscoverLayoutManagerProvider.kt",
    "content": "package com.michaldrabik.ui_discover.helpers\n\nimport android.content.Context\nimport androidx.recyclerview.widget.GridLayoutManager\nimport com.michaldrabik.common.Config.MAIN_GRID_SPAN\nimport com.michaldrabik.common.Config.MAIN_GRID_SPAN_TABLET\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\n\ninternal object DiscoverLayoutManagerProvider {\n\n  fun provideLayoutManager(context: Context): GridLayoutManager {\n    val span = if (context.isTablet()) MAIN_GRID_SPAN_TABLET else MAIN_GRID_SPAN\n    return GridLayoutManager(context, span)\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/helpers/itemtype/ImageTypeProvider.kt",
    "content": "package com.michaldrabik.ui_discover.helpers.itemtype\n\nimport com.michaldrabik.ui_model.ImageType\n\ninternal interface ImageTypeProvider {\n\n  val twitterAdPosition: Int\n  val premiumAdPosition: Int\n\n  fun getImageType(position: Int): ImageType\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/helpers/itemtype/PhoneImageTypeProvider.kt",
    "content": "package com.michaldrabik.ui_discover.helpers.itemtype\n\nimport com.michaldrabik.ui_model.ImageType\n\nprivate const val BUFFER = 14\n\ninternal class PhoneImageTypeProvider : ImageTypeProvider {\n\n  override val twitterAdPosition = 14\n  override val premiumAdPosition = 29\n\n  override fun getImageType(position: Int): ImageType {\n    if (position % BUFFER == 0) return ImageType.FANART_WIDE\n    if ((position + (BUFFER - 5)) % BUFFER == 0) return ImageType.FANART\n    if ((position + (BUFFER - 9)) % BUFFER == 0) return ImageType.FANART\n    return ImageType.POSTER\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/helpers/itemtype/TabletImageTypeProvider.kt",
    "content": "package com.michaldrabik.ui_discover.helpers.itemtype\n\nimport com.michaldrabik.ui_model.ImageType\n\nprivate const val BUFFER = 11\n\ninternal class TabletImageTypeProvider : ImageTypeProvider {\n\n  override val twitterAdPosition = 14\n  override val premiumAdPosition = 30\n\n  override fun getImageType(position: Int): ImageType {\n    if (position % BUFFER == 0) return ImageType.FANART_WIDE\n    if ((position + (BUFFER - 10)) % BUFFER == 0) return ImageType.FANART_WIDE\n    if ((position + (BUFFER - 2)) % BUFFER == 0) return ImageType.FANART\n    if ((position + (BUFFER - 5)) % BUFFER == 0) return ImageType.FANART\n    if ((position + (BUFFER - 8)) % BUFFER == 0) return ImageType.FANART\n    return ImageType.POSTER\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/recycler/DiscoverAdapter.kt",
    "content": "package com.michaldrabik.ui_discover.recycler\n\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseAdapter\nimport com.michaldrabik.ui_discover.views.ShowFanartView\nimport com.michaldrabik.ui_discover.views.ShowPosterView\nimport com.michaldrabik.ui_discover.views.ShowPremiumView\nimport com.michaldrabik.ui_discover.views.ShowTwitterView\nimport com.michaldrabik.ui_model.ImageType.FANART\nimport com.michaldrabik.ui_model.ImageType.FANART_WIDE\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport com.michaldrabik.ui_model.ImageType.PREMIUM\nimport com.michaldrabik.ui_model.ImageType.TWITTER\n\nclass DiscoverAdapter(\n  private val itemClickListener: (DiscoverListItem) -> Unit,\n  private val itemLongClickListener: (DiscoverListItem) -> Unit,\n  private val missingImageListener: (DiscoverListItem, Boolean) -> Unit,\n  private val twitterCancelClickListener: (() -> Unit)?,\n  listChangeListener: () -> Unit,\n) : BaseAdapter<DiscoverListItem>(\n  listChangeListener = listChangeListener\n) {\n\n  override val asyncDiffer = AsyncListDiffer(this, DiscoverItemDiffCallback())\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) {\n    POSTER.id -> BaseViewHolder(\n      ShowPosterView(parent.context).apply {\n        itemClickListener = this@DiscoverAdapter.itemClickListener\n        itemLongClickListener = this@DiscoverAdapter.itemLongClickListener\n        missingImageListener = this@DiscoverAdapter.missingImageListener\n      }\n    )\n    FANART.id, FANART_WIDE.id -> BaseViewHolder(\n      ShowFanartView(parent.context).apply {\n        itemClickListener = this@DiscoverAdapter.itemClickListener\n        itemLongClickListener = this@DiscoverAdapter.itemLongClickListener\n        missingImageListener = this@DiscoverAdapter.missingImageListener\n      }\n    )\n    TWITTER.id -> BaseViewHolder(\n      ShowTwitterView(parent.context).apply {\n        itemClickListener = this@DiscoverAdapter.itemClickListener\n        twitterCancelClickListener = this@DiscoverAdapter.twitterCancelClickListener\n      }\n    )\n    PREMIUM.id -> BaseViewHolder(\n      ShowPremiumView(parent.context).apply {\n        itemClickListener = this@DiscoverAdapter.itemClickListener\n      }\n    )\n    else -> throw IllegalStateException(\"Unknown view type.\")\n  }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    when (holder.itemViewType) {\n      POSTER.id ->\n        (holder.itemView as ShowPosterView).bind(item)\n      FANART.id, FANART_WIDE.id ->\n        (holder.itemView as ShowFanartView).bind(item)\n      TWITTER.id ->\n        (holder.itemView as ShowTwitterView).bind(item)\n      PREMIUM.id ->\n        (holder.itemView as ShowPremiumView).bind(item)\n    }\n  }\n\n  override fun getItemViewType(position: Int) = asyncDiffer.currentList[position].image.type.id\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/recycler/DiscoverItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_discover.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass DiscoverItemDiffCallback : DiffUtil.ItemCallback<DiscoverListItem>() {\n\n  override fun areItemsTheSame(oldItem: DiscoverListItem, newItem: DiscoverListItem) =\n    oldItem.show.ids.trakt == newItem.show.ids.trakt\n\n  override fun areContentsTheSame(oldItem: DiscoverListItem, newItem: DiscoverListItem) =\n    oldItem.image == newItem.image &&\n      oldItem.isLoading == newItem.isLoading &&\n      oldItem.isFollowed == newItem.isFollowed &&\n      oldItem.isWatchlist == newItem.isWatchlist &&\n      oldItem.translation == newItem.translation\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/recycler/DiscoverListItem.kt",
    "content": "package com.michaldrabik.ui_discover.recycler\n\nimport com.michaldrabik.ui_base.common.ListItem\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.Translation\n\ndata class DiscoverListItem(\n  override val show: Show,\n  override val image: Image,\n  override var isLoading: Boolean = false,\n  val isFollowed: Boolean = false,\n  val isWatchlist: Boolean = false,\n  val translation: Translation? = null\n) : ListItem\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/views/ShowFanartView.kt",
    "content": "package com.michaldrabik.ui_discover.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_discover.R\nimport com.michaldrabik.ui_discover.databinding.ViewShowFanartBinding\nimport com.michaldrabik.ui_discover.recycler.DiscoverListItem\nimport com.michaldrabik.ui_model.ImageStatus.AVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNAVAILABLE\n\nclass ShowFanartView : ShowView<DiscoverListItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewShowFanartBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    with(binding) {\n      showFanartRoot.onClick { itemClickListener?.invoke(item) }\n      showFanartRoot.onLongClick { itemLongClickListener?.invoke(item) }\n    }\n  }\n\n  override val imageView: ImageView = binding.showFanartImage\n  override val placeholderView: ImageView = binding.showFanartPlaceholder\n\n  private lateinit var item: DiscoverListItem\n\n  override fun bind(item: DiscoverListItem) {\n    super.bind(item)\n    clear()\n    this.item = item\n    with(binding) {\n      showFanartTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.show.title\n        else item.translation?.title\n      showFanartProgress.visibleIf(item.isLoading)\n      showFanartBadge.visibleIf(item.isFollowed)\n      showFanartBadgeLater.visibleIf(item.isWatchlist)\n    }\n    loadImage(item)\n  }\n\n  override fun loadImage(item: DiscoverListItem) {\n    super.loadImage(item)\n    if (item.image.status == UNAVAILABLE) {\n      binding.showFanartRoot.setBackgroundResource(R.drawable.bg_media_view_placeholder)\n    }\n  }\n\n  override fun onImageLoadFail(item: DiscoverListItem) {\n    super.onImageLoadFail(item)\n    if (item.image.status == AVAILABLE) {\n      binding.showFanartRoot.setBackgroundResource(R.drawable.bg_media_view_placeholder)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      showFanartTitle.text = \"\"\n      showFanartProgress.gone()\n      showFanartPlaceholder.gone()\n      showFanartRoot.setBackgroundResource(R.drawable.bg_media_view_elevation)\n      showFanartBadge.gone()\n      Glide.with(this@ShowFanartView).clear(showFanartImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/views/ShowPosterView.kt",
    "content": "package com.michaldrabik.ui_discover.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_discover.R\nimport com.michaldrabik.ui_discover.databinding.ViewShowPosterBinding\nimport com.michaldrabik.ui_discover.recycler.DiscoverListItem\nimport com.michaldrabik.ui_model.ImageStatus.AVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNAVAILABLE\n\nclass ShowPosterView : ShowView<DiscoverListItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewShowPosterBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    with(binding) {\n      showPosterRoot.onClick { itemClickListener?.invoke(item) }\n      showPosterRoot.onLongClick { itemLongClickListener?.invoke(item) }\n    }\n  }\n\n  override val imageView: ImageView = binding.showPosterImage\n  override val placeholderView: ImageView = binding.showPosterPlaceholder\n\n  private lateinit var item: DiscoverListItem\n\n  override fun bind(item: DiscoverListItem) {\n    super.bind(item)\n    clear()\n    this.item = item\n\n    with(binding) {\n      showPosterTitle.text = item.show.title\n      showPosterProgress.visibleIf(item.isLoading)\n      showPosterBadge.visibleIf(item.isFollowed)\n      showPosterLaterBadge.visibleIf(item.isWatchlist)\n    }\n\n    loadImage(item)\n  }\n\n  override fun loadImage(item: DiscoverListItem) {\n    if (item.image.status == UNAVAILABLE) {\n      with(binding) {\n        showPosterTitle.visible()\n        showPosterRoot.setBackgroundResource(R.drawable.bg_media_view_placeholder)\n      }\n    }\n    super.loadImage(item)\n  }\n\n  override fun onImageLoadFail(item: DiscoverListItem) {\n    super.onImageLoadFail(item)\n    if (item.image.status == AVAILABLE) {\n      with(binding) {\n        showPosterTitle.visible()\n        showPosterRoot.setBackgroundResource(R.drawable.bg_media_view_placeholder)\n      }\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      showPosterTitle.text = \"\"\n      showPosterTitle.gone()\n      showPosterRoot.setBackgroundResource(R.drawable.bg_media_view_elevation)\n      showPosterPlaceholder.gone()\n      showPosterProgress.gone()\n      showPosterBadge.gone()\n      Glide.with(this@ShowPosterView).clear(showPosterImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/views/ShowPremiumView.kt",
    "content": "package com.michaldrabik.ui_discover.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.ImageView\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_discover.databinding.ViewShowPremiumBinding\nimport com.michaldrabik.ui_discover.recycler.DiscoverListItem\n\nclass ShowPremiumView : ShowView<DiscoverListItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewShowPremiumBinding.inflate(LayoutInflater.from(context), this, true)\n\n  init {\n    binding.viewShowPremiumRoot.onClick { itemClickListener?.invoke(item) }\n  }\n\n  override val imageView: ImageView = binding.viewShowPremiumImageStub\n  override val placeholderView: ImageView = binding.viewShowPremiumImageStub\n\n  private lateinit var item: DiscoverListItem\n\n  override fun bind(item: DiscoverListItem) {\n    super.bind(item)\n    this.item = item\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/main/java/com/michaldrabik/ui_discover/views/ShowTwitterView.kt",
    "content": "package com.michaldrabik.ui_discover.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.ImageView\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_discover.databinding.ViewShowTwitterBinding\nimport com.michaldrabik.ui_discover.recycler.DiscoverListItem\n\nclass ShowTwitterView : ShowView<DiscoverListItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewShowTwitterBinding.inflate(LayoutInflater.from(context), this)\n\n  var twitterCancelClickListener: (() -> Unit)? = null\n\n  init {\n    with(binding) {\n      viewTwitterRoot.onClick { itemClickListener?.invoke(item) }\n      viewTwitterCancel.onClick { twitterCancelClickListener?.invoke() }\n    }\n  }\n\n  override val imageView: ImageView = binding.viewTwitterLogo\n  override val placeholderView: ImageView = binding.viewTwitterLogo\n\n  private lateinit var item: DiscoverListItem\n\n  override fun bind(item: DiscoverListItem) {\n    super.bind(item)\n    this.item = item\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/main/res/color/selector_filters_button.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"@color/colorGrayLight\" android:state_enabled=\"false\" />\n  <item android:color=\"?attr/colorAccent\" />\n</selector>"
  },
  {
    "path": "ui-discover/src/main/res/drawable/bg_twitter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <solid android:color=\"@color/colorTwitterBlue\" />\n  <corners android:radius=\"@dimen/mediaTileCorner\" />\n</shape>"
  },
  {
    "path": "ui-discover/src/main/res/drawable/bg_twitter_cancel.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"oval\"\n  >\n  <solid android:color=\"@color/colorBlackTranslucentLight\" />\n</shape>"
  },
  {
    "path": "ui-discover/src/main/res/layout/fragment_discover.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  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/discoverRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.swiperefreshlayout.widget.SwipeRefreshLayout\n    android:id=\"@+id/discoverSwipeRefresh\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    >\n\n    <androidx.recyclerview.widget.RecyclerView\n      android:id=\"@+id/discoverRecycler\"\n      style=\"@style/ScrollbarsStyle\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:background=\"@android:color/transparent\"\n      android:clipChildren=\"false\"\n      android:clipToPadding=\"false\"\n      android:overScrollMode=\"never\"\n      android:paddingLeft=\"@dimen/gridPadding\"\n      android:paddingTop=\"@dimen/discoverRecyclerPadding\"\n      android:paddingRight=\"@dimen/gridPadding\"\n      android:paddingBottom=\"@dimen/bottomNavigationHeightPadded\"\n      android:scrollbars=\"vertical\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>\n\n  <com.michaldrabik.ui_base.common.views.ModeTabsView\n    android:id=\"@+id/discoverModeTabsView\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/collectionTabsMargin\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    />\n\n  <com.michaldrabik.ui_discover.filters.views.DiscoverFiltersView\n    android:id=\"@+id/discoverFiltersView\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/collectionFiltersMargin\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:paddingStart=\"@dimen/screenMarginHorizontal\"\n    android:paddingEnd=\"@dimen/screenMarginHorizontal\"\n    android:visibility=\"gone\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.SearchView\n    android:id=\"@+id/discoverSearchView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/searchViewHeight\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "ui-discover/src/main/res/layout/view_discover_filters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <HorizontalScrollView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:overScrollMode=\"never\"\n    android:scrollbars=\"none\"\n    >\n\n    <com.google.android.material.chip.ChipGroup\n      android:id=\"@+id/discoverChips\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:paddingTop=\"@dimen/spaceSmall\"\n      android:paddingBottom=\"@dimen/spaceSmall\"\n      app:singleLine=\"true\"\n      >\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/discoverFeedChip\"\n        style=\"@style/ShowlyChip.Filter\"\n        android:checkable=\"false\"\n        android:text=\"@string/textHot\"\n        />\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/discoverNetworksChip\"\n        style=\"@style/ShowlyChip.Filter\"\n        android:checkable=\"false\"\n        android:text=\"@string/textNetworks\"\n        />\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/discoverGenresChip\"\n        style=\"@style/ShowlyChip.Filter\"\n        android:checkable=\"false\"\n        android:text=\"@string/textGenres\"\n        />\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/discoverCollectionChip\"\n        style=\"@style/ShowlyChip.Filter\"\n        android:text=\"@string/textDiscoverFilterCollection\"\n        />\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/discoverAnticipatedChip\"\n        style=\"@style/ShowlyChip.Filter\"\n        android:checked=\"true\"\n        android:text=\"@string/textDiscoverFilterAnticipated\"\n        />\n\n    </com.google.android.material.chip.ChipGroup>\n\n  </HorizontalScrollView>\n\n</merge>"
  },
  {
    "path": "ui-discover/src/main/res/layout/view_discover_filters_feed.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  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/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_filters_sheet\"\n  android:orientation=\"vertical\"\n  android:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.core.widget.NestedScrollView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\"\n    >\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:clipToPadding=\"false\"\n      android:paddingStart=\"@dimen/spaceNormal\"\n      android:paddingTop=\"@dimen/spaceNormal\"\n      android:paddingEnd=\"@dimen/spaceNormal\"\n      tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n      >\n\n      <TextView\n        android:id=\"@+id/feedTitle\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/textSortOrder\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"18sp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintBottom_toTopOf=\"@id/feedChipGroup\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n      <com.google.android.material.chip.ChipGroup\n        android:id=\"@+id/feedChipGroup\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceSmall\"\n        android:layout_marginBottom=\"@dimen/spaceMicro\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/feedTitle\"\n        app:selectionRequired=\"true\"\n        app:singleSelection=\"true\"\n        >\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/feedChipHot\"\n          style=\"@style/Widget.Material3.Chip.Suggestion\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:checked=\"true\"\n          android:text=\"@string/textHot\"\n          android:textColor=\"@color/selector_discover_chip_text\"\n          app:checkedIconTint=\"@color/selector_discover_chip_text\"\n          app:chipBackgroundColor=\"@color/selector_discover_chip_background\"\n          app:chipCornerRadius=\"100dp\"\n          app:chipStrokeColor=\"@color/selector_discover_chip_text\"\n          app:chipStrokeWidth=\"1dp\"\n          />\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/feedChipTopRated\"\n          style=\"@style/Widget.Material3.Chip.Suggestion\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/textSortRated\"\n          android:textColor=\"@color/selector_discover_chip_text\"\n          app:checkedIconTint=\"@color/selector_discover_chip_text\"\n          app:chipBackgroundColor=\"@color/selector_discover_chip_background\"\n          app:chipCornerRadius=\"100dp\"\n          app:chipStrokeColor=\"@color/selector_discover_chip_text\"\n          app:chipStrokeWidth=\"1dp\"\n          />\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/feedChipRecent\"\n          style=\"@style/Widget.Material3.Chip.Suggestion\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/textSortNewest\"\n          android:textColor=\"@color/selector_discover_chip_text\"\n          app:checkedIconTint=\"@color/selector_discover_chip_text\"\n          app:chipBackgroundColor=\"@color/selector_discover_chip_background\"\n          app:chipCornerRadius=\"100dp\"\n          app:chipStrokeColor=\"@color/selector_discover_chip_text\"\n          app:chipStrokeWidth=\"1dp\"\n          />\n\n      </com.google.android.material.chip.ChipGroup>\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n  </androidx.core.widget.NestedScrollView>\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/applyButton\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:layout_marginBottom=\"@dimen/spaceNormal\"\n    android:backgroundTint=\"?attr/colorAccent\"\n    android:gravity=\"center\"\n    android:text=\"@string/textApply\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-discover/src/main/res/layout/view_discover_filters_genres.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  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/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_filters_sheet\"\n  android:orientation=\"vertical\"\n  android:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.core.widget.NestedScrollView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\"\n    >\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:clipToPadding=\"false\"\n      android:paddingStart=\"@dimen/spaceNormal\"\n      android:paddingTop=\"@dimen/spaceNormal\"\n      android:paddingEnd=\"@dimen/spaceNormal\"\n      tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n      >\n\n      <TextView\n        android:id=\"@+id/genresTitle\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/textGenres\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"18sp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintBottom_toTopOf=\"@id/genresChipGroup\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n      <com.google.android.material.chip.ChipGroup\n        android:id=\"@+id/genresChipGroup\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceTiny\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/genresTitle\"\n        app:lineSpacing=\"10dp\"\n        />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n  </androidx.core.widget.NestedScrollView>\n\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingTop=\"@dimen/spaceMedium\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceNormal\"\n    >\n\n    <ImageView\n      android:id=\"@+id/clearButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center_vertical\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_delete\"\n      app:tint=\"?android:textColorSecondary\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/applyButton\"\n      style=\"@style/RoundMaterialButton\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:backgroundTint=\"?attr/colorAccent\"\n      android:gravity=\"center\"\n      android:text=\"@string/textApply\"\n      android:textColor=\"?attr/textColorOnSurface\"\n      app:rippleColor=\"?android:attr/textColorPrimary\"\n      />\n\n  </LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-discover/src/main/res/layout/view_discover_filters_networks.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  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/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_filters_sheet\"\n  android:orientation=\"vertical\"\n  android:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.core.widget.NestedScrollView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\"\n    >\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:clipToPadding=\"false\"\n      android:paddingStart=\"@dimen/spaceNormal\"\n      android:paddingTop=\"@dimen/spaceNormal\"\n      android:paddingEnd=\"@dimen/spaceNormal\"\n      tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n      >\n\n      <TextView\n        android:id=\"@+id/networksTitle\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/textNetworks\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"18sp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintBottom_toTopOf=\"@+id/networksChipGroup\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n      <com.google.android.material.chip.ChipGroup\n        android:id=\"@+id/networksChipGroup\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceTiny\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/networksTitle\"\n        app:lineSpacing=\"10dp\"\n        app:singleSelection=\"true\"\n        />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n  </androidx.core.widget.NestedScrollView>\n\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingTop=\"@dimen/spaceMedium\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceNormal\"\n    >\n\n    <ImageView\n      android:id=\"@+id/clearButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center_vertical\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_delete\"\n      app:tint=\"?android:textColorSecondary\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/applyButton\"\n      style=\"@style/RoundMaterialButton\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:backgroundTint=\"?attr/colorAccent\"\n      android:gravity=\"center\"\n      android:text=\"@string/textApply\"\n      android:textColor=\"?attr/textColorOnSurface\"\n      app:rippleColor=\"?android:attr/textColorPrimary\"\n      />\n\n  </LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-discover/src/main/res/layout/view_show_fanart.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/showFanartRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_margin=\"@dimen/gridSpacing\"\n    android:background=\"@drawable/bg_media_view_elevation\"\n    android:elevation=\"@dimen/elevationSmall\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/showFanartImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/showFanartPlaceholder\"\n      android:layout_width=\"@dimen/showTilePlaceholder\"\n      android:layout_height=\"@dimen/showTilePlaceholder\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/showFanartTitle\"\n      style=\"@style/ImageTitle\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"bottom|start\"\n      android:layout_margin=\"@dimen/spaceSmall\"\n      android:gravity=\"start\"\n      android:maxLines=\"2\"\n      android:textAlignment=\"viewStart\"\n      android:textSize=\"14sp\"\n      tools:text=\"Game Of Thrones\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/showFanartProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      />\n\n  </FrameLayout>\n\n  <ImageView\n    android:id=\"@+id/showFanartBadge\"\n    style=\"@style/Badge\"\n    app:srcCompat=\"@drawable/ic_bookmark_full\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/showFanartBadgeLater\"\n    style=\"@style/Badge.Watchlist\"\n    app:srcCompat=\"@drawable/ic_bookmark_full\"\n    tools:visibility=\"visible\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-discover/src/main/res/layout/view_show_poster.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/showPosterRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_margin=\"@dimen/gridSpacing\"\n    android:background=\"@drawable/bg_media_view_elevation\"\n    android:elevation=\"@dimen/elevationSmall\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/showPosterImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/showPosterPlaceholder\"\n      android:layout_width=\"@dimen/showTilePlaceholder\"\n      android:layout_height=\"@dimen/showTilePlaceholder\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/showPosterTitle\"\n      style=\"@style/ImageTitle\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"bottom|start\"\n      android:layout_margin=\"@dimen/spaceSmall\"\n      android:gravity=\"start\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textSize=\"12sp\"\n      android:visibility=\"gone\"\n      tools:text=\"Game Of Thrones\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/showPosterProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      />\n\n  </FrameLayout>\n\n  <ImageView\n    android:id=\"@+id/showPosterBadge\"\n    style=\"@style/Badge\"\n    app:srcCompat=\"@drawable/ic_bookmark_full\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/showPosterLaterBadge\"\n    style=\"@style/Badge.Watchlist\"\n    android:elevation=\"@dimen/elevationSmall\"\n    app:srcCompat=\"@drawable/ic_bookmark_full\"\n    tools:visibility=\"visible\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-discover/src/main/res/layout/view_show_premium.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/viewShowPremiumRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  >\n\n  <include layout=\"@layout/view_premium_ad_list\" />\n\n  <ImageView\n    android:id=\"@+id/viewShowPremiumImageStub\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:visibility=\"gone\"\n    />\n\n</FrameLayout>\n"
  },
  {
    "path": "ui-discover/src/main/res/layout/view_show_twitter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:targetApi=\"m\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/viewTwitterRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_margin=\"@dimen/gridSpacing\"\n    android:background=\"@drawable/bg_twitter\"\n    android:elevation=\"@dimen/elevationSmall\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/viewTwitterLogo\"\n      android:layout_width=\"120dp\"\n      android:layout_height=\"120dp\"\n      android:layout_gravity=\"start|center_vertical\"\n      android:layout_marginStart=\"@dimen/elevationNormal\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_twitter\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewTwitterTitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:gravity=\"start\"\n      android:text=\"@string/textTwitterTitle\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"@color/colorWhite\"\n      android:textSize=\"22sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewTwitterDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewTwitterLogo\"\n      app:layout_constraintTop_toTopOf=\"@id/viewTwitterLogo\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewTwitterDescription\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceMicro\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:gravity=\"start\"\n      android:text=\"@string/textTwitterDescription\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"@color/colorWhite\"\n      android:textSize=\"13sp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/viewTwitterLogo\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"@id/viewTwitterTitle\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewTwitterTitle\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewTwitterCancel\"\n      android:layout_width=\"30dp\"\n      android:layout_height=\"30dp\"\n      android:layout_gravity=\"end\"\n      android:layout_margin=\"@dimen/spaceMedium\"\n      android:background=\"@drawable/bg_twitter_cancel\"\n      android:padding=\"5dp\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_close\"\n      app:tint=\"@color/colorWhite\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-discover/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrder\">Feed:</string>\n  <string name=\"textDiscoverFilterAnticipated\">Hide Anticipated Shows</string>\n  <string name=\"textDiscoverFilterCollection\">Hide Collection</string>\n\n  <string name=\"textTwitterTitle\" translatable=\"false\">Showly is on Twitter!</string>\n  <string name=\"textTwitterDescription\" translatable=\"false\">Follow <b>@AppShowly</b> and get the latest news and information about the app.</string>\n</resources>\n"
  },
  {
    "path": "ui-discover/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrder\">ترتيب حسب:</string>\n  <string name=\"textDiscoverFilterAnticipated\">إخفاء المسلسلات المرتقبة</string>\n  <string name=\"textDiscoverFilterCollection\">إخفاء المجموعة</string>\n</resources>\n"
  },
  {
    "path": "ui-discover/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrder\">Feed:</string>\n  <string name=\"textDiscoverFilterAnticipated\">Bald erscheinende Serien ausblenden</string>\n  <string name=\"textDiscoverFilterCollection\">Sammlung ausblenden</string>\n</resources>\n"
  },
  {
    "path": "ui-discover/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrder\">Feed:</string>\n  <string name=\"textDiscoverFilterAnticipated\">Ocultar series anticipadas</string>\n  <string name=\"textDiscoverFilterCollection\">Ocultar Colección</string>\n</resources>\n"
  },
  {
    "path": "ui-discover/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrder\">Syöte:</string>\n  <string name=\"textDiscoverFilterAnticipated\">Piilota odotetut sarjat</string>\n  <string name=\"textDiscoverFilterCollection\">Piilota kokoelma</string>\n</resources>\n"
  },
  {
    "path": "ui-discover/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrder\">Fil :</string>\n  <string name=\"textDiscoverFilterAnticipated\">Cacher les séries attendues</string>\n  <string name=\"textDiscoverFilterCollection\">Cacher la collection</string>\n</resources>\n"
  },
  {
    "path": "ui-discover/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrder\">Riepilogo:</string>\n  <string name=\"textDiscoverFilterAnticipated\">Nascondi gli show attesi</string>\n  <string name=\"textDiscoverFilterCollection\">Nascondi Raccolta</string>\n</resources>\n"
  },
  {
    "path": "ui-discover/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrder\">Strumień:</string>\n  <string name=\"textDiscoverFilterAnticipated\">Ukryj nadchodzące</string>\n  <string name=\"textDiscoverFilterCollection\">Ukryj kolekcję</string>\n</resources>\n"
  },
  {
    "path": "ui-discover/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrder\">Feed:</string>\n  <string name=\"textDiscoverFilterAnticipated\">Ocultar séries aguardadas</string>\n  <string name=\"textDiscoverFilterCollection\">Ocultar assistidos</string>\n</resources>\n"
  },
  {
    "path": "ui-discover/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrder\">Сортировать по:</string>\n  <string name=\"textDiscoverFilterAnticipated\">Скрыть ожидаемые сериалы</string>\n  <string name=\"textDiscoverFilterCollection\">Скрыть коллекцию</string>\n</resources>\n"
  },
  {
    "path": "ui-discover/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrder\">Akış:</string>\n  <string name=\"textDiscoverFilterAnticipated\">Beklenen Dizileri Gizle</string>\n  <string name=\"textDiscoverFilterCollection\">Koleksiyonu Gizle</string>\n</resources>\n"
  },
  {
    "path": "ui-discover/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrder\">Стрічка:</string>\n  <string name=\"textDiscoverFilterAnticipated\">Приховати очікувані серіали</string>\n  <string name=\"textDiscoverFilterCollection\">Приховати колекцію</string>\n</resources>\n"
  },
  {
    "path": "ui-discover/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrder\">Nguồn cấp:</string>\n  <string name=\"textDiscoverFilterAnticipated\">Ẩn các chương trình được mong đợi</string>\n  <string name=\"textDiscoverFilterCollection\">Ẩn bộ sưu tập</string>\n\n  <string name=\"textTwitterTitle\" translatable=\"false\">Showly có trên Twitter!</string>\n  <string name=\"textTwitterDescription\" translatable=\"false\">Theo dõi <b>@AppShowly</b> để nhận tin tức và thông tin mới nhất về ứng dụng.</string>\n</resources>\n"
  },
  {
    "path": "ui-discover/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrder\">信息流：</string>\n  <string name=\"textDiscoverFilterAnticipated\">隐藏备受期待的剧集</string>\n  <string name=\"textDiscoverFilterCollection\">隐藏合集中的项目</string>\n</resources>\n"
  },
  {
    "path": "ui-discover/src/test/java/BaseMockTest.kt",
    "content": "import com.michaldrabik.common_test.MainDispatcherRule\nimport io.mockk.MockKAnnotations\nimport io.mockk.mockkStatic\nimport org.junit.Before\nimport org.junit.Rule\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nabstract class BaseMockTest {\n\n  @get:Rule\n  val mainDispatcherRule = MainDispatcherRule()\n\n  @Before\n  open fun setUp() {\n    MockKAnnotations.init(this)\n    mockkStatic(\"androidx.room.RoomDatabaseKt\")\n  }\n}\n"
  },
  {
    "path": "ui-discover/src/test/java/TestData.kt",
    "content": "import com.michaldrabik.ui_discover.recycler.DiscoverListItem\nimport com.michaldrabik.ui_model.AirTime\nimport com.michaldrabik.ui_model.IdImdb\nimport com.michaldrabik.ui_model.IdSlug\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.IdTvRage\nimport com.michaldrabik.ui_model.IdTvdb\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageFamily\nimport com.michaldrabik.ui_model.ImageSource\nimport com.michaldrabik.ui_model.ImageStatus\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.ShowStatus\n\nobject TestData {\n\n  val DISCOVER_LIST_ITEM = DiscoverListItem(\n    show = Show(\n      ids = Ids(\n        trakt = IdTrakt(id = 0),\n        slug = IdSlug(id = \"\"),\n        tvdb = IdTvdb(id = 0),\n        imdb = IdImdb(id = \"\"),\n        tmdb = IdTmdb(id = 0),\n        tvrage = IdTvRage(id = 0)\n      ),\n      title = \"DISCOVER_LIST_ITEM\",\n      year = 0,\n      overview = \"\",\n      firstAired = \"\",\n      runtime = 0,\n      airTime = AirTime(day = \"\", time = \"\", timezone = \"\"),\n      certification = \"\",\n      network = \"\",\n      country = \"\",\n      trailer = \"\",\n      homepage = \"\",\n      status = ShowStatus.UNKNOWN,\n      rating = 0.0f,\n      votes = 0,\n      commentCount = 0,\n      genres = listOf(),\n      airedEpisodes = 0,\n      createdAt = 0,\n      updatedAt = 0\n    ),\n    image = Image(\n      id = 0,\n      idTvdb = IdTvdb(id = 0),\n      idTmdb = IdTmdb(id = 0),\n      type = ImageType.POSTER,\n      family = ImageFamily.SHOW,\n      fileUrl = \"\",\n      thumbnailUrl = \"\",\n      status = ImageStatus.UNKNOWN,\n      source = ImageSource.TVDB\n    ),\n    isLoading = false, isFollowed = false, isWatchlist = false\n  )\n}\n"
  },
  {
    "path": "ui-discover/src/test/java/com/michaldrabik/ui_discover/DiscoverViewModelTest.kt",
    "content": "package com.michaldrabik.ui_discover\n\nimport BaseMockTest\nimport TestData\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkManager\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_discover.cases.DiscoverFiltersCase\nimport com.michaldrabik.ui_discover.cases.DiscoverShowsCase\nimport com.michaldrabik.ui_discover.cases.DiscoverTwitterCase\nimport com.michaldrabik.ui_model.DiscoverFilters\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.impl.annotations.RelaxedMockK\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.toList\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.advanceUntilIdle\nimport kotlinx.coroutines.test.runTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\nimport java.util.concurrent.TimeUnit\n\n@OptIn(ExperimentalCoroutinesApi::class)\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass DiscoverViewModelTest : BaseMockTest() {\n\n  @MockK internal lateinit var showsCase: DiscoverShowsCase\n  @MockK lateinit var filtersCase: DiscoverFiltersCase\n  @MockK lateinit var twitterCase: DiscoverTwitterCase\n  @MockK lateinit var imagesProvider: ShowImagesProvider\n  @RelaxedMockK lateinit var workManager: WorkManager\n\n  private lateinit var SUT: DiscoverViewModel\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n\n    coEvery { filtersCase.loadFilters() } returns DiscoverFilters()\n    coEvery { showsCase.loadCachedShows(any()) } returns emptyList()\n    coEvery { showsCase.loadRemoteShows(any()) } returns emptyList()\n\n    SUT = DiscoverViewModel(showsCase, filtersCase, twitterCase, imagesProvider, workManager)\n  }\n\n  @After\n  fun tearDown() {\n    SUT.viewModelScope.cancel()\n  }\n\n  @Test\n  fun `Should not pull to refresh data too often`() = runTest {\n    SUT.lastPullToRefreshMs = nowUtcMillis() - TimeUnit.SECONDS.toMillis(5)\n    SUT.loadShows(pullToRefresh = true)\n\n    coVerify(exactly = 0) { showsCase.loadCachedShows(any()) }\n    coVerify(exactly = 0) { showsCase.loadRemoteShows(any()) }\n  }\n\n  @Test\n  fun `Should load cached data and not load remote data if cache is valid`() {\n    coEvery { showsCase.isCacheValid() } returns true\n\n    SUT.loadShows()\n\n    coVerify(exactly = 1) { showsCase.loadCachedShows(any()) }\n    coVerify(exactly = 0) { showsCase.loadRemoteShows(any()) }\n  }\n\n  @Test\n  fun `Should load cached data and load remote data if cache is no longer valid`() {\n    coEvery { showsCase.isCacheValid() } returns false\n\n    SUT.loadShows()\n\n    coVerify(exactly = 1) { showsCase.loadCachedShows(any()) }\n    coVerify(exactly = 1) { showsCase.loadRemoteShows(any()) }\n  }\n\n  @Test\n  fun `Should load remote data only if pull to refresh`() {\n    coEvery { showsCase.isCacheValid() } returns true\n\n    SUT.loadShows(pullToRefresh = true)\n\n    coVerify(exactly = 0) { showsCase.loadCachedShows(any()) }\n    coVerify(exactly = 1) { showsCase.loadRemoteShows(any()) }\n  }\n\n  @Test\n  fun `Should load remote data only if skipping cache`() {\n    coEvery { showsCase.isCacheValid() } returns true\n\n    SUT.loadShows(skipCache = true)\n\n    coVerify(exactly = 0) { showsCase.loadCachedShows(any()) }\n    coVerify(exactly = 1) { showsCase.loadRemoteShows(any()) }\n  }\n\n  @Test\n  fun `Should not load cached data if skipping cache`() {\n    SUT.loadShows(skipCache = true)\n    coVerify(exactly = 0) { showsCase.loadCachedShows(any()) }\n  }\n\n  @Test\n  fun `Should update last PTR stamp if PTR`() = runTest {\n    coEvery { showsCase.isCacheValid() } returns false\n\n    SUT.loadShows(pullToRefresh = true)\n    assertThat(SUT.lastPullToRefreshMs).isGreaterThan(nowUtcMillis() - TimeUnit.MINUTES.toMillis(1))\n  }\n\n  @Test\n  fun `Should not update last PTR stamp if was not PTR`() {\n    coEvery { showsCase.isCacheValid() } returns false\n\n    SUT.loadShows(pullToRefresh = false)\n    assertThat(SUT.lastPullToRefreshMs).isEqualTo(0)\n  }\n\n  @Test\n  fun `Should hide loading state when PTR is run too often`() = runTest {\n    val stateResult = mutableListOf<DiscoverUiState>()\n    val messagesResult = mutableListOf<MessageEvent>()\n\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val job2 = launch(UnconfinedTestDispatcher()) { SUT.messageFlow.toList(messagesResult) }\n\n    SUT.lastPullToRefreshMs = nowUtcMillis() - TimeUnit.SECONDS.toMillis(5)\n    SUT.loadShows(pullToRefresh = true)\n\n    assertThat(stateResult[0].isLoading).isNull()\n    assertThat(stateResult[1].isLoading).isFalse()\n    assertThat(stateResult[2].isLoading).isTrue()\n    assertThat(stateResult[3].isLoading).isFalse()\n    assertThat(messagesResult).isEmpty()\n\n    job.cancel()\n    job2.cancel()\n  }\n\n  @Test\n  fun `Should show loading state instantly if pull to refresh`() = runTest {\n    val stateResult = mutableListOf<DiscoverUiState>()\n    val messagesResult = mutableListOf<MessageEvent>()\n\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val job2 = launch(UnconfinedTestDispatcher()) { SUT.messageFlow.toList(messagesResult) }\n\n    SUT.loadShows(pullToRefresh = true)\n\n    assertThat(stateResult[0].isLoading).isNull()\n    assertThat(stateResult[1].isLoading).isFalse()\n    assertThat(stateResult[2].isLoading).isTrue()\n    assertThat(messagesResult).isEmpty()\n\n    job.cancel()\n    job2.cancel()\n  }\n\n  @Test\n  fun `Should not emit cached results if pull to refresh`() = runTest {\n    val stateResult = mutableListOf<DiscoverUiState>()\n    val messagesResult = mutableListOf<MessageEvent>()\n\n    val job = launch { SUT.uiState.toList(stateResult) }\n    val job2 = launch { SUT.messageFlow.toList(messagesResult) }\n\n    val cachedItem = TestData.DISCOVER_LIST_ITEM\n    coEvery { showsCase.loadCachedShows(any()) } returns listOf(cachedItem)\n\n    SUT.loadShows(pullToRefresh = true)\n\n    stateResult.forEach {\n      assertThat(it.items.isNullOrEmpty()).isTrue()\n    }\n    assertThat(messagesResult).isEmpty()\n\n    job.cancel()\n    job2.cancel()\n  }\n\n  @Test\n  fun `Should not post cached results if skipping cache`() = runTest {\n    val stateResult = mutableListOf<DiscoverUiState>()\n    val messagesResult = mutableListOf<MessageEvent>()\n\n    val job = launch { SUT.uiState.toList(stateResult) }\n    val job2 = launch { SUT.messageFlow.toList(messagesResult) }\n\n    val cachedItem = TestData.DISCOVER_LIST_ITEM\n    coEvery { showsCase.loadCachedShows(any()) } returns listOf(cachedItem)\n\n    SUT.loadShows(skipCache = true)\n\n    stateResult.forEach {\n      assertThat(it.items.isNullOrEmpty()).isTrue()\n    }\n    assertThat(messagesResult).isEmpty()\n\n    job.cancel()\n    job2.cancel()\n  }\n\n  @Test\n  fun `Should post cached results and then fresh remote results`() = runTest {\n    val stateResult = mutableListOf<DiscoverUiState>()\n    val messagesResult = mutableListOf<MessageEvent>()\n\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val job2 = launch(UnconfinedTestDispatcher()) { SUT.messageFlow.toList(messagesResult) }\n\n    val cachedItem = TestData.DISCOVER_LIST_ITEM\n    val remoteItem = cachedItem.copy(isFollowed = true)\n    coEvery { showsCase.loadCachedShows(any()) } returns listOf(cachedItem)\n    coEvery { showsCase.loadRemoteShows(any()) } coAnswers {\n      delay(1000)\n      listOf(remoteItem)\n    }\n    coEvery { showsCase.isCacheValid() } returns false\n\n    SUT.loadShows()\n    advanceUntilIdle()\n\n    assertThat(stateResult.any { it.items?.contains(cachedItem) == true }).isTrue()\n    assertThat(stateResult.last().items?.contains(remoteItem)).isTrue()\n    assertThat(messagesResult).isEmpty()\n\n    job.cancel()\n    job2.cancel()\n  }\n\n  @Test\n  fun `Should post error message on error`() = runTest {\n    val stateResult = mutableListOf<DiscoverUiState>()\n    val messagesResult = mutableListOf<MessageEvent>()\n\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val job2 = launch(UnconfinedTestDispatcher()) { SUT.messageFlow.toList(messagesResult) }\n\n    coEvery { showsCase.loadCachedShows(any()) } throws Error()\n\n    SUT.loadShows()\n\n    assertThat(messagesResult.last().consume()).isEqualTo(com.michaldrabik.ui_base.R.string.errorCouldNotLoadDiscover)\n\n    job.cancel()\n    job2.cancel()\n  }\n}\n"
  },
  {
    "path": "ui-discover-movies/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-discover-movies/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_discover_movies'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-local')\n  implementation project(':ui-base')\n  implementation project(':repository')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest />\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/DiscoverMoviesFragment.kt",
    "content": "package com.michaldrabik.ui_discover_movies\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.View.VISIBLE\nimport android.view.ViewGroup\nimport androidx.activity.addCallback\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateMargins\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.clearFragmentResultListener\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.OnTabReselectedListener\nimport com.michaldrabik.ui_base.common.sheets.context_menu.ContextMenuBottomSheet\nimport com.michaldrabik.ui_base.utilities.extensions.add\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.disableUi\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.enableUi\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.withSpanSizeLookup\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_discover_movies.databinding.FragmentDiscoverMoviesBinding\nimport com.michaldrabik.ui_discover_movies.helpers.DiscoverMoviesLayoutManagerProvider\nimport com.michaldrabik.ui_discover_movies.recycler.DiscoverMovieListItem\nimport com.michaldrabik.ui_discover_movies.recycler.DiscoverMoviesAdapter\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlin.random.Random\n\n@AndroidEntryPoint\ninternal class DiscoverMoviesFragment :\n  BaseFragment<DiscoverMoviesViewModel>(R.layout.fragment_discover_movies),\n  OnTabReselectedListener {\n\n  companion object {\n    const val REQUEST_DISCOVER_FILTERS = \"REQUEST_DISCOVER_FILTERS\"\n  }\n\n  private val binding by viewBinding(FragmentDiscoverMoviesBinding::bind)\n\n  override val viewModel by viewModels<DiscoverMoviesViewModel>()\n  override val navigationId = R.id.discoverMoviesFragment\n\n  private val swipeRefreshStartOffset by lazy { requireContext().dimenToPx(R.dimen.swipeRefreshStartOffset) }\n  private val swipeRefreshEndOffset by lazy { requireContext().dimenToPx(R.dimen.swipeRefreshEndOffset) }\n\n  private var adapter: DiscoverMoviesAdapter? = null\n  private var layoutManager: GridLayoutManager? = null\n\n  private var searchViewPosition = 0F\n  private var tabsViewPosition = 0F\n  private var filtersViewPosition = 0F\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    savedInstanceState?.let {\n      searchViewPosition = it.getFloat(\"ARG_SEARCH_POS\", 0F)\n      tabsViewPosition = it.getFloat(\"ARG_TABS_POS\", 0F)\n      filtersViewPosition = it.getFloat(\"ARG_FILTERS_POS\", 0F)\n    }\n  }\n\n  override fun onSaveInstanceState(outState: Bundle) {\n    super.onSaveInstanceState(outState)\n    outState.putFloat(\"ARG_SEARCH_POS\", searchViewPosition)\n    outState.putFloat(\"ARG_TABS_POS\", tabsViewPosition)\n    outState.putFloat(\"ARG_FILTERS_POS\", filtersViewPosition)\n  }\n\n  override fun onResume() {\n    super.onResume()\n    showNavigation()\n  }\n\n  override fun onPause() {\n    enableUi()\n    with(binding) {\n      searchViewPosition = discoverMoviesSearchView.translationY\n      tabsViewPosition = discoverMoviesTabsView.translationY\n      filtersViewPosition = discoverMoviesFiltersView.translationY\n    }\n    super.onPause()\n  }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupStatusBar()\n    setupRecycler()\n    setupSwipeRefresh()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.messageFlow.collect { showSnack(it) } },\n      doAfterLaunch = { viewModel.loadMovies() }\n    )\n\n    setFragmentResultListener(REQUEST_DISCOVER_FILTERS) { _, _ ->\n      viewModel.loadMovies(resetScroll = true, skipCache = true, instantProgress = true)\n    }\n  }\n\n  private fun setupView() {\n    with(binding) {\n      discoverMoviesSearchView.run {\n        translationY = searchViewPosition\n        settingsIconVisible = true\n        isEnabled = false\n        onClick { openSearch() }\n        onSettingsClickListener = {\n          hideNavigation()\n          navigateToSafe(R.id.actionDiscoverMoviesFragmentToSettingsFragment)\n        }\n      }\n      discoverMoviesTabsView.run {\n        translationY = tabsViewPosition\n        onModeSelected = { mode = it }\n        selectMovies()\n      }\n      discoverMoviesFiltersView.run {\n        translationY = filtersViewPosition\n        onGenresChipClick = { navigateToSafe(R.id.actionDiscoverMoviesFragmentToFiltersGenres) }\n        onFeedChipClick = { navigateToSafe(R.id.actionDiscoverMoviesFragmentToFiltersFeed) }\n        onHideAnticipatedChipClick = { viewModel.toggleAnticipated() }\n        onHideCollectionChipClick = { viewModel.toggleCollection() }\n      }\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      discoverMoviesRoot.doOnApplyWindowInsets { _, insets, _, _ ->\n        val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n        val statusBarSize = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n        discoverMoviesRecycler\n          .updatePadding(top = statusBarSize + dimenToPx(R.dimen.discoverRecyclerPadding))\n        (discoverMoviesSearchView.layoutParams as ViewGroup.MarginLayoutParams)\n          .updateMargins(top = statusBarSize + dimenToPx(R.dimen.spaceMedium))\n        (discoverMoviesTabsView.layoutParams as ViewGroup.MarginLayoutParams)\n          .updateMargins(top = statusBarSize + dimenToPx(R.dimen.collectionTabsMargin))\n        (discoverMoviesFiltersView.layoutParams as ViewGroup.MarginLayoutParams)\n          .updateMargins(top = statusBarSize + dimenToPx(R.dimen.collectionFiltersMargin))\n        discoverMoviesSwipeRefresh.setProgressViewOffset(\n          true,\n          swipeRefreshStartOffset + statusBarSize,\n          swipeRefreshEndOffset\n        )\n      }\n    }\n  }\n\n  private fun setupRecycler() {\n    layoutManager = DiscoverMoviesLayoutManagerProvider.provideLayoutManager(requireContext())\n    adapter = DiscoverMoviesAdapter(\n      itemClickListener = {\n        when (it.image.type) {\n          ImageType.PREMIUM -> openPremium()\n          else -> openDetails(it)\n        }\n      },\n      itemLongClickListener = { openMovieMenu(it.movie) },\n      missingImageListener = { ids, force -> viewModel.loadMissingImage(ids, force) },\n      listChangeListener = { binding.discoverMoviesRecycler.scrollToPosition(0) }\n    )\n    binding.discoverMoviesRecycler.apply {\n      adapter = this@DiscoverMoviesFragment.adapter\n      layoutManager = this@DiscoverMoviesFragment.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      setHasFixedSize(true)\n    }\n  }\n\n  private fun setupSwipeRefresh() {\n    binding.discoverMoviesSwipeRefresh.apply {\n      val color = requireContext().colorFromAttr(R.attr.colorAccent)\n      setProgressBackgroundColorSchemeColor(requireContext().colorFromAttr(R.attr.colorSearchViewBackground))\n      setColorSchemeColors(color, color, color)\n      setOnRefreshListener {\n        searchViewPosition = 0F\n        tabsViewPosition = 0F\n        viewModel.loadMovies(pullToRefresh = true)\n      }\n    }\n  }\n\n  override fun setupBackPressed() {\n    val dispatcher = requireActivity().onBackPressedDispatcher\n    dispatcher.addCallback(viewLifecycleOwner) {\n      isEnabled = false\n      activity?.onBackPressed()\n    }\n  }\n\n  private fun openSearch() {\n    disableUi()\n    hideNavigation()\n    with(binding) {\n      discoverMoviesTabsView.fadeOut(duration = 200).add(animations)\n      discoverMoviesFiltersView.fadeOut(duration = 200).add(animations)\n      discoverMoviesRecycler.fadeOut(duration = 200) {\n        navigateToSafe(R.id.actionDiscoverMoviesFragmentToSearchFragment)\n      }.add(animations)\n    }\n  }\n\n  private fun openDetails(item: DiscoverMovieListItem) {\n    if (!binding.discoverMoviesRecycler.isEnabled) return\n    disableUi()\n    hideNavigation()\n    animateItemsExit(item)\n  }\n\n  private fun openMovieMenu(movie: Movie) {\n    if (!binding.discoverMoviesRecycler.isEnabled) return\n    setFragmentResultListener(NavigationArgs.REQUEST_ITEM_MENU) { requestKey, _ ->\n      if (requestKey == NavigationArgs.REQUEST_ITEM_MENU) {\n        viewModel.loadMovies()\n      }\n      clearFragmentResultListener(NavigationArgs.REQUEST_ITEM_MENU)\n    }\n    val bundle = ContextMenuBottomSheet.createBundle(movie.ids.trakt)\n    navigateToSafe(R.id.actionDiscoverMoviesFragmentToItemMenu, bundle)\n  }\n\n  private fun openPremium() {\n    if (!binding.discoverMoviesRecycler.isEnabled) return\n    disableUi()\n    hideNavigation()\n    navigateToSafe(R.id.actionDiscoverMoviesFragmentToPremium, Bundle.EMPTY)\n  }\n\n  private fun animateItemsExit(item: DiscoverMovieListItem) {\n    with(binding) {\n      discoverMoviesSearchView.fadeOut().add(animations)\n      discoverMoviesTabsView.fadeOut().add(animations)\n      discoverMoviesFiltersView.fadeOut().add(animations)\n\n      val clickedIndex = adapter?.indexOf(item) ?: 0\n      val itemCount = adapter?.itemCount ?: 0\n      (0..itemCount).forEach {\n        if (it != clickedIndex) {\n          val view = discoverMoviesRecycler.findViewHolderForAdapterPosition(it)\n          view?.let { v ->\n            val randomDelay = Random.nextLong(50, 200)\n            v.itemView.fadeOut(duration = 150, startDelay = randomDelay).add(animations)\n          }\n        }\n      }\n\n      val clickedView = discoverMoviesRecycler.findViewHolderForAdapterPosition(clickedIndex)\n      clickedView?.itemView?.fadeOut(\n        duration = 150, startDelay = 350,\n        endAction = {\n          if (!isResumed) return@fadeOut\n          val bundle = Bundle().apply { putLong(NavigationArgs.ARG_MOVIE_ID, item.movie.traktId) }\n          navigateToSafe(R.id.actionDiscoverMoviesFragmentToMovieDetailsFragment, bundle)\n        }\n      ).add(animations)\n    }\n  }\n\n  private fun render(uiState: DiscoverMoviesUiState) {\n    uiState.run {\n      with(binding) {\n        items?.let {\n          val resetScroll = resetScroll?.consume() == true\n          adapter?.setItems(it, resetScroll)\n          layoutManager?.withSpanSizeLookup { pos ->\n            adapter?.getItems()?.get(pos)?.image?.type?.getSpan(isTablet)!!\n          }\n          discoverMoviesRecycler.fadeIn(200, withHardware = true)\n        }\n        isSyncing?.let {\n          discoverMoviesSearchView.setTraktProgress(it)\n          discoverMoviesSearchView.isEnabled = !it\n        }\n        isLoading?.let {\n          discoverMoviesSwipeRefresh.isRefreshing = it\n          discoverMoviesSearchView.isEnabled = !it\n          discoverMoviesTabsView.isEnabled = !it\n          discoverMoviesFiltersView.isEnabled = !it\n          discoverMoviesRecycler.isEnabled = !it\n        }\n        filters?.let {\n          if (discoverMoviesFiltersView.visibility != VISIBLE) {\n            discoverMoviesFiltersView.visible()\n          }\n          discoverMoviesFiltersView.bind(it)\n        }\n      }\n    }\n  }\n\n  override fun onTabReselected() = openSearch()\n\n  override fun onDestroyView() {\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/DiscoverMoviesUiState.kt",
    "content": "package com.michaldrabik.ui_discover_movies\n\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_discover_movies.recycler.DiscoverMovieListItem\nimport com.michaldrabik.ui_model.DiscoverFilters\n\ndata class DiscoverMoviesUiState(\n  val items: List<DiscoverMovieListItem>? = null,\n  val isLoading: Boolean? = null,\n  val isSyncing: Boolean? = null,\n  var filters: DiscoverFilters? = null,\n  var resetScroll: Event<Boolean>? = null,\n)\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/DiscoverMoviesViewModel.kt",
    "content": "package com.michaldrabik.ui_discover_movies\n\nimport androidx.annotation.VisibleForTesting\nimport androidx.annotation.VisibleForTesting.Companion.PRIVATE\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkInfo\nimport androidx.work.WorkManager\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.ui_base.trakt.TraktSyncWorker\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_discover_movies.cases.DiscoverFiltersCase\nimport com.michaldrabik.ui_discover_movies.cases.DiscoverMoviesCase\nimport com.michaldrabik.ui_discover_movies.recycler.DiscoverMovieListItem\nimport com.michaldrabik.ui_model.DiscoverFilters\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageFamily.MOVIE\nimport com.michaldrabik.ui_model.ImageSource.TMDB\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@HiltViewModel\ninternal class DiscoverMoviesViewModel @Inject constructor(\n  private val moviesCase: DiscoverMoviesCase,\n  private val filtersCase: DiscoverFiltersCase,\n  private val imagesProvider: MovieImagesProvider,\n  workManager: WorkManager,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val itemsState = MutableStateFlow<List<DiscoverMovieListItem>?>(null)\n  private val loadingState = MutableStateFlow(false)\n  private val syncingState = MutableStateFlow(false)\n  private val filtersState = MutableStateFlow<DiscoverFilters?>(null)\n  private val scrollState = MutableStateFlow(Event(false))\n\n  @VisibleForTesting(otherwise = PRIVATE) var lastPullToRefreshMs = 0L\n  private var initialFilters: DiscoverFilters? = null\n\n  init {\n    workManager.getWorkInfosByTagLiveData(TraktSyncWorker.TAG_ID).observeForever { work ->\n      syncingState.value = work.any { it.state == WorkInfo.State.RUNNING }\n    }\n    viewModelScope.launch {\n      initialFilters = filtersCase.loadFilters()\n    }\n  }\n\n  fun loadMovies(\n    pullToRefresh: Boolean = false,\n    resetScroll: Boolean = false,\n    skipCache: Boolean = false,\n    instantProgress: Boolean = false,\n  ) {\n    loadingState.value = true\n\n    if (pullToRefresh && nowUtcMillis() - lastPullToRefreshMs < Config.PULL_TO_REFRESH_COOLDOWN_MS) {\n      loadingState.value = false\n      return\n    }\n\n    loadingState.value = pullToRefresh\n\n    viewModelScope.launch {\n      val progressJob = launch {\n        delay(if (pullToRefresh || instantProgress) 0 else 750)\n        loadingState.value = true\n      }\n\n      try {\n        val filters = filtersCase.loadFilters()\n        filtersState.value = filters\n\n        if (!pullToRefresh && !skipCache) {\n          val movies = moviesCase.loadCachedMovies(filters)\n          itemsState.value = movies\n          scrollState.value = Event(resetScroll)\n        }\n\n        if (pullToRefresh || skipCache || !moviesCase.isCacheValid()) {\n          val movies = moviesCase.loadRemoteMovies(filters)\n          itemsState.value = movies\n          initialFilters = filters\n          scrollState.value = Event(resetScroll)\n        }\n\n        if (pullToRefresh) {\n          lastPullToRefreshMs = nowUtcMillis()\n        }\n      } catch (error: Throwable) {\n        onError(error)\n      } finally {\n        loadingState.value = false\n        progressJob.cancel()\n      }\n    }\n  }\n\n  fun loadMissingImage(item: DiscoverMovieListItem, force: Boolean) {\n\n    fun updateItem(newItem: DiscoverMovieListItem) {\n      val currentItems = uiState.value.items?.toMutableList()\n      currentItems?.findReplace(newItem) { it isSameAs newItem }\n      itemsState.value = currentItems\n      scrollState.value = Event(false)\n    }\n\n    viewModelScope.launch {\n      val loadingJob = launch {\n        delay(750)\n        updateItem(item.copy(isLoading = true))\n      }\n      try {\n        val image = imagesProvider.loadRemoteImage(item.movie, item.image.type, force)\n        updateItem(item.copy(isLoading = false, image = image))\n      } catch (t: Throwable) {\n        updateItem(item.copy(isLoading = false, image = Image.createUnavailable(item.image.type, MOVIE, TMDB)))\n        rethrowCancellation(t)\n      } finally {\n        loadingJob.cancel()\n      }\n    }\n  }\n\n  fun toggleAnticipated() {\n    viewModelScope.launch {\n      filtersCase.toggleAnticipated()\n      loadMovies(resetScroll = true, skipCache = true, instantProgress = true)\n    }\n  }\n\n  fun toggleCollection() {\n    viewModelScope.launch {\n      filtersCase.toggleCollection()\n      loadMovies(resetScroll = true, skipCache = true, instantProgress = true)\n    }\n  }\n\n  private suspend fun onError(error: Throwable) {\n    if (error !is CancellationException) {\n      messageChannel.send(MessageEvent.Error(R.string.errorCouldNotLoadDiscover))\n      Timber.e(error)\n    }\n    rethrowCancellation(error)\n  }\n\n  override fun onCleared() {\n    filtersCase.revertFilters(\n      initialFilters = initialFilters,\n      currentFilters = filtersState.value\n    )\n    super.onCleared()\n  }\n\n  val uiState = combine(\n    itemsState,\n    loadingState,\n    syncingState,\n    filtersState,\n    scrollState\n  ) { s1, s2, s3, s4, s5 ->\n    DiscoverMoviesUiState(\n      items = s1,\n      isLoading = s2,\n      isSyncing = s3,\n      filters = s4,\n      resetScroll = s5\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = DiscoverMoviesUiState()\n  )\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/cases/DiscoverFiltersCase.kt",
    "content": "package com.michaldrabik.ui_discover_movies.cases\n\nimport android.content.Context\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.AppScopeProvider\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_model.DiscoverFilters\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass DiscoverFiltersCase @Inject constructor(\n  @ApplicationContext private val context: Context,\n  private val dispatchers: CoroutineDispatchers,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun loadFilters(): DiscoverFilters =\n    withContext(dispatchers.IO) {\n      val settings = settingsRepository.load()\n      DiscoverFilters(\n        feedOrder = settings.discoverMoviesFilterFeed,\n        hideAnticipated = !settings.showAnticipatedMovies,\n        hideCollection = !settings.showCollectionMovies,\n        genres = settings.discoverMoviesFilterGenres.toList()\n      )\n    }\n\n  suspend fun toggleAnticipated() {\n    withContext(dispatchers.IO) {\n      val settings = settingsRepository.load()\n      settingsRepository.update(\n        settings.copy(showAnticipatedMovies = !settings.showAnticipatedMovies)\n      )\n    }\n  }\n\n  suspend fun toggleCollection() {\n    withContext(dispatchers.IO) {\n      val settings = settingsRepository.load()\n      settingsRepository.update(\n        settings.copy(showCollectionMovies = !settings.showCollectionMovies)\n      )\n    }\n  }\n\n  fun revertFilters(\n    initialFilters: DiscoverFilters?,\n    currentFilters: DiscoverFilters?,\n  ) {\n    (context as AppScopeProvider).appScope.launch {\n      try {\n        if (initialFilters != currentFilters) {\n          initialFilters?.let { initial ->\n            val settings = settingsRepository.load()\n            settingsRepository.update(\n              settings.copy(\n                discoverMoviesFilterFeed = initial.feedOrder,\n                discoverMoviesFilterGenres = initial.genres,\n                showAnticipatedMovies = !initial.hideAnticipated,\n                showCollectionMovies = !initial.hideCollection\n              )\n            )\n          }\n        }\n      } catch (error: Throwable) {\n        rethrowCancellation(error)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/cases/DiscoverMoviesCase.kt",
    "content": "package com.michaldrabik.ui_discover_movies.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.ConfigVariant\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_discover_movies.helpers.itemtype.ImageTypeProvider\nimport com.michaldrabik.ui_discover_movies.recycler.DiscoverMovieListItem\nimport com.michaldrabik.ui_model.DiscoverFilters\nimport com.michaldrabik.ui_model.DiscoverSortOrder\nimport com.michaldrabik.ui_model.DiscoverSortOrder.HOT\nimport com.michaldrabik.ui_model.DiscoverSortOrder.NEWEST\nimport com.michaldrabik.ui_model.DiscoverSortOrder.RATING\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport com.michaldrabik.ui_model.ImageType.PREMIUM\nimport com.michaldrabik.ui_model.Movie\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\ninternal class DiscoverMoviesCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val moviesRepository: MoviesRepository,\n  private val imagesProvider: MovieImagesProvider,\n  private val imageTypeProvider: ImageTypeProvider,\n  private val translationsRepository: TranslationsRepository,\n  private val settingsRepository: SettingsRepository\n) {\n\n  suspend fun isCacheValid() = withContext(dispatchers.IO) {\n    moviesRepository.discoverMovies.isCacheValid()\n  }\n\n  suspend fun loadCachedMovies(filters: DiscoverFilters) = withContext(dispatchers.IO) {\n    val myIds = async { moviesRepository.myMovies.loadAllIds() }\n    val watchlistIds = async { moviesRepository.watchlistMovies.loadAllIds() }\n    val hiddenIds = async { moviesRepository.hiddenMovies.loadAllIds() }\n    val cachedMovies = async { moviesRepository.discoverMovies.loadAllCached() }\n    val language = translationsRepository.getLanguage()\n\n    prepareItems(\n      cachedMovies.await(),\n      myIds.await(),\n      watchlistIds.await(),\n      hiddenIds.await(),\n      filters,\n      language\n    )\n  }\n\n  suspend fun loadRemoteMovies(filters: DiscoverFilters) = withContext(dispatchers.IO) {\n    val showAnticipated = !filters.hideAnticipated\n    val showCollection = !filters.hideCollection\n    val genres = filters.genres.toList()\n\n    val myAsync = async { moviesRepository.myMovies.loadAllIds() }\n    val watchlistSync = async { moviesRepository.watchlistMovies.loadAllIds() }\n    val hiddenAsync = async { moviesRepository.hiddenMovies.loadAllIds() }\n    val (myIds, watchlistIds, hiddenIds) = awaitAll(myAsync, watchlistSync, hiddenAsync)\n    val collectionSize = myIds.size + watchlistIds.size + hiddenIds.size\n\n    val remoteMovies = moviesRepository.discoverMovies.loadAllRemote(showAnticipated, showCollection, collectionSize, genres)\n    val language = translationsRepository.getLanguage()\n\n    moviesRepository.discoverMovies.cacheDiscoverMovies(remoteMovies)\n    prepareItems(remoteMovies, myIds, watchlistIds, hiddenIds, filters, language)\n  }\n\n  private suspend fun prepareItems(\n    movies: List<Movie>,\n    myMoviesIds: List<Long>,\n    watchlistMoviesIds: List<Long>,\n    hiddenMoviesIds: List<Long>,\n    filters: DiscoverFilters?,\n    language: String\n  ) = coroutineScope {\n    val collectionIds = myMoviesIds + watchlistMoviesIds\n    movies\n      .filter { !hiddenMoviesIds.contains(it.traktId) }\n      .filter {\n        if (filters?.hideCollection == false) true\n        else !collectionIds.contains(it.traktId)\n      }\n      .sortedBy(filters?.feedOrder ?: HOT)\n      .mapIndexed { index, movie ->\n        async {\n          val itemType = imageTypeProvider.getImageType(index)\n          val image = imagesProvider.findCachedImage(movie, itemType)\n          val translation = loadTranslation(language, itemType, movie)\n          DiscoverMovieListItem(\n            movie,\n            image,\n            isCollected = movie.ids.trakt.id in myMoviesIds,\n            isWatchlist = movie.ids.trakt.id in watchlistMoviesIds,\n            translation = translation\n          )\n        }\n      }\n      .awaitAll()\n      .toMutableList()\n      //.apply { insertPremiumAdItem(this) }\n      .toList()\n  }\n\n  private fun insertPremiumAdItem(items: MutableList<DiscoverMovieListItem>) {\n    val isPremium = settingsRepository.isPremium\n    val isTimePassed = (nowUtcMillis() - settingsRepository.installTimestamp) > ConfigVariant.PREMIUM_AD_DELAY\n    if (isPremium || !isTimePassed) return\n\n    val premiumAd = DiscoverMovieListItem(Movie.EMPTY, Image.createUnknown(PREMIUM))\n    if (items.size >= imageTypeProvider.premiumAdPosition) {\n      items.add(imageTypeProvider.premiumAdPosition, premiumAd)\n    } else if (items.isNotEmpty()) {\n      items.add(premiumAd)\n    }\n  }\n\n  private suspend fun loadTranslation(language: String, itemType: ImageType, movie: Movie) =\n    if (language == Config.DEFAULT_LANGUAGE || itemType == POSTER) null\n    else translationsRepository.loadTranslation(movie, language, true)\n\n  private fun List<Movie>.sortedBy(order: DiscoverSortOrder) = when (order) {\n    HOT -> this\n    RATING -> this.sortedWith(compareByDescending<Movie> { it.votes }.thenBy { it.rating })\n    NEWEST -> this.sortedWith(compareByDescending<Movie> { it.year }.thenByDescending { it.released })\n  }\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/di/DiscoverMoviesModule.kt",
    "content": "package com.michaldrabik.ui_discover_movies.di\n\nimport android.content.Context\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_discover_movies.helpers.itemtype.ImageTypeProvider\nimport com.michaldrabik.ui_discover_movies.helpers.itemtype.PhoneImageTypeProvider\nimport com.michaldrabik.ui_discover_movies.helpers.itemtype.TabletImageTypeProvider\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.components.SingletonComponent\n\n@Module\n@InstallIn(SingletonComponent::class)\nclass DiscoverMoviesModule {\n\n  @Provides\n  internal fun providesItemTypeProvider(@ApplicationContext context: Context): ImageTypeProvider {\n    return if (context.isTablet()) {\n      TabletImageTypeProvider()\n    } else {\n      PhoneImageTypeProvider()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/filters/feed/DiscoverMoviesFiltersFeedBottomSheet.kt",
    "content": "package com.michaldrabik.ui_discover_movies.filters.feed\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetDialog\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenHeight\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_discover_movies.DiscoverMoviesFragment.Companion.REQUEST_DISCOVER_FILTERS\nimport com.michaldrabik.ui_discover_movies.R\nimport com.michaldrabik.ui_discover_movies.databinding.ViewDiscoverMoviesFiltersFeedBinding\nimport com.michaldrabik.ui_discover_movies.filters.feed.DiscoverMoviesFiltersFeedUiEvent.ApplyFilters\nimport com.michaldrabik.ui_discover_movies.filters.feed.DiscoverMoviesFiltersFeedUiEvent.CloseFilters\nimport com.michaldrabik.ui_model.DiscoverSortOrder\nimport com.michaldrabik.ui_model.DiscoverSortOrder.HOT\nimport com.michaldrabik.ui_model.DiscoverSortOrder.NEWEST\nimport com.michaldrabik.ui_model.DiscoverSortOrder.RATING\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\ninternal class DiscoverMoviesFiltersFeedBottomSheet : BaseBottomSheetFragment(R.layout.view_discover_movies_filters_feed) {\n\n  private val viewModel by viewModels<DiscoverMoviesFiltersFeedViewModel>()\n  private val binding by viewBinding(ViewDiscoverMoviesFiltersFeedBinding::bind)\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } }\n    )\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun setupView() {\n    val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior\n    behavior.skipCollapsed = true\n    behavior.maxHeight = (screenHeight() * 0.9).toInt()\n\n    with(binding) {\n      applyButton.onClick { saveFeedOrder() }\n    }\n  }\n\n  private fun saveFeedOrder() {\n    with(binding) {\n      val feedOrder = when {\n        feedChipHot.isChecked -> HOT\n        feedChipTopRated.isChecked -> RATING\n        feedChipRecent.isChecked -> NEWEST\n        else -> throw IllegalStateException()\n      }\n      viewModel.saveFeedOrder(feedOrder)\n    }\n  }\n\n  private fun render(uiState: DiscoverMoviesFiltersFeedUiState) {\n    with(uiState) {\n      feedOrder?.let { renderFilters(it) }\n    }\n  }\n\n  private fun renderFilters(feedOrder: DiscoverSortOrder) {\n    with(binding) {\n      feedChipHot.isChecked = feedOrder == HOT\n      feedChipTopRated.isChecked = feedOrder == RATING\n      feedChipRecent.isChecked = feedOrder == NEWEST\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is ApplyFilters -> {\n        setFragmentResult(REQUEST_DISCOVER_FILTERS, Bundle.EMPTY)\n        closeSheet()\n      }\n      is CloseFilters -> closeSheet()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/filters/feed/DiscoverMoviesFiltersFeedUiEvent.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_discover_movies.filters.feed\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\ninternal sealed class DiscoverMoviesFiltersFeedUiEvent<T>(action: T) : Event<T>(action) {\n  object ApplyFilters : DiscoverMoviesFiltersFeedUiEvent<Unit>(Unit)\n  object CloseFilters : DiscoverMoviesFiltersFeedUiEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/filters/feed/DiscoverMoviesFiltersFeedUiState.kt",
    "content": "package com.michaldrabik.ui_discover_movies.filters.feed\n\nimport com.michaldrabik.ui_model.DiscoverSortOrder\n\ninternal data class DiscoverMoviesFiltersFeedUiState(\n  val feedOrder: DiscoverSortOrder? = null,\n  val isLoading: Boolean? = null,\n)\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/filters/feed/DiscoverMoviesFiltersFeedViewModel.kt",
    "content": "package com.michaldrabik.ui_discover_movies.filters.feed\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_discover_movies.filters.feed.DiscoverMoviesFiltersFeedUiEvent.ApplyFilters\nimport com.michaldrabik.ui_discover_movies.filters.feed.DiscoverMoviesFiltersFeedUiEvent.CloseFilters\nimport com.michaldrabik.ui_model.DiscoverSortOrder\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\ninternal class DiscoverMoviesFiltersFeedViewModel @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val feedOrderState = MutableStateFlow<DiscoverSortOrder?>(null)\n  private val loadingState = MutableStateFlow(false)\n\n  init {\n    loadFilters()\n  }\n\n  private fun loadFilters() {\n    viewModelScope.launch {\n      val settings = settingsRepository.load()\n      feedOrderState.value = settings.discoverMoviesFilterFeed\n    }\n  }\n\n  fun saveFeedOrder(feedOrder: DiscoverSortOrder) {\n    viewModelScope.launch {\n      if (feedOrder == feedOrderState.value) {\n        eventChannel.send(CloseFilters)\n        return@launch\n      }\n      val settings = settingsRepository.load()\n      settingsRepository.update(\n        settings.copy(discoverMoviesFilterFeed = feedOrder)\n      )\n      eventChannel.send(ApplyFilters)\n    }\n  }\n\n  val uiState = combine(\n    feedOrderState,\n    loadingState,\n  ) { s1, s2 ->\n    DiscoverMoviesFiltersFeedUiState(\n      feedOrder = s1,\n      isLoading = s2,\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = DiscoverMoviesFiltersFeedUiState()\n  )\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/filters/genres/DiscoverMoviesFiltersGenresBottomSheet.kt",
    "content": "package com.michaldrabik.ui_discover_movies.filters.genres\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.forEach\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetDialog\nimport com.google.android.material.chip.Chip\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenHeight\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_discover_movies.DiscoverMoviesFragment.Companion.REQUEST_DISCOVER_FILTERS\nimport com.michaldrabik.ui_discover_movies.R\nimport com.michaldrabik.ui_discover_movies.databinding.ViewDiscoverMoviesFiltersGenresBinding\nimport com.michaldrabik.ui_discover_movies.filters.genres.DiscoverMoviesFiltersGenresUiEvent.ApplyFilters\nimport com.michaldrabik.ui_discover_movies.filters.genres.DiscoverMoviesFiltersGenresUiEvent.CloseFilters\nimport com.michaldrabik.ui_model.Genre\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\ninternal class DiscoverMoviesFiltersGenresBottomSheet : BaseBottomSheetFragment(R.layout.view_discover_movies_filters_genres) {\n\n  private val viewModel by viewModels<DiscoverMoviesFiltersGenresViewModel>()\n  private val binding by viewBinding(ViewDiscoverMoviesFiltersGenresBinding::bind)\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } }\n    )\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun setupView() {\n    val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior\n    behavior.skipCollapsed = true\n    behavior.maxHeight = (screenHeight() * 0.9).toInt()\n\n    with(binding) {\n      applyButton.onClick { saveGenres() }\n      clearButton.onClick { renderGenres(emptyList()) }\n    }\n  }\n\n  private fun saveGenres() {\n    with(binding) {\n      val genres = mutableListOf<Genre>().apply {\n        genresChipGroup.forEach { chip ->\n          if ((chip as Chip).isChecked) {\n            add(Genre.valueOf(chip.tag.toString()))\n          }\n        }\n      }\n      viewModel.saveGenres(genres)\n    }\n  }\n\n  private fun render(uiState: DiscoverMoviesFiltersGenresUiState) {\n    with(uiState) {\n      genres?.let { renderGenres(it) }\n    }\n  }\n\n  private fun renderGenres(genres: List<Genre>) {\n    binding.genresChipGroup.removeAllViews()\n    binding.clearButton.visibleIf(genres.isNotEmpty())\n\n    val genresNames = genres.map { it.name }\n    Genre.values()\n      .sortedBy { requireContext().getString(it.displayName) }\n      .forEach { genre ->\n        val chip = Chip(requireContext()).apply {\n          tag = genre.name\n          text = requireContext().getString(genre.displayName)\n          isCheckable = true\n          isCheckedIconVisible = false\n          setEnsureMinTouchTargetSize(false)\n          shapeAppearanceModel = shapeAppearanceModel.toBuilder()\n            .setAllCornerSizes(100f)\n            .build()\n          chipBackgroundColor = ContextCompat.getColorStateList(context, R.color.selector_discover_chip_background)\n          setChipStrokeColorResource(R.color.selector_discover_chip_text)\n          setChipStrokeWidthResource(R.dimen.discoverFilterChipStroke)\n          setTextColor(ContextCompat.getColorStateList(context, R.color.selector_discover_chip_text))\n          isChecked = genre.name in genresNames\n        }\n        binding.genresChipGroup.addView(chip)\n      }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is ApplyFilters -> {\n        setFragmentResult(REQUEST_DISCOVER_FILTERS, Bundle.EMPTY)\n        closeSheet()\n      }\n      is CloseFilters -> closeSheet()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/filters/genres/DiscoverMoviesFiltersGenresUiEvent.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_discover_movies.filters.genres\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\ninternal sealed class DiscoverMoviesFiltersGenresUiEvent<T>(action: T) : Event<T>(action) {\n  object ApplyFilters : DiscoverMoviesFiltersGenresUiEvent<Unit>(Unit)\n  object CloseFilters : DiscoverMoviesFiltersGenresUiEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/filters/genres/DiscoverMoviesFiltersGenresUiState.kt",
    "content": "package com.michaldrabik.ui_discover_movies.filters.genres\n\nimport com.michaldrabik.ui_model.Genre\n\ninternal data class DiscoverMoviesFiltersGenresUiState(\n  val genres: List<Genre>? = null,\n  val isLoading: Boolean? = null,\n)\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/filters/genres/DiscoverMoviesFiltersGenresViewModel.kt",
    "content": "package com.michaldrabik.ui_discover_movies.filters.genres\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_discover_movies.filters.genres.DiscoverMoviesFiltersGenresUiEvent.ApplyFilters\nimport com.michaldrabik.ui_discover_movies.filters.genres.DiscoverMoviesFiltersGenresUiEvent.CloseFilters\nimport com.michaldrabik.ui_model.Genre\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\ninternal class DiscoverMoviesFiltersGenresViewModel @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val genresState = MutableStateFlow<List<Genre>?>(null)\n  private val loadingState = MutableStateFlow(false)\n\n  init {\n    loadGenres()\n  }\n\n  private fun loadGenres() {\n    viewModelScope.launch {\n      val settings = settingsRepository.load()\n      genresState.value = settings.discoverMoviesFilterGenres.toList()\n    }\n  }\n\n  fun saveGenres(genres: List<Genre>) {\n    viewModelScope.launch {\n      if (genres == genresState.value) {\n        eventChannel.send(CloseFilters)\n        return@launch\n      }\n      val settings = settingsRepository.load()\n      settingsRepository.update(\n        settings.copy(\n          discoverMoviesFilterGenres = genres.toList(),\n        )\n      )\n      eventChannel.send(ApplyFilters)\n    }\n  }\n\n  val uiState = combine(\n    genresState,\n    loadingState,\n  ) { s1, s2 ->\n    DiscoverMoviesFiltersGenresUiState(\n      genres = s1,\n      isLoading = s2,\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = DiscoverMoviesFiltersGenresUiState()\n  )\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/filters/views/DiscoverMoviesFiltersView.kt",
    "content": "package com.michaldrabik.ui_discover_movies.filters.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport androidx.core.view.children\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_discover_movies.R\nimport com.michaldrabik.ui_discover_movies.databinding.ViewDiscoverMoviesFiltersBinding\nimport com.michaldrabik.ui_model.DiscoverFilters\nimport com.michaldrabik.ui_model.DiscoverSortOrder\nimport com.michaldrabik.ui_model.DiscoverSortOrder.HOT\nimport com.michaldrabik.ui_model.DiscoverSortOrder.NEWEST\nimport com.michaldrabik.ui_model.DiscoverSortOrder.RATING\nimport com.michaldrabik.ui_model.Genre\n\nclass DiscoverMoviesFiltersView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewDiscoverMoviesFiltersBinding.inflate(LayoutInflater.from(context), this)\n\n  var onFeedChipClick: (() -> Unit)? = null\n  var onGenresChipClick: (() -> Unit)? = null\n  var onHideCollectionChipClick: (() -> Unit)? = null\n  var onHideAnticipatedChipClick: (() -> Unit)? = null\n\n  private lateinit var filters: DiscoverFilters\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    with(binding) {\n      discoverMoviesGenresChip.text = discoverMoviesGenresChip.text.toString().filter { it.isLetter() }\n      discoverMoviesGenresChip.onClick { onGenresChipClick?.invoke() }\n      discoverMoviesFeedChip.isSelected = true\n      discoverMoviesFeedChip.onClick { onFeedChipClick?.invoke() }\n      discoverMoviesCollectionChip.onClick { onHideCollectionChipClick?.invoke() }\n      discoverMoviesAnticipatedChip.onClick { onHideAnticipatedChipClick?.invoke() }\n    }\n  }\n\n  fun bind(filters: DiscoverFilters) {\n    this.filters = filters\n    bindFeed(filters.feedOrder)\n    bindGenres(filters.genres)\n    with(binding) {\n      discoverMoviesCollectionChip.isChecked = filters.hideCollection\n      discoverMoviesAnticipatedChip.isChecked = filters.hideAnticipated\n    }\n  }\n\n  private fun bindFeed(feed: DiscoverSortOrder) {\n    with(binding) {\n      discoverMoviesFeedChip.text = when (feed) {\n        HOT -> context.getString(R.string.textHot)\n        RATING -> context.getString(R.string.textSortRated)\n        NEWEST -> context.getString(R.string.textSortNewest)\n      }\n    }\n  }\n\n  private fun bindGenres(genres: List<Genre>) {\n    with(binding) {\n      discoverMoviesGenresChip.isSelected = genres.isNotEmpty()\n      discoverMoviesGenresChip.text = when {\n        genres.isEmpty() -> context.getString(R.string.textGenres).filter { it.isLetter() }\n        genres.size == 1 -> context.getString(genres.first().displayName)\n        genres.size == 2 -> \"${context.getString(genres[0].displayName)}, ${context.getString(genres[1].displayName)}\"\n        else -> \"${context.getString(genres[0].displayName)}, ${context.getString(genres[1].displayName)} + ${genres.size - 2}\"\n      }\n    }\n  }\n\n  override fun setEnabled(enabled: Boolean) {\n    binding.discoverMoviesChips.children.forEach { it.isEnabled = enabled }\n  }\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/helpers/DiscoverMoviesLayoutManagerProvider.kt",
    "content": "package com.michaldrabik.ui_discover_movies.helpers\n\nimport android.content.Context\nimport androidx.recyclerview.widget.GridLayoutManager\nimport com.michaldrabik.common.Config.MAIN_GRID_SPAN\nimport com.michaldrabik.common.Config.MAIN_GRID_SPAN_TABLET\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\n\ninternal object DiscoverMoviesLayoutManagerProvider {\n\n  fun provideLayoutManager(context: Context): GridLayoutManager {\n    val span = if (context.isTablet()) MAIN_GRID_SPAN_TABLET else MAIN_GRID_SPAN\n    return GridLayoutManager(context, span)\n  }\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/helpers/itemtype/ImageTypeProvider.kt",
    "content": "package com.michaldrabik.ui_discover_movies.helpers.itemtype\n\nimport com.michaldrabik.ui_model.ImageType\n\ninternal interface ImageTypeProvider {\n\n  val premiumAdPosition: Int\n\n  fun getImageType(position: Int): ImageType\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/helpers/itemtype/PhoneImageTypeProvider.kt",
    "content": "package com.michaldrabik.ui_discover_movies.helpers.itemtype\n\nimport com.michaldrabik.ui_model.ImageType\n\nprivate const val BUFFER = 14\n\ninternal class PhoneImageTypeProvider : ImageTypeProvider {\n\n  override val premiumAdPosition = 29\n\n  override fun getImageType(position: Int): ImageType {\n    if (position % BUFFER == 0) return ImageType.FANART_WIDE\n    if ((position + (BUFFER - 5)) % BUFFER == 0) return ImageType.FANART\n    if ((position + (BUFFER - 9)) % BUFFER == 0) return ImageType.FANART\n    return ImageType.POSTER\n  }\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/helpers/itemtype/TabletImageTypeProvider.kt",
    "content": "package com.michaldrabik.ui_discover_movies.helpers.itemtype\n\nimport com.michaldrabik.ui_model.ImageType\n\nprivate const val BUFFER = 11\n\ninternal class TabletImageTypeProvider : ImageTypeProvider {\n\n  override val premiumAdPosition = 30\n\n  override fun getImageType(position: Int): ImageType {\n    if (position % BUFFER == 0) return ImageType.FANART_WIDE\n    if ((position + (BUFFER - 10)) % BUFFER == 0) return ImageType.FANART_WIDE\n    if ((position + (BUFFER - 2)) % BUFFER == 0) return ImageType.FANART\n    if ((position + (BUFFER - 5)) % BUFFER == 0) return ImageType.FANART\n    if ((position + (BUFFER - 8)) % BUFFER == 0) return ImageType.FANART\n    return ImageType.POSTER\n  }\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/recycler/DiscoverMovieItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_discover_movies.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass DiscoverMovieItemDiffCallback : DiffUtil.ItemCallback<DiscoverMovieListItem>() {\n\n  override fun areItemsTheSame(oldItem: DiscoverMovieListItem, newItem: DiscoverMovieListItem) =\n    oldItem.movie.ids.trakt == newItem.movie.ids.trakt\n\n  override fun areContentsTheSame(oldItem: DiscoverMovieListItem, newItem: DiscoverMovieListItem) =\n    oldItem.image == newItem.image &&\n      oldItem.isLoading == newItem.isLoading &&\n      oldItem.isCollected == newItem.isCollected &&\n      oldItem.isWatchlist == newItem.isWatchlist &&\n      oldItem.translation == newItem.translation\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/recycler/DiscoverMovieListItem.kt",
    "content": "package com.michaldrabik.ui_discover_movies.recycler\n\nimport com.michaldrabik.ui_base.common.MovieListItem\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Translation\n\ndata class DiscoverMovieListItem(\n  override val movie: Movie,\n  override val image: Image,\n  override var isLoading: Boolean = false,\n  val isCollected: Boolean = false,\n  val isWatchlist: Boolean = false,\n  val translation: Translation? = null\n) : MovieListItem\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/recycler/DiscoverMoviesAdapter.kt",
    "content": "package com.michaldrabik.ui_discover_movies.recycler\n\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseMovieAdapter\nimport com.michaldrabik.ui_discover_movies.views.MovieFanartView\nimport com.michaldrabik.ui_discover_movies.views.MoviePosterView\nimport com.michaldrabik.ui_discover_movies.views.MoviePremiumView\nimport com.michaldrabik.ui_model.ImageType.FANART\nimport com.michaldrabik.ui_model.ImageType.FANART_WIDE\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport com.michaldrabik.ui_model.ImageType.PREMIUM\n\nclass DiscoverMoviesAdapter(\n  private val itemClickListener: (DiscoverMovieListItem) -> Unit,\n  private val itemLongClickListener: (DiscoverMovieListItem) -> Unit,\n  private val missingImageListener: (DiscoverMovieListItem, Boolean) -> Unit,\n  listChangeListener: () -> Unit\n) : BaseMovieAdapter<DiscoverMovieListItem>(\n  listChangeListener = listChangeListener\n) {\n\n  init {\n    stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY\n  }\n\n  override val asyncDiffer = AsyncListDiffer(this, DiscoverMovieItemDiffCallback())\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) {\n    POSTER.id -> BaseViewHolder(\n      MoviePosterView(parent.context).apply {\n        itemClickListener = this@DiscoverMoviesAdapter.itemClickListener\n        itemLongClickListener = this@DiscoverMoviesAdapter.itemLongClickListener\n        missingImageListener = this@DiscoverMoviesAdapter.missingImageListener\n      }\n    )\n    FANART.id, FANART_WIDE.id -> BaseViewHolder(\n      MovieFanartView(parent.context).apply {\n        itemClickListener = this@DiscoverMoviesAdapter.itemClickListener\n        itemLongClickListener = this@DiscoverMoviesAdapter.itemLongClickListener\n        missingImageListener = this@DiscoverMoviesAdapter.missingImageListener\n      }\n    )\n    PREMIUM.id -> BaseViewHolder(\n      MoviePremiumView(parent.context).apply {\n        itemClickListener = this@DiscoverMoviesAdapter.itemClickListener\n      }\n    )\n    else -> throw IllegalStateException(\"Unknown view type.\")\n  }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    when (holder.itemViewType) {\n      POSTER.id ->\n        (holder.itemView as MoviePosterView).bind(item)\n      FANART.id, FANART_WIDE.id ->\n        (holder.itemView as MovieFanartView).bind(item)\n      PREMIUM.id ->\n        (holder.itemView as MoviePremiumView).bind(item)\n    }\n  }\n\n  override fun getItemViewType(position: Int) = asyncDiffer.currentList[position].image.type.id\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/views/MovieFanartView.kt",
    "content": "package com.michaldrabik.ui_discover_movies.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.ui_base.common.views.MovieView\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_discover_movies.R\nimport com.michaldrabik.ui_discover_movies.databinding.ViewMovieFanartBinding\nimport com.michaldrabik.ui_discover_movies.recycler.DiscoverMovieListItem\nimport com.michaldrabik.ui_model.ImageStatus.AVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNAVAILABLE\n\nclass MovieFanartView : MovieView<DiscoverMovieListItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewMovieFanartBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    with(binding.movieFanartRoot) {\n      onClick { itemClickListener?.invoke(item) }\n      onLongClick { itemLongClickListener?.invoke(item) }\n    }\n  }\n\n  override val imageView: ImageView = binding.movieFanartImage\n  override val placeholderView: ImageView = binding.movieFanartPlaceholder\n\n  private lateinit var item: DiscoverMovieListItem\n\n  override fun bind(item: DiscoverMovieListItem) {\n    super.bind(item)\n    clear()\n    this.item = item\n    with(binding) {\n      movieFanartTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.movie.title\n        else item.translation?.title\n      movieFanartProgress.visibleIf(item.isLoading)\n      movieFanartBadge.visibleIf(item.isCollected)\n      movieFanartBadgeLater.visibleIf(item.isWatchlist)\n    }\n    loadImage(item)\n  }\n\n  override fun loadImage(item: DiscoverMovieListItem) {\n    super.loadImage(item)\n    if (item.image.status == UNAVAILABLE) {\n      binding.movieFanartRoot.setBackgroundResource(R.drawable.bg_media_view_placeholder)\n    }\n  }\n\n  override fun onImageLoadFail(item: DiscoverMovieListItem) {\n    super.onImageLoadFail(item)\n    if (item.image.status == AVAILABLE) {\n      binding.movieFanartRoot.setBackgroundResource(R.drawable.bg_media_view_placeholder)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      movieFanartTitle.text = \"\"\n      movieFanartProgress.gone()\n      movieFanartPlaceholder.gone()\n      movieFanartRoot.setBackgroundResource(R.drawable.bg_media_view_elevation)\n      movieFanartBadge.gone()\n      Glide.with(this@MovieFanartView).clear(movieFanartImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/views/MoviePosterView.kt",
    "content": "package com.michaldrabik.ui_discover_movies.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.ui_base.common.views.MovieView\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_discover_movies.R\nimport com.michaldrabik.ui_discover_movies.databinding.ViewMoviePosterBinding\nimport com.michaldrabik.ui_discover_movies.recycler.DiscoverMovieListItem\nimport com.michaldrabik.ui_model.ImageStatus.AVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNAVAILABLE\n\nclass MoviePosterView : MovieView<DiscoverMovieListItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewMoviePosterBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    with(binding.moviePosterRoot) {\n      onClick { itemClickListener?.invoke(item) }\n      onLongClick { itemLongClickListener?.invoke(item) }\n    }\n  }\n\n  override val imageView: ImageView = binding.moviePosterImage\n  override val placeholderView: ImageView = binding.moviePosterPlaceholder\n\n  private lateinit var item: DiscoverMovieListItem\n\n  override fun bind(item: DiscoverMovieListItem) {\n    super.bind(item)\n    clear()\n    this.item = item\n    with(binding) {\n      moviePosterTitle.text = item.movie.title\n      moviePosterProgress.visibleIf(item.isLoading)\n      moviePosterBadge.visibleIf(item.isCollected)\n      moviePosterLaterBadge.visibleIf(item.isWatchlist)\n    }\n    loadImage(item)\n  }\n\n  override fun loadImage(item: DiscoverMovieListItem) {\n    if (item.image.status == UNAVAILABLE) {\n      with(binding) {\n        moviePosterTitle.visible()\n        moviePosterRoot.setBackgroundResource(R.drawable.bg_media_view_placeholder)\n      }\n    }\n    super.loadImage(item)\n  }\n\n  override fun onImageLoadFail(item: DiscoverMovieListItem) {\n    super.onImageLoadFail(item)\n    if (item.image.status == AVAILABLE) {\n      with(binding) {\n        moviePosterTitle.visible()\n        moviePosterRoot.setBackgroundResource(R.drawable.bg_media_view_placeholder)\n      }\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      moviePosterTitle.text = \"\"\n      moviePosterTitle.gone()\n      moviePosterRoot.setBackgroundResource(R.drawable.bg_media_view_elevation)\n      moviePosterPlaceholder.gone()\n      moviePosterProgress.gone()\n      moviePosterBadge.gone()\n      Glide.with(this@MoviePosterView).clear(moviePosterImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/java/com/michaldrabik/ui_discover_movies/views/MoviePremiumView.kt",
    "content": "package com.michaldrabik.ui_discover_movies.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.ImageView\nimport com.michaldrabik.ui_base.common.views.MovieView\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_discover_movies.databinding.ViewMoviePremiumBinding\nimport com.michaldrabik.ui_discover_movies.recycler.DiscoverMovieListItem\n\nclass MoviePremiumView : MovieView<DiscoverMovieListItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewMoviePremiumBinding.inflate(LayoutInflater.from(context), this, true)\n\n  init {\n    binding.viewMoviePremiumRoot.onClick { itemClickListener?.invoke(item) }\n  }\n\n  override val imageView: ImageView = binding.viewMoviePremiumImageStub\n  override val placeholderView: ImageView = binding.viewMoviePremiumImageStub\n\n  private lateinit var item: DiscoverMovieListItem\n\n  override fun bind(item: DiscoverMovieListItem) {\n    super.bind(item)\n    this.item = item\n  }\n}\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/layout/fragment_discover_movies.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  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/discoverMoviesRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.swiperefreshlayout.widget.SwipeRefreshLayout\n    android:id=\"@+id/discoverMoviesSwipeRefresh\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    >\n\n    <androidx.recyclerview.widget.RecyclerView\n      android:id=\"@+id/discoverMoviesRecycler\"\n      style=\"@style/ScrollbarsStyle\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:background=\"@android:color/transparent\"\n      android:clipChildren=\"false\"\n      android:clipToPadding=\"false\"\n      android:overScrollMode=\"never\"\n      android:paddingLeft=\"@dimen/gridPadding\"\n      android:paddingTop=\"@dimen/discoverRecyclerPadding\"\n      android:paddingRight=\"@dimen/gridPadding\"\n      android:paddingBottom=\"@dimen/bottomNavigationHeightPadded\"\n      android:scrollbars=\"vertical\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>\n\n  <com.michaldrabik.ui_base.common.views.ModeTabsView\n    android:id=\"@+id/discoverMoviesTabsView\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/collectionTabsMargin\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    />\n\n  <com.michaldrabik.ui_discover_movies.filters.views.DiscoverMoviesFiltersView\n    android:id=\"@+id/discoverMoviesFiltersView\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/collectionFiltersMargin\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:paddingStart=\"@dimen/screenMarginHorizontal\"\n    android:paddingEnd=\"@dimen/screenMarginHorizontal\"\n    android:visibility=\"gone\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.SearchView\n    android:id=\"@+id/discoverMoviesSearchView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/searchViewHeight\"\n    android:layout_marginLeft=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginRight=\"@dimen/screenMarginHorizontal\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/layout/view_discover_movies_filters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <HorizontalScrollView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:overScrollMode=\"never\"\n    android:scrollbars=\"none\"\n    >\n\n    <com.google.android.material.chip.ChipGroup\n      android:id=\"@+id/discoverMoviesChips\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:paddingTop=\"@dimen/spaceSmall\"\n      android:paddingBottom=\"@dimen/spaceSmall\"\n      app:singleLine=\"true\"\n      >\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/discoverMoviesFeedChip\"\n        style=\"@style/ShowlyChip.Filter\"\n        android:checkable=\"false\"\n        android:text=\"@string/textHot\"\n        />\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/discoverMoviesGenresChip\"\n        style=\"@style/ShowlyChip.Filter\"\n        android:checkable=\"false\"\n        android:text=\"@string/textGenres\"\n        />\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/discoverMoviesCollectionChip\"\n        style=\"@style/ShowlyChip.Filter\"\n        android:text=\"@string/textDiscoverFilterCollectionMovies\"\n        />\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/discoverMoviesAnticipatedChip\"\n        style=\"@style/ShowlyChip.Filter\"\n        android:checked=\"true\"\n        android:text=\"@string/textDiscoverFilterAnticipatedMovies\"\n        />\n\n    </com.google.android.material.chip.ChipGroup>\n\n  </HorizontalScrollView>\n\n</merge>"
  },
  {
    "path": "ui-discover-movies/src/main/res/layout/view_discover_movies_filters_feed.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  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/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_filters_sheet\"\n  android:orientation=\"vertical\"\n  android:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.core.widget.NestedScrollView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\"\n    >\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:clipToPadding=\"false\"\n      android:paddingStart=\"@dimen/spaceNormal\"\n      android:paddingTop=\"@dimen/spaceNormal\"\n      android:paddingEnd=\"@dimen/spaceNormal\"\n      tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n      >\n\n      <TextView\n        android:id=\"@+id/feedTitle\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/textSortOrderMovies\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"18sp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintBottom_toTopOf=\"@id/feedChipGroup\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n      <com.google.android.material.chip.ChipGroup\n        android:id=\"@+id/feedChipGroup\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceSmall\"\n        android:layout_marginBottom=\"@dimen/spaceMicro\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/feedTitle\"\n        app:selectionRequired=\"true\"\n        app:singleSelection=\"true\"\n        >\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/feedChipHot\"\n          style=\"@style/Widget.Material3.Chip.Suggestion\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:checked=\"true\"\n          android:text=\"@string/textHot\"\n          android:textColor=\"@color/selector_discover_chip_text\"\n          app:checkedIconTint=\"@color/selector_discover_chip_text\"\n          app:chipBackgroundColor=\"@color/selector_discover_chip_background\"\n          app:chipCornerRadius=\"100dp\"\n          app:chipStrokeColor=\"@color/selector_discover_chip_text\"\n          app:chipStrokeWidth=\"1dp\"\n          />\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/feedChipTopRated\"\n          style=\"@style/Widget.Material3.Chip.Suggestion\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/textSortRated\"\n          android:textColor=\"@color/selector_discover_chip_text\"\n          app:checkedIconTint=\"@color/selector_discover_chip_text\"\n          app:chipBackgroundColor=\"@color/selector_discover_chip_background\"\n          app:chipCornerRadius=\"100dp\"\n          app:chipStrokeColor=\"@color/selector_discover_chip_text\"\n          app:chipStrokeWidth=\"1dp\"\n          />\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/feedChipRecent\"\n          style=\"@style/Widget.Material3.Chip.Suggestion\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/textSortNewest\"\n          android:textColor=\"@color/selector_discover_chip_text\"\n          app:checkedIconTint=\"@color/selector_discover_chip_text\"\n          app:chipBackgroundColor=\"@color/selector_discover_chip_background\"\n          app:chipCornerRadius=\"100dp\"\n          app:chipStrokeColor=\"@color/selector_discover_chip_text\"\n          app:chipStrokeWidth=\"1dp\"\n          />\n\n      </com.google.android.material.chip.ChipGroup>\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n  </androidx.core.widget.NestedScrollView>\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/applyButton\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:layout_marginBottom=\"@dimen/spaceNormal\"\n    android:backgroundTint=\"?attr/colorAccent\"\n    android:gravity=\"center\"\n    android:text=\"@string/textApply\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/layout/view_discover_movies_filters_genres.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  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/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_filters_sheet\"\n  android:orientation=\"vertical\"\n  android:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.core.widget.NestedScrollView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\"\n    >\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:clipToPadding=\"false\"\n      android:paddingStart=\"@dimen/spaceNormal\"\n      android:paddingTop=\"@dimen/spaceNormal\"\n      android:paddingEnd=\"@dimen/spaceNormal\"\n      tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n      >\n\n      <TextView\n        android:id=\"@+id/genresTitle\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/textGenres\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"18sp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintBottom_toTopOf=\"@+id/genresChipGroup\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n      <com.google.android.material.chip.ChipGroup\n        android:id=\"@+id/genresChipGroup\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceTiny\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/genresTitle\"\n        app:lineSpacing=\"10dp\"\n        />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n  </androidx.core.widget.NestedScrollView>\n\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingTop=\"@dimen/spaceMedium\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceNormal\"\n    >\n\n    <ImageView\n      android:id=\"@+id/clearButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center_vertical\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_delete\"\n      app:tint=\"?android:textColorSecondary\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/applyButton\"\n      style=\"@style/RoundMaterialButton\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:backgroundTint=\"?attr/colorAccent\"\n      android:gravity=\"center\"\n      android:text=\"@string/textApply\"\n      android:textColor=\"?attr/textColorOnSurface\"\n      app:rippleColor=\"?android:attr/textColorPrimary\"\n      />\n\n  </LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/layout/view_movie_fanart.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/movieFanartRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_margin=\"@dimen/gridSpacing\"\n    android:background=\"@drawable/bg_media_view_elevation\"\n    android:elevation=\"@dimen/elevationSmall\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/movieFanartImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/movieFanartPlaceholder\"\n      android:layout_width=\"@dimen/movieTilePlaceholder\"\n      android:layout_height=\"@dimen/movieTilePlaceholder\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_film\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/movieFanartTitle\"\n      style=\"@style/ImageTitle\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"bottom|start\"\n      android:layout_margin=\"@dimen/spaceSmall\"\n      android:gravity=\"start\"\n      android:maxLines=\"2\"\n      android:textAlignment=\"viewStart\"\n      android:textSize=\"14sp\"\n      tools:text=\"Lord Of The Rings\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/movieFanartProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      />\n\n  </FrameLayout>\n\n  <ImageView\n    android:id=\"@+id/movieFanartBadge\"\n    style=\"@style/Badge\"\n    app:srcCompat=\"@drawable/ic_bookmark_full\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/movieFanartBadgeLater\"\n    style=\"@style/Badge.Watchlist\"\n    app:srcCompat=\"@drawable/ic_bookmark_full\"\n    tools:visibility=\"visible\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-discover-movies/src/main/res/layout/view_movie_poster.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/moviePosterRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_margin=\"@dimen/gridSpacing\"\n    android:background=\"@drawable/bg_media_view_elevation\"\n    android:elevation=\"@dimen/elevationSmall\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/moviePosterImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/moviePosterPlaceholder\"\n      android:layout_width=\"@dimen/movieTilePlaceholder\"\n      android:layout_height=\"@dimen/movieTilePlaceholder\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_film\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/moviePosterTitle\"\n      style=\"@style/ImageTitle\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"bottom|start\"\n      android:layout_margin=\"@dimen/spaceSmall\"\n      android:gravity=\"start\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textSize=\"12sp\"\n      android:visibility=\"gone\"\n      tools:text=\"Lord Of The Rings\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/moviePosterProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      />\n\n  </FrameLayout>\n\n  <ImageView\n    android:id=\"@+id/moviePosterBadge\"\n    style=\"@style/Badge\"\n    app:srcCompat=\"@drawable/ic_bookmark_full\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/moviePosterLaterBadge\"\n    style=\"@style/Badge.Watchlist\"\n    app:srcCompat=\"@drawable/ic_bookmark_full\"\n    tools:visibility=\"visible\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-discover-movies/src/main/res/layout/view_movie_premium.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/viewMoviePremiumRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  >\n\n  <include layout=\"@layout/view_premium_ad_list\" />\n\n  <ImageView\n    android:id=\"@+id/viewMoviePremiumImageStub\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:visibility=\"gone\"\n    />\n\n</FrameLayout>\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrderMovies\">Feed:</string>\n  <string name=\"textDiscoverFilterAnticipatedMovies\">Hide Anticipated Movies</string>\n  <string name=\"textDiscoverFilterCollectionMovies\">Hide Collection</string>\n</resources>"
  },
  {
    "path": "ui-discover-movies/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrderMovies\">ترتيب حسب:</string>\n  <string name=\"textDiscoverFilterAnticipatedMovies\">إخفاء الأفلام المرتقبة</string>\n  <string name=\"textDiscoverFilterCollectionMovies\">إخفاء محتويات المجموعة</string>\n</resources>\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrderMovies\">Feed:</string>\n  <string name=\"textDiscoverFilterAnticipatedMovies\">Bald erscheinende Filme ausblenden</string>\n  <string name=\"textDiscoverFilterCollectionMovies\">Sammlung ausblenden</string>\n</resources>\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrderMovies\">Feed:</string>\n  <string name=\"textDiscoverFilterAnticipatedMovies\">Ocultar Películas Anticipadas</string>\n  <string name=\"textDiscoverFilterCollectionMovies\">Ocultar Colección</string>\n</resources>\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrderMovies\">Syöte:</string>\n  <string name=\"textDiscoverFilterAnticipatedMovies\">Piilota odotetut elokuvat</string>\n  <string name=\"textDiscoverFilterCollectionMovies\">Piilota kokoelma</string>\n</resources>\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrderMovies\">Fil :</string>\n  <string name=\"textDiscoverFilterAnticipatedMovies\">Cacher les films attendus</string>\n  <string name=\"textDiscoverFilterCollectionMovies\">Cacher la collection</string>\n</resources>\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrderMovies\">Riepilogo:</string>\n  <string name=\"textDiscoverFilterAnticipatedMovies\">Nascondi film attesi</string>\n  <string name=\"textDiscoverFilterCollectionMovies\">Nascondi raccolta</string>\n</resources>\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrderMovies\">Strumień:</string>\n  <string name=\"textDiscoverFilterAnticipatedMovies\">Ukryj nadchodzące</string>\n  <string name=\"textDiscoverFilterCollectionMovies\">Ukryj kolekcję</string>\n</resources>\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrderMovies\">Feed:</string>\n  <string name=\"textDiscoverFilterAnticipatedMovies\">Ocultar filmes aguardados</string>\n  <string name=\"textDiscoverFilterCollectionMovies\">Ocultar assistidos</string>\n</resources>\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrderMovies\">Сортировать по:</string>\n  <string name=\"textDiscoverFilterAnticipatedMovies\">Скрыть ожидаемые фильмы</string>\n  <string name=\"textDiscoverFilterCollectionMovies\">Скрыть коллекцию</string>\n</resources>\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrderMovies\">Akış:</string>\n  <string name=\"textDiscoverFilterAnticipatedMovies\">Beklenen Filmleri Gizle</string>\n  <string name=\"textDiscoverFilterCollectionMovies\">Koleksiyonu Gizle</string>\n</resources>\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrderMovies\">Стрічка:</string>\n  <string name=\"textDiscoverFilterAnticipatedMovies\">Приховати очікувані фільми</string>\n  <string name=\"textDiscoverFilterCollectionMovies\">Приховати колекцію</string>\n</resources>\n"
  },
  {
    "path": "ui-discover-movies/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrderMovies\">Nguồn cấp:</string>\n  <string name=\"textDiscoverFilterAnticipatedMovies\">Ẩn phim được mong đợi</string>\n  <string name=\"textDiscoverFilterCollectionMovies\">Ẩn bộ sưu tập</string>\n</resources>"
  },
  {
    "path": "ui-discover-movies/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortOrderMovies\">信息流：</string>\n  <string name=\"textDiscoverFilterAnticipatedMovies\">隐藏备受期待的电影</string>\n  <string name=\"textDiscoverFilterCollectionMovies\">隐藏合集中的项目</string>\n</resources>\n"
  },
  {
    "path": "ui-episodes/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-episodes/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-parcelize'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_episodes'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-local')\n  implementation project(':data-remote')\n  implementation project(':ui-base')\n  implementation project(':ui-navigation')\n  implementation project(':repository')\n  implementation project(':ui-model')\n  implementation project(':ui-comments')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-episodes/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "ui-episodes/src/main/java/com/michaldrabik/ui_episodes/details/EpisodeDetailsBottomSheet.kt",
    "content": "package com.michaldrabik.ui_episodes.details\n\nimport android.annotation.SuppressLint\nimport android.graphics.Typeface.BOLD\nimport android.graphics.Typeface.NORMAL\nimport android.os.Bundle\nimport android.os.Parcelable\nimport android.view.View\nimport androidx.core.content.ContextCompat\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.google.android.material.tabs.TabLayout\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Config.IMAGE_FADE_DURATION_MS\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.common.extensions.dateFromMillis\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Operation\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Type\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.invisible\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.requireParcelable\nimport com.michaldrabik.ui_base.utilities.extensions.setTextFade\nimport com.michaldrabik.ui_base.utilities.extensions.showErrorSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_comments.CommentView\nimport com.michaldrabik.ui_episodes.R\nimport com.michaldrabik.ui_episodes.databinding.ViewEpisodeDetailsBinding\nimport com.michaldrabik.ui_model.Comment\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ACTION_EPISODE_TAB_SELECTED\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ACTION_EPISODE_WATCHED\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ACTION_NEW_COMMENT\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_COMMENT\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_COMMENT_ACTION\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_COMMENT_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_EPISODE_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_OPTIONS\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_REPLY_USER\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_COMMENT\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_EPISODE_DETAILS\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.parcelize.Parcelize\nimport timber.log.Timber\nimport java.util.Locale.ENGLISH\n\n@AndroidEntryPoint\nclass EpisodeDetailsBottomSheet : BaseBottomSheetFragment(R.layout.view_episode_details) {\n\n  companion object {\n    fun createBundle(\n      ids: Ids,\n      episode: Episode,\n      seasonEpisodesIds: List<Int>?,\n      isWatched: Boolean,\n      showButton: Boolean,\n      showTabs: Boolean,\n    ): Bundle {\n      val options = Options(\n        ids = ids,\n        episode = episode,\n        seasonEpisodesIds = seasonEpisodesIds,\n        isWatched = isWatched,\n        showButton = showButton,\n        showTabs = showTabs\n      )\n      return bundleOf(ARG_OPTIONS to options)\n    }\n  }\n\n  private val viewModel by viewModels<EpisodeDetailsViewModel>()\n  private val binding by viewBinding(ViewEpisodeDetailsBinding::bind)\n\n  private val options by lazy { requireParcelable<Options>(ARG_OPTIONS) }\n  private val cornerRadius by lazy { dimenToPx(R.dimen.bottomSheetCorner).toFloat() }\n\n  private var spoilerTitle: String? = null\n  private var spoilerDescription: String? = null\n  private var spoilerRating: String? = null\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    with(viewModel) {\n      launchAndRepeatStarted(\n        { uiState.collect { render(it) } },\n        { messageFlow.collect { renderSnackbar(it) } },\n        doAfterLaunch = {\n          val (ids, episode, seasonEpisodes) = options\n          loadSeason(ids.trakt, episode, seasonEpisodes?.toIntArray())\n          loadTranslation(ids.trakt, episode)\n          loadImage(ids.tmdb, episode)\n          loadRatings(episode)\n        }\n      )\n    }\n  }\n\n  private fun setupView() {\n    binding.run {\n      val (ids, episode, _, isWatched, showButton, showTabs) = options\n      episodeDetailsTitle.text = when (episode.title) {\n        \"Episode ${episode.number}\" -> String.format(ENGLISH, requireContext().getString(R.string.textEpisode), episode.number)\n        else -> episode.title\n      }\n      episodeDetailsOverview.text = episode.overview.ifBlank { getString(R.string.textNoDescription) }\n      episodeDetailsButton.run {\n        visibleIf(showButton && !isWatched)\n        onClick {\n          setFragmentResult(REQUEST_EPISODE_DETAILS, bundleOf(ACTION_EPISODE_WATCHED to !isWatched))\n          closeSheet()\n        }\n      }\n      episodeDetailsRatingLayout.visibleIf(episode.votes > 0)\n      if (!showTabs) episodeDetailsTabs.gone()\n      episodeDetailsRating.text = String.format(ENGLISH, getString(R.string.textVotes), episode.rating, episode.votes)\n      episodeDetailsCommentsButton.text = String.format(ENGLISH, getString(R.string.textLoadCommentsCount), episode.commentCount)\n      episodeDetailsCommentsButton.onClick {\n        viewModel.loadComments(ids.trakt, episode.season, episode.number)\n      }\n      episodeDetailsPostCommentButton.onClick { openPostCommentSheet() }\n    }\n  }\n\n  private fun openRateDialog() {\n    setFragmentResultListener(NavigationArgs.REQUEST_RATING) { _, bundle ->\n      when (bundle.getParcelable<Operation>(NavigationArgs.RESULT)) {\n        Operation.SAVE -> renderSnackbar(MessageEvent.Info(R.string.textRateSaved))\n        Operation.REMOVE -> renderSnackbar(MessageEvent.Info(R.string.textRateRemoved))\n        else -> Timber.w(\"Unknown result.\")\n      }\n      viewModel.loadRatings(options.episode)\n      setFragmentResult(REQUEST_EPISODE_DETAILS, bundleOf(NavigationArgs.ACTION_RATING_CHANGED to true))\n    }\n    val bundle = RatingsBottomSheet.createBundle(options.episode.ids.trakt, Type.EPISODE)\n    navigateTo(R.id.actionEpisodeDetailsDialogToRate, bundle)\n  }\n\n  private fun openPostCommentSheet(comment: Comment? = null) {\n    setFragmentResultListener(REQUEST_COMMENT) { _, bundle ->\n      renderSnackbar(MessageEvent.Info(R.string.textCommentPosted))\n      when (bundle.getString(ARG_COMMENT_ACTION)) {\n        ACTION_NEW_COMMENT -> {\n          val newComment = bundle.getParcelable<Comment>(ARG_COMMENT)!!\n          viewModel.addNewComment(newComment)\n        }\n      }\n    }\n    val bundle = when {\n      comment != null -> bundleOf(\n        ARG_COMMENT_ID to comment.getReplyId(),\n        ARG_REPLY_USER to comment.user.username\n      )\n      else -> bundleOf(ARG_EPISODE_ID to options.episode.ids.trakt.id)\n    }\n    navigateTo(R.id.actionEpisodeDetailsDialogToPostComment, bundle)\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun render(uiState: EpisodeDetailsUiState) {\n    uiState.run {\n      with(binding) {\n        val episode = options.episode\n        dateFormat?.let {\n          val millis = episode.firstAired?.toInstant()?.toEpochMilli() ?: -1\n          val date = if (millis == -1L) {\n            getString(R.string.textTba)\n          } else {\n            it.format(dateFromMillis(millis).toLocalZone()).capitalizeWords()\n          }\n          val name = String.format(ENGLISH, getString(R.string.textSeasonEpisodeDate), episode.season, episode.number, date)\n          val runtime = \"${episode.runtime} ${getString(R.string.textMinutesShort)}\"\n          episodeDetailsName.text = if (episode.runtime > 0) \"$name | $runtime\" else name\n        }\n        isImageLoading.let { episodeDetailsProgress.visibleIf(it) }\n        image?.let { renderImage(it, spoilers) }\n        isCommentsLoading.let {\n          episodeDetailsButtons.visibleIf(!it)\n          episodeDetailsCommentsProgress.visibleIf(it)\n        }\n        episodes?.let { renderEpisodes(it) }\n        comments?.let { comments ->\n          episodeDetailsComments.removeAllViews()\n          comments.forEach {\n            val view = CommentView(requireContext()).apply {\n              bind(it, commentsDateFormat)\n              if (it.replies > 0) {\n                onRepliesClickListener = { comment -> viewModel.loadCommentReplies(comment) }\n              }\n              if (it.isSignedIn) {\n                onReplyClickListener = { comment -> openPostCommentSheet(comment) }\n              }\n              if (it.replies == 0L && it.isMe && it.isSignedIn) {\n                onDeleteClickListener = { comment -> openDeleteCommentDialog(comment) }\n              }\n            }\n            episodeDetailsComments.addView(view)\n          }\n          episodeDetailsComments.fadeIf(comments.isNotEmpty())\n          episodeDetailsCommentsEmpty.fadeIf(comments.isEmpty())\n          episodeDetailsPostCommentButton.fadeIf(isSignedIn)\n          episodeDetailsCommentsButton.isEnabled = false\n          episodeDetailsCommentsButton.text = String.format(ENGLISH, getString(R.string.textLoadCommentsCount), comments.size)\n        }\n        rating?.let { state ->\n          episodeDetailsRateProgress.visibleIf(state.rateLoading == true)\n          episodeDetailsRateButton.visibleIf(state.rateLoading == false)\n          episodeDetailsRateButton.onClick {\n            if (state.rateAllowed == true) {\n              openRateDialog()\n            } else {\n              renderSnackbar(MessageEvent.Info(R.string.textSignBeforeRate))\n            }\n          }\n          if (state.hasRating()) {\n            episodeDetailsRateButton.setTypeface(null, BOLD)\n            episodeDetailsRateButton.text = \"${state.userRating?.rating}/10\"\n          } else {\n            episodeDetailsRateButton.setTypeface(null, NORMAL)\n            episodeDetailsRateButton.setText(R.string.textRate)\n          }\n        }\n        spoilers?.let { renderRating(it) }\n        renderTitle(translation, spoilers)\n        renderDescription(translation, spoilers)\n      }\n    }\n  }\n\n  private fun renderTitle(\n    translation: Translation?,\n    spoilersSettings: SpoilersSettings?,\n  ) {\n    with(binding) {\n      var title =\n        if (translation?.title?.isNotBlank() == true) {\n          translation.title\n        } else if (episodeDetailsTitle.text.isBlank()) {\n          when (options.episode.title) {\n            \"Episode ${options.episode.number}\" ->\n              String.format(ENGLISH, requireContext().getString(R.string.textEpisode), options.episode.number)\n            else -> options.episode.title\n          }\n        } else {\n          episodeDetailsTitle.text.toString()\n        }\n\n      val isEpisodeTitleHidden = !options.isWatched && spoilersSettings?.isEpisodeTitleHidden == true\n      if (isEpisodeTitleHidden) {\n        if (spoilerTitle == null) {\n          spoilerTitle = String(title.toCharArray())\n        }\n        title = SPOILERS_REGEX.replace(title, SPOILERS_HIDE_SYMBOL)\n      }\n\n      if (title.isNotBlank()) {\n        episodeDetailsTitle.setTextFade(title, duration = 0)\n      }\n\n      if (spoilersSettings?.isTapToReveal == true) {\n        episodeDetailsTitle.onClick {\n          spoilerTitle?.let {\n            episodeDetailsTitle.setTextFade(it, duration = 0)\n          }\n        }\n      }\n    }\n  }\n\n  private fun renderDescription(\n    translation: Translation?,\n    spoilersSettings: SpoilersSettings?,\n  ) {\n    with(binding) {\n      var description =\n        if (translation?.overview?.isNotBlank() == true) {\n          translation.overview\n        } else if (episodeDetailsOverview.text.isBlank()) {\n          options.episode.overview.ifBlank {\n            getString(R.string.textNoDescription)\n          }\n        } else {\n          episodeDetailsOverview.text.toString()\n        }\n\n      if (!options.isWatched && spoilersSettings?.isEpisodeDescriptionHidden == true) {\n        if (spoilerDescription == null) {\n          spoilerDescription = String(description.toCharArray())\n        }\n        description = SPOILERS_REGEX.replace(description, SPOILERS_HIDE_SYMBOL)\n      }\n\n      if (description.isNotBlank()) {\n        episodeDetailsOverview.setTextFade(description, duration = 0)\n      }\n\n      if (spoilersSettings?.isTapToReveal == true) {\n        episodeDetailsOverview.onClick {\n          spoilerDescription?.let {\n            episodeDetailsOverview.setTextFade(it, duration = 0)\n          }\n        }\n      }\n    }\n  }\n\n  private fun renderRating(spoilersSettings: SpoilersSettings) {\n    with(binding) {\n      val isSpoilerHidden = !options.isWatched && spoilersSettings.isEpisodeRatingHidden\n      if (isSpoilerHidden) {\n        if (spoilerRating == null) {\n          spoilerRating = episodeDetailsRating.text.toString()\n        }\n        episodeDetailsRating.text = Config.SPOILERS_RATINGS_VOTES_HIDE_SYMBOL\n      }\n\n      if (spoilersSettings.isTapToReveal) {\n        episodeDetailsRating.onClick {\n          spoilerRating?.let {\n            episodeDetailsRating.text = it\n          }\n        }\n      }\n    }\n  }\n\n  private fun renderImage(\n    image: Image,\n    spoilers: SpoilersSettings?,\n    tapToReveal: Boolean = false,\n  ) {\n    with(binding) {\n      if (!options.isWatched && spoilers?.isEpisodeImageHidden == true && !tapToReveal) {\n        episodeDetailsImage.invisible()\n        episodeDetailsImagePlaceholder.visible()\n        episodeDetailsImagePlaceholder.setImageResource(R.drawable.ic_eye_no)\n        if (spoilers.isTapToReveal) {\n          episodeDetailsImagePlaceholder.onClick {\n            renderImage(image, spoilers, tapToReveal = true)\n          }\n        }\n        return\n      }\n      episodeDetailsImage.visible()\n      episodeDetailsImagePlaceholder.invisible()\n      Glide.with(this@EpisodeDetailsBottomSheet)\n        .load(\"${Config.TMDB_IMAGE_BASE_STILL_URL}${image.fileUrl}\")\n        .transform(CenterCrop(), GranularRoundedCorners(cornerRadius, cornerRadius, 0F, 0F))\n        .transition(DrawableTransitionOptions.withCrossFade(IMAGE_FADE_DURATION_MS))\n        .withFailListener {\n          episodeDetailsImagePlaceholder.visible()\n          episodeDetailsImagePlaceholder.setImageResource(R.drawable.ic_television)\n          episodeDetailsImagePlaceholder.setOnClickListener(null)\n        }\n        .into(episodeDetailsImage)\n    }\n  }\n\n  private fun renderEpisodes(episodes: List<Episode>) {\n    with(binding.episodeDetailsTabs) {\n      removeAllTabs()\n      removeOnTabSelectedListener(tabSelectedListener)\n      episodes.forEach {\n        addTab(\n          newTab()\n            .setText(\"${options.episode.season}x${it.number.toString().padStart(2, '0')}\")\n            .setTag(it)\n        )\n      }\n      val index = episodes.indexOfFirst { it.number == options.episode.number }\n      // Small trick to avoid UI tab change flick\n      getTabAt(index)?.select()\n      post {\n        getTabAt(index)?.select()\n        addOnTabSelectedListener(tabSelectedListener)\n      }\n      if (options.showTabs && episodes.isNotEmpty()) {\n        fadeIn(duration = 200, startDelay = 100, withHardware = true)\n      } else {\n        gone()\n      }\n    }\n  }\n\n  private fun renderSnackbar(message: MessageEvent) {\n    when (message) {\n      is MessageEvent.Info -> binding.episodeDetailsSnackbarHost.showInfoSnackbar(getString(message.textRestId))\n      is MessageEvent.Error -> binding.episodeDetailsSnackbarHost.showErrorSnackbar(getString(message.textRestId))\n    }\n  }\n\n  private fun openDeleteCommentDialog(comment: Comment) {\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setTitle(R.string.textCommentConfirmDeleteTitle)\n      .setMessage(R.string.textCommentConfirmDelete)\n      .setPositiveButton(R.string.textYes) { _, _ -> viewModel.deleteComment(comment) }\n      .setNegativeButton(R.string.textNo) { _, _ -> }\n      .show()\n  }\n\n  private val tabSelectedListener = object : TabLayout.OnTabSelectedListener {\n    override fun onTabSelected(tab: TabLayout.Tab?) {\n      binding.episodeDetailsTabs.removeOnTabSelectedListener(this)\n      closeSheet()\n      setFragmentResult(REQUEST_EPISODE_DETAILS, bundleOf(ACTION_EPISODE_TAB_SELECTED to tab?.tag))\n    }\n\n    override fun onTabUnselected(tab: TabLayout.Tab?) = Unit\n    override fun onTabReselected(tab: TabLayout.Tab?) = Unit\n  }\n\n  @Parcelize\n  private data class Options(\n    val ids: Ids,\n    val episode: Episode,\n    val seasonEpisodesIds: List<Int>?,\n    val isWatched: Boolean,\n    val showButton: Boolean,\n    val showTabs: Boolean,\n  ) : Parcelable\n}\n"
  },
  {
    "path": "ui-episodes/src/main/java/com/michaldrabik/ui_episodes/details/EpisodeDetailsUiState.kt",
    "content": "package com.michaldrabik.ui_episodes.details\n\nimport com.michaldrabik.ui_model.Comment\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.RatingState\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.Translation\nimport java.time.format.DateTimeFormatter\n\ndata class EpisodeDetailsUiState(\n  val image: Image? = null,\n  val isImageLoading: Boolean = false,\n  val episodes: List<Episode>? = null,\n  val comments: List<Comment>? = null,\n  val isCommentsLoading: Boolean = false,\n  val isSignedIn: Boolean = false,\n  val rating: RatingState? = null,\n  val translation: Translation? = null,\n  val dateFormat: DateTimeFormatter? = null,\n  val commentsDateFormat: DateTimeFormatter? = null,\n  val spoilers: SpoilersSettings? = null\n)\n"
  },
  {
    "path": "ui-episodes/src/main/java/com/michaldrabik/ui_episodes/details/EpisodeDetailsViewModel.kt",
    "content": "package com.michaldrabik.ui_episodes.details\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError.CoroutineCancellation\nimport com.michaldrabik.common.errors.ShowlyError.ResourceConflictError\nimport com.michaldrabik.repository.CommentsRepository\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.images.EpisodeImagesProvider\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.combine\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_episodes.R\nimport com.michaldrabik.ui_episodes.details.cases.EpisodeDetailsSeasonCase\nimport com.michaldrabik.ui_model.Comment\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.RatingState\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.Translation\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport java.time.format.DateTimeFormatter\nimport javax.inject.Inject\n\n@HiltViewModel\nclass EpisodeDetailsViewModel @Inject constructor(\n  settingsSpoilersRepository: SettingsSpoilersRepository,\n  private val seasonsCase: EpisodeDetailsSeasonCase,\n  private val imagesProvider: EpisodeImagesProvider,\n  private val dateFormatProvider: DateFormatProvider,\n  private val ratingsRepository: RatingsRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val commentsRepository: CommentsRepository,\n  private val userTraktManager: UserTraktManager,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val imageState = MutableStateFlow<Image?>(null)\n  private val imageLoadingState = MutableStateFlow(false)\n  private val episodesState = MutableStateFlow<List<Episode>?>(null)\n  private val commentsState = MutableStateFlow<List<Comment>?>(null)\n  private val commentsLoadingState = MutableStateFlow(false)\n  private val signedInState = MutableStateFlow(false)\n  private val ratingState = MutableStateFlow<RatingState?>(null)\n  private val translationState = MutableStateFlow<Translation?>(null)\n  private val dateFormatState = MutableStateFlow<DateTimeFormatter?>(null)\n  private val commentsDateFormatState = MutableStateFlow<DateTimeFormatter?>(null)\n  private val spoilersState = MutableStateFlow<SpoilersSettings?>(null)\n\n  init {\n    dateFormatState.value = dateFormatProvider.loadFullHourFormat()\n    spoilersState.value = settingsSpoilersRepository.getAll()\n  }\n\n  fun loadImage(showId: IdTmdb, episode: Episode) {\n    viewModelScope.launch {\n      try {\n        imageLoadingState.value = true\n        val episodeImage = imagesProvider.loadRemoteImage(showId, episode)\n        imageState.value = episodeImage\n        imageLoadingState.value = false\n      } catch (t: Throwable) {\n        imageLoadingState.value = false\n      }\n    }\n  }\n\n  fun loadSeason(showTraktId: IdTrakt, episode: Episode, seasonEpisodes: IntArray?) {\n    viewModelScope.launch {\n      val episodes = seasonsCase.loadSeason(showTraktId, episode, seasonEpisodes)\n      if (episodes.isNotEmpty()) {\n        delay(100)\n      }\n      episodesState.value = episodes\n    }\n  }\n\n  fun loadTranslation(showTraktId: IdTrakt, episode: Episode) {\n    viewModelScope.launch {\n      try {\n        val language = translationsRepository.getLanguage()\n        if (language == Config.DEFAULT_LANGUAGE) {\n          return@launch\n        }\n        val translation = translationsRepository.loadTranslation(episode, showTraktId, language)\n        translation?.let {\n          translationState.value = it\n        }\n      } catch (error: Throwable) {\n        Timber.e(error)\n      }\n    }\n  }\n\n  fun loadComments(idTrakt: IdTrakt, season: Int, episode: Int) {\n    viewModelScope.launch {\n      try {\n        commentsLoadingState.value = true\n\n        val isSignedIn = userTraktManager.isAuthorized()\n        val username = userTraktManager.getUsername()\n        val comments = commentsRepository.loadEpisodeComments(idTrakt, season, episode)\n          .map {\n            it.copy(\n              isMe = it.user.username == username,\n              isSignedIn = isSignedIn\n            )\n          }\n          .partition { it.isMe }\n\n        signedInState.value = isSignedIn\n        commentsState.value = comments.first + comments.second\n        commentsLoadingState.value = false\n        commentsDateFormatState.value = dateFormatProvider.loadFullHourFormat()\n      } catch (t: Throwable) {\n        Timber.w(\"Failed to load comments. ${t.message}\")\n        commentsLoadingState.value = false\n      }\n    }\n  }\n\n  fun loadCommentReplies(comment: Comment) {\n    var current = uiState.value.comments?.toMutableList() ?: mutableListOf()\n    if (current.any { it.parentId == comment.id }) return\n\n    viewModelScope.launch {\n      try {\n        val parent = current.find { it.id == comment.id }\n        parent?.let { p ->\n          val copy = p.copy(isLoading = true)\n          current.findReplace(copy) { it.id == p.id }\n          commentsState.value = current\n        }\n\n        val isSignedIn = userTraktManager.isAuthorized()\n        val username = userTraktManager.getUsername()\n        val replies = commentsRepository.loadReplies(comment.id)\n          .map {\n            it.copy(\n              isSignedIn = isSignedIn,\n              isMe = it.user.username == username\n            )\n          }\n\n        current = uiState.value.comments?.toMutableList() ?: mutableListOf()\n        val parentIndex = current.indexOfFirst { it.id == comment.id }\n        if (parentIndex > -1) current.addAll(parentIndex + 1, replies)\n        parent?.let {\n          current.findReplace(parent.copy(isLoading = false, replies = 0)) { it.id == comment.id }\n        }\n        commentsState.value = current\n      } catch (t: Throwable) {\n        commentsState.value = current\n      }\n    }\n  }\n\n  fun addNewComment(comment: Comment) {\n    val current = uiState.value.comments?.toMutableList() ?: mutableListOf()\n    if (!comment.isReply()) {\n      current.add(0, comment)\n    } else {\n      val parentIndex = current.indexOfLast { it.id == comment.parentId }\n      if (parentIndex > -1) {\n        val parent = current[parentIndex]\n        current.add(parentIndex + 1, comment)\n        val repliesCount = current.count { it.parentId == parent.id }.toLong()\n        current.findReplace(parent.copy(replies = repliesCount)) { it.id == comment.parentId }\n      }\n    }\n    commentsState.value = current\n  }\n\n  fun deleteComment(comment: Comment) {\n    var current = uiState.value.comments?.toMutableList() ?: mutableListOf()\n    val target = current.find { it.id == comment.id } ?: return\n\n    viewModelScope.launch {\n      try {\n        val copy = target.copy(isLoading = true)\n        current.findReplace(copy) { it.id == target.id }\n        commentsState.value = current\n\n        commentsRepository.deleteComment(target.id)\n\n        current = uiState.value.comments?.toMutableList() ?: mutableListOf()\n        val targetIndex = current.indexOfFirst { it.id == target.id }\n        if (targetIndex > -1) {\n          current.removeAt(targetIndex)\n          if (target.isReply()) {\n            val parent = current.first { it.id == target.parentId }\n            val repliesCount = current.count { it.parentId == parent.id }.toLong()\n            current.findReplace(parent.copy(replies = repliesCount)) { it.id == target.parentId }\n          }\n        }\n\n        commentsState.value = current\n        messageChannel.send(MessageEvent.Info(R.string.textCommentDeleted))\n      } catch (t: Throwable) {\n        when (ErrorHelper.parse(t)) {\n          is CoroutineCancellation -> rethrowCancellation(t)\n          is ResourceConflictError -> messageChannel.send(MessageEvent.Error(R.string.errorCommentDelete))\n          else -> messageChannel.send(MessageEvent.Error(R.string.errorGeneral))\n        }\n        commentsState.value = current\n      }\n    }\n  }\n\n  fun loadRatings(episode: Episode) {\n    viewModelScope.launch {\n      try {\n        if (!userTraktManager.isAuthorized()) {\n          ratingState.value = RatingState(rateAllowed = false, rateLoading = false)\n          return@launch\n        }\n        ratingState.value = RatingState(rateAllowed = true, rateLoading = true)\n        val rating = ratingsRepository.shows.loadRating(episode)\n        ratingState.value = RatingState(rateAllowed = true, rateLoading = false, userRating = rating)\n      } catch (error: Throwable) {\n        ratingState.value = RatingState(rateAllowed = false, rateLoading = false)\n      }\n    }\n  }\n\n  val uiState = combine(\n    imageState,\n    imageLoadingState,\n    episodesState,\n    commentsState,\n    commentsLoadingState,\n    signedInState,\n    ratingState,\n    translationState,\n    dateFormatState,\n    commentsDateFormatState,\n    spoilersState\n  ) { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11 ->\n    EpisodeDetailsUiState(\n      image = s1,\n      isImageLoading = s2,\n      episodes = s3,\n      comments = s4,\n      isCommentsLoading = s5,\n      isSignedIn = s6,\n      rating = s7,\n      translation = s8,\n      dateFormat = s9,\n      commentsDateFormat = s10,\n      spoilers = s11\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = EpisodeDetailsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-episodes/src/main/java/com/michaldrabik/ui_episodes/details/cases/EpisodeDetailsSeasonCase.kt",
    "content": "package com.michaldrabik.ui_episodes.details.cases\n\nimport com.michaldrabik.data_local.sources.EpisodesLocalDataSource\nimport com.michaldrabik.data_local.sources.MyShowsLocalDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass EpisodeDetailsSeasonCase @Inject constructor(\n  private val myShowsDataSource: MyShowsLocalDataSource,\n  private val episodesDataSource: EpisodesLocalDataSource,\n  private val mappers: Mappers,\n) {\n\n  suspend fun loadSeason(showId: IdTrakt, episode: Episode, seasonEpisodes: IntArray?): List<Episode> {\n    val isMyShow = myShowsDataSource.checkExists(showId.id)\n    if (!isMyShow) {\n      return seasonEpisodes?.map {\n        Episode.EMPTY.copy(season = episode.season, number = it)\n      } ?: emptyList()\n    }\n\n    val episodes = episodesDataSource.getAllByShowId(showId.id, episode.season)\n      .map { mappers.episode.fromDatabase(it) }\n      .sortedBy { it.number }\n\n    if (episodes.isNotEmpty()) return episodes\n    return seasonEpisodes?.map {\n      Episode.EMPTY.copy(season = episode.season, number = it)\n    } ?: emptyList()\n  }\n}\n"
  },
  {
    "path": "ui-episodes/src/main/java/com/michaldrabik/ui_episodes/details/cases/EpisodeDetailsWatchedCase.kt",
    "content": "package com.michaldrabik.ui_episodes.details.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.sources.EpisodesLocalDataSource\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass EpisodeDetailsWatchedCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val episodesDataSource: EpisodesLocalDataSource,\n) {\n\n  suspend fun isWatched(showId: IdTrakt, episode: Episode): Boolean {\n    return withContext(dispatchers.IO) {\n      val episodes = episodesDataSource.getAllByShowId(showId.id, episode.season)\n      episodes.find {\n        it.idShowTrakt == showId.id &&\n          it.idTrakt == episode.ids.trakt.id\n      }?.isWatched == true\n    }\n  }\n}\n"
  },
  {
    "path": "ui-episodes/src/main/res/color/selector_comment_button.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"@color/colorGrayLight\" android:state_enabled=\"false\" />\n  <item android:color=\"?attr/colorAccent\" />\n</selector>"
  },
  {
    "path": "ui-episodes/src/main/res/drawable/bg_bottom_sheet_placeholder.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners\n    android:topLeftRadius=\"16dp\"\n    android:topRightRadius=\"16dp\"\n    />\n  <solid android:color=\"?attr/colorPlaceholderBackground\" />\n</shape>"
  },
  {
    "path": "ui-episodes/src/main/res/drawable/divider_comments_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <size\n    android:width=\"18dp\"\n    android:height=\"18dp\"\n    />\n</shape>"
  },
  {
    "path": "ui-episodes/src/main/res/layout/view_episode_details.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/episodeDetailsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <ImageView\n    android:id=\"@+id/episodeDetailsImage\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/episodeDetailsImageHeight\"\n    app:layout_constraintBottom_toTopOf=\"@id/episodeDetailsSeparator\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_constraintVertical_bias=\"0\"\n    app:layout_constraintVertical_chainStyle=\"packed\"\n    />\n\n  <ImageView\n    android:id=\"@+id/episodeDetailsImagePlaceholder\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/episodeDetailsImageHeight\"\n    android:background=\"@drawable/bg_bottom_sheet_placeholder\"\n    android:paddingLeft=\"70dp\"\n    android:paddingTop=\"70dp\"\n    android:paddingRight=\"70dp\"\n    android:paddingBottom=\"84dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/episodeDetailsSeparator\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_television\"\n    app:tint=\"?attr/colorPlaceholderIcon\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/episodeDetailsProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    app:layout_constraintBottom_toBottomOf=\"@id/episodeDetailsImage\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/episodeDetailsImage\"\n    />\n\n  <View\n    android:id=\"@+id/episodeDetailsSeparator\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"1dp\"\n    android:background=\"?attr/colorAccent\"\n    app:layout_constraintBottom_toTopOf=\"@id/episodeDetailsRootScroll\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/episodeDetailsImage\"\n    />\n\n  <androidx.core.widget.NestedScrollView\n    android:id=\"@+id/episodeDetailsRootScroll\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:fillViewport=\"true\"\n    android:overScrollMode=\"never\"\n    android:paddingTop=\"@dimen/spaceTiny\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/episodeDetailsSeparator\"\n    >\n\n    <LinearLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:clipChildren=\"false\"\n      android:clipToPadding=\"false\"\n      android:orientation=\"vertical\"\n      android:paddingBottom=\"@dimen/spaceBig\"\n      >\n\n      <com.google.android.material.tabs.TabLayout\n        android:id=\"@+id/episodeDetailsTabs\"\n        style=\"@style/ScrollableTabsEpisodesStyle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"36dp\"\n        android:layout_marginBottom=\"@dimen/spaceTiny\"\n        android:clipToPadding=\"false\"\n        android:overScrollMode=\"never\"\n        android:paddingStart=\"@dimen/spaceSmall\"\n        android:paddingEnd=\"@dimen/spaceSmall\"\n        android:visibility=\"invisible\"\n        tools:visibility=\"visible\"\n        />\n\n      <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginTop=\"@dimen/spaceSmall\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceNormal\"\n        >\n\n        <LinearLayout\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:orientation=\"vertical\"\n          >\n\n          <TextView\n            android:id=\"@+id/episodeDetailsTitle\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginEnd=\"52dp\"\n            android:layout_marginBottom=\"@dimen/spaceMicro\"\n            android:ellipsize=\"end\"\n            android:gravity=\"center_vertical|start\"\n            android:maxLines=\"2\"\n            android:textAlignment=\"viewStart\"\n            android:textColor=\"?android:attr/textColorPrimary\"\n            android:textSize=\"20sp\"\n            android:textStyle=\"bold\"\n            tools:text=\"That is not an example.\"\n            />\n\n          <TextView\n            android:id=\"@+id/episodeDetailsName\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginEnd=\"48dp\"\n            android:ellipsize=\"end\"\n            android:textColor=\"?android:attr/textColorSecondary\"\n            android:textSize=\"12sp\"\n            tools:text=\"S.01 E.99 | Thursday, 23 December 2019 at 15:00 | 23 mins\"\n            />\n\n          <LinearLayout\n            android:id=\"@+id/episodeDetailsRatingLayout\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"@dimen/spaceMicro\"\n            android:gravity=\"center_vertical|start\"\n            android:orientation=\"horizontal\"\n            android:translationX=\"-1dp\"\n            >\n\n            <ImageView\n              android:id=\"@+id/episodeDetailsRatingIcon\"\n              android:layout_width=\"16dp\"\n              android:layout_height=\"match_parent\"\n              android:layout_marginEnd=\"@dimen/spaceMicro\"\n              app:srcCompat=\"@drawable/ic_star\"\n              app:tint=\"?attr/colorAccent\"\n              />\n\n            <TextView\n              android:id=\"@+id/episodeDetailsRating\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"wrap_content\"\n              android:ellipsize=\"end\"\n              android:gravity=\"center_vertical\"\n              android:textColor=\"?android:attr/textColorSecondary\"\n              android:textSize=\"12sp\"\n              tools:text=\"8.2 (1000 votes)\"\n              />\n\n          </LinearLayout>\n\n        </LinearLayout>\n\n        <com.google.android.material.floatingactionbutton.FloatingActionButton\n          android:id=\"@+id/episodeDetailsButton\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:layout_gravity=\"center_vertical|end\"\n          app:backgroundTint=\"?attr/colorAccent\"\n          app:fabCustomSize=\"42dp\"\n          app:srcCompat=\"@drawable/ic_check\"\n          app:tint=\"?attr/textColorOnSurface\"\n          />\n\n      </FrameLayout>\n\n      <TextView\n        android:id=\"@+id/episodeDetailsOverview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:ellipsize=\"end\"\n        android:gravity=\"start\"\n        android:maxLines=\"20\"\n        android:textAlignment=\"viewStart\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"14sp\"\n        tools:targetApi=\"o\"\n        tools:text=\"That is not an example.\"\n        />\n\n      <LinearLayout\n        android:id=\"@+id/episodeDetailsButtons\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginTop=\"@dimen/spaceBig\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:clipChildren=\"false\"\n        android:gravity=\"center_vertical|start\"\n        android:orientation=\"horizontal\"\n        >\n\n        <TextView\n          android:id=\"@+id/episodeDetailsCommentsButton\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"match_parent\"\n          android:layout_marginEnd=\"18dp\"\n          android:drawablePadding=\"6dp\"\n          android:gravity=\"center_vertical\"\n          android:text=\"@string/textLoadCommentsCount\"\n          android:textAllCaps=\"true\"\n          android:textColor=\"?android:textColorPrimary\"\n          app:drawableStartCompat=\"@drawable/ic_comment\"\n          app:drawableTint=\"?android:textColorPrimary\"\n          />\n\n        <TextView\n          android:id=\"@+id/episodeDetailsRateButton\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"match_parent\"\n          android:drawablePadding=\"5dp\"\n          android:gravity=\"center_vertical\"\n          android:text=\"@string/textRate\"\n          android:textAllCaps=\"true\"\n          android:textColor=\"?android:textColorPrimary\"\n          app:drawableStartCompat=\"@drawable/ic_star\"\n          app:drawableTint=\"?android:textColorPrimary\"\n          />\n\n        <ProgressBar\n          android:id=\"@+id/episodeDetailsRateProgress\"\n          style=\"@style/ProgressBar.Dark\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"28dp\"\n          android:layout_gravity=\"center\"\n          android:layout_weight=\"1\"\n          android:visibility=\"gone\"\n          tools:visibility=\"visible\"\n          />\n\n      </LinearLayout>\n\n      <ProgressBar\n        android:id=\"@+id/episodeDetailsCommentsProgress\"\n        style=\"@style/ProgressBar.Accent\"\n        android:layout_width=\"30dp\"\n        android:layout_height=\"30dp\"\n        android:layout_gravity=\"center\"\n        android:layout_marginTop=\"18dp\"\n        android:visibility=\"gone\"\n        tools:visibility=\"visible\"\n        />\n\n      <TextView\n        android:id=\"@+id/episodeDetailsCommentsEmpty\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceBig\"\n        android:layout_marginBottom=\"@dimen/spaceNormal\"\n        android:gravity=\"center\"\n        android:text=\"@string/textNoComments\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"14sp\"\n        android:visibility=\"gone\"\n        tools:visibility=\"visible\"\n        />\n\n      <LinearLayout\n        android:id=\"@+id/episodeDetailsComments\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"260dp\"\n        android:clipToPadding=\"false\"\n        android:divider=\"@drawable/divider_comments_list\"\n        android:orientation=\"vertical\"\n        android:paddingTop=\"@dimen/spaceBig\"\n        android:showDividers=\"middle|end\"\n        android:visibility=\"gone\"\n        tools:visibility=\"gone\"\n        />\n\n    </LinearLayout>\n\n  </androidx.core.widget.NestedScrollView>\n\n  <com.google.android.material.floatingactionbutton.FloatingActionButton\n    android:id=\"@+id/episodeDetailsPostCommentButton\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:layout_marginBottom=\"@dimen/spaceNormal\"\n    android:visibility=\"gone\"\n    app:backgroundTint=\"?attr/colorAccent\"\n    app:fabSize=\"mini\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_add_comment\"\n    app:tint=\"?attr/textColorOnSurface\"\n    tools:visibility=\"visible\"\n    />\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/episodeDetailsSnackbarHost\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"60dp\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-episodes/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"episodeDetailsImageHeight\">220dp</dimen>\n</resources>"
  },
  {
    "path": "ui-episodes/src/main/res/values/integers.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <integer name=\"buttonCommentsWeight\">4</integer>\n</resources>"
  },
  {
    "path": "ui-episodes/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSeasonEpisodeDate\">S.%1$02d E.%2$02d | %3$s</string>\n  <string name=\"textLoadCommentsCount\">Comments (%d)</string>\n  <string name=\"textVotes\">%.1f (%d votes)</string>\n</resources>"
  },
  {
    "path": "ui-episodes/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSeasonEpisodeDate\">الموسم %02d الحلقة %02d | %s</string>\n  <string name=\"textLoadCommentsCount\">تعليقات (%d)</string>\n  <string name=\"textVotes\">%.1f (%d تقييم)</string>\n</resources>\n"
  },
  {
    "path": "ui-episodes/src/main/res/values-de/integers.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <integer name=\"buttonCommentsWeight\">2</integer>\n</resources>"
  },
  {
    "path": "ui-episodes/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSeasonEpisodeDate\">S.%1$02d E.%2$02d | %3$s</string>\n  <string name=\"textLoadCommentsCount\">Kommentare (%d)</string>\n  <string name=\"textVotes\">%.1f (%d Bewertungen)</string>\n</resources>\n"
  },
  {
    "path": "ui-episodes/src/main/res/values-es/integers.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <integer name=\"buttonCommentsWeight\">2</integer>\n</resources>"
  },
  {
    "path": "ui-episodes/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSeasonEpisodeDate\">T.%1$02d E.%2$02d | %3$s</string>\n  <string name=\"textLoadCommentsCount\">Comentarios (%d)</string>\n  <string name=\"textVotes\">%.1f (%d votos)</string>\n</resources>\n"
  },
  {
    "path": "ui-episodes/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSeasonEpisodeDate\">K.%1$02d J.%2$02d | %3$s</string>\n  <string name=\"textLoadCommentsCount\">Kommentit (%d)</string>\n  <string name=\"textVotes\">%.1f (%d ääntä)</string>\n</resources>\n"
  },
  {
    "path": "ui-episodes/src/main/res/values-fr/integers.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <integer name=\"buttonCommentsWeight\">2</integer>\n</resources>"
  },
  {
    "path": "ui-episodes/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSeasonEpisodeDate\">S.%1$02d E.%2$02d | %3$s</string>\n  <string name=\"textLoadCommentsCount\">Commentaires (%d)</string>\n  <string name=\"textVotes\">%.1f (%d votes)</string>\n</resources>\n"
  },
  {
    "path": "ui-episodes/src/main/res/values-it/integers.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <integer name=\"buttonCommentsWeight\">2</integer>\n</resources>"
  },
  {
    "path": "ui-episodes/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSeasonEpisodeDate\">S.%1$02d E.%2$02d | %3$s</string>\n  <string name=\"textLoadCommentsCount\">Commenti (%d)</string>\n  <string name=\"textVotes\">%.1f (%d voti)</string>\n</resources>\n"
  },
  {
    "path": "ui-episodes/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSeasonEpisodeDate\">S.%1$02d E.%2$02d | %3$s</string>\n  <string name=\"textLoadCommentsCount\">Komentarze (%d)</string>\n  <string name=\"textVotes\">%.1f (%d ocen)</string>\n</resources>\n"
  },
  {
    "path": "ui-episodes/src/main/res/values-pt/integers.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <integer name=\"buttonCommentsWeight\">2</integer>\n</resources>"
  },
  {
    "path": "ui-episodes/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSeasonEpisodeDate\">S.%1$02d E.%2$02d | %3$s</string>\n  <string name=\"textLoadCommentsCount\">Comentários (%d)</string>\n  <string name=\"textVotes\">%.1f (%d votos)</string>\n</resources>\n"
  },
  {
    "path": "ui-episodes/src/main/res/values-ru/integers.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <integer name=\"buttonCommentsWeight\">2</integer>\n</resources>"
  },
  {
    "path": "ui-episodes/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSeasonEpisodeDate\">S.%1$02d Е.%2$02d | %3$s</string>\n  <string name=\"textLoadCommentsCount\">Мнения (%d)</string>\n  <string name=\"textVotes\">%.1f (%d голосов)</string>\n</resources>\n"
  },
  {
    "path": "ui-episodes/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"episodeDetailsImageHeight\">320dp</dimen>\n</resources>"
  },
  {
    "path": "ui-episodes/src/main/res/values-tr/integers.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <integer name=\"buttonCommentsWeight\">3</integer>\n</resources>"
  },
  {
    "path": "ui-episodes/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSeasonEpisodeDate\">S.%1$02d E.%2$02d | %3$s</string>\n  <string name=\"textLoadCommentsCount\">Yorumlar (%d)</string>\n  <string name=\"textVotes\">%.1f (%d oy)</string>\n</resources>\n"
  },
  {
    "path": "ui-episodes/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSeasonEpisodeDate\">S.%1$02d E.%2$02d | %3$s</string>\n  <string name=\"textLoadCommentsCount\">Коментарі (%d)</string>\n  <string name=\"textVotes\">%.1f (%d оцінок)</string>\n</resources>\n"
  },
  {
    "path": "ui-episodes/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSeasonEpisodeDate\">M.%1$02d T.%2$02d | %3$s</string>\n  <string name=\"textLoadCommentsCount\">Bình luận (%d)</string>\n  <string name=\"textVotes\">%.1f (%d phiếu)</string>\n</resources>"
  },
  {
    "path": "ui-episodes/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSeasonEpisodeDate\">%1$02d 季 %2$02d 集 | %3$s</string>\n  <string name=\"textLoadCommentsCount\">评论 (%d)</string>\n  <string name=\"textVotes\">%.1f (%d 个评分 )</string>\n</resources>\n"
  },
  {
    "path": "ui-gallery/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-gallery/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_gallery'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':ui-base')\n  implementation project(':repository')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  implementation libs.circleIndicator\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-gallery/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest />\n"
  },
  {
    "path": "ui-gallery/src/main/java/com/michaldrabik/ui_gallery/custom/CustomImagesBottomSheet.kt",
    "content": "package com.michaldrabik.ui_gallery.custom\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.ImageView\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.requireLong\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_gallery.R\nimport com.michaldrabik.ui_gallery.databinding.ViewCustomImagesBinding\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.ImageFamily\nimport com.michaldrabik.ui_model.ImageStatus\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.ImageType.FANART\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_CUSTOM_IMAGE_CLEARED\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_FAMILY\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_MOVIE_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_PICK_MODE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SHOW_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_TYPE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_CUSTOM_IMAGE\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass CustomImagesBottomSheet : BaseBottomSheetFragment(R.layout.view_custom_images) {\n\n  private val viewModel by viewModels<CustomImagesViewModel>()\n  private val binding by viewBinding(ViewCustomImagesBinding::bind)\n\n  private val family by lazy { arguments?.getSerializable(ARG_FAMILY) as ImageFamily }\n  private val showTraktId by lazy { IdTrakt(requireLong(ARG_SHOW_ID)) }\n  private val movieTraktId by lazy { IdTrakt(requireLong(ARG_MOVIE_ID)) }\n\n  private val cornerRadius by lazy { requireContext().dimenToPx(R.dimen.customImagesCorner) }\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      doAfterLaunch = {\n        viewModel.loadPoster(showTraktId, movieTraktId, family)\n        viewModel.loadFanart(showTraktId, movieTraktId, family)\n      }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      viewCustomImagesPosterLayout.onClick { showGallery(POSTER) }\n      viewCustomImagesFanartLayout.onClick { showGallery(FANART) }\n      viewCustomImagesPosterDelete.onClick { viewModel.deletePoster(showTraktId, movieTraktId, family) }\n      viewCustomImagesFanartDelete.onClick { viewModel.deleteFanart(showTraktId, movieTraktId, family) }\n      viewCustomImagesCloseButton.onClick { closeSheet() }\n    }\n  }\n\n  private fun showGallery(type: ImageType) {\n    val bundle = bundleOf(\n      ARG_SHOW_ID to showTraktId.id,\n      ARG_MOVIE_ID to movieTraktId.id,\n      ARG_FAMILY to family,\n      ARG_TYPE to type,\n      ARG_PICK_MODE to true\n    )\n    navigateTo(R.id.actionCustomImagesDialogToArtGallery, bundle)\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun render(uiState: CustomImagesUiState) {\n\n    fun loadImage(url: String, imageView: ImageView, progressView: View, deleteView: View) {\n      progressView.visible()\n      deleteView.gone()\n      Glide.with(requireContext())\n        .load(url)\n        .transform(CenterCrop(), RoundedCorners(cornerRadius))\n        .withSuccessListener {\n          progressView.gone()\n          deleteView.visible()\n        }\n        .withFailListener {\n          progressView.gone()\n          deleteView.gone()\n        }\n        .into(imageView)\n    }\n\n    uiState.run {\n      with(binding) {\n        posterImage?.let {\n          if (it.status == ImageStatus.UNAVAILABLE) {\n            Glide.with(requireContext()).clear(viewCustomImagesPosterImage)\n            viewCustomImagesPosterAddButton.visible()\n            viewCustomImagesPosterDelete.gone()\n            return@let\n          }\n          viewCustomImagesPosterAddButton.gone()\n          loadImage(\n            it.fullFileUrl,\n            viewCustomImagesPosterImage,\n            viewCustomImagesPosterProgress,\n            viewCustomImagesPosterDelete\n          )\n        }\n        fanartImage?.let {\n          if (it.status == ImageStatus.UNAVAILABLE) {\n            Glide.with(requireContext()).clear(viewCustomImagesFanartImage)\n            viewCustomImagesFanartAddButton.visible()\n            viewCustomImagesFanartDelete.gone()\n            setFragmentResult(REQUEST_CUSTOM_IMAGE, bundleOf(ARG_CUSTOM_IMAGE_CLEARED to true))\n            return@let\n          }\n          viewCustomImagesFanartAddButton.gone()\n          loadImage(\n            it.fullFileUrl,\n            viewCustomImagesFanartImage,\n            viewCustomImagesFanartProgress,\n            viewCustomImagesFanartDelete\n          )\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-gallery/src/main/java/com/michaldrabik/ui_gallery/custom/CustomImagesUiState.kt",
    "content": "package com.michaldrabik.ui_gallery.custom\n\nimport com.michaldrabik.ui_model.Image\n\ndata class CustomImagesUiState(\n  val posterImage: Image? = null,\n  val fanartImage: Image? = null\n)\n"
  },
  {
    "path": "ui-gallery/src/main/java/com/michaldrabik/ui_gallery/custom/CustomImagesViewModel.kt",
    "content": "package com.michaldrabik.ui_gallery.custom\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageFamily\nimport com.michaldrabik.ui_model.ImageFamily.MOVIE\nimport com.michaldrabik.ui_model.ImageFamily.SHOW\nimport com.michaldrabik.ui_model.ImageType.FANART\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass CustomImagesViewModel @Inject constructor(\n  private val showImagesProvider: ShowImagesProvider,\n  private val movieImagesProvider: MovieImagesProvider,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val posterImageState = MutableStateFlow<Image?>(null)\n  private val fanartImageState = MutableStateFlow<Image?>(null)\n\n  fun loadPoster(\n    showTraktId: IdTrakt,\n    movieTraktId: IdTrakt,\n    family: ImageFamily,\n  ) {\n    viewModelScope.launch {\n      val image = when (family) {\n        SHOW -> showImagesProvider.findCustomImage(showTraktId.id, POSTER)\n        MOVIE -> movieImagesProvider.findCustomImage(movieTraktId.id, POSTER)\n        else -> error(\"Invalid type\")\n      }\n      posterImageState.value = image\n    }\n  }\n\n  fun loadFanart(\n    showTraktId: IdTrakt,\n    movieTraktId: IdTrakt,\n    family: ImageFamily,\n  ) {\n    viewModelScope.launch {\n      val image = when (family) {\n        SHOW -> showImagesProvider.findCustomImage(showTraktId.id, FANART)\n        MOVIE -> movieImagesProvider.findCustomImage(movieTraktId.id, FANART)\n        else -> error(\"Invalid type\")\n      }\n      fanartImageState.value = image\n    }\n  }\n\n  fun deletePoster(\n    showTraktId: IdTrakt,\n    movieTraktId: IdTrakt,\n    family: ImageFamily,\n  ) {\n    viewModelScope.launch {\n      when (family) {\n        SHOW -> showImagesProvider.deleteCustomImage(showTraktId, family, POSTER)\n        MOVIE -> movieImagesProvider.deleteCustomImage(movieTraktId, family, POSTER)\n        else -> error(\"Invalid type\")\n      }\n      posterImageState.value = Image.createUnavailable(POSTER, family)\n    }\n  }\n\n  fun deleteFanart(\n    showTraktId: IdTrakt,\n    movieTraktId: IdTrakt,\n    family: ImageFamily,\n  ) {\n    viewModelScope.launch {\n      when (family) {\n        SHOW -> showImagesProvider.deleteCustomImage(showTraktId, family, FANART)\n        MOVIE -> movieImagesProvider.deleteCustomImage(movieTraktId, family, FANART)\n        else -> error(\"Invalid type\")\n      }\n      fanartImageState.value = Image.createUnavailable(FANART, family)\n    }\n  }\n\n  val uiState = combine(\n    posterImageState,\n    fanartImageState\n  ) { posterImageState, fanartImageState ->\n    CustomImagesUiState(\n      posterImage = posterImageState,\n      fanartImage = fanartImageState\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = CustomImagesUiState()\n  )\n}\n"
  },
  {
    "path": "ui-gallery/src/main/java/com/michaldrabik/ui_gallery/fanart/ArtGalleryFragment.kt",
    "content": "package com.michaldrabik.ui_gallery.fanart\n\nimport android.annotation.SuppressLint\nimport android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER\nimport android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT\nimport android.content.res.Configuration\nimport android.content.res.Configuration.ORIENTATION_LANDSCAPE\nimport android.content.res.Configuration.ORIENTATION_PORTRAIT\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport androidx.activity.addCallback\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.os.bundleOf\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.colorStateListFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.nextPage\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.openWebUrl\nimport com.michaldrabik.ui_base.utilities.extensions.updateTopMargin\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_gallery.R\nimport com.michaldrabik.ui_gallery.databinding.FragmentArtGalleryBinding\nimport com.michaldrabik.ui_gallery.databinding.ViewGalleryUrlDialogBinding\nimport com.michaldrabik.ui_gallery.fanart.recycler.ArtGalleryAdapter\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.ImageFamily\nimport com.michaldrabik.ui_model.ImageFamily.SHOW\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_FAMILY\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_MOVIE_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_PICK_MODE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SHOW_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_TYPE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_CUSTOM_IMAGE\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\n\n@SuppressLint(\"SetTextI18n\", \"DefaultLocale\", \"SourceLockedOrientationActivity\")\n@AndroidEntryPoint\nclass ArtGalleryFragment : BaseFragment<ArtGalleryViewModel>(R.layout.fragment_art_gallery) {\n\n  companion object {\n    private const val IMAGE_URL_PATTERN = \"(http)?s?:?(//[^\\\"']*\\\\.(?:jpg|jpeg|png))\"\n  }\n\n  override val viewModel by viewModels<ArtGalleryViewModel>()\n  private val binding by viewBinding(FragmentArtGalleryBinding::bind)\n\n  private val showId by lazy { IdTrakt(arguments?.getLong(ARG_SHOW_ID, -1) ?: -1) }\n  private val movieId by lazy { IdTrakt(arguments?.getLong(ARG_MOVIE_ID, -1) ?: -1) }\n  private val family by lazy { arguments?.getSerializable(ARG_FAMILY) as ImageFamily }\n  private val type by lazy { arguments?.getSerializable(ARG_TYPE) as ImageType }\n  private val isPickMode by lazy { arguments?.getBoolean(ARG_PICK_MODE, false) }\n\n  private var galleryAdapter: ArtGalleryAdapter? = null\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    if (type != POSTER) {\n      requireActivity().requestedOrientation = SCREEN_ORIENTATION_FULL_USER\n    }\n    setupView()\n    setupStatusBar()\n\n    viewLifecycleOwner.lifecycleScope.launch {\n      repeatOnLifecycle(Lifecycle.State.STARTED) {\n        with(viewModel) {\n          launch { uiState.collect { render(it) } }\n          val id = if (family == SHOW) showId else movieId\n          loadImages(id, family, type)\n        }\n      }\n    }\n  }\n\n  override fun onDestroyView() {\n    galleryAdapter = null\n    requireActivity().requestedOrientation = SCREEN_ORIENTATION_PORTRAIT\n    super.onDestroyView()\n  }\n\n  override fun onConfigurationChanged(newConfig: Configuration) {\n    super.onConfigurationChanged(newConfig)\n    with(binding) {\n      when (newConfig.orientation) {\n        ORIENTATION_LANDSCAPE -> {\n          val color = requireContext().colorStateListFromAttr(R.attr.textColorOnSurface)\n          artGalleryBackArrow.imageTintList = color\n          artGalleryPagerIndicatorWhite.visible()\n          artGalleryPagerIndicator.gone()\n          artGalleryPagerIndicatorWhite.setViewPager(artGalleryPager)\n        }\n        ORIENTATION_PORTRAIT -> {\n          val color = requireContext().colorStateListFromAttr(android.R.attr.textColorPrimary)\n          artGalleryBackArrow.imageTintList = color\n          artGalleryPagerIndicatorWhite.gone()\n          artGalleryPagerIndicator.visible()\n          artGalleryPagerIndicator.setViewPager(artGalleryPager)\n        }\n        else -> Timber.d(\"Unused orientation\")\n      }\n    }\n  }\n\n  private fun setupView() {\n    with(binding) {\n      artGalleryBackArrow.onClick {\n        if (isPickMode == true) setFragmentResult(REQUEST_CUSTOM_IMAGE, bundleOf())\n        requireActivity().onBackPressed()\n      }\n      artGalleryBrowserIcon.onClick {\n        val currentIndex = artGalleryPager.currentItem\n        val image = galleryAdapter?.getItem(currentIndex)\n        image?.fullFileUrl?.let { openWebUrl(it) }\n      }\n      galleryAdapter = ArtGalleryAdapter(\n        onItemClickListener = { artGalleryPager.nextPage() }\n      )\n      artGalleryPager.run {\n        adapter = galleryAdapter\n        offscreenPageLimit = 2\n        artGalleryPagerIndicator.setViewPager(this)\n        adapter?.registerAdapterDataObserver(artGalleryPagerIndicator.adapterDataObserver)\n      }\n      artGallerySelectButton.run {\n        onClick {\n          val id = if (family == SHOW) showId else movieId\n          val currentImage = galleryAdapter?.getItem(artGalleryPager.currentItem)\n          currentImage?.let {\n            viewModel.saveCustomImage(id, it, family, type)\n          }\n        }\n      }\n      artGalleryUrlButton.onClick { showUrlInput() }\n    }\n  }\n\n  private fun setupStatusBar() {\n    requireView().doOnApplyWindowInsets { _, insets, _, _ ->\n      val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top\n      with(binding) {\n        artGalleryBackArrow.updateTopMargin(inset)\n        artGalleryBrowserIcon.updateTopMargin(inset)\n        artGallerySelectButton.updateTopMargin(inset)\n      }\n    }\n  }\n\n  private fun showUrlInput() {\n\n    fun onUrlInput(input: String) {\n      with(binding) {\n        if (input.matches(IMAGE_URL_PATTERN.toRegex())) {\n          artGalleryUrlProgress.visible()\n          artGalleryUrlButton.gone()\n          artGallerySelectButton.gone()\n          Glide.with(requireContext())\n            .load(input)\n            .withSuccessListener {\n              viewModel.addImageFromUrl(input, family, type)\n              artGalleryUrlProgress.gone()\n              artGalleryUrlButton.visible()\n              artGallerySelectButton.visible()\n            }\n            .withFailListener {\n              showSnack(MessageEvent.Error(R.string.textUrlDialogInvalidImage))\n              artGalleryUrlProgress.gone()\n              artGalleryUrlButton.visible()\n              artGallerySelectButton.visible()\n            }\n            .preload(100, 100)\n        } else {\n          showSnack(MessageEvent.Error(R.string.textUrlDialogInvalidUrl))\n        }\n      }\n    }\n\n    AlertDialog.Builder(requireContext(), R.style.UrlInputDialog).apply {\n      val view = ViewGalleryUrlDialogBinding.inflate(LayoutInflater.from(context), binding.fanartGalleryRoot, false)\n      setView(view.root)\n      setTitle(R.string.textUrlDialogTitle)\n      setPositiveButton(R.string.textOk) { _, _ -> onUrlInput(view.urlDialogInput.text.toString()) }\n      setNegativeButton(R.string.textCancel) { dialog, _ -> dialog.dismiss() }\n      show()\n    }\n  }\n\n  private fun render(uiState: ArtGalleryUiState) {\n    uiState.run {\n      with(binding) {\n        images?.let {\n          galleryAdapter?.setItems(it, type)\n          artGalleryEmptyView.visibleIf(it.isEmpty())\n          artGallerySelectButton.visibleIf(it.isNotEmpty() && isPickMode == true)\n          artGalleryBrowserIcon.visibleIf(it.isNotEmpty() && isPickMode == false)\n          artGalleryUrlButton.visibleIf(isPickMode == true)\n        }\n        pickedImage?.let {\n          it.consume()?.let {\n            setFragmentResult(REQUEST_CUSTOM_IMAGE, bundleOf())\n            requireActivity().onBackPressed()\n          }\n        }\n        artGalleryImagesProgress.visibleIf(isLoading)\n      }\n    }\n  }\n\n  override fun setupBackPressed() {\n    val dispatcher = requireActivity().onBackPressedDispatcher\n    dispatcher.addCallback(viewLifecycleOwner) {\n      if (isPickMode == true) {\n        setFragmentResult(REQUEST_CUSTOM_IMAGE, bundleOf())\n      }\n      isEnabled = false\n      findNavControl()?.popBackStack()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-gallery/src/main/java/com/michaldrabik/ui_gallery/fanart/ArtGalleryUiState.kt",
    "content": "package com.michaldrabik.ui_gallery.fanart\n\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\n\ndata class ArtGalleryUiState(\n  val images: List<Image>? = null,\n  val type: ImageType = ImageType.FANART,\n  val pickedImage: Event<Image>? = null,\n  val isLoading: Boolean = false,\n)\n"
  },
  {
    "path": "ui-gallery/src/main/java/com/michaldrabik/ui_gallery/fanart/ArtGalleryViewModel.kt",
    "content": "package com.michaldrabik.ui_gallery.fanart\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_gallery.fanart.cases.ArtLoadImagesCase\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageFamily\nimport com.michaldrabik.ui_model.ImageSource.CUSTOM\nimport com.michaldrabik.ui_model.ImageType\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ArtGalleryViewModel @Inject constructor(\n  private val imagesCase: ArtLoadImagesCase,\n) : ViewModel() {\n\n  private val imagesState = MutableStateFlow<List<Image>?>(null)\n  private val typeState = MutableStateFlow(ImageType.FANART)\n  private val pickedImageState = MutableStateFlow<Event<Image>?>(null)\n  private val loadingState = MutableStateFlow(false)\n\n  fun loadImages(id: IdTrakt, family: ImageFamily, type: ImageType) {\n    viewModelScope.launch {\n      try {\n        loadingState.value = true\n        val allImages = imagesCase.loadImages(id, family, type)\n        imagesState.value = allImages\n        typeState.value = type\n        loadingState.value = false\n      } catch (t: Throwable) {\n        loadingState.value = false\n      }\n    }\n  }\n\n  fun saveCustomImage(id: IdTrakt, image: Image, family: ImageFamily, type: ImageType) {\n    viewModelScope.launch {\n      imagesCase.saveCustomImage(id, image, family, type)\n      pickedImageState.value = Event(image)\n    }\n  }\n\n  fun addImageFromUrl(imageUrl: String, family: ImageFamily, type: ImageType) {\n    if (imageUrl.isBlank()) return\n\n    val currentImages = uiState.value.images?.toMutableList() ?: mutableListOf()\n    val image = Image.createAvailable(Ids.EMPTY, type, family, imageUrl.trim(), CUSTOM)\n    currentImages.add(0, image)\n\n    imagesState.value = currentImages\n  }\n\n  val uiState = combine(\n    imagesState,\n    typeState,\n    pickedImageState,\n    loadingState\n  ) { s1, s2, s3, s4 ->\n    ArtGalleryUiState(\n      images = s1,\n      type = s2,\n      pickedImage = s3,\n      isLoading = s4\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ArtGalleryUiState()\n  )\n}\n"
  },
  {
    "path": "ui-gallery/src/main/java/com/michaldrabik/ui_gallery/fanart/cases/ArtLoadImagesCase.kt",
    "content": "package com.michaldrabik.ui_gallery.fanart.cases\n\nimport com.michaldrabik.common.Config.FANART_GALLERY_IMAGES_LIMIT\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageFamily\nimport com.michaldrabik.ui_model.ImageFamily.MOVIE\nimport com.michaldrabik.ui_model.ImageFamily.SHOW\nimport com.michaldrabik.ui_model.ImageStatus.AVAILABLE\nimport com.michaldrabik.ui_model.ImageType\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ArtLoadImagesCase @Inject constructor(\n  private val showsRepository: ShowsRepository,\n  private val moviesRepository: MoviesRepository,\n  private val showImagesProvider: ShowImagesProvider,\n  private val movieImagesProvider: MovieImagesProvider,\n) {\n\n  suspend fun loadImages(\n    id: IdTrakt,\n    family: ImageFamily,\n    type: ImageType\n  ): List<Image> {\n    val images = mutableListOf<Image>()\n    val initialImage = loadInitialImage(id, family, type)\n    if (initialImage.status == AVAILABLE) {\n      images.add(initialImage)\n    }\n\n    var remoteImages: List<Image> = emptyList()\n    if (family == SHOW) {\n      val show = showsRepository.detailsShow.load(id)\n      remoteImages = showImagesProvider.loadRemoteImages(show, type)\n    } else if (family == MOVIE) {\n      val movie = moviesRepository.movieDetails.load(id)\n      remoteImages = movieImagesProvider.loadRemoteImages(movie, type)\n    }\n    images.addAll(remoteImages.filter { it.fullFileUrl != initialImage.fullFileUrl })\n    return images.take(FANART_GALLERY_IMAGES_LIMIT)\n  }\n\n  private suspend fun loadInitialImage(id: IdTrakt, family: ImageFamily, type: ImageType) =\n    when (family) {\n      SHOW -> {\n        val show = showsRepository.detailsShow.load(id)\n        showImagesProvider.findCachedImage(show, type)\n      }\n      MOVIE -> {\n        val movie = moviesRepository.movieDetails.load(id)\n        movieImagesProvider.findCachedImage(movie, type)\n      }\n      else -> throw IllegalStateException()\n    }\n\n  suspend fun saveCustomImage(id: IdTrakt, image: Image, family: ImageFamily, type: ImageType) {\n    when (family) {\n      SHOW -> showImagesProvider.saveCustomImage(id, image, family, type)\n      MOVIE -> movieImagesProvider.saveCustomImage(id, image, family, type)\n      else -> error(\"Invalid image family\")\n    }\n  }\n}\n"
  },
  {
    "path": "ui-gallery/src/main/java/com/michaldrabik/ui_gallery/fanart/recycler/ArtGalleryAdapter.kt",
    "content": "package com.michaldrabik.ui_gallery.fanart.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_gallery.fanart.recycler.views.ArtGalleryFanartView\nimport com.michaldrabik.ui_gallery.fanart.recycler.views.ArtGalleryPosterView\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.ImageType.FANART\nimport com.michaldrabik.ui_model.ImageType.FANART_WIDE\nimport com.michaldrabik.ui_model.ImageType.POSTER\n\nclass ArtGalleryAdapter(\n  val onItemClickListener: (() -> Unit)\n) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n  companion object {\n    private const val VIEW_TYPE_POSTER = 0\n    private const val VIEW_TYPE_FANART = 1\n  }\n\n  private lateinit var type: ImageType\n  private val asyncDiffer = AsyncListDiffer(this, ImageItemDiffCallback())\n\n  fun setItems(items: List<Image>, type: ImageType) {\n    this.type = type\n    asyncDiffer.submitList(items)\n  }\n\n  fun getItem(index: Int) = asyncDiffer.currentList.getOrNull(index)\n\n  override fun getItemViewType(position: Int) = when (type) {\n    POSTER -> VIEW_TYPE_POSTER\n    FANART, FANART_WIDE -> VIEW_TYPE_FANART\n    else -> throw Error(\"Invalid type\")\n  }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    when (viewType) {\n      VIEW_TYPE_POSTER -> ViewHolderShow(\n        ArtGalleryPosterView(parent.context).apply {\n          onItemClickListener = this@ArtGalleryAdapter.onItemClickListener\n        }\n      )\n      VIEW_TYPE_FANART -> ViewHolderShow(\n        ArtGalleryFanartView(parent.context).apply {\n          onItemClickListener = this@ArtGalleryAdapter.onItemClickListener\n        }\n      )\n      else -> error(\"Invalid type\")\n    }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    when (val itemView = holder.itemView) {\n      is ArtGalleryPosterView -> itemView.bind(asyncDiffer.currentList[position])\n      is ArtGalleryFanartView -> itemView.bind(asyncDiffer.currentList[position])\n    }\n  }\n\n  class ViewHolderShow(itemView: View) : RecyclerView.ViewHolder(itemView)\n\n  override fun getItemCount() = asyncDiffer.currentList.size\n}\n"
  },
  {
    "path": "ui-gallery/src/main/java/com/michaldrabik/ui_gallery/fanart/recycler/ImageItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_gallery.fanart.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\nimport com.michaldrabik.ui_model.Image\n\nclass ImageItemDiffCallback : DiffUtil.ItemCallback<Image>() {\n\n  override fun areItemsTheSame(oldItem: Image, newItem: Image) =\n    oldItem.id == newItem.id\n\n  override fun areContentsTheSame(oldItem: Image, newItem: Image) =\n    oldItem == newItem\n}\n"
  },
  {
    "path": "ui-gallery/src/main/java/com/michaldrabik/ui_gallery/fanart/recycler/views/ArtGalleryFanartView.kt",
    "content": "package com.michaldrabik.ui_gallery.fanart.recycler.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.widget.FrameLayout\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_gallery.databinding.ViewGalleryFanartImageBinding\nimport com.michaldrabik.ui_model.Image\n\nclass ArtGalleryFanartView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewGalleryFanartImageBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n  }\n\n  var onItemClickListener: (() -> Unit)? = null\n\n  fun bind(image: Image) {\n    clear()\n    with(binding) {\n      viewGalleryFanarImage.onClick { onItemClickListener?.invoke() }\n      viewGalleryFanarImageProgress.visible()\n    }\n    loadImage(image)\n  }\n\n  private fun loadImage(image: Image) {\n    with(binding) {\n      Glide.with(this@ArtGalleryFanartView)\n        .load(image.fullFileUrl)\n        .withFailListener { viewGalleryFanarImageProgress.gone() }\n        .withSuccessListener { viewGalleryFanarImageProgress.gone() }\n        .into(viewGalleryFanarImage)\n    }\n  }\n\n  private fun clear() {\n    binding.viewGalleryFanarImageProgress.gone()\n    Glide.with(this)\n  }\n}\n"
  },
  {
    "path": "ui-gallery/src/main/java/com/michaldrabik/ui_gallery/fanart/recycler/views/ArtGalleryPosterView.kt",
    "content": "package com.michaldrabik.ui_gallery.fanart.recycler.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.widget.FrameLayout\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_gallery.R\nimport com.michaldrabik.ui_gallery.databinding.ViewGalleryPosterImageBinding\nimport com.michaldrabik.ui_model.Image\n\nclass ArtGalleryPosterView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewGalleryPosterImageBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n  }\n\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.mediaTileCorner) }\n  var onItemClickListener: (() -> Unit)? = null\n\n  fun bind(image: Image) {\n    clear()\n    with(binding) {\n      viewGalleryPosterImage.onClick { onItemClickListener?.invoke() }\n      viewGalleryPosterImageProgress.visible()\n    }\n    loadImage(image)\n  }\n\n  private fun loadImage(image: Image) {\n    with(binding) {\n      Glide.with(this@ArtGalleryPosterView)\n        .load(image.fullFileUrl)\n        .transform(CenterCrop(), RoundedCorners(cornerRadius))\n        .withFailListener { viewGalleryPosterImageProgress.gone() }\n        .withSuccessListener { viewGalleryPosterImageProgress.gone() }\n        .into(viewGalleryPosterImage)\n    }\n  }\n\n  private fun clear() {\n    binding.viewGalleryPosterImageProgress.gone()\n    Glide.with(this)\n  }\n}\n"
  },
  {
    "path": "ui-gallery/src/main/java/com/michaldrabik/ui_gallery/fanart/recycler/views/ArtGalleryUrlView.kt",
    "content": "package com.michaldrabik.ui_gallery.fanart.recycler.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_gallery.R\n\nclass ArtGalleryUrlView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  init {\n    inflate(context, R.layout.view_gallery_url_dialog, this)\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n  }\n}\n"
  },
  {
    "path": "ui-gallery/src/main/res/color/selector_url_input_layout.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?android:attr/textColorPrimary\" android:state_focused=\"true\" />\n  <item android:color=\"?android:attr/textColorSecondary\" />\n</selector>"
  },
  {
    "path": "ui-gallery/src/main/res/drawable/bg_custom_image_frame.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <solid android:color=\"?attr/colorPlaceholderBackground\" />\n  <stroke\n    android:width=\"1dp\"\n    android:color=\"?attr/colorPlaceholderIcon\"\n    />\n  <corners android:radius=\"@dimen/customImagesCorner\" />\n</shape>"
  },
  {
    "path": "ui-gallery/src/main/res/drawable/bg_delete_circle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"oval\"\n  >\n  <solid android:color=\"@color/colorBlackTranslucent\" />\n</shape>"
  },
  {
    "path": "ui-gallery/src/main/res/drawable/bg_indicator_circle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"oval\"\n  >\n  <solid android:color=\"@color/colorWhite\" />\n</shape>"
  },
  {
    "path": "ui-gallery/src/main/res/drawable/ic_custom_image2x.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"36dp\"\n  android:height=\"36dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24\"\n  android:viewportHeight=\"24\"\n  >\n  <path\n    android:fillColor=\"@android:color/white\"\n    android:pathData=\"M18,13v7L4,20L4,6h5.02c0.05,-0.71 0.22,-1.38 0.48,-2L4,4c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-5l-2,-2zM16.5,18h-11l2.75,-3.53 1.96,2.36 2.75,-3.54zM19.3,8.89c0.44,-0.7 0.7,-1.51 0.7,-2.39C20,4.01 17.99,2 15.5,2S11,4.01 11,6.5s2.01,4.5 4.49,4.5c0.88,0 1.7,-0.26 2.39,-0.7L21,13.42 22.42,12 19.3,8.89zM15.5,9C14.12,9 13,7.88 13,6.5S14.12,4 15.5,4 18,5.12 18,6.5 16.88,9 15.5,9z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-gallery/src/main/res/drawable/ic_delete.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8.46,11.88l1.41,-1.41L12,12.59l2.12,-2.12 1.41,1.41L13.41,14l2.12,2.12 -1.41,1.41L12,15.41l-2.12,2.12 -1.41,-1.41L10.59,14l-2.13,-2.12zM15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4z\"/>\n</vector>\n"
  },
  {
    "path": "ui-gallery/src/main/res/drawable/ic_download.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z\"/>\n</vector>\n"
  },
  {
    "path": "ui-gallery/src/main/res/drawable-notnight/bg_indicator_circle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"oval\"\n  >\n  <solid android:color=\"@color/colorBlue\" />\n</shape>"
  },
  {
    "path": "ui-gallery/src/main/res/layout/fragment_art_gallery.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/fanartGalleryRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  >\n\n  <androidx.viewpager2.widget.ViewPager2\n    android:id=\"@+id/artGalleryPager\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n  <ImageView\n    android:id=\"@+id/artGalleryBackArrow\"\n    android:layout_width=\"@dimen/backArrowSize\"\n    android:layout_height=\"@dimen/backArrowSize\"\n    android:layout_marginStart=\"@dimen/galleryBackArrowMargin\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:padding=\"@dimen/backArrowPadding\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_arrow_back\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n  <ImageView\n    android:id=\"@+id/artGalleryBrowserIcon\"\n    android:layout_width=\"@dimen/backArrowSize\"\n    android:layout_height=\"@dimen/backArrowSize\"\n    android:padding=\"16dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_open_browser\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    tools:visibility=\"visible\"\n    />\n\n  <me.relex.circleindicator.CircleIndicator3\n    android:id=\"@+id/artGalleryPagerIndicator\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"12dp\"\n    android:layout_gravity=\"center_horizontal|bottom\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    app:ci_drawable=\"@drawable/bg_indicator_circle\"\n    app:ci_drawable_unselected=\"@drawable/bg_indicator_circle\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    />\n\n  <me.relex.circleindicator.CircleIndicator3\n    android:id=\"@+id/artGalleryPagerIndicatorWhite\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"12dp\"\n    android:layout_gravity=\"center_horizontal|bottom\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    />\n\n  <TextView\n    android:id=\"@+id/artGalleryEmptyView\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:text=\"@string/textGalleryEmpty\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"16sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/artGallerySelectButton\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:layout_marginBottom=\"40dp\"\n    android:backgroundTint=\"?attr/colorAccent\"\n    android:minWidth=\"120dp\"\n    android:text=\"@string/textSelect\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toStartOf=\"@id/artGalleryUrlButton\"\n    app:layout_constraintHorizontal_chainStyle=\"packed\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/artGalleryUrlButton\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"48dp\"\n    android:layout_marginBottom=\"40dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/artGallerySelectButton\"\n    app:srcCompat=\"@drawable/ic_download\"\n    app:tint=\"?attr/colorAccent\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/artGalleryUrlProgress\"\n    style=\"@style/ProgressBar.Accent\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"40dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/artGalleryImagesProgress\"\n    style=\"@style/ProgressBar\"\n    android:layout_width=\"20dp\"\n    android:layout_height=\"20dp\"\n    android:layout_margin=\"@dimen/spaceMedium\"\n    android:indeterminateTint=\"?android:attr/textColorPrimary\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "ui-gallery/src/main/res/layout/view_custom_images.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/viewCustomImagesRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  android:clipToPadding=\"false\"\n  android:paddingStart=\"@dimen/spaceNormal\"\n  android:paddingTop=\"@dimen/spaceMedium\"\n  android:paddingEnd=\"@dimen/spaceNormal\"\n  android:paddingBottom=\"@dimen/spaceNormal\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <TextView\n    android:id=\"@+id/viewCustomImagesTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"start\"\n    android:text=\"@string/textSetCustomImages\"\n    android:textAlignment=\"viewStart\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewCustomImagesSubTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"start\"\n    android:text=\"@string/textSetCustomImagesDescription\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewCustomImagesTitle\"\n    />\n\n  <FrameLayout\n    android:id=\"@+id/viewCustomImagesPosterLayout\"\n    android:layout_width=\"@dimen/customImagesPosterWidth\"\n    android:layout_height=\"@dimen/customImagesPosterHeight\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:background=\"@drawable/bg_custom_image_frame\"\n    android:elevation=\"@dimen/elevationTiny\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewCustomImagesFanartLayout\"\n    app:layout_constraintHorizontal_bias=\"0\"\n    app:layout_constraintHorizontal_chainStyle=\"spread\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewCustomImagesSubTitle\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewCustomImagesPosterAddButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center\"\n      android:drawablePadding=\"@dimen/spaceTiny\"\n      android:gravity=\"center\"\n      android:text=\"@string/textPoster\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?attr/colorPlaceholderIcon\"\n      android:textSize=\"16sp\"\n      app:drawableTint=\"?attr/colorPlaceholderIcon\"\n      app:drawableTopCompat=\"@drawable/ic_custom_image2x\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewCustomImagesPosterImage\"\n      android:layout_width=\"@dimen/customImagesPosterWidth\"\n      android:layout_height=\"@dimen/customImagesPosterHeight\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/viewCustomImagesPosterProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"36dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewCustomImagesPosterDelete\"\n      android:layout_width=\"30dp\"\n      android:layout_height=\"30dp\"\n      android:layout_gravity=\"end\"\n      android:layout_margin=\"6dp\"\n      android:background=\"@drawable/bg_delete_circle\"\n      android:padding=\"5dp\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_delete\"\n      app:tint=\"?attr/textColorOnSurface\"\n      tools:visibility=\"visible\"\n      />\n\n  </FrameLayout>\n\n  <FrameLayout\n    android:id=\"@+id/viewCustomImagesFanartLayout\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"@dimen/customImagesFanartHeight\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:background=\"@drawable/bg_custom_image_frame\"\n    android:elevation=\"@dimen/elevationTiny\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewCustomImagesPosterLayout\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewCustomImagesSubTitle\"\n    app:layout_constraintWidth_max=\"@dimen/customImagesFanartWidth\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewCustomImagesFanartAddButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center\"\n      android:drawablePadding=\"@dimen/spaceTiny\"\n      android:gravity=\"center\"\n      android:text=\"@string/textFanart\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?attr/colorPlaceholderIcon\"\n      android:textSize=\"16sp\"\n      app:drawableTint=\"?attr/colorPlaceholderIcon\"\n      app:drawableTopCompat=\"@drawable/ic_custom_image2x\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewCustomImagesFanartImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/viewCustomImagesFanartProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"36dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewCustomImagesFanartDelete\"\n      android:layout_width=\"30dp\"\n      android:layout_height=\"30dp\"\n      android:layout_gravity=\"end\"\n      android:layout_margin=\"6dp\"\n      android:background=\"@drawable/bg_delete_circle\"\n      android:padding=\"5dp\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_delete\"\n      app:tint=\"?attr/textColorOnSurface\"\n      tools:visibility=\"visible\"\n      />\n\n  </FrameLayout>\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewCustomImagesCloseButton\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:backgroundTint=\"?attr/colorAccent\"\n    android:gravity=\"center\"\n    android:text=\"@string/textClose\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewCustomImagesPosterLayout\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-gallery/src/main/res/layout/view_gallery_fanart_image.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <ImageView\n    android:id=\"@+id/viewGalleryFanarImage\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:scaleType=\"fitCenter\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/viewGalleryFanarImageProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    />\n</merge>"
  },
  {
    "path": "ui-gallery/src/main/res/layout/view_gallery_poster_image.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <ImageView\n    android:id=\"@+id/viewGalleryPosterImage\"\n    android:layout_width=\"@dimen/galleryPosterWidth\"\n    android:layout_height=\"@dimen/galleryPosterHeight\"\n    android:layout_gravity=\"center\"\n    android:background=\"@drawable/bg_media_view_elevation\"\n    android:elevation=\"@dimen/elevationTiny\"\n    android:scaleType=\"fitCenter\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/viewGalleryPosterImageProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:translationZ=\"@dimen/elevationTiny\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-gallery/src/main/res/layout/view_gallery_url_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipToPadding=\"false\"\n  android:focusable=\"true\"\n  android:focusableInTouchMode=\"true\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <com.google.android.material.textfield.TextInputLayout\n    style=\"@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:minWidth=\"300dp\"\n    app:boxStrokeColor=\"@color/selector_url_input_layout\"\n    app:hintTextColor=\"?android:attr/textColorPrimary\"\n    >\n\n    <com.google.android.material.textfield.TextInputEditText\n      android:id=\"@+id/urlDialogInput\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:hint=\"@string/textUrlDialogHint\"\n      android:imeOptions=\"actionDone\"\n      android:inputType=\"text\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textColorHint=\"?android:attr/textColorPrimary\"\n      android:textCursorDrawable=\"@null\"\n      />\n\n  </com.google.android.material.textfield.TextInputLayout>\n\n</FrameLayout>"
  },
  {
    "path": "ui-gallery/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"customImagesPosterHeight\">150dp</dimen>\n  <dimen name=\"customImagesPosterWidth\">100dp</dimen>\n\n  <dimen name=\"customImagesFanartHeight\">150dp</dimen>\n  <dimen name=\"customImagesFanartWidth\">225dp</dimen>\n\n  <dimen name=\"galleryPosterHeight\">200dp</dimen>\n  <dimen name=\"galleryPosterWidth\">133dp</dimen>\n  <dimen name=\"galleryBackArrowMargin\">0dp</dimen>\n\n  <dimen name=\"customImagesCorner\">6dp</dimen>\n</resources>"
  },
  {
    "path": "ui-gallery/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPoster\">Poster</string>\n  <string name=\"textFanart\">Fanart</string>\n  <string name=\"textGalleryEmpty\">No images available</string>\n\n  <string name=\"textUrlDialogTitle\">Download Image</string>\n  <string name=\"textUrlDialogHint\">Image\\'s URL</string>\n  <string name=\"textUrlDialogInvalidUrl\">Invalid image URL format.\\nExample: https://test.com/image.jpg</string>\n  <string name=\"textUrlDialogInvalidImage\">Sorry. This image could not be loaded.</string>\n\n</resources>\n"
  },
  {
    "path": "ui-gallery/src/main/res/values/styles.xml",
    "content": "<resources>\n\n  <style name=\"UrlInputDialog\" parent=\"@style/Theme.Material3.DayNight.Dialog\">\n    <item name=\"android:windowBackground\">@color/colorPrimary</item>\n    <item name=\"colorAccent\">@color/colorAccent</item>\n    <item name=\"colorPrimary\">@color/colorAccent</item>\n    <item name=\"buttonBarNegativeButtonStyle\">@style/UrlInputDialog.NegativeButton</item>\n  </style>\n\n  <style name=\"UrlInputDialog.NegativeButton\" parent=\"Widget.MaterialComponents.Button.TextButton.Dialog\">\n    <item name=\"android:textColor\">@color/colorGrayLight</item>\n    <item name=\"cornerRadius\">100dp</item>\n  </style>\n\n</resources>"
  },
  {
    "path": "ui-gallery/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPoster\">ملصق</string>\n  <string name=\"textFanart\">فان أرت</string>\n  <string name=\"textGalleryEmpty\">لا توجد صور</string>\n  <string name=\"textUrlDialogTitle\">تحميل صورة</string>\n  <string name=\"textUrlDialogHint\">رابط الصورة</string>\n  <string name=\"textUrlDialogInvalidUrl\">رابط الصورة غير صالح.\\nتأكد أنه بالصيغة التالية: https://test.com/image.jpg</string>\n  <string name=\"textUrlDialogInvalidImage\">عفوًا، لا يمكن تحميل الصورة.</string>\n</resources>\n"
  },
  {
    "path": "ui-gallery/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPoster\">Poster</string>\n  <string name=\"textFanart\">Fanart</string>\n  <string name=\"textGalleryEmpty\">Keine Bilder verfügbar</string>\n  <string name=\"textUrlDialogTitle\">Bild herunterladen</string>\n  <string name=\"textUrlDialogHint\">Bildadresse</string>\n  <string name=\"textUrlDialogInvalidUrl\">Ungültiges Bild URL-Format.\\nBeispiel: https://test.com/image.jpg</string>\n  <string name=\"textUrlDialogInvalidImage\">Entschuldigung. Das Bild konnte nicht geladen werden.</string>\n</resources>\n"
  },
  {
    "path": "ui-gallery/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPoster\">Póster</string>\n  <string name=\"textFanart\">Fanart</string>\n  <string name=\"textGalleryEmpty\">No hay imágenes disponibles</string>\n  <string name=\"textUrlDialogTitle\">Descargar Imagen</string>\n  <string name=\"textUrlDialogHint\">URL de la imagen</string>\n  <string name=\"textUrlDialogInvalidUrl\">Formato de URL de imagen no válido.\\nEjemplo: https://test.com/image.jpg</string>\n  <string name=\"textUrlDialogInvalidImage\">Lo sentimos. Esta imagen no se ha podido cargar.</string>\n</resources>\n"
  },
  {
    "path": "ui-gallery/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPoster\">Juliste</string>\n  <string name=\"textFanart\">Fanitaide</string>\n  <string name=\"textGalleryEmpty\">Kuvia ei ole saatavilla</string>\n  <string name=\"textUrlDialogTitle\">Lataa kuva</string>\n  <string name=\"textUrlDialogHint\">Kuvan URL-osoite</string>\n  <string name=\"textUrlDialogInvalidUrl\">Kuvan URL-osoitteen muoto on virheellinen.\\nEsimerkki: https://esimerkki.fi/kuva.jpg</string>\n  <string name=\"textUrlDialogInvalidImage\">Valitettavasti kuvan lataus ei onnistunut.</string>\n</resources>\n"
  },
  {
    "path": "ui-gallery/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPoster\">Affiche</string>\n  <string name=\"textFanart\">Fanart</string>\n  <string name=\"textGalleryEmpty\">Aucune image disponible</string>\n  <string name=\"textUrlDialogTitle\">Télécharger l\\'image</string>\n  <string name=\"textUrlDialogHint\">URL de l’image</string>\n  <string name=\"textUrlDialogInvalidUrl\">URL d\\'image invalide.\\nExemple : https://test.com/image.jpg</string>\n  <string name=\"textUrlDialogInvalidImage\">Désolé, cette image n\\'a pu être chargée.</string>\n</resources>\n"
  },
  {
    "path": "ui-gallery/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPoster\">Poster</string>\n  <string name=\"textFanart\">Fanart</string>\n  <string name=\"textGalleryEmpty\">Nessuna immagine disponibile</string>\n  <string name=\"textUrlDialogTitle\">Scarica immagine</string>\n  <string name=\"textUrlDialogHint\">URL dell\\'immagine</string>\n  <string name=\"textUrlDialogInvalidUrl\">Formato URL immagine non valido.\\nEsempio: https://test.com/image.jpg</string>\n  <string name=\"textUrlDialogInvalidImage\">Siamo spiacenti. Questa immagine non può essere caricata.</string>\n</resources>\n"
  },
  {
    "path": "ui-gallery/src/main/res/values-notnight/styles.xml",
    "content": "<resources>\n\n  <style name=\"UrlInputDialog\" parent=\"@style/Theme.Material3.DayNight.Dialog\">\n    <item name=\"android:windowBackground\">@color/colorPrimary</item>\n    <item name=\"android:textColor\">@color/colorBlue</item>\n    <item name=\"colorAccent\">@color/colorAccent</item>\n    <item name=\"colorPrimary\">@color/colorAccent</item>\n    <item name=\"android:textColorSecondary\">@color/colorBlueLight</item>\n    <item name=\"android:textColorPrimary\">@color/colorBlue</item>\n    <item name=\"buttonBarNegativeButtonStyle\">@style/AlertDialog.NegativeButton</item>\n  </style>\n\n  <style name=\"UrlInputDialog.NegativeButton\" parent=\"Widget.MaterialComponents.Button.TextButton.Dialog\">\n    <item name=\"android:textColor\">@color/colorGrayLight</item>\n    <item name=\"cornerRadius\">100dp</item>\n  </style>\n</resources>"
  },
  {
    "path": "ui-gallery/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPoster\">Plakat</string>\n  <string name=\"textFanart\">Fanart</string>\n  <string name=\"textGalleryEmpty\">Brak dostępnych obrazów</string>\n  <string name=\"textUrlDialogTitle\">Pobierz Grafikę</string>\n  <string name=\"textUrlDialogHint\">URL Grafiki</string>\n  <string name=\"textUrlDialogInvalidUrl\">Nieprawidłowy format URL grafiki.\\nPrzykład: https://test.com/image.jpg</string>\n  <string name=\"textUrlDialogInvalidImage\">Ten obrazek nie mógł zostać załadowany.</string>\n</resources>\n"
  },
  {
    "path": "ui-gallery/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPoster\">Pôster</string>\n  <string name=\"textFanart\">Fanart</string>\n  <string name=\"textGalleryEmpty\">Nenhuma imagem disponível</string>\n  <string name=\"textUrlDialogTitle\">Baixar imagem</string>\n  <string name=\"textUrlDialogHint\">URL da imagem</string>\n  <string name=\"textUrlDialogInvalidUrl\">Formato URL da imagem inválido.\\nExemplo: https://teste.com/imagem.jpg</string>\n  <string name=\"textUrlDialogInvalidImage\">Infelizmente, esta imagem não pôde ser carregada.</string>\n</resources>\n"
  },
  {
    "path": "ui-gallery/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPoster\">Постер</string>\n  <string name=\"textFanart\">Фан-арт</string>\n  <string name=\"textGalleryEmpty\">Нет доступных изображений</string>\n  <string name=\"textUrlDialogTitle\">Скачать изображение</string>\n  <string name=\"textUrlDialogHint\">Ссылка на изображение</string>\n  <string name=\"textUrlDialogInvalidUrl\">Неверный формат URL изображения.\\nПример: https://test.com/image.jpg</string>\n  <string name=\"textUrlDialogInvalidImage\">Извините, данное изображение не может быть загружено.</string>\n</resources>\n"
  },
  {
    "path": "ui-gallery/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"galleryBackArrowMargin\">8dp</dimen>\n</resources>"
  },
  {
    "path": "ui-gallery/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPoster\">Afiş</string>\n  <string name=\"textFanart\">Hayran Çalışması</string>\n  <string name=\"textGalleryEmpty\">Kullanılabilir fotoğraf yok</string>\n  <string name=\"textUrlDialogTitle\">Fotoğraf İndir</string>\n  <string name=\"textUrlDialogHint\">Fotoğrafın URL\\'si</string>\n  <string name=\"textUrlDialogInvalidUrl\">Geçersiz URL formatı.\\nÖrnek: https://test.com/image.jpg</string>\n  <string name=\"textUrlDialogInvalidImage\">Üzgünüz. Bu fotoğraf yüklenemedi.</string>\n</resources>\n"
  },
  {
    "path": "ui-gallery/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPoster\">Постер</string>\n  <string name=\"textFanart\">Фанарт</string>\n  <string name=\"textGalleryEmpty\">Немає доступних зображень</string>\n  <string name=\"textUrlDialogTitle\">Завантажити зображення</string>\n  <string name=\"textUrlDialogHint\">Посилання на зображення</string>\n  <string name=\"textUrlDialogInvalidUrl\">Невірний формат посилання на зображення.\\nПриклад: https://test.com/image.jpg</string>\n  <string name=\"textUrlDialogInvalidImage\">Вибачте, це зображення не вдалося завантажити.</string>\n</resources>\n"
  },
  {
    "path": "ui-gallery/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPoster\">Áp phích</string>\n  <string name=\"textFanart\">Fanart</string>\n  <string name=\"textGalleryEmpty\">Không có hình ảnh nào</string>\n\n  <string name=\"textUrlDialogTitle\">Tải hình ảnh</string>\n  <string name=\"textUrlDialogHint\">URL của hình ảnh</string>\n  <string name=\"textUrlDialogInvalidUrl\">Định dạng URL hình ảnh không hợp lệ.\\Ví dụ: https://test.com/image.jpg</string>\n  <string name=\"textUrlDialogInvalidImage\">Lấy làm tiếc. Không thể tải hình ảnh này.</string>\n\n</resources>\n"
  },
  {
    "path": "ui-gallery/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPoster\">海报</string>\n  <string name=\"textFanart\">粉丝作品</string>\n  <string name=\"textGalleryEmpty\">暂未能获取图片</string>\n  <string name=\"textUrlDialogTitle\">下载图片</string>\n  <string name=\"textUrlDialogHint\">图片 URL</string>\n  <string name=\"textUrlDialogInvalidUrl\">图片 URL 格式无效。\\n正确示例：https://test.com/image.jpg</string>\n  <string name=\"textUrlDialogInvalidImage\">抱歉。无法加载此图片。</string>\n</resources>\n"
  },
  {
    "path": "ui-lists/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-lists/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_lists'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-local')\n  implementation project(':data-remote')\n  implementation project(':ui-base')\n  implementation project(':repository')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-lists/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n  <application android:theme=\"@style/AppTheme\" />\n</manifest>\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/create/CreateListBottomSheet.kt",
    "content": "package com.michaldrabik.ui_lists.create\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.optionalParcelable\nimport com.michaldrabik.ui_base.utilities.extensions.shake\nimport com.michaldrabik.ui_base.utilities.extensions.showErrorSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_lists.R\nimport com.michaldrabik.ui_lists.databinding.ViewCreateListBinding\nimport com.michaldrabik.ui_model.CustomList\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_LIST\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_CREATE_LIST\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.launch\n\n@AndroidEntryPoint\nclass CreateListBottomSheet : BaseBottomSheetFragment(R.layout.view_create_list) {\n\n  private val viewModel by viewModels<CreateListViewModel>()\n  private val binding by viewBinding(ViewCreateListBinding::bind)\n\n  private val list: CustomList? by lazy { optionalParcelable(ARG_LIST) }\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    viewLifecycleOwner.lifecycleScope.launch {\n      repeatOnLifecycle(Lifecycle.State.STARTED) {\n        with(viewModel) {\n          launch { uiState.collect { render(it) } }\n          launch { messageFlow.collect { renderSnackbar(it) } }\n          if (isEditMode()) {\n            viewModel.loadDetails(list?.id!!)\n          }\n        }\n      }\n    }\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun setupView() {\n    with(binding) {\n      viewCreateListButton.onClick { onCreateListClick() }\n      if (isEditMode()) {\n        viewCreateListTitle.setText(R.string.textEditList)\n        viewCreateListSubtitle.setText(R.string.textEditListDescription)\n        viewCreateListButton.setText(R.string.textApply)\n      }\n    }\n  }\n\n  private fun onCreateListClick() {\n    val name = binding.viewCreateListNameValue.text?.toString() ?: \"\"\n    val description = binding.viewCreateListDescriptionValue.text?.toString()\n    if (name.trim().isBlank()) {\n      binding.viewCreateListNameInput.shake()\n      return\n    }\n    if (isEditMode()) {\n      viewModel.updateList(list!!.copy(name = name, description = description))\n    } else {\n      viewModel.createList(name, description)\n    }\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun render(uiState: CreateListUiState) {\n    uiState.run {\n      listDetails?.let {\n        binding.viewCreateListNameValue.setText(it.name)\n        binding.viewCreateListDescriptionValue.setText(it.description)\n      }\n      isLoading?.let {\n        with(binding) {\n          viewCreateListNameInput.isEnabled = !it\n          viewCreateListDescriptionInput.isEnabled = !it\n          viewCreateListButton.isEnabled = !it\n          viewCreateListButton.setText(\n            when {\n              it -> R.string.textPleaseWait\n              !it && isEditMode() -> R.string.textEditList\n              else -> R.string.textCreateList\n            }\n          )\n        }\n      }\n      onListUpdated?.let {\n        it.consume()?.let {\n          setFragmentResult(REQUEST_CREATE_LIST, bundleOf())\n          closeSheet()\n        }\n      }\n    }\n  }\n\n  private fun renderSnackbar(message: MessageEvent) {\n    when (message) {\n      is MessageEvent.Info -> binding.viewCreateListSnackHost.showInfoSnackbar(getString(message.textRestId))\n      is MessageEvent.Error -> binding.viewCreateListSnackHost.showErrorSnackbar(getString(message.textRestId))\n    }\n  }\n\n  private fun isEditMode() = list != null\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/create/CreateListUiState.kt",
    "content": "package com.michaldrabik.ui_lists.create\n\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.CustomList\n\ndata class CreateListUiState(\n  val listDetails: CustomList? = null,\n  val isLoading: Boolean? = null,\n  val onListUpdated: Event<CustomList>? = null,\n)\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/create/CreateListViewModel.kt",
    "content": "package com.michaldrabik.ui_lists.create\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError.AccountLimitsError\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_lists.R\nimport com.michaldrabik.ui_lists.create.cases.CreateListCase\nimport com.michaldrabik.ui_lists.create.cases.ListDetailsCase\nimport com.michaldrabik.ui_model.CustomList\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass CreateListViewModel @Inject constructor(\n  private val createListCase: CreateListCase,\n  private val listDetailsCase: ListDetailsCase,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val detailsState = MutableStateFlow<CustomList?>(null)\n  private val loadingState = MutableStateFlow(false)\n  private val listUpdateState = MutableStateFlow<Event<CustomList>?>(null)\n\n  fun loadDetails(id: Long) {\n    viewModelScope.launch {\n      loadingState.value = true\n      detailsState.value = listDetailsCase.loadDetails(id)\n      loadingState.value = false\n    }\n  }\n\n  fun createList(name: String, description: String?) {\n    if (name.trim().isBlank()) return\n    viewModelScope.launch {\n      try {\n        loadingState.value = true\n        val list = createListCase.createList(name, description)\n        listUpdateState.value = Event(list)\n      } catch (error: Throwable) {\n        loadingState.value = false\n        handleError(error, R.string.errorCouldNotCreateList)\n      }\n    }\n  }\n\n  fun updateList(list: CustomList) {\n    if (list.name.trim().isBlank()) return\n    viewModelScope.launch {\n      try {\n        loadingState.value = true\n        detailsState.value = list\n        val updatedList = createListCase.updateList(list)\n        listUpdateState.value = Event(updatedList)\n      } catch (error: Throwable) {\n        detailsState.value = list\n        loadingState.value = false\n        handleError(error, R.string.errorCouldNotUpdateList)\n      }\n    }\n  }\n\n  private suspend fun handleError(error: Throwable, defaultErrorMessage: Int) {\n    when (ErrorHelper.parse(error)) {\n      AccountLimitsError ->\n        messageChannel.send(MessageEvent.Error(R.string.errorAccountListsLimitsReached))\n      else ->\n        messageChannel.send(MessageEvent.Error(defaultErrorMessage))\n    }\n  }\n\n  val uiState = combine(\n    detailsState,\n    loadingState,\n    listUpdateState\n  ) { s1, s2, s3 ->\n    CreateListUiState(\n      listDetails = s1,\n      isLoading = s2,\n      onListUpdated = s3\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = CreateListUiState()\n  )\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/create/cases/CreateListCase.kt",
    "content": "package com.michaldrabik.ui_lists.create.cases\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError.ResourceNotFoundError\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.ListsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.TraktListQuickSyncSuccess\nimport com.michaldrabik.ui_base.events.TraktQuickSyncSuccess\nimport com.michaldrabik.ui_model.CustomList\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.delay\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass CreateListCase @Inject constructor(\n  private val remoteSource: RemoteDataSource,\n  private val mappers: Mappers,\n  private val listsRepository: ListsRepository,\n  private val settingsRepository: SettingsRepository,\n  private val userTraktManager: UserTraktManager,\n  private val eventsManager: EventsManager,\n) {\n\n  suspend fun createList(name: String, description: String?): CustomList {\n    val isAuthorized = userTraktManager.isAuthorized()\n    val isQuickSyncEnabled = settingsRepository.load().traktQuickSyncEnabled\n    if (isAuthorized && isQuickSyncEnabled) {\n      userTraktManager.checkAuthorization()\n      val list = remoteSource.trakt.postCreateList(name, description).run {\n        mappers.customList.fromNetwork(this)\n      }\n      return listsRepository.createList(name, description, list.idTrakt, list.idSlug)\n        .also { eventsManager.sendEvent(TraktListQuickSyncSuccess) }\n    }\n    return listsRepository.createList(name, description, null, null)\n  }\n\n  suspend fun updateList(list: CustomList): CustomList {\n    val isAuthorized = userTraktManager.isAuthorized()\n    val isQuickSyncEnabled = settingsRepository.load().traktQuickSyncEnabled\n\n    if (isAuthorized && isQuickSyncEnabled) {\n      userTraktManager.checkAuthorization()\n      val updateList = mappers.customList.toNetwork(list)\n      return try {\n        val result = remoteSource.trakt.postUpdateList(updateList).run {\n          mappers.customList.fromNetwork(this)\n        }\n        listsRepository.updateList(list.id, result.idTrakt, result.idSlug, result.name, result.description)\n          .also { eventsManager.sendEvent(TraktQuickSyncSuccess(1)) }\n      } catch (error: Throwable) {\n        if (ErrorHelper.parse(error) is ResourceNotFoundError) {\n          // If list does not exist in Trakt account we need to create it and upload items as well.\n          delay(1000)\n          val result = remoteSource.trakt.postCreateList(updateList.name, updateList.description)\n            .run { mappers.customList.fromNetwork(this) }\n          listsRepository.updateList(list.id, result.idTrakt, result.idSlug, result.name, result.description)\n\n          val localItems = listsRepository.loadListItemsForId(list.id)\n          if (localItems.isNotEmpty()) {\n            val showsIds = localItems.filter { it.type == Mode.SHOWS.type }.map { it.idTrakt }\n            val moviesIds = localItems.filter { it.type == Mode.MOVIES.type }.map { it.idTrakt }\n            delay(1000)\n            remoteSource.trakt.postAddListItems(result.idTrakt!!, showsIds, moviesIds)\n          }\n\n          listsRepository.updateList(list.id, result.idTrakt, result.idSlug, result.name, result.description)\n            .also { eventsManager.sendEvent(TraktQuickSyncSuccess(1)) }\n        } else {\n          Logger.record(error, \"CreateListCase::updateList()\")\n          throw error\n        }\n      }\n    }\n\n    return listsRepository.updateList(list.id, null, null, list.name, list.description)\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/create/cases/ListDetailsCase.kt",
    "content": "package com.michaldrabik.ui_lists.create.cases\n\nimport com.michaldrabik.repository.ListsRepository\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ListDetailsCase @Inject constructor(\n  private val listsRepository: ListsRepository,\n) {\n\n  suspend fun loadDetails(id: Long) = listsRepository.loadById(id)\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/ListDetailsFragment.kt",
    "content": "package com.michaldrabik.ui_lists.details\n\nimport android.os.Bundle\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.activity.addCallback\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.core.content.ContextCompat\nimport androidx.core.os.bundleOf\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.common.sheets.sort_order.SortOrderBottomSheet\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.add\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.disableUi\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.enableUi\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.requireParcelable\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withSpanSizeLookup\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_lists.R\nimport com.michaldrabik.ui_lists.databinding.FragmentListDetailsBinding\nimport com.michaldrabik.ui_lists.details.ListDetailsUiEvent.OpenPremium\nimport com.michaldrabik.ui_lists.details.helpers.ListItemDragListener\nimport com.michaldrabik.ui_lists.details.helpers.ListItemSwipeListener\nimport com.michaldrabik.ui_lists.details.helpers.ReorderListCallback\nimport com.michaldrabik.ui_lists.details.helpers.ReorderListCallbackAdapter\nimport com.michaldrabik.ui_lists.details.recycler.ListDetailsAdapter\nimport com.michaldrabik.ui_lists.details.recycler.ListDetailsItem\nimport com.michaldrabik.ui_lists.details.recycler.helpers.ListDetailsGridItemDecoration\nimport com.michaldrabik.ui_lists.details.recycler.helpers.ListDetailsLayoutManagerProvider\nimport com.michaldrabik.ui_lists.details.recycler.helpers.ListDetailsListItemDecoration\nimport com.michaldrabik.ui_lists.details.views.ListDetailsDeleteConfirmView\nimport com.michaldrabik.ui_model.CustomList\nimport com.michaldrabik.ui_model.PremiumFeature\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_ADDED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RANK\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ITEM\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_LIST\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_MOVIE_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_SORT_ORDER\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_SORT_TYPE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SHOW_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_SORT_ORDER\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ListDetailsFragment :\n  BaseFragment<ListDetailsViewModel>(R.layout.fragment_list_details),\n  ListItemDragListener,\n  ListItemSwipeListener {\n\n  companion object {\n    private const val ARG_HEADER_TRANSLATION = \"ARG_HEADER_TRANSLATION\"\n  }\n\n  @Inject lateinit var settings: SettingsViewModeRepository\n\n  override val navigationId = R.id.listDetailsFragment\n  override val viewModel by viewModels<ListDetailsViewModel>()\n  private val binding by viewBinding(FragmentListDetailsBinding::bind)\n\n  private val list by lazy { requireParcelable<CustomList>(ARG_LIST) }\n\n  private val recyclerPaddingBottom by lazy { requireContext().dimenToPx(R.dimen.spaceSmall) }\n  private val recyclerPaddingTop by lazy { requireContext().dimenToPx(R.dimen.listDetailsRecyclerTopPadding) }\n  private val recyclerPaddingGridTop by lazy { requireContext().dimenToPx(R.dimen.listDetailsRecyclerTopGridPadding) }\n  private val tabletGridSpanSize by lazy { settings.tabletGridSpanSize }\n\n  private var adapter: ListDetailsAdapter? = null\n  private var touchHelper: ItemTouchHelper? = null\n  private var layoutManager: LayoutManager? = null\n\n  private var headerTranslation = 0F\n  private var isReorderMode = false\n\n  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {\n    savedInstanceState?.let {\n      headerTranslation = it.getFloat(ARG_HEADER_TRANSLATION)\n    }\n    return super.onCreateView(inflater, container, savedInstanceState)\n  }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupRecycler()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.messageFlow.collect { showSnack(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = { viewModel.loadDetails(list.id) }\n    )\n  }\n\n  override fun onResume() {\n    super.onResume()\n    hideNavigation()\n  }\n\n  override fun onPause() {\n    enableUi()\n    headerTranslation = binding.fragmentListDetailsFiltersView.translationY\n    super.onPause()\n  }\n\n  override fun onSaveInstanceState(outState: Bundle) {\n    super.onSaveInstanceState(outState)\n    outState.putFloat(ARG_HEADER_TRANSLATION, headerTranslation)\n  }\n\n  private fun setupView() {\n    with(binding) {\n      fragmentListDetailsRoot.doOnApplyWindowInsets { view, insets, padding, _ ->\n        val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top\n        view.updatePadding(top = padding.top + inset)\n      }\n      with(fragmentListDetailsToolbar) {\n        title = list.name\n        subtitle = list.description\n        setNavigationOnClickListener {\n          if (isReorderMode) toggleReorderMode()\n          else activity?.onBackPressed()\n        }\n      }\n      with(fragmentListDetailsFiltersView) {\n        onTypesChangeListener = { viewModel.setFilterTypes(list.id, it) }\n        onSortClickListener = { order, type -> openSortOrderDialog(order, type) }\n        translationY = headerTranslation\n      }\n      fragmentListDetailsManageButton.onClick { toggleReorderMode() }\n      fragmentListDetailsViewModeButton.onClick(safe = false) { viewModel.toggleViewMode() }\n    }\n  }\n\n  private fun setupRecycler() {\n    layoutManager = ListDetailsLayoutManagerProvider.provideLayoutManger(requireContext(), LIST_NORMAL, tabletGridSpanSize)\n    adapter = ListDetailsAdapter(\n      itemClickListener = { openItemDetails(it) },\n      missingImageListener = { item: ListDetailsItem, force: Boolean ->\n        viewModel.loadMissingImage(item, force)\n      },\n      missingTranslationListener = {\n        viewModel.loadMissingTranslation(it)\n      },\n      itemsChangedListener = {\n        with(binding) {\n          fragmentListDetailsRecycler.scrollToPosition(0)\n          fragmentListDetailsFiltersView.translationY = 0F\n        }\n      },\n      itemsClearedListener = {\n        if (isReorderMode) viewModel.updateRanks(list.id, it)\n      },\n      itemsSwipedListener = {\n        viewModel.deleteListItem(list.id, it)\n      },\n      itemDragStartListener = this,\n      itemSwipeStartListener = this\n    ).apply {\n      stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY\n    }\n    binding.fragmentListDetailsRecycler.apply {\n      adapter = this@ListDetailsFragment.adapter\n      layoutManager = this@ListDetailsFragment.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      setHasFixedSize(true)\n      addItemDecoration(ListDetailsGridItemDecoration(requireContext(), R.dimen.spaceSmall))\n      addItemDecoration(ListDetailsListItemDecoration(requireContext(), R.dimen.spaceSmall))\n    }\n\n    val touchCallback = ReorderListCallback(adapter as ReorderListCallbackAdapter)\n    touchHelper = ItemTouchHelper(touchCallback)\n    touchHelper?.attachToRecyclerView(binding.fragmentListDetailsRecycler)\n  }\n\n  override fun setupBackPressed() {\n    val dispatcher = requireActivity().onBackPressedDispatcher\n    dispatcher.addCallback(viewLifecycleOwner) {\n      if (isReorderMode) {\n        toggleReorderMode()\n      } else {\n        isEnabled = false\n        findNavControl()?.popBackStack()\n      }\n    }\n  }\n\n  private fun openSortOrderDialog(order: SortOrder, type: SortType) {\n    val options = listOf(RANK, NAME, RATING, USER_RATING, NEWEST, DATE_ADDED)\n    val args = SortOrderBottomSheet.createBundle(options, order, type)\n\n    setFragmentResultListener(REQUEST_SORT_ORDER) { _, bundle ->\n      val sortOrder = bundle.getSerializable(ARG_SELECTED_SORT_ORDER) as SortOrder\n      val sortType = bundle.getSerializable(ARG_SELECTED_SORT_TYPE) as SortType\n      viewModel.setSortOrder(list.id, sortOrder, sortType)\n    }\n\n    navigateTo(R.id.actionListDetailsFragmentToSortOrder, args)\n  }\n\n  private fun openDeleteDialog(quickRemoveEnabled: Boolean) {\n    val view = ListDetailsDeleteConfirmView(requireContext())\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .apply { if (quickRemoveEnabled) setView(view) }\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setTitle(R.string.textConfirmDeleteListTitle)\n      .setMessage(R.string.textConfirmDeleteListSubtitle)\n      .setPositiveButton(R.string.textYes) { _, _ ->\n        val removeFromTrakt = view.binding.viewListDeleteConfirmCheckbox?.isChecked\n        viewModel.deleteList(list.id, removeFromTrakt == true)\n      }\n      .setNegativeButton(R.string.textNo) { _, _ -> }\n      .show()\n  }\n\n  private fun openEditDialog() {\n    setFragmentResultListener(NavigationArgs.REQUEST_CREATE_LIST) { _, _ ->\n      viewModel.loadDetails(list.id)\n    }\n    val bundle = bundleOf(ARG_LIST to list)\n    navigateTo(R.id.actionListDetailsFragmentToEditListDialog, bundle)\n  }\n\n  private fun openItemDetails(listItem: ListDetailsItem) {\n    disableUi()\n    binding.fragmentListDetailsRoot.fadeOut(150) {\n      val bundle = bundleOf(\n        ARG_SHOW_ID to listItem.show?.traktId,\n        ARG_MOVIE_ID to listItem.movie?.traktId\n      )\n      val destination =\n        when {\n          listItem.isShow() -> R.id.actionListDetailsFragmentToShowDetailsFragment\n          listItem.isMovie() -> R.id.actionListDetailsFragmentToMovieDetailsFragment\n          else -> throw IllegalStateException()\n        }\n      navigateTo(destination, bundle)\n    }.add(animations)\n  }\n\n  private fun openPopupMenu(quickRemoveEnabled: Boolean) {\n    PopupMenu(requireContext(), binding.fragmentListDetailsMoreButton, Gravity.CENTER).apply {\n      inflate(R.menu.menu_list_details)\n      setOnMenuItemClickListener { menuItem ->\n        when (menuItem.itemId) {\n          R.id.menuListDetailsEdit -> openEditDialog()\n          R.id.menuListDetailsDelete -> openDeleteDialog(quickRemoveEnabled)\n        }\n        true\n      }\n      show()\n    }\n  }\n\n  private fun toggleReorderMode() {\n    isReorderMode = !isReorderMode\n    viewModel.setReorderMode(list.id, isReorderMode)\n  }\n\n  private fun render(uiState: ListDetailsUiState) {\n\n    fun renderTitle(name: String?, itemsCount: Int? = null) {\n      if (name.isNullOrBlank()) return\n      binding.fragmentListDetailsToolbar.title = when {\n        itemsCount != null && itemsCount > 0 -> \"$name ($itemsCount)\"\n        else -> name\n      }\n    }\n\n    uiState.run {\n      renderTitle(listDetails?.name, listItems?.size)\n      with(binding) {\n        viewMode.let {\n          if (adapter?.listViewMode != it) {\n            layoutManager = ListDetailsLayoutManagerProvider.provideLayoutManger(requireContext(), it, tabletGridSpanSize)\n            adapter?.listViewMode = it\n            fragmentListDetailsRecycler?.let { recycler ->\n              recycler.layoutManager = layoutManager\n              recycler.adapter = adapter\n            }\n            fragmentListDetailsViewModeButton.setImageResource(\n              when (it) {\n                LIST_NORMAL, LIST_COMPACT -> R.drawable.ic_view_list\n                GRID, GRID_TITLE -> R.drawable.ic_view_grid\n              }\n            )\n          }\n        }\n        listDetails?.let { details ->\n          val isQuickRemoveEnabled = isQuickRemoveEnabled\n          fragmentListDetailsToolbar.subtitle = details.description\n          fragmentListDetailsMoreButton.onClick { openPopupMenu(isQuickRemoveEnabled) }\n          fragmentListDetailsFiltersView.setFilters(details.filterTypeLocal, details.sortByLocal, details.sortHowLocal)\n        }\n        listItems?.let {\n          val isRealEmpty = it.isEmpty() && listDetails?.filterTypeLocal?.containsAll(Mode.getAll()) == true\n          fragmentListDetailsEmptyView.root.fadeIf(it.isEmpty())\n          fragmentListDetailsManageButton.visibleIf(!isRealEmpty)\n          fragmentListDetailsViewModeButton.visibleIf(!isRealEmpty)\n\n          val scrollTop = resetScroll?.consume() == true\n          adapter?.setItems(it, scrollTop)\n          (layoutManager as? GridLayoutManager)?.withSpanSizeLookup { pos ->\n            adapter?.items?.get(pos)?.image?.type?.getSpan(isTablet)!!\n          }\n        }\n        isManageMode.let { isManageMode ->\n          if (listItems?.isEmpty() == true && listDetails?.filterTypeLocal?.containsAll(Mode.getAll()) == true) {\n            return@let\n          }\n\n          fragmentListDetailsManageButton.visibleIf(!isManageMode)\n          fragmentListDetailsMoreButton.visibleIf(!isManageMode)\n          fragmentListDetailsViewModeButton.visibleIf(!isManageMode)\n\n          if (isManageMode) {\n            fragmentListDetailsToolbar.title = getString(R.string.textChangeRanks)\n            fragmentListDetailsToolbar.subtitle = getString(R.string.textChangeRanksSubtitle)\n            fragmentListDetailsRecycler.updatePadding(\n              top = if (layoutManager is GridLayoutManager) dimenToPx(R.dimen.spaceTiny) else 0,\n              bottom = recyclerPaddingBottom\n            )\n          } else {\n            renderTitle(listDetails?.name ?: list.name, listItems?.size)\n            fragmentListDetailsToolbar.subtitle = listDetails?.description\n            fragmentListDetailsRecycler.updatePadding(\n              top = if (layoutManager is GridLayoutManager) recyclerPaddingGridTop else recyclerPaddingTop,\n              bottom = recyclerPaddingBottom\n            )\n          }\n\n          if (resetScroll?.consume() == true) {\n            fragmentListDetailsRecycler.scrollToPosition(0)\n            fragmentListDetailsFiltersView.translationY = 0F\n          }\n        }\n        isFiltersVisible.let {\n          fragmentListDetailsFiltersView.visibleIf(it)\n        }\n        isLoading.let {\n          fragmentListDetailsLoadingView.visibleIf(it)\n          if (it) disableUi() else enableUi()\n        }\n        deleteEvent?.let { event ->\n          event.consume()?.let { activity?.onBackPressed() }\n        }\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is OpenPremium -> {\n        val args = bundleOf(ARG_ITEM to PremiumFeature.VIEW_TYPES)\n        navigateToSafe(R.id.actionListDetailsFragmentToPremium, args)\n      }\n    }\n  }\n\n  override fun onListItemDragStarted(viewHolder: RecyclerView.ViewHolder) {\n    touchHelper?.startDrag(viewHolder)\n  }\n\n  override fun onListItemSwipeStarted(viewHolder: RecyclerView.ViewHolder) {\n    touchHelper?.startSwipe(viewHolder)\n  }\n\n  override fun onDestroyView() {\n    adapter = null\n    touchHelper = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/ListDetailsUiEvents.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_lists.details\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\nsealed class ListDetailsUiEvent<T>(action: T) : Event<T>(action) {\n\n  object OpenPremium : ListDetailsUiEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/ListDetailsUiState.kt",
    "content": "package com.michaldrabik.ui_lists.details\n\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_lists.details.recycler.ListDetailsItem\nimport com.michaldrabik.ui_model.CustomList\n\ndata class ListDetailsUiState(\n  val listDetails: CustomList? = null,\n  val listItems: List<ListDetailsItem>? = null,\n  val resetScroll: Event<Boolean>? = null,\n  val deleteEvent: Event<Boolean>? = null,\n  val isFiltersVisible: Boolean = false,\n  val isManageMode: Boolean = false,\n  val isQuickRemoveEnabled: Boolean = false,\n  val isLoading: Boolean = false,\n  val viewMode: ListViewMode = ListViewMode.LIST_NORMAL\n)\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/ListDetailsViewModel.kt",
    "content": "package com.michaldrabik.ui_lists.details\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.Mode.MOVIES\nimport com.michaldrabik.common.Mode.SHOWS\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.combine\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_lists.R\nimport com.michaldrabik.ui_lists.details.ListDetailsUiEvent.OpenPremium\nimport com.michaldrabik.ui_lists.details.cases.ListDetailsItemsCase\nimport com.michaldrabik.ui_lists.details.cases.ListDetailsMainCase\nimport com.michaldrabik.ui_lists.details.cases.ListDetailsSortCase\nimport com.michaldrabik.ui_lists.details.cases.ListDetailsTipsCase\nimport com.michaldrabik.ui_lists.details.cases.ListDetailsTranslationsCase\nimport com.michaldrabik.ui_lists.details.cases.ListDetailsViewModeCase\nimport com.michaldrabik.ui_lists.details.recycler.ListDetailsItem\nimport com.michaldrabik.ui_model.CustomList\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.Tip\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ListDetailsViewModel @Inject constructor(\n  private val mainCase: ListDetailsMainCase,\n  private val itemsCase: ListDetailsItemsCase,\n  private val translationsCase: ListDetailsTranslationsCase,\n  private val sortCase: ListDetailsSortCase,\n  private val tipsCase: ListDetailsTipsCase,\n  private val viewModeCase: ListDetailsViewModeCase,\n  private val showImagesProvider: ShowImagesProvider,\n  private val movieImagesProvider: MovieImagesProvider,\n  private val settingsRepository: SettingsRepository\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val listDetailsState = MutableStateFlow<CustomList?>(null)\n  private val listItemsState = MutableStateFlow<List<ListDetailsItem>?>(null)\n  private val listDeleteState = MutableStateFlow<Event<Boolean>?>(null)\n  private val manageModeState = MutableStateFlow(false)\n  private val quickRemoveState = MutableStateFlow(false)\n  private val scrollState = MutableStateFlow<Event<Boolean>?>(null)\n  private val loadingState = MutableStateFlow(false)\n  private val filtersVisibleState = MutableStateFlow(false)\n  private val viewModeState = MutableStateFlow(ListViewMode.LIST_NORMAL)\n\n  fun loadDetails(id: Long) {\n    viewModelScope.launch {\n      val list = mainCase.loadDetails(id)\n      val (listItems, totalCount) = itemsCase.loadItems(list)\n\n      viewModeState.value = viewModeCase.getListViewMode()\n      listDetailsState.value = list\n      listItemsState.value = listItems\n      manageModeState.value = false\n      filtersVisibleState.value = totalCount > 0\n      quickRemoveState.value = mainCase.isQuickRemoveEnabled(list)\n\n      val tip = Tip.LIST_ITEM_SWIPE_DELETE\n      if (listItems.isNotEmpty() && !tipsCase.isTipShown(tip)) {\n        messageChannel.send(MessageEvent.Info(tip.textResId, isIndefinite = true))\n        tipsCase.setTipShown(tip)\n      }\n    }\n  }\n\n  fun loadMissingImage(item: ListDetailsItem, force: Boolean) {\n    viewModelScope.launch {\n      updateItem(item.copy(isLoading = true))\n      try {\n        val image =\n          when {\n            item.isShow() -> showImagesProvider.loadRemoteImage(item.requireShow(), item.image.type, force)\n            item.isMovie() -> movieImagesProvider.loadRemoteImage(item.requireMovie(), item.image.type, force)\n            else -> throw IllegalStateException()\n          }\n        updateItem(item.copy(isLoading = false, image = image))\n      } catch (t: Throwable) {\n        updateItem(item.copy(isLoading = false, image = Image.createUnavailable(item.image.type)))\n      }\n    }\n  }\n\n  fun loadMissingTranslation(item: ListDetailsItem) {\n    if (item.translation != null || translationsCase.getLanguage() == Config.DEFAULT_LANGUAGE) return\n    viewModelScope.launch {\n      try {\n        val translation = translationsCase.loadTranslation(item, false)\n        updateItem(item.copy(translation = translation))\n      } catch (error: Throwable) {\n        Timber.e(error)\n      }\n    }\n  }\n\n  fun toggleViewMode() {\n    if (settingsRepository.isPremium) {\n      viewModeState.value = viewModeCase.setNextViewMode()\n      return\n    }\n    viewModelScope.launch {\n      eventChannel.send(OpenPremium)\n    }\n  }\n\n  fun setReorderMode(listId: Long, isReorderMode: Boolean) {\n    viewModelScope.launch {\n      if (isReorderMode) {\n        val list = mainCase.loadDetails(listId).copy(\n          sortByLocal = SortOrder.RANK,\n          sortHowLocal = SortType.ASCENDING,\n          filterTypeLocal = Mode.getAll()\n        )\n        val listItems = itemsCase.loadItems(list).first.map { it.copy(isManageMode = true) }\n        listItemsState.value = listItems\n        manageModeState.value = true\n        filtersVisibleState.value = false\n        scrollState.value = Event(false)\n      } else {\n        val list = mainCase.loadDetails(listId)\n        val listItems = itemsCase.loadItems(list).first.map { it.copy(isManageMode = false) }\n        listItemsState.value = listItems\n        manageModeState.value = false\n        filtersVisibleState.value = true\n        scrollState.value = Event(true)\n      }\n    }\n  }\n\n  fun updateRanks(listId: Long, items: List<ListDetailsItem>) {\n    viewModelScope.launch {\n      val updatedItems = mainCase.updateRanks(listId, items)\n      listItemsState.value = updatedItems\n    }\n  }\n\n  fun setSortOrder(\n    id: Long,\n    sortOrder: SortOrder,\n    sortType: SortType\n  ) {\n    viewModelScope.launch {\n      val list = sortCase.setSortOrder(id, sortOrder, sortType)\n\n      val currentItems = uiState.value.listItems?.toList() ?: emptyList()\n      val sortedItems = itemsCase.sortItems(\n        currentItems,\n        list.sortByLocal,\n        list.sortHowLocal,\n        list.filterTypeLocal\n      )\n\n      listDetailsState.value = list\n      listItemsState.value = sortedItems\n      scrollState.value = Event(true)\n    }\n  }\n\n  fun setFilterTypes(listId: Long, types: List<Mode>) {\n    viewModelScope.launch {\n      val list = sortCase.setFilterTypes(listId, types)\n      val (sortedItems, _) = itemsCase.loadItems(list)\n\n      listDetailsState.value = list\n      listItemsState.value = sortedItems\n      filtersVisibleState.value = true\n      scrollState.value = Event(true)\n    }\n  }\n\n  fun deleteList(listId: Long, removeFromTrakt: Boolean) {\n    viewModelScope.launch {\n      try {\n        if (removeFromTrakt) {\n          loadingState.value = true\n        }\n        mainCase.deleteList(listId, removeFromTrakt)\n        loadingState.value = false\n        listDeleteState.value = Event(true)\n      } catch (error: Throwable) {\n        loadingState.value = false\n        messageChannel.send(MessageEvent.Error(R.string.errorCouldNotDeleteList))\n      }\n    }\n  }\n\n  fun deleteListItem(listId: Long, item: ListDetailsItem) {\n    viewModelScope.launch {\n      val type =\n        when {\n          item.isShow() -> SHOWS\n          item.isMovie() -> MOVIES\n          else -> throw IllegalStateException()\n        }\n      itemsCase.deleteListItem(listId, item.getTraktId(), type)\n      loadDetails(listId)\n    }\n  }\n\n  private fun updateItem(newItem: ListDetailsItem) {\n    val currentItems = uiState.value.listItems?.toMutableList() ?: mutableListOf()\n    currentItems.findReplace(newItem) { it.id == newItem.id }\n    listItemsState.value = currentItems\n  }\n\n  val uiState = combine(\n    listDetailsState,\n    listItemsState,\n    manageModeState,\n    quickRemoveState,\n    loadingState,\n    listDeleteState,\n    scrollState,\n    filtersVisibleState,\n    viewModeState\n  ) { s1, s2, s3, s4, s5, s6, s7, s8, s9 ->\n    ListDetailsUiState(\n      listDetails = s1,\n      listItems = s2,\n      isManageMode = s3,\n      isQuickRemoveEnabled = s4,\n      isLoading = s5,\n      deleteEvent = s6,\n      resetScroll = s7,\n      isFiltersVisible = s8,\n      viewMode = s9\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ListDetailsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/cases/ListDetailsItemsCase.kt",
    "content": "package com.michaldrabik.ui_lists.details.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.Mode.MOVIES\nimport com.michaldrabik.common.Mode.SHOWS\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.CustomListItem\nimport com.michaldrabik.repository.ListsRepository\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_lists.details.helpers.ListDetailsSorter\nimport com.michaldrabik.ui_lists.details.recycler.ListDetailsItem\nimport com.michaldrabik.ui_model.CustomList\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.TraktRating\nimport com.michaldrabik.ui_model.Translation\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport java.time.Instant\nimport java.time.ZoneId\nimport java.time.ZonedDateTime\nimport java.util.Collections\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ListDetailsItemsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n  private val showsRepository: ShowsRepository,\n  private val moviesRepository: MoviesRepository,\n  private val listsRepository: ListsRepository,\n  private val showImagesProvider: ShowImagesProvider,\n  private val movieImagesProvider: MovieImagesProvider,\n  private val translationsRepository: TranslationsRepository,\n  private val ratingsRepository: RatingsRepository,\n  private val settingsRepository: SettingsRepository,\n  private val quickSyncManager: QuickSyncManager,\n  private val sorter: ListDetailsSorter,\n) {\n\n  suspend fun loadItems(list: CustomList): Pair<List<ListDetailsItem>, Int> =\n    withContext(dispatchers.IO) {\n      val moviesEnabled = settingsRepository.isMoviesEnabled\n      val language = translationsRepository.getLanguage()\n      val listItems = listsRepository.loadItemsById(list.id)\n      val spoilers = settingsRepository.spoilers.getAll()\n\n      val showsAsync = async {\n        val ids = listItems.filter { it.type == SHOWS.type }.map { it.idTrakt }\n        localSource.shows.getAllChunked(ids)\n      }\n      val moviesAsync = async {\n        val ids = listItems.filter { it.type == MOVIES.type }.map { it.idTrakt }\n        localSource.movies.getAllChunked(ids)\n      }\n\n      val showsTranslationsAsync = async {\n        if (language == Config.DEFAULT_LANGUAGE) emptyMap()\n        else translationsRepository.loadAllShowsLocal(language)\n      }\n      val moviesTranslationsAsync = async {\n        if (language == Config.DEFAULT_LANGUAGE) emptyMap()\n        else translationsRepository.loadAllMoviesLocal(language)\n      }\n\n      val showsRatingsAsync = async {\n        ratingsRepository.shows.loadShowsRatings()\n      }\n      val moviesRatingsAsync = async {\n        ratingsRepository.movies.loadMoviesRatings()\n      }\n\n      val (shows, movies) = Pair(showsAsync.await(), moviesAsync.await())\n      val (showsTranslations, moviesTranslations) = Pair(showsTranslationsAsync.await(), moviesTranslationsAsync.await())\n      val (showsRatings, moviesRatings) = Pair(showsRatingsAsync.await(), moviesRatingsAsync.await())\n\n      val isRankSort = list.sortByLocal == SortOrder.RANK\n      val itemsToDelete = Collections.synchronizedList(mutableListOf<CustomListItem>())\n      val items = listItems.map { listItem ->\n        async {\n          val listedAt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(listItem.listedAt), ZoneId.of(\"UTC\"))\n          when (listItem.type) {\n            SHOWS.type -> {\n              val listShow = shows.firstOrNull { it.idTrakt == listItem.idTrakt }\n              if (listShow == null) {\n                itemsToDelete.add(listItem)\n                return@async null\n              }\n              val show = mappers.show.fromDatabase(listShow)\n              val translation = showsTranslations[show.traktId]\n              val rating = showsRatings.find { it.idTrakt == show.ids.trakt }\n              createListDetailsItem(\n                show = show,\n                listItem = listItem,\n                translation = translation,\n                userRating = rating,\n                isRankSort = isRankSort,\n                listedAt = listedAt,\n                sortOrder = list.sortByLocal,\n                spoilers = spoilers\n              )\n            }\n            MOVIES.type -> {\n              val listMovie = movies.firstOrNull { it.idTrakt == listItem.idTrakt }\n              if (listMovie == null) {\n                itemsToDelete.add(listItem)\n                return@async null\n              }\n              val movie = mappers.movie.fromDatabase(listMovie)\n              val translation = moviesTranslations[movie.traktId]\n              val rating = moviesRatings.find { it.idTrakt == movie.ids.trakt }\n              createListDetailsItem(\n                movie = movie,\n                listItem = listItem,\n                translation = translation,\n                userRating = rating,\n                isRankSort = isRankSort,\n                listedAt = listedAt,\n                moviesEnabled = moviesEnabled,\n                sortOrder = list.sortByLocal,\n                spoilers = spoilers\n              )\n            }\n            else -> throw IllegalStateException(\"Unsupported list item type.\")\n          }\n        }\n      }.awaitAll()\n\n      itemsToDelete.forEach {\n        listsRepository.removeFromList(list.id, IdTrakt(it.idTrakt), it.type)\n      }\n\n      val sortedItems = sortItems(\n        items = items.filterNotNull(),\n        sort = list.sortByLocal,\n        sortHow = list.sortHowLocal,\n        typeFilters = list.filterTypeLocal\n      )\n      Pair(sortedItems, listItems.count())\n    }\n\n  private suspend fun createListDetailsItem(\n    movie: Movie,\n    listItem: CustomListItem,\n    translation: Translation?,\n    userRating: TraktRating?,\n    isRankSort: Boolean,\n    listedAt: ZonedDateTime,\n    moviesEnabled: Boolean,\n    sortOrder: SortOrder,\n    spoilers: SpoilersSettings\n  ): ListDetailsItem {\n    val image = movieImagesProvider.findCachedImage(movie, ImageType.POSTER)\n    return ListDetailsItem(\n      id = listItem.id,\n      rank = listItem.rank,\n      rankDisplay = listItem.rank.toInt(),\n      show = null,\n      movie = movie,\n      image = image,\n      translation = translation,\n      userRating = userRating?.rating,\n      isLoading = false,\n      isRankDisplayed = isRankSort,\n      isManageMode = false,\n      isEnabled = moviesEnabled,\n      isWatched = moviesRepository.myMovies.exists(movie.ids.trakt),\n      isWatchlist = moviesRepository.watchlistMovies.exists(movie.ids.trakt),\n      listedAt = listedAt,\n      sortOrder = sortOrder,\n      spoilers = spoilers\n    )\n  }\n\n  private suspend fun createListDetailsItem(\n    show: Show,\n    listItem: CustomListItem,\n    translation: Translation?,\n    userRating: TraktRating?,\n    isRankSort: Boolean,\n    listedAt: ZonedDateTime,\n    sortOrder: SortOrder,\n    spoilers: SpoilersSettings\n  ): ListDetailsItem {\n    val image = showImagesProvider.findCachedImage(show, ImageType.POSTER)\n    return ListDetailsItem(\n      id = listItem.id,\n      rank = listItem.rank,\n      rankDisplay = listItem.rank.toInt(),\n      show = show,\n      movie = null,\n      image = image,\n      translation = translation,\n      userRating = userRating?.rating,\n      isLoading = false,\n      isRankDisplayed = isRankSort,\n      isManageMode = false,\n      isEnabled = true,\n      isWatched = showsRepository.myShows.exists(show.ids.trakt),\n      isWatchlist = showsRepository.watchlistShows.exists(show.ids.trakt),\n      listedAt = listedAt,\n      sortOrder = sortOrder,\n      spoilers = spoilers\n    )\n  }\n\n  fun sortItems(\n    items: List<ListDetailsItem>,\n    sort: SortOrder,\n    sortHow: SortType,\n    typeFilters: List<Mode>,\n  ) = items\n    .filter {\n      if (typeFilters.isEmpty()) {\n        return@filter true\n      }\n      when {\n        it.isShow() -> typeFilters.contains(SHOWS)\n        it.isMovie() -> typeFilters.contains(MOVIES)\n        else -> throw IllegalStateException()\n      }\n    }\n    .sortedWith(sorter.sort(sort, sortHow))\n    .mapIndexed { index, item ->\n      val rankDisplay = if (sortHow == SortType.ASCENDING) index + 1 else items.size - index\n      item.copy(\n        isRankDisplayed = sort == SortOrder.RANK,\n        rankDisplay = rankDisplay,\n        sortOrder = sort\n      )\n    }\n\n  suspend fun deleteListItem(\n    listId: Long,\n    itemTraktId: IdTrakt,\n    itemType: Mode,\n  ) = withContext(dispatchers.IO) {\n    listsRepository.removeFromList(listId, itemTraktId, itemType.type)\n    val isQuickRemoveEnabled = settingsRepository.load().traktQuickRemoveEnabled\n    if (isQuickRemoveEnabled) {\n      quickSyncManager.scheduleRemoveFromList(itemTraktId.id, listId, itemType)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/cases/ListDetailsMainCase.kt",
    "content": "package com.michaldrabik.ui_lists.details.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.CustomListItem\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.ListsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_lists.details.recycler.ListDetailsItem\nimport com.michaldrabik.ui_model.CustomList\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ListDetailsMainCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val localSource: LocalDataSource,\n  private val remoteSource: RemoteDataSource,\n  private val transactions: TransactionsProvider,\n  private val listsRepository: ListsRepository,\n  private val settingsRepository: SettingsRepository,\n  private val userTraktManager: UserTraktManager,\n) {\n\n  suspend fun loadDetails(id: Long) = withContext(dispatchers.IO) {\n    listsRepository.loadById(id)\n  }\n\n  suspend fun updateRanks(listId: Long, items: List<ListDetailsItem>): List<ListDetailsItem> =\n    withContext(dispatchers.IO) {\n      val now = nowUtcMillis()\n      val listItems = listsRepository.loadItemsById(listId)\n      val updateItems = mutableListOf<ListDetailsItem>()\n      val updateItemsDb = mutableListOf<CustomListItem>()\n      items.forEachIndexed { index, item ->\n        val dbItem = listItems.first { it.id == item.id }.copy(rank = index + 1L, updatedAt = now)\n        val updatedItem = item.copy(rank = index + 1L)\n        updateItems.add(updatedItem)\n        updateItemsDb.add(dbItem)\n      }\n      transactions.withTransaction {\n        localSource.customListsItems.update(updateItemsDb)\n        localSource.customLists.updateTimestamp(listId, now)\n      }\n      updateItems\n    }\n\n  suspend fun deleteList(listId: Long, removeFromTrakt: Boolean) =\n    withContext(dispatchers.IO) {\n      val isAuthorized = userTraktManager.isAuthorized()\n      val isQuickRemove = settingsRepository.load().traktQuickRemoveEnabled\n      val list = listsRepository.loadById(listId)\n      val listIdTrakt = list.idTrakt\n\n      if (isQuickRemove && isAuthorized && removeFromTrakt && listIdTrakt != null) {\n        userTraktManager.checkAuthorization()\n        try {\n          remoteSource.trakt.deleteList(listIdTrakt)\n        } catch (error: Throwable) {\n          when (ErrorHelper.parse(error)) {\n            is ShowlyError.ResourceNotFoundError -> Unit // NOOP List does not exist in Trakt.\n            else -> throw error\n          }\n        }\n      }\n\n      listsRepository.deleteList(listId)\n    }\n\n  suspend fun isQuickRemoveEnabled(list: CustomList) =\n    withContext(dispatchers.IO) {\n      list.idTrakt != null && settingsRepository.load().traktQuickRemoveEnabled\n    }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/cases/ListDetailsSortCase.kt",
    "content": "package com.michaldrabik.ui_lists.details.cases\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.CustomList\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ListDetailsSortCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers\n) {\n\n  suspend fun setSortOrder(listId: Long, sortOrder: SortOrder, sortType: SortType): CustomList =\n    withContext(dispatchers.IO) {\n      localSource.customLists.updateSortByLocal(\n        listId,\n        sortOrder.slug,\n        sortType.slug,\n        nowUtcMillis()\n      )\n      val list = localSource.customLists.getById(listId)!!\n      mappers.customList.fromDatabase(list)\n    }\n\n  suspend fun setFilterTypes(listId: Long, types: List<Mode>): CustomList = withContext(dispatchers.IO) {\n    localSource.customLists.updateFilterTypeLocal(\n      listId,\n      types.joinToString(\",\") { it.type },\n      nowUtcMillis()\n    )\n    val list = localSource.customLists.getById(listId)!!\n    mappers.customList.fromDatabase(list)\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/cases/ListDetailsTipsCase.kt",
    "content": "package com.michaldrabik.ui_lists.details.cases\n\nimport android.content.SharedPreferences\nimport com.michaldrabik.ui_model.Tip\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\nimport javax.inject.Named\n\n@ViewModelScoped\nclass ListDetailsTipsCase @Inject constructor(\n  @Named(\"tipsPreferences\") private val sharedPreferences: SharedPreferences\n) {\n\n  fun isTipShown(tip: Tip) = sharedPreferences.getBoolean(tip.name, false)\n\n  fun setTipShown(tip: Tip) {\n    sharedPreferences.edit().putBoolean(tip.name, true).apply()\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/cases/ListDetailsTranslationsCase.kt",
    "content": "package com.michaldrabik.ui_lists.details.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.ui_lists.details.recycler.ListDetailsItem\nimport com.michaldrabik.ui_model.Translation\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ListDetailsTranslationsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val translationsRepository: TranslationsRepository,\n) {\n\n  fun getLanguage() = translationsRepository.getLanguage()\n\n  suspend fun loadTranslation(item: ListDetailsItem, onlyLocal: Boolean): Translation? =\n    withContext(dispatchers.IO) {\n      val language = getLanguage()\n      if (language == Config.DEFAULT_LANGUAGE) {\n        return@withContext Translation.EMPTY\n      }\n      when {\n        item.isShow() ->\n          translationsRepository.loadTranslation(\n            show = item.requireShow(),\n            language = language,\n            onlyLocal = onlyLocal\n          )\n        item.isMovie() ->\n          translationsRepository.loadTranslation(\n            movie = item.requireMovie(),\n            language = language,\n            onlyLocal = onlyLocal\n          )\n        else -> throw IllegalStateException()\n      }\n    }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/cases/ListDetailsViewModeCase.kt",
    "content": "package com.michaldrabik.ui_lists.details.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ListDetailsViewModeCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setNextViewMode(): ListViewMode {\n    if (!settingsRepository.isPremium) {\n      return ListViewMode.valueOf(Config.DEFAULT_LIST_VIEW_MODE)\n    }\n    val viewModes = ListViewMode.values()\n    val index = viewModes.indexOf(getListViewMode()) + 1\n    val nextIndex = if (index >= viewModes.size) 0 else index\n    settingsRepository.viewMode.customListsViewMode = viewModes[nextIndex].name\n    return viewModes[nextIndex]\n  }\n\n  fun getListViewMode(): ListViewMode {\n    if (!settingsRepository.isPremium) {\n      return ListViewMode.valueOf(Config.DEFAULT_LIST_VIEW_MODE)\n    }\n    val viewMode = settingsRepository.viewMode.customListsViewMode\n    return ListViewMode.valueOf(viewMode)\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/helpers/ListDetailsSorter.kt",
    "content": "package com.michaldrabik.ui_lists.details.helpers\n\nimport com.michaldrabik.ui_lists.details.recycler.ListDetailsItem\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_ADDED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RANK\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SortType.ASCENDING\nimport com.michaldrabik.ui_model.SortType.DESCENDING\nimport javax.inject.Inject\n\nclass ListDetailsSorter @Inject constructor() {\n\n  fun sort(sortOrder: SortOrder, sortType: SortType) = when (sortType) {\n    ASCENDING -> sortAscending(sortOrder)\n    DESCENDING -> sortDescending(sortOrder)\n  }\n\n  private fun sortAscending(sortOrder: SortOrder): Comparator<ListDetailsItem> =\n    when (sortOrder) {\n      RANK -> compareBy { it.rank }\n      NAME -> compareBy { getTitle(it) }\n      NEWEST -> compareBy<ListDetailsItem> { it.getYear() }.thenBy { it.getDate() }\n      RATING -> compareBy { it.getRating() }\n      USER_RATING ->\n        compareByDescending<ListDetailsItem> { it.userRating != null }\n          .thenBy { it.userRating }\n          .thenBy { getTitle(it) }\n      DATE_ADDED -> compareBy { it.listedAt }\n      else -> throw IllegalStateException(\"Invalid sort order\")\n    }\n\n  private fun sortDescending(sortOrder: SortOrder): Comparator<ListDetailsItem> =\n    when (sortOrder) {\n      RANK -> compareByDescending { it.rank }\n      NAME -> compareByDescending { getTitle(it) }\n      NEWEST -> compareByDescending<ListDetailsItem> { it.getYear() }.thenByDescending { it.getDate() }\n      RATING -> compareByDescending { it.getRating() }\n      USER_RATING ->\n        compareByDescending<ListDetailsItem> { it.userRating != null }\n          .thenByDescending { it.userRating }\n          .thenBy { getTitle(it) }\n      DATE_ADDED -> compareByDescending { it.listedAt }\n      else -> throw IllegalStateException(\"Invalid sort order\")\n    }\n\n  private fun getTitle(item: ListDetailsItem): String {\n    return if (item.translation?.hasTitle == true) item.translation.title\n    else item.getTitleNoThe().uppercase()\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/helpers/ListItemDragListener.kt",
    "content": "package com.michaldrabik.ui_lists.details.helpers\n\nimport androidx.recyclerview.widget.RecyclerView\n\ninterface ListItemDragListener {\n  fun onListItemDragStarted(viewHolder: RecyclerView.ViewHolder)\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/helpers/ListItemSwipeListener.kt",
    "content": "package com.michaldrabik.ui_lists.details.helpers\n\nimport androidx.recyclerview.widget.RecyclerView\n\ninterface ListItemSwipeListener {\n  fun onListItemSwipeStarted(viewHolder: RecyclerView.ViewHolder)\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/helpers/ReorderListCallback.kt",
    "content": "package com.michaldrabik.ui_lists.details.helpers\n\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.ItemTouchHelper.DOWN\nimport androidx.recyclerview.widget.ItemTouchHelper.END\nimport androidx.recyclerview.widget.ItemTouchHelper.START\nimport androidx.recyclerview.widget.ItemTouchHelper.UP\nimport androidx.recyclerview.widget.RecyclerView\n\nclass ReorderListCallback(\n  private val adapter: ReorderListCallbackAdapter\n) : ItemTouchHelper.SimpleCallback(UP or DOWN or START or END, START) {\n\n  override fun onMove(\n    recyclerView: RecyclerView,\n    viewHolder: RecyclerView.ViewHolder,\n    target: RecyclerView.ViewHolder\n  ): Boolean {\n    adapter.onItemMove(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition)\n    return true\n  }\n\n  override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {\n    adapter.onItemSwiped(viewHolder)\n  }\n\n  override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n    super.clearView(recyclerView, viewHolder)\n    adapter.onItemCleared()\n  }\n\n  override fun isItemViewSwipeEnabled() = false\n\n  override fun isLongPressDragEnabled() = false\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/helpers/ReorderListCallbackAdapter.kt",
    "content": "package com.michaldrabik.ui_lists.details.helpers\n\nimport androidx.recyclerview.widget.RecyclerView\n\ninterface ReorderListCallbackAdapter {\n  fun onItemSwiped(viewHolder: RecyclerView.ViewHolder)\n  fun onItemMove(fromPosition: Int, toPosition: Int): Boolean\n  fun onItemCleared()\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/recycler/ListDetailsAdapter.kt",
    "content": "package com.michaldrabik.ui_lists.details.recycler\n\nimport android.annotation.SuppressLint\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_lists.details.helpers.ListItemDragListener\nimport com.michaldrabik.ui_lists.details.helpers.ListItemSwipeListener\nimport com.michaldrabik.ui_lists.details.helpers.ReorderListCallbackAdapter\nimport com.michaldrabik.ui_lists.details.views.ListDetailsItemView\nimport com.michaldrabik.ui_lists.details.views.ListDetailsMovieItemView\nimport com.michaldrabik.ui_lists.details.views.ListDetailsShowItemView\nimport com.michaldrabik.ui_lists.details.views.compact.ListDetailsCompactMovieItemView\nimport com.michaldrabik.ui_lists.details.views.compact.ListDetailsCompactShowItemView\nimport com.michaldrabik.ui_lists.details.views.grid.ListDetailsGridItemView\nimport com.michaldrabik.ui_lists.details.views.grid.ListDetailsGridTitleItemView\nimport java.util.Collections\n\nclass ListDetailsAdapter(\n  val itemClickListener: (ListDetailsItem) -> Unit,\n  val missingImageListener: (ListDetailsItem, Boolean) -> Unit,\n  val missingTranslationListener: (ListDetailsItem) -> Unit,\n  val itemsChangedListener: () -> Unit,\n  val itemsClearedListener: (List<ListDetailsItem>) -> Unit,\n  val itemsSwipedListener: (ListDetailsItem) -> Unit,\n  val itemDragStartListener: ListItemDragListener,\n  val itemSwipeStartListener: ListItemSwipeListener,\n) : RecyclerView.Adapter<RecyclerView.ViewHolder>(),\n  ReorderListCallbackAdapter {\n\n  companion object {\n    private const val VIEW_TYPE_SHOW = 1\n    private const val VIEW_TYPE_MOVIE = 2\n  }\n\n  var items = listOf<ListDetailsItem>()\n\n  var listViewMode: ListViewMode = LIST_NORMAL\n    set(value) {\n      field = value\n      notifyItemRangeChanged(0, items.size)\n    }\n\n  fun setItems(newItems: List<ListDetailsItem>, notifyItemsChange: Boolean) {\n    // Using old DiffUtil method here because of drag and drop issues with asyncDiff.\n    val diff = DiffUtil.calculateDiff(ListDetailsDiffCallback(items, newItems))\n    diff.dispatchUpdatesTo(this)\n    items = newItems\n    if (notifyItemsChange) itemsChangedListener.invoke()\n  }\n\n  override fun getItemViewType(position: Int): Int {\n    val item = items[position]\n    return when {\n      item.isShow() -> VIEW_TYPE_SHOW\n      item.isMovie() -> VIEW_TYPE_MOVIE\n      else -> throw IllegalStateException()\n    }\n  }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) {\n    VIEW_TYPE_SHOW -> {\n      val view = when (listViewMode) {\n        LIST_NORMAL -> ListDetailsShowItemView(parent.context)\n        LIST_COMPACT -> ListDetailsCompactShowItemView(parent.context)\n        GRID -> ListDetailsGridItemView(parent.context)\n        GRID_TITLE -> ListDetailsGridTitleItemView(parent.context)\n      }.apply {\n        itemClickListener = { item -> this@ListDetailsAdapter.itemClickListener(item) }\n        missingImageListener = { item, force -> this@ListDetailsAdapter.missingImageListener(item, force) }\n        missingTranslationListener = { item -> this@ListDetailsAdapter.missingTranslationListener(item) }\n      }\n      ListDetailsItemViewHolder(\n        view,\n        itemDragStartListener,\n        itemSwipeStartListener\n      )\n    }\n    VIEW_TYPE_MOVIE -> {\n      val view = when (listViewMode) {\n        LIST_NORMAL -> ListDetailsMovieItemView(parent.context)\n        LIST_COMPACT -> ListDetailsCompactMovieItemView(parent.context)\n        GRID -> ListDetailsGridItemView(parent.context)\n        GRID_TITLE -> ListDetailsGridTitleItemView(parent.context)\n      }.apply {\n        itemClickListener = { item -> this@ListDetailsAdapter.itemClickListener(item) }\n        missingImageListener = { item, force -> this@ListDetailsAdapter.missingImageListener(item, force) }\n        missingTranslationListener = { item -> this@ListDetailsAdapter.missingTranslationListener(item) }\n      }\n      ListDetailsItemViewHolder(\n        view,\n        itemDragStartListener,\n        itemSwipeStartListener\n      )\n    }\n    else -> throw IllegalStateException()\n  }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = items[position]\n    when (holder.itemViewType) {\n      VIEW_TYPE_SHOW -> when (listViewMode) {\n        LIST_NORMAL -> (holder.itemView as ListDetailsShowItemView).bind(item)\n        LIST_COMPACT -> (holder.itemView as ListDetailsCompactShowItemView).bind(item)\n        GRID -> (holder.itemView as ListDetailsGridItemView).bind(item)\n        GRID_TITLE -> (holder.itemView as ListDetailsGridTitleItemView).bind(item)\n      }\n      VIEW_TYPE_MOVIE -> when (listViewMode) {\n        LIST_NORMAL -> (holder.itemView as ListDetailsMovieItemView).bind(item)\n        LIST_COMPACT -> (holder.itemView as ListDetailsCompactMovieItemView).bind(item)\n        GRID -> (holder.itemView as ListDetailsGridItemView).bind(item)\n        GRID_TITLE -> (holder.itemView as ListDetailsGridTitleItemView).bind(item)\n      }\n      else -> throw IllegalStateException()\n    }\n  }\n\n  override fun getItemCount() = items.size\n\n  override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {\n    if (fromPosition < toPosition) {\n      for (i in fromPosition until toPosition) {\n        Collections.swap(items, i, i + 1)\n      }\n    } else {\n      for (i in fromPosition downTo toPosition + 1) {\n        Collections.swap(items, i, i - 1)\n      }\n    }\n    notifyItemMoved(fromPosition, toPosition)\n    return true\n  }\n\n  override fun onItemCleared() = itemsClearedListener(items)\n\n  override fun onItemSwiped(viewHolder: RecyclerView.ViewHolder) {\n    val item = ((viewHolder as ListDetailsItemViewHolder).itemView as ListDetailsItemView).item\n    itemsSwipedListener(item)\n  }\n\n  @SuppressLint(\"ClickableViewAccessibility\")\n  class ListDetailsItemViewHolder(\n    itemView: ListDetailsItemView,\n    dragStartListener: ListItemDragListener,\n    swipeStartListener: ListItemSwipeListener\n  ) : RecyclerView.ViewHolder(itemView) {\n    init {\n      itemView.itemDragStartListener = {\n        dragStartListener.onListItemDragStarted(this)\n      }\n      itemView.itemSwipeStartListener = {\n        swipeStartListener.onListItemSwipeStarted(this)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/recycler/ListDetailsDiffCallback.kt",
    "content": "package com.michaldrabik.ui_lists.details.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass ListDetailsDiffCallback(\n  private val oldItems: List<ListDetailsItem>,\n  private val newItems: List<ListDetailsItem>,\n) : DiffUtil.Callback() {\n\n  override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {\n    if (oldItems[oldPos].isMovie() && newItems[newPos].isShow()) return false\n    if (oldItems[oldPos].isShow() && newItems[newPos].isMovie()) return false\n    return oldItems[oldPos].id == newItems[newPos].id\n  }\n\n  override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean {\n    val oldItem = oldItems[oldPos]\n    val newItem = newItems[newPos]\n    return when {\n      oldItem.isShow() -> {\n        oldItem.show == newItem.show &&\n          oldItem.isLoading == newItem.isLoading &&\n          oldItem.isRankDisplayed == newItem.isRankDisplayed &&\n          oldItem.isEnabled == newItem.isEnabled &&\n          oldItem.isWatchlist == newItem.isWatchlist &&\n          oldItem.isWatched == newItem.isWatched &&\n          oldItem.isManageMode == newItem.isManageMode &&\n          oldItem.translation == newItem.translation &&\n          oldItem.userRating == newItem.userRating &&\n          oldItem.image == newItem.image &&\n          oldItem.listedAt == newItem.listedAt &&\n          oldItem.sortOrder == newItem.sortOrder &&\n          oldItem.rankDisplay == newItem.rankDisplay &&\n          oldItem.spoilers == newItem.spoilers &&\n          oldItem.rank == newItem.rank\n      }\n      oldItem.isMovie() -> {\n        oldItem.movie == newItem.movie &&\n          oldItem.isLoading == newItem.isLoading &&\n          oldItem.isRankDisplayed == newItem.isRankDisplayed &&\n          oldItem.isManageMode == newItem.isManageMode &&\n          oldItem.isWatchlist == newItem.isWatchlist &&\n          oldItem.isWatched == newItem.isWatched &&\n          oldItem.isEnabled == newItem.isEnabled &&\n          oldItem.translation == newItem.translation &&\n          oldItem.userRating == newItem.userRating &&\n          oldItem.image == newItem.image &&\n          oldItem.listedAt == newItem.listedAt &&\n          oldItem.sortOrder == newItem.sortOrder &&\n          oldItem.rankDisplay == newItem.rankDisplay &&\n          oldItem.spoilers == newItem.spoilers &&\n          oldItem.rank == newItem.rank\n      }\n      else -> throw IllegalStateException()\n    }\n  }\n\n  override fun getOldListSize() = oldItems.size\n\n  override fun getNewListSize() = newItems.size\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/recycler/ListDetailsItem.kt",
    "content": "package com.michaldrabik.ui_lists.details.recycler\n\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.Translation\nimport java.time.ZoneOffset\nimport java.time.ZonedDateTime\n\ndata class ListDetailsItem(\n  val id: Long,\n  val rank: Long,\n  val rankDisplay: Int,\n  val show: Show?,\n  val movie: Movie?,\n  val image: Image,\n  val translation: Translation?,\n  val userRating: Int?,\n  val isLoading: Boolean,\n  val isRankDisplayed: Boolean,\n  val isManageMode: Boolean,\n  val isEnabled: Boolean,\n  val isWatched: Boolean,\n  val isWatchlist: Boolean,\n  val listedAt: ZonedDateTime,\n  val sortOrder: SortOrder,\n  val spoilers: SpoilersSettings\n) {\n\n  fun getTitleNoThe(): String {\n    if (isShow()) return requireShow().titleNoThe\n    if (isMovie()) return requireMovie().titleNoThe\n    throw IllegalStateException()\n  }\n\n  fun getYear(): Int {\n    if (isShow()) return requireShow().year\n    if (isMovie()) return requireMovie().year\n    throw IllegalStateException()\n  }\n\n  fun getDate(): Long {\n    if (isShow()) {\n      return if (requireShow().firstAired.isBlank()) 0 else ZonedDateTime.parse(requireShow().firstAired).toMillis()\n    }\n    if (isMovie()) {\n      return requireMovie().released?.atStartOfDay()?.toInstant(ZoneOffset.UTC)?.toEpochMilli() ?: 0\n    }\n    throw IllegalStateException()\n  }\n\n  fun getRating(): Float {\n    if (isShow()) return requireShow().rating\n    if (isMovie()) return requireMovie().rating\n    throw IllegalStateException()\n  }\n\n  fun getTraktId(): IdTrakt {\n    if (isShow()) return IdTrakt(requireShow().traktId)\n    if (isMovie()) return IdTrakt(requireMovie().traktId)\n    throw IllegalStateException()\n  }\n\n  fun isShow() = show != null\n\n  fun isMovie() = movie != null\n\n  fun requireShow() = show!!\n\n  fun requireMovie() = movie!!\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/recycler/helpers/ListDetailsGridItemDecoration.kt",
    "content": "package com.michaldrabik.ui_lists.details.recycler.helpers\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.annotation.DimenRes\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ItemDecoration\nimport com.michaldrabik.ui_lists.details.views.grid.ListDetailsGridItemView\nimport com.michaldrabik.ui_lists.details.views.grid.ListDetailsGridTitleItemView\n\nclass ListDetailsGridItemDecoration : ItemDecoration {\n\n  private var spacing: Int\n  private var halfSpacing: Int\n\n  constructor(\n    context: Context,\n    @DimenRes spacingDimen: Int,\n  ) {\n    this.spacing = context.resources.getDimensionPixelSize(spacingDimen)\n    this.halfSpacing = spacing / 2\n  }\n\n  override fun getItemOffsets(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n    state: RecyclerView.State,\n  ) {\n    if (parent.layoutManager !is GridLayoutManager) return\n\n    val totalSpan = (parent.layoutManager as GridLayoutManager).spanCount\n\n    if (view is ListDetailsGridItemView || view is ListDetailsGridTitleItemView) {\n      outRect.top = halfSpacing\n      outRect.bottom = halfSpacing\n\n      val position = parent.getChildAdapterPosition(view)\n      val column = position % totalSpan\n\n      outRect.left = spacing * column / totalSpan\n      outRect.right = spacing * ((totalSpan - 1) - column) / totalSpan\n    }\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/recycler/helpers/ListDetailsLayoutManagerProvider.kt",
    "content": "package com.michaldrabik.ui_lists.details.recycler.helpers\n\nimport android.content.Context\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN_TABLET\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\n\ninternal object ListDetailsLayoutManagerProvider {\n\n  fun provideLayoutManger(\n    context: Context,\n    viewMode: ListViewMode,\n    gridSpanSize: Int,\n  ): RecyclerView.LayoutManager {\n    return if (context.isTablet()) {\n      provideTabletLayout(context, viewMode, gridSpanSize)\n    } else {\n      providePhoneLayout(context, viewMode)\n    }\n  }\n\n  private fun provideTabletLayout(\n    context: Context,\n    viewMode: ListViewMode,\n    gridSpanSize: Int,\n  ): RecyclerView.LayoutManager {\n    return when (viewMode) {\n      LIST_NORMAL, LIST_COMPACT -> GridLayoutManager(context, gridSpanSize)\n      GRID, GRID_TITLE -> GridLayoutManager(context, LISTS_GRID_SPAN_TABLET)\n    }\n  }\n\n  private fun providePhoneLayout(\n    context: Context,\n    viewMode: ListViewMode,\n  ): RecyclerView.LayoutManager {\n    return when (viewMode) {\n      LIST_NORMAL, LIST_COMPACT -> LinearLayoutManager(context, VERTICAL, false)\n      GRID, GRID_TITLE -> GridLayoutManager(context, LISTS_GRID_SPAN)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/recycler/helpers/ListDetailsListItemDecoration.kt",
    "content": "package com.michaldrabik.ui_lists.details.recycler.helpers\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.annotation.DimenRes\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ItemDecoration\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_lists.details.views.ListDetailsMovieItemView\nimport com.michaldrabik.ui_lists.details.views.ListDetailsShowItemView\nimport com.michaldrabik.ui_lists.details.views.compact.ListDetailsCompactMovieItemView\nimport com.michaldrabik.ui_lists.details.views.compact.ListDetailsCompactShowItemView\n\nclass ListDetailsListItemDecoration : ItemDecoration {\n\n  private var spacing: Int\n  private var halfSpacing: Int\n  private val isTablet: Boolean\n\n  constructor(\n    context: Context,\n    @DimenRes spacingDimen: Int,\n  ) {\n    this.spacing = context.resources.getDimensionPixelSize(spacingDimen)\n    this.halfSpacing = spacing / 2\n    this.isTablet = context.isTablet()\n  }\n\n  override fun getItemOffsets(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n    state: RecyclerView.State,\n  ) {\n    if (view !is ListDetailsShowItemView &&\n      view !is ListDetailsCompactShowItemView &&\n      view !is ListDetailsMovieItemView &&\n      view !is ListDetailsCompactMovieItemView\n    ) {\n      return\n    }\n    if (!isTablet && (parent.layoutManager is LinearLayoutManager)) {\n      getItemOffsetsPhone(outRect, view)\n      return\n    }\n    if (isTablet && (parent.layoutManager is GridLayoutManager)) {\n      getItemOffsetsTablet(outRect, view, parent)\n      return\n    }\n  }\n\n  private fun getItemOffsetsTablet(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n  ) {\n    if (view is ListDetailsShowItemView || view is ListDetailsMovieItemView) {\n      outRect.top = spacing\n      outRect.bottom = spacing\n    } else if (view is ListDetailsCompactShowItemView || view is ListDetailsCompactMovieItemView) {\n      outRect.top = halfSpacing\n      outRect.bottom = halfSpacing\n    }\n\n    val totalSpan = (parent.layoutManager as GridLayoutManager).spanCount\n    val column = getPosition(parent, view) % totalSpan\n\n    outRect.left = (spacing * 2) * column / totalSpan\n    outRect.right = (spacing * 2) * ((totalSpan - 1) - column) / totalSpan\n  }\n\n  private fun getItemOffsetsPhone(outRect: Rect, view: View) {\n    if (view is ListDetailsShowItemView || view is ListDetailsMovieItemView) {\n      outRect.top = spacing\n      outRect.bottom = spacing\n    } else if (view is ListDetailsCompactShowItemView || view is ListDetailsCompactMovieItemView) {\n      outRect.top = halfSpacing\n      outRect.bottom = halfSpacing\n    }\n    outRect.left = 0\n    outRect.right = 0\n  }\n\n  private fun getPosition(parent: RecyclerView, view: View): Int =\n    parent.getChildAdapterPosition(view)\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/views/ListDetailsDeleteConfirmView.kt",
    "content": "package com.michaldrabik.ui_lists.details.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_lists.databinding.ViewListDeleteConfirmBinding\n\nclass ListDetailsDeleteConfirmView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  val binding = ViewListDeleteConfirmBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/views/ListDetailsFilterView.kt",
    "content": "package com.michaldrabik.ui_lists.details.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.forEach\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_lists.R\nimport com.michaldrabik.ui_lists.databinding.ViewListDetailsFiltersBinding\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\n\nclass ListDetailsFilterView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewListDetailsFiltersBinding.inflate(LayoutInflater.from(context), this)\n\n  var onSortClickListener: ((SortOrder, SortType) -> Unit)? = null\n  var onTypesChangeListener: ((List<Mode>) -> Unit)? = null\n\n  init {\n    with(binding) {\n      showsChip.onClick {\n        showsChip.isSelected = !showsChip.isSelected\n        onTypeClick()\n      }\n      moviesChip.onClick {\n        moviesChip.isSelected = !moviesChip.isSelected\n        onTypeClick()\n      }\n    }\n  }\n\n  override fun setEnabled(enabled: Boolean) {\n    binding.chipsGroup.forEach {\n      it.isEnabled = enabled\n    }\n  }\n\n  fun setFilters(\n    types: List<Mode>,\n    sortOrder: SortOrder,\n    sortType: SortType,\n  ) {\n    with(binding) {\n      showsChip.isSelected = Mode.SHOWS in types\n      moviesChip.isSelected = Mode.MOVIES in types\n      sortingChip.text = context.getString(sortOrder.displayString)\n      sortingChip.onClick {\n        onSortClickListener?.invoke(sortOrder, sortType)\n      }\n      val sortIcon = when (sortType) {\n        SortType.ASCENDING -> R.drawable.ic_arrow_alt_up\n        SortType.DESCENDING -> R.drawable.ic_arrow_alt_down\n      }\n      sortingChip.closeIcon = ContextCompat.getDrawable(context, sortIcon)\n    }\n  }\n\n  private fun onTypeClick() {\n    val types = mutableListOf<Mode>().apply {\n      if (binding.showsChip.isSelected) add(Mode.SHOWS)\n      if (binding.moviesChip.isSelected) add(Mode.MOVIES)\n    }\n    onTypesChangeListener?.invoke(types)\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/views/ListDetailsItemView.kt",
    "content": "package com.michaldrabik.ui_lists.details.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.widget.FrameLayout\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade\nimport com.michaldrabik.common.Config.IMAGE_FADE_DURATION_MS\nimport com.michaldrabik.common.Config.TVDB_IMAGE_BASE_FANART_URL\nimport com.michaldrabik.common.Config.TVDB_IMAGE_BASE_POSTER_URL\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_lists.details.recycler.ListDetailsItem\nimport com.michaldrabik.ui_model.ImageStatus.AVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNAVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNKNOWN\nimport com.michaldrabik.ui_model.ImageType.POSTER\n\n@SuppressLint(\"ClickableViewAccessibility\")\nabstract class ListDetailsItemView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.mediaTileCorner) }\n  private val centerCropTransformation by lazy { CenterCrop() }\n  private val cornersTransformation by lazy { RoundedCorners(cornerRadius) }\n\n  protected abstract val imageView: ImageView\n  protected abstract val placeholderView: ImageView\n\n  var itemClickListener: ((ListDetailsItem) -> Unit)? = null\n  var imageLoadCompleteListener: (() -> Unit)? = null\n  var missingImageListener: ((ListDetailsItem, Boolean) -> Unit)? = null\n  var missingTranslationListener: ((ListDetailsItem) -> Unit)? = null\n  var itemDragStartListener: (() -> Unit)? = null\n  var itemSwipeStartListener: (() -> Unit)? = null\n\n  lateinit var item: ListDetailsItem\n\n  open fun bind(item: ListDetailsItem) {\n    this.item = item\n  }\n\n  protected open fun loadImage(item: ListDetailsItem) {\n    if (item.isLoading) return\n\n    if (item.image.status == UNAVAILABLE) {\n      placeholderView.visible()\n      return\n    }\n\n    val unknownBase = when (item.image.type) {\n      POSTER -> TVDB_IMAGE_BASE_POSTER_URL\n      else -> TVDB_IMAGE_BASE_FANART_URL\n    }\n    val url = when (item.image.status) {\n      UNKNOWN -> {\n        when {\n          item.isShow() -> \"${unknownBase}${item.show?.ids?.tvdb?.id}-1.jpg\"\n          item.isMovie() -> \"${unknownBase}${item.movie?.ids?.tvdb?.id}-1.jpg\"\n          else -> throw IllegalStateException()\n        }\n      }\n      AVAILABLE -> item.image.fullFileUrl\n      else -> error(\"Should not handle other statuses.\")\n    }\n\n    Glide.with(this)\n      .load(url)\n      .transform(centerCropTransformation, cornersTransformation)\n      .transition(withCrossFade(IMAGE_FADE_DURATION_MS))\n      .withSuccessListener { onImageLoadSuccess() }\n      .withFailListener { onImageLoadFail(item) }\n      .into(imageView)\n  }\n\n  protected open fun onImageLoadSuccess() {\n    placeholderView.gone()\n    imageLoadCompleteListener?.invoke()\n  }\n\n  protected open fun onImageLoadFail(item: ListDetailsItem) {\n    if (item.image.status == AVAILABLE) {\n      placeholderView.visible()\n      imageLoadCompleteListener?.invoke()\n      return\n    }\n    val force = (item.image.status == UNKNOWN)\n    missingImageListener?.invoke(item, force)\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/views/ListDetailsMovieItemView.kt",
    "content": "package com.michaldrabik.ui_lists.details.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.MotionEvent.ACTION_DOWN\nimport android.view.MotionEvent.ACTION_MOVE\nimport android.view.MotionEvent.ACTION_UP\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport androidx.core.content.ContextCompat\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Config.SPOILERS_RATINGS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.expandTouch\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.setOutboundRipple\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_lists.R\nimport com.michaldrabik.ui_lists.databinding.ViewListDetailsMovieItemBinding\nimport com.michaldrabik.ui_lists.details.recycler.ListDetailsItem\nimport com.michaldrabik.ui_model.Movie\nimport java.util.Locale.ENGLISH\nimport kotlin.math.abs\n\n@SuppressLint(\"ClickableViewAccessibility\")\nclass ListDetailsMovieItemView : ListDetailsItemView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewListDetailsMovieItemBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    setBackgroundColor(context.colorFromAttr(android.R.attr.windowBackground))\n\n    clipChildren = false\n    clipToPadding = false\n\n    imageLoadCompleteListener = {\n      if (item.translation == null) {\n        missingTranslationListener?.invoke(item)\n      }\n    }\n\n    with(binding) {\n      listDetailsMovieHandle.expandTouch(100)\n      listDetailsMovieHandle.setOnTouchListener { _, event ->\n        if (item.isManageMode && event.action == ACTION_DOWN) {\n          itemDragStartListener?.invoke()\n        }\n        false\n      }\n\n      var x = 0F\n      listDetailsMovieRoot.setOnTouchListener { _, event ->\n        if (item.isManageMode) {\n          return@setOnTouchListener false\n        }\n        if (event.action == ACTION_DOWN) x = event.x\n        if (event.action == ACTION_UP) x = 0F\n        if (event.action == ACTION_MOVE && abs(x - event.x) > 50F) {\n          itemSwipeStartListener?.invoke()\n          return@setOnTouchListener true\n        }\n        false\n      }\n\n      listDetailsMovieRoot.onClick {\n        if (item.isEnabled && !item.isManageMode) itemClickListener?.invoke(item)\n      }\n      listDetailsMovieRoot.setOutboundRipple(\n        size = (context.dimenToPx(R.dimen.collectionItemRippleSpace)).toFloat(),\n        corner = context.dimenToPx(R.dimen.mediaTileCorner).toFloat()\n      )\n    }\n  }\n\n  override val imageView: ImageView = binding.listDetailsMovieImage\n  override val placeholderView: ImageView = binding.listDetailsMoviePlaceholder\n\n  override fun bind(item: ListDetailsItem) {\n    super.bind(item)\n\n    with(binding) {\n      Glide.with(this@ListDetailsMovieItemView).clear(listDetailsMovieImage)\n      val movie = item.requireMovie()\n\n      listDetailsMovieProgress.visibleIf(item.isLoading)\n\n      listDetailsMovieTitle.text =\n        if (item.translation?.title.isNullOrBlank()) movie.title\n        else item.translation?.title\n\n      bindDescription(item, movie)\n      bindRating(item, movie)\n\n      listDetailsMovieHeader.text = String.format(ENGLISH, \"%d\", movie.year)\n      listDetailsMovieUserRating.text = String.format(ENGLISH, \"%d\", item.userRating)\n\n      listDetailsMovieRank.visibleIf(item.isRankDisplayed)\n      listDetailsMovieRank.text = String.format(ENGLISH, \"%d\", item.rankDisplay)\n\n      listDetailsMovieHandle.visibleIf(item.isManageMode)\n      listDetailsMovieStarIcon.visibleIf(!item.isManageMode)\n      listDetailsMovieUserStarIcon.visibleIf(!item.isManageMode && item.userRating != null)\n      listDetailsMovieUserRating.visibleIf(!item.isManageMode && item.userRating != null)\n\n      with(listDetailsMovieHeaderBadge) {\n        val inCollection = item.isWatched || item.isWatchlist\n        visibleIf(inCollection)\n        if (inCollection) {\n          val color = if (item.isWatched) R.color.colorAccent else R.color.colorGrayLight\n          imageTintList = ColorStateList.valueOf(ContextCompat.getColor(context, color))\n        }\n      }\n\n      listDetailsMovieRoot.alpha = if (item.isEnabled) 1F else 0.45F\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindDescription(\n    item: ListDetailsItem,\n    movie: Movie,\n  ) {\n    var description =\n      when {\n        item.translation?.overview.isNullOrBlank() -> movie.overview.ifBlank {\n          context.getString(R.string.textNoDescription)\n        }\n        else -> item.translation?.overview\n      }\n\n    val isMyHidden = item.spoilers.isMyMoviesHidden && item.isWatched\n    val isWatchlistHidden = item.spoilers.isWatchlistMoviesHidden && item.isWatchlist\n    val isNotCollectedHidden = item.spoilers.isNotCollectedMoviesHidden && (!item.isWatched && !item.isWatchlist)\n    if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n      binding.listDetailsMovieDescription.tag = description\n      description = SPOILERS_REGEX.replace(description.toString(), Config.SPOILERS_HIDE_SYMBOL)\n    }\n\n    binding.listDetailsMovieDescription.text = description\n    if (item.spoilers.isTapToReveal) {\n      with(binding.listDetailsMovieDescription) {\n        onClick {\n          tag?.let { text = it.toString() }\n          isClickable = false\n        }\n      }\n    }\n  }\n\n  private fun bindRating(\n    item: ListDetailsItem,\n    movie: Movie,\n  ) {\n    var rating = String.format(ENGLISH, \"%.1f\", movie.rating)\n\n    val isMyHidden = item.spoilers.isMyMoviesRatingsHidden && item.isWatched\n    val isWatchlistHidden = item.spoilers.isWatchlistMoviesRatingsHidden && item.isWatchlist\n    val isNotCollectedHidden = item.spoilers.isNotCollectedMoviesRatingsHidden && (!item.isWatched && !item.isWatchlist)\n    if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n      binding.listDetailsMovieRating.tag = rating\n      rating = SPOILERS_RATINGS_HIDE_SYMBOL\n    }\n\n    binding.listDetailsMovieRating.visibleIf(!item.isManageMode)\n    binding.listDetailsMovieRating.text = rating\n\n    if (item.spoilers.isTapToReveal) {\n      with(binding.listDetailsMovieRating) {\n        onClick {\n          tag?.let { text = it.toString() }\n          isClickable = false\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/views/ListDetailsShowItemView.kt",
    "content": "package com.michaldrabik.ui_lists.details.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.MotionEvent.ACTION_DOWN\nimport android.view.MotionEvent.ACTION_MOVE\nimport android.view.MotionEvent.ACTION_UP\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport androidx.core.content.ContextCompat\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_RATINGS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.expandTouch\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.setOutboundRipple\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_lists.R\nimport com.michaldrabik.ui_lists.databinding.ViewListDetailsShowItemBinding\nimport com.michaldrabik.ui_lists.details.recycler.ListDetailsItem\nimport com.michaldrabik.ui_model.Show\nimport java.util.Locale.ENGLISH\nimport kotlin.math.abs\n\n@SuppressLint(\"ClickableViewAccessibility\")\nclass ListDetailsShowItemView : ListDetailsItemView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewListDetailsShowItemBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    setBackgroundColor(context.colorFromAttr(android.R.attr.windowBackground))\n\n    clipChildren = false\n    clipToPadding = false\n\n    imageLoadCompleteListener = {\n      if (item.translation == null) {\n        missingTranslationListener?.invoke(item)\n      }\n    }\n\n    with(binding) {\n      listDetailsShowHandle.expandTouch(100)\n      listDetailsShowHandle.setOnTouchListener { _, event ->\n        if (item.isManageMode && event.action == ACTION_DOWN) {\n          itemDragStartListener?.invoke()\n        }\n        false\n      }\n\n      var x = 0F\n      listDetailsShowRoot.setOnTouchListener { _, event ->\n        if (item.isManageMode) {\n          return@setOnTouchListener false\n        }\n        if (event.action == ACTION_DOWN) x = event.x\n        if (event.action == ACTION_UP) x = 0F\n        if (event.action == ACTION_MOVE && abs(x - event.x) > 50F) {\n          itemSwipeStartListener?.invoke()\n          return@setOnTouchListener true\n        }\n        false\n      }\n\n      listDetailsShowRoot.onClick {\n        if (!item.isManageMode) itemClickListener?.invoke(item)\n      }\n      listDetailsShowRoot.setOutboundRipple(\n        size = (context.dimenToPx(R.dimen.collectionItemRippleSpace)).toFloat(),\n        corner = context.dimenToPx(R.dimen.mediaTileCorner).toFloat()\n      )\n    }\n  }\n\n  override val imageView: ImageView = binding.listDetailsShowImage\n  override val placeholderView: ImageView = binding.listDetailsShowPlaceholder\n\n  override fun bind(item: ListDetailsItem) {\n    super.bind(item)\n\n    with(binding) {\n      Glide.with(this@ListDetailsShowItemView).clear(listDetailsShowImage)\n\n      val show = item.requireShow()\n\n      listDetailsShowProgress.visibleIf(item.isLoading)\n\n      listDetailsShowTitle.text =\n        if (item.translation?.title.isNullOrBlank()) show.title\n        else item.translation?.title\n\n      bindDescription(item, show)\n      bindRating(item, show)\n\n      listDetailsShowHeader.text =\n        if (show.year > 0) context.getString(R.string.textNetwork, show.year.toString(), show.network)\n        else String.format(\"%s\", show.network)\n\n      listDetailsShowUserRating.text = String.format(ENGLISH, \"%d\", item.userRating)\n\n      listDetailsShowRank.visibleIf(item.isRankDisplayed)\n      listDetailsShowRank.text = String.format(ENGLISH, \"%d\", item.rankDisplay)\n\n      listDetailsShowHandle.visibleIf(item.isManageMode)\n      listDetailsShowStarIcon.visibleIf(!item.isManageMode)\n      listDetailsShowUserStarIcon.visibleIf(!item.isManageMode && item.userRating != null)\n      listDetailsShowUserRating.visibleIf(!item.isManageMode && item.userRating != null)\n\n      with(listDetailsShowHeaderBadge) {\n        val inCollection = item.isWatched || item.isWatchlist\n        visibleIf(inCollection)\n        if (inCollection) {\n          val color = if (item.isWatched) R.color.colorAccent else R.color.colorGrayLight\n          imageTintList = ColorStateList.valueOf(ContextCompat.getColor(context, color))\n        }\n      }\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindDescription(\n    item: ListDetailsItem,\n    show: Show,\n  ) {\n    var description = when {\n      item.translation?.overview.isNullOrBlank() -> show.overview.ifBlank {\n        context.getString(R.string.textNoDescription)\n      }\n      else -> item.translation?.overview\n    }\n\n    val isMyHidden = item.spoilers.isMyShowsHidden && item.isWatched\n    val isWatchlistHidden = item.spoilers.isWatchlistShowsHidden && item.isWatchlist\n    val isNotCollectedHidden = item.spoilers.isNotCollectedShowsHidden && (!item.isWatched && !item.isWatchlist)\n    if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n      binding.listDetailsShowDescription.tag = description\n      description = SPOILERS_REGEX.replace(description.toString(), SPOILERS_HIDE_SYMBOL)\n    }\n\n    binding.listDetailsShowDescription.text = description\n    if (item.spoilers.isTapToReveal) {\n      with(binding.listDetailsShowDescription) {\n        onClick {\n          tag?.let { text = it.toString() }\n          isClickable = false\n        }\n      }\n    }\n  }\n\n  private fun bindRating(\n    item: ListDetailsItem,\n    show: Show,\n  ) {\n    var rating = String.format(ENGLISH, \"%.1f\", show.rating)\n\n    val isMyHidden = item.spoilers.isMyShowsRatingsHidden && item.isWatched\n    val isWatchlistHidden = item.spoilers.isWatchlistShowsRatingsHidden && item.isWatchlist\n    val isNotCollectedHidden = item.spoilers.isNotCollectedShowsRatingsHidden && (!item.isWatched && !item.isWatchlist)\n    if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n      binding.listDetailsShowRating.tag = rating\n      rating = SPOILERS_RATINGS_HIDE_SYMBOL\n    }\n\n    binding.listDetailsShowRating.visibleIf(!item.isManageMode)\n    binding.listDetailsShowRating.text = rating\n\n    if (item.spoilers.isTapToReveal) {\n      with(binding.listDetailsShowRating) {\n        onClick {\n          tag?.let { text = it.toString() }\n          isClickable = false\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/views/compact/ListDetailsCompactMovieItemView.kt",
    "content": "package com.michaldrabik.ui_lists.details.views.compact\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.MotionEvent.ACTION_DOWN\nimport android.view.MotionEvent.ACTION_MOVE\nimport android.view.MotionEvent.ACTION_UP\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport androidx.core.content.ContextCompat\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config.SPOILERS_RATINGS_HIDE_SYMBOL\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.expandTouch\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.setOutboundRipple\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_lists.R\nimport com.michaldrabik.ui_lists.databinding.ViewListDetailsMovieItemCompactBinding\nimport com.michaldrabik.ui_lists.details.recycler.ListDetailsItem\nimport com.michaldrabik.ui_lists.details.views.ListDetailsItemView\nimport com.michaldrabik.ui_model.Movie\nimport java.util.Locale.ENGLISH\nimport kotlin.math.abs\n\n@SuppressLint(\"ClickableViewAccessibility\")\nclass ListDetailsCompactMovieItemView : ListDetailsItemView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewListDetailsMovieItemCompactBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    setBackgroundColor(context.colorFromAttr(android.R.attr.windowBackground))\n\n    clipChildren = false\n    clipToPadding = false\n\n    imageLoadCompleteListener = {\n      if (item.translation == null) {\n        missingTranslationListener?.invoke(item)\n      }\n    }\n\n    with(binding) {\n      listDetailsMovieHandle.expandTouch(100)\n      listDetailsMovieHandle.setOnTouchListener { _, event ->\n        if (item.isManageMode && event.action == ACTION_DOWN) {\n          itemDragStartListener?.invoke()\n        }\n        false\n      }\n\n      var x = 0F\n      listDetailsMovieRoot.setOnTouchListener { _, event ->\n        if (item.isManageMode) {\n          return@setOnTouchListener false\n        }\n        if (event.action == ACTION_DOWN) x = event.x\n        if (event.action == ACTION_UP) x = 0F\n        if (event.action == ACTION_MOVE && abs(x - event.x) > 50F) {\n          itemSwipeStartListener?.invoke()\n          return@setOnTouchListener true\n        }\n        false\n      }\n\n      listDetailsMovieRoot.onClick {\n        if (item.isEnabled && !item.isManageMode) itemClickListener?.invoke(item)\n      }\n      listDetailsMovieRoot.setOutboundRipple(\n        size = (context.dimenToPx(R.dimen.collectionItemRippleSpace)).toFloat(),\n        corner = context.dimenToPx(R.dimen.mediaTileCorner).toFloat()\n      )\n    }\n  }\n\n  override val imageView: ImageView = binding.listDetailsMovieImage\n  override val placeholderView: ImageView = binding.listDetailsMoviePlaceholder\n\n  override fun bind(item: ListDetailsItem) {\n    super.bind(item)\n    with(binding) {\n      Glide.with(this@ListDetailsCompactMovieItemView).clear(listDetailsMovieImage)\n      val movie = item.requireMovie()\n\n      listDetailsMovieProgress.visibleIf(item.isLoading)\n\n      listDetailsMovieTitle.text =\n        if (item.translation?.title.isNullOrBlank()) movie.title\n        else item.translation?.title\n\n      listDetailsMovieHeader.text = String.format(ENGLISH, \"%d\", movie.year)\n      listDetailsMovieUserRating.text = String.format(ENGLISH, \"%d\", item.userRating)\n      bindRating(item, movie)\n\n      listDetailsMovieRank.visibleIf(item.isRankDisplayed)\n      listDetailsMovieRank.text = String.format(ENGLISH, \"%d\", item.rankDisplay)\n\n      listDetailsMovieHandle.visibleIf(item.isManageMode)\n      listDetailsMovieStarIcon.visibleIf(!item.isManageMode)\n      listDetailsMovieUserStarIcon.visibleIf(!item.isManageMode && item.userRating != null)\n      listDetailsMovieUserRating.visibleIf(!item.isManageMode && item.userRating != null)\n\n      with(listDetailsMovieHeaderBadge) {\n        val inCollection = item.isWatched || item.isWatchlist\n        visibleIf(inCollection)\n        if (inCollection) {\n          val color = if (item.isWatched) R.color.colorAccent else R.color.colorGrayLight\n          imageTintList = ColorStateList.valueOf(ContextCompat.getColor(context, color))\n        }\n      }\n\n      listDetailsMovieRoot.alpha = if (item.isEnabled) 1F else 0.45F\n    }\n    loadImage(item)\n  }\n\n  private fun bindRating(\n    item: ListDetailsItem,\n    movie: Movie,\n  ) {\n    with(binding) {\n      var rating = String.format(ENGLISH, \"%.1f\", movie.rating)\n\n      val isMyHidden = item.spoilers.isMyMoviesRatingsHidden && item.isWatched\n      val isWatchlistHidden = item.spoilers.isWatchlistMoviesRatingsHidden && item.isWatchlist\n      val isNotCollectedHidden = item.spoilers.isNotCollectedMoviesRatingsHidden && (!item.isWatched && !item.isWatchlist)\n      if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n        listDetailsMovieRating.tag = rating\n        rating = SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isTapToReveal) {\n          with(listDetailsMovieRating) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      }\n\n      listDetailsMovieRating.visibleIf(!item.isManageMode)\n      listDetailsMovieRating.text = rating\n    }\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/views/compact/ListDetailsCompactShowItemView.kt",
    "content": "package com.michaldrabik.ui_lists.details.views.compact\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.MotionEvent.ACTION_DOWN\nimport android.view.MotionEvent.ACTION_MOVE\nimport android.view.MotionEvent.ACTION_UP\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport androidx.core.content.ContextCompat\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config.SPOILERS_RATINGS_HIDE_SYMBOL\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.expandTouch\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.setOutboundRipple\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_lists.R\nimport com.michaldrabik.ui_lists.databinding.ViewListDetailsShowItemCompactBinding\nimport com.michaldrabik.ui_lists.details.recycler.ListDetailsItem\nimport com.michaldrabik.ui_lists.details.views.ListDetailsItemView\nimport com.michaldrabik.ui_model.Show\nimport java.util.Locale.ENGLISH\nimport kotlin.math.abs\n\n@SuppressLint(\"ClickableViewAccessibility\")\nclass ListDetailsCompactShowItemView : ListDetailsItemView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewListDetailsShowItemCompactBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    setBackgroundColor(context.colorFromAttr(android.R.attr.windowBackground))\n\n    clipChildren = false\n    clipToPadding = false\n\n    imageLoadCompleteListener = {\n      if (item.translation == null) {\n        missingTranslationListener?.invoke(item)\n      }\n    }\n\n    with(binding) {\n      listDetailsShowHandle.expandTouch(100)\n      listDetailsShowHandle.setOnTouchListener { _, event ->\n        if (item.isManageMode && event.action == ACTION_DOWN) {\n          itemDragStartListener?.invoke()\n        }\n        false\n      }\n\n      var x = 0F\n      listDetailsShowRoot.setOnTouchListener { _, event ->\n        if (item.isManageMode) {\n          return@setOnTouchListener false\n        }\n        if (event.action == ACTION_DOWN) x = event.x\n        if (event.action == ACTION_UP) x = 0F\n        if (event.action == ACTION_MOVE && abs(x - event.x) > 50F) {\n          itemSwipeStartListener?.invoke()\n          return@setOnTouchListener true\n        }\n        false\n      }\n\n      listDetailsShowRoot.onClick {\n        if (!item.isManageMode) itemClickListener?.invoke(item)\n      }\n      listDetailsShowRoot.setOutboundRipple(\n        size = (context.dimenToPx(R.dimen.collectionItemRippleSpace)).toFloat(),\n        corner = context.dimenToPx(R.dimen.mediaTileCorner).toFloat()\n      )\n    }\n  }\n\n  override val imageView: ImageView = binding.listDetailsShowImage\n  override val placeholderView: ImageView = binding.listDetailsShowPlaceholder\n\n  override fun bind(item: ListDetailsItem) {\n    super.bind(item)\n\n    with(binding) {\n      Glide.with(this@ListDetailsCompactShowItemView).clear(listDetailsShowImage)\n\n      val show = item.requireShow()\n\n      listDetailsShowProgress.visibleIf(item.isLoading)\n\n      listDetailsShowTitle.text =\n        if (item.translation?.title.isNullOrBlank()) show.title\n        else item.translation?.title\n\n      listDetailsShowHeader.text =\n        if (show.year > 0) context.getString(R.string.textNetwork, show.year.toString(), show.network)\n        else String.format(\"%s\", show.network)\n\n      bindRating(item, show)\n      listDetailsShowUserRating.text = String.format(ENGLISH, \"%d\", item.userRating)\n\n      listDetailsShowRank.visibleIf(item.isRankDisplayed)\n      listDetailsShowRank.text = String.format(ENGLISH, \"%d\", item.rankDisplay)\n\n      listDetailsShowHandle.visibleIf(item.isManageMode)\n      listDetailsShowStarIcon.visibleIf(!item.isManageMode)\n      listDetailsShowUserStarIcon.visibleIf(!item.isManageMode && item.userRating != null)\n      listDetailsShowUserRating.visibleIf(!item.isManageMode && item.userRating != null)\n\n      with(listDetailsShowHeaderBadge) {\n        val inCollection = item.isWatched || item.isWatchlist\n        visibleIf(inCollection)\n        if (inCollection) {\n          val color = if (item.isWatched) R.color.colorAccent else R.color.colorGrayLight\n          imageTintList = ColorStateList.valueOf(ContextCompat.getColor(context, color))\n        }\n      }\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindRating(\n    item: ListDetailsItem,\n    show: Show,\n  ) {\n    with(binding) {\n      var rating = String.format(ENGLISH, \"%.1f\", show.rating)\n\n      val isMyHidden = item.spoilers.isMyShowsRatingsHidden && item.isWatched\n      val isWatchlistHidden = item.spoilers.isWatchlistShowsRatingsHidden && item.isWatchlist\n      val isNotCollectedHidden = item.spoilers.isNotCollectedShowsRatingsHidden && (!item.isWatched && !item.isWatchlist)\n      if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n        listDetailsShowRating.tag = rating\n        rating = SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isTapToReveal) {\n          with(listDetailsShowRating) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      }\n\n      listDetailsShowRating.visibleIf(!item.isManageMode)\n      listDetailsShowRating.text = rating\n    }\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/views/grid/ListDetailsGridItemView.kt",
    "content": "package com.michaldrabik.ui_lists.details.views.grid\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.MotionEvent\nimport android.widget.ImageView\nimport androidx.core.content.ContextCompat\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Config.SPOILERS_RATINGS_HIDE_SYMBOL\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.views.ShowView.Companion.ASPECT_RATIO\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_lists.databinding.LayoutListDetailsItemGridBinding\nimport com.michaldrabik.ui_lists.databinding.ViewListDetailsItemGridBinding\nimport com.michaldrabik.ui_lists.details.recycler.ListDetailsItem\nimport com.michaldrabik.ui_lists.details.views.ListDetailsItemView\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport java.util.Locale.ENGLISH\nimport kotlin.math.abs\n\n@SuppressLint(\"ClickableViewAccessibility\")\nclass ListDetailsGridItemView : ListDetailsItemView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewListDetailsItemGridBinding.inflate(LayoutInflater.from(context), this)\n  private val contentBinding = LayoutListDetailsItemGridBinding.bind(binding.listDetailsGridItemRoot)\n\n  private val width by lazy {\n    val span = if (context.isTablet()) Config.LISTS_GRID_SPAN_TABLET else Config.LISTS_GRID_SPAN\n    val itemSpacing = context.dimenToPx(R.dimen.spaceSmall)\n    val screenMargin = context.dimenToPx(R.dimen.screenMarginHorizontal)\n    val screenWidth = screenWidth().toFloat()\n    ((screenWidth - (screenMargin * 2.0)) - ((span - 1) * itemSpacing)) / span\n  }\n  private val height by lazy { width * ASPECT_RATIO }\n\n  init {\n    layoutParams = LayoutParams(width.toInt(), height.toInt())\n\n    clipChildren = false\n    clipToPadding = false\n\n    binding.listDetailsGridItemRoot.onClick {\n      if (item.isEnabled && !item.isManageMode) {\n        itemClickListener?.invoke(item)\n      }\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n    initSwipeListener()\n    initDragListener()\n  }\n\n  override val imageView: ImageView = contentBinding.listDetailsGridItemImage\n  override val placeholderView: ImageView = contentBinding.listDetailsGridItemPlaceholder\n\n  private fun initSwipeListener() {\n    var x = 0F\n    binding.listDetailsGridItemRoot.setOnTouchListener { _, event ->\n      if (item.isManageMode) {\n        return@setOnTouchListener false\n      }\n      if (event.action == MotionEvent.ACTION_DOWN) x = event.x\n      if (event.action == MotionEvent.ACTION_UP) x = 0F\n      if (event.action == MotionEvent.ACTION_MOVE && abs(x - event.x) > 50F) {\n        itemSwipeStartListener?.invoke()\n        return@setOnTouchListener true\n      }\n      false\n    }\n  }\n\n  private fun initDragListener() {\n    contentBinding.listDetailsGridItemHandle.setOnTouchListener { _, event ->\n      if (item.isManageMode && event.action == MotionEvent.ACTION_DOWN) {\n        itemDragStartListener?.invoke()\n      }\n      false\n    }\n  }\n\n  override fun bind(item: ListDetailsItem) {\n    clear()\n    this.item = item\n\n    with(contentBinding) {\n      listDetailsGridItemProgress.visibleIf(item.isLoading)\n\n      with(listDetailsGridItemImage) {\n        if (item.isShow()) setImageResource(R.drawable.ic_television)\n        if (item.isMovie()) setImageResource(R.drawable.ic_film)\n      }\n\n      with(listDetailsGridItemBadge) {\n        val inCollection = item.isWatched || item.isWatchlist\n        visibleIf(inCollection)\n        if (inCollection) {\n          val color = if (item.isWatched) R.color.colorAccent else R.color.colorGrayLight\n          imageTintList = ColorStateList.valueOf(ContextCompat.getColor(context, color))\n        }\n      }\n\n      with(listDetailsGridItemRating) {\n        when {\n          item.isRankDisplayed -> gone()\n          item.sortOrder == RATING -> bindRating(item)\n          item.sortOrder == USER_RATING && item.userRating != null -> {\n            visible()\n            text = String.format(ENGLISH, \"%d\", item.userRating)\n          }\n          else -> gone()\n        }\n      }\n\n      listDetailsGridItemRank.visibleIf(item.isRankDisplayed)\n      listDetailsGridItemRank.text = String.format(ENGLISH, \"%d\", item.rankDisplay)\n\n      listDetailsGridItemHandle.visibleIf(item.isManageMode)\n    }\n    loadImage(item)\n  }\n\n  private fun bindRating(item: ListDetailsItem) {\n    with(contentBinding) {\n      var rating = String.format(ENGLISH, \"%.1f\", item.getRating())\n\n      if (item.isShow()) {\n        val isMyHidden = item.spoilers.isMyShowsRatingsHidden && item.isWatched\n        val isWatchlistHidden = item.spoilers.isWatchlistShowsRatingsHidden && item.isWatchlist\n        val isNotCollectedHidden = item.spoilers.isNotCollectedShowsRatingsHidden && (!item.isWatched && !item.isWatchlist)\n        if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n          listDetailsGridItemRating.tag = rating\n          rating = SPOILERS_RATINGS_HIDE_SYMBOL\n        }\n      }\n\n      if (item.isMovie()) {\n        val isMyHidden = item.spoilers.isMyMoviesRatingsHidden && item.isWatched\n        val isWatchlistHidden = item.spoilers.isWatchlistMoviesRatingsHidden && item.isWatchlist\n        val isNotCollectedHidden = item.spoilers.isNotCollectedMoviesRatingsHidden && (!item.isWatched && !item.isWatchlist)\n        if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n          listDetailsGridItemRating.tag = rating\n          rating = SPOILERS_RATINGS_HIDE_SYMBOL\n        }\n      }\n\n      if (item.spoilers.isTapToReveal) {\n        with(listDetailsGridItemRating) {\n          onClick {\n            tag?.let { text = it.toString() }\n            isClickable = false\n          }\n        }\n      }\n\n      listDetailsGridItemRating.visible()\n      listDetailsGridItemRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(contentBinding) {\n      listDetailsGridItemPlaceholder.gone()\n      Glide.with(this@ListDetailsGridItemView).clear(listDetailsGridItemImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/details/views/grid/ListDetailsGridTitleItemView.kt",
    "content": "package com.michaldrabik.ui_lists.details.views.grid\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.MotionEvent\nimport android.widget.ImageView\nimport androidx.core.content.ContextCompat\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_lists.databinding.LayoutListDetailsItemGridBinding\nimport com.michaldrabik.ui_lists.databinding.ViewListDetailsItemGridTitleBinding\nimport com.michaldrabik.ui_lists.details.recycler.ListDetailsItem\nimport com.michaldrabik.ui_lists.details.views.ListDetailsItemView\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport java.util.Locale\nimport java.util.Locale.ENGLISH\nimport kotlin.math.abs\n\n@SuppressLint(\"ClickableViewAccessibility\")\nclass ListDetailsGridTitleItemView : ListDetailsItemView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewListDetailsItemGridTitleBinding.inflate(LayoutInflater.from(context), this)\n  private val contentBinding = LayoutListDetailsItemGridBinding.bind(binding.listDetailsGridItemRoot)\n\n  private val width by lazy {\n    val span = if (context.isTablet()) Config.LISTS_GRID_SPAN_TABLET else Config.LISTS_GRID_SPAN\n    val itemSpacing = context.dimenToPx(R.dimen.spaceSmall)\n    val screenMargin = context.dimenToPx(R.dimen.screenMarginHorizontal)\n    val screenWidth = screenWidth().toFloat()\n    ((screenWidth - (screenMargin * 2.0)) - ((span - 1) * itemSpacing)) / span\n  }\n  private val height by lazy { width * 1.7305 }\n\n  init {\n    layoutParams = LayoutParams(width.toInt(), height.toInt())\n\n    clipToPadding = false\n    clipChildren = false\n\n    binding.listDetailsGridItemRoot.onClick {\n      if (item.isEnabled && !item.isManageMode) {\n        itemClickListener?.invoke(item)\n      }\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n    initSwipeListener()\n    initDragListener()\n  }\n\n  override val imageView: ImageView = contentBinding.listDetailsGridItemImage\n  override val placeholderView: ImageView = contentBinding.listDetailsGridItemPlaceholder\n\n  private fun initSwipeListener() {\n    var x = 0F\n    binding.listDetailsGridItemRoot.setOnTouchListener { _, event ->\n      if (item.isManageMode) {\n        return@setOnTouchListener false\n      }\n      if (event.action == MotionEvent.ACTION_DOWN) x = event.x\n      if (event.action == MotionEvent.ACTION_UP) x = 0F\n      if (event.action == MotionEvent.ACTION_MOVE && abs(x - event.x) > 50F) {\n        itemSwipeStartListener?.invoke()\n        return@setOnTouchListener true\n      }\n      false\n    }\n  }\n\n  private fun initDragListener() {\n    contentBinding.listDetailsGridItemHandle.setOnTouchListener { _, event ->\n      if (item.isManageMode && event.action == MotionEvent.ACTION_DOWN) {\n        itemDragStartListener?.invoke()\n      }\n      false\n    }\n  }\n\n  override fun bind(item: ListDetailsItem) {\n    clear()\n    this.item = item\n\n    with(contentBinding) {\n      listDetailsGridItemProgress.visibleIf(item.isLoading)\n\n      with(binding.listDetailsGridItemTitle) {\n        if (item.isShow()) {\n          text =\n            if (item.translation?.title.isNullOrBlank()) item.requireShow().title\n            else item.translation?.title\n        }\n        if (item.isMovie()) {\n          text =\n            if (item.translation?.title.isNullOrBlank()) item.requireMovie().title\n            else item.translation?.title\n        }\n      }\n\n      with(listDetailsGridItemImage) {\n        if (item.isShow()) setImageResource(R.drawable.ic_television)\n        if (item.isMovie()) setImageResource(R.drawable.ic_film)\n      }\n\n      with(listDetailsGridItemBadge) {\n        val inCollection = item.isWatched || item.isWatchlist\n        visibleIf(inCollection)\n        if (inCollection) {\n          val color = if (item.isWatched) R.color.colorAccent else R.color.colorGrayLight\n          imageTintList = ColorStateList.valueOf(ContextCompat.getColor(context, color))\n        }\n      }\n\n      with(listDetailsGridItemRating) {\n        when {\n          item.isRankDisplayed -> gone()\n          item.sortOrder == RATING -> bindRating(item)\n          item.sortOrder == USER_RATING && item.userRating != null -> {\n            visible()\n            text = String.format(ENGLISH, \"%d\", item.userRating)\n          }\n          else -> gone()\n        }\n      }\n\n      listDetailsGridItemRank.visibleIf(item.isRankDisplayed)\n      listDetailsGridItemRank.text = String.format(Locale.ENGLISH, \"%d\", item.rankDisplay)\n\n      listDetailsGridItemHandle.visibleIf(item.isManageMode)\n    }\n    loadImage(item)\n  }\n\n  private fun bindRating(item: ListDetailsItem) {\n    with(contentBinding) {\n      var rating = String.format(ENGLISH, \"%.1f\", item.getRating())\n\n      if (item.isShow()) {\n        val isMyHidden = item.spoilers.isMyShowsRatingsHidden && item.isWatched\n        val isWatchlistHidden = item.spoilers.isWatchlistShowsRatingsHidden && item.isWatchlist\n        val isNotCollectedHidden = item.spoilers.isNotCollectedShowsRatingsHidden && (!item.isWatched && !item.isWatchlist)\n        if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n          listDetailsGridItemRating.tag = rating\n          rating = Config.SPOILERS_RATINGS_HIDE_SYMBOL\n        }\n      }\n\n      if (item.isMovie()) {\n        val isMyHidden = item.spoilers.isMyMoviesRatingsHidden && item.isWatched\n        val isWatchlistHidden = item.spoilers.isWatchlistMoviesRatingsHidden && item.isWatchlist\n        val isNotCollectedHidden = item.spoilers.isNotCollectedMoviesRatingsHidden && (!item.isWatched && !item.isWatchlist)\n        if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n          listDetailsGridItemRating.tag = rating\n          rating = Config.SPOILERS_RATINGS_HIDE_SYMBOL\n        }\n      }\n\n      if (item.spoilers.isTapToReveal) {\n        with(listDetailsGridItemRating) {\n          onClick {\n            tag?.let { text = it.toString() }\n            isClickable = false\n          }\n        }\n      }\n\n      listDetailsGridItemRating.visible()\n      listDetailsGridItemRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(contentBinding) {\n      listDetailsGridItemPlaceholder.gone()\n      Glide.with(this@ListDetailsGridTitleItemView).clear(listDetailsGridItemImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/lists/ListsFragment.kt",
    "content": "package com.michaldrabik.ui_lists.lists\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.View.GONE\nimport android.view.View.VISIBLE\nimport androidx.activity.addCallback\nimport androidx.core.os.bundleOf\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.postDelayed\nimport androidx.core.view.updatePadding\nimport androidx.core.widget.doAfterTextChanged\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.OnTabReselectedListener\nimport com.michaldrabik.ui_base.common.sheets.sort_order.SortOrderBottomSheet\nimport com.michaldrabik.ui_base.events.Event\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.TraktListQuickSyncSuccess\nimport com.michaldrabik.ui_base.events.TraktQuickSyncSuccess\nimport com.michaldrabik.ui_base.utilities.ModeHost\nimport com.michaldrabik.ui_base.utilities.extensions.add\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.disableUi\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.enableUi\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.hideKeyboard\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.showKeyboard\nimport com.michaldrabik.ui_base.utilities.extensions.updateTopMargin\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_lists.R\nimport com.michaldrabik.ui_lists.databinding.FragmentListsBinding\nimport com.michaldrabik.ui_lists.lists.helpers.ListsLayoutManagerProvider\nimport com.michaldrabik.ui_lists.lists.recycler.ListsAdapter\nimport com.michaldrabik.ui_lists.lists.recycler.ListsItem\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_UPDATED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_LIST\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_CREATE_LIST\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ListsFragment :\n  BaseFragment<ListsViewModel>(R.layout.fragment_lists),\n  OnTabReselectedListener {\n\n  companion object {\n    private const val TRANSLATION_DURATION = 225L\n  }\n\n  override val viewModel by viewModels<ListsViewModel>()\n  private val binding by viewBinding(FragmentListsBinding::bind)\n\n  @Inject lateinit var eventsManager: EventsManager\n  @Inject lateinit var settings: SettingsViewModeRepository\n\n  private var adapter: ListsAdapter? = null\n  private var layoutManager: LayoutManager? = null\n\n  private var searchViewTranslation = 0F\n  private var tabsTranslation = 0F\n  private var isFabHidden = false\n  private var isSearching = false\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n\n    savedInstanceState?.let {\n      searchViewTranslation = it.getFloat(\"ARG_SEARCH_POSITION\")\n      tabsTranslation = it.getFloat(\"ARG_TABS_POSITION\")\n      isFabHidden = it.getBoolean(\"ARG_FAB_HIDDEN\")\n    }\n  }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupStatusBar()\n    setupRecycler()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { eventsManager.events.collect { handleEvent(it) } },\n      doAfterLaunch = { viewModel.loadItems(resetScroll = false) }\n    )\n  }\n\n  override fun onResume() {\n    super.onResume()\n    showNavigation()\n  }\n\n  override fun onSaveInstanceState(outState: Bundle) {\n    super.onSaveInstanceState(outState)\n    outState.putFloat(\"ARG_SEARCH_POSITION\", searchViewTranslation)\n    outState.putFloat(\"ARG_TABS_POSITION\", tabsTranslation)\n    outState.putBoolean(\"ARG_FAB_HIDDEN\", isFabHidden)\n  }\n\n  override fun onPause() {\n    enableUi()\n    with(binding) {\n      tabsTranslation = fragmentListsModeTabs.translationY\n      searchViewTranslation = fragmentListsSearchView.translationY\n      isFabHidden = fragmentListsCreateListButton.visibility != VISIBLE\n    }\n    super.onPause()\n  }\n\n  private fun setupView() {\n    with(binding) {\n      fragmentListsSearchView.run {\n        hint = getString(R.string.textSearchFor)\n        onSettingsClickListener = { openSettings() }\n      }\n      with(fragmentListsSearchLocalView) {\n        onCloseClickListener = { exitSearch() }\n      }\n      fragmentListsModeTabs.run {\n        onModeSelected = { (requireActivity() as ModeHost).setMode(it, force = true) }\n        showMovies(moviesEnabled)\n        showLists(true, anchorEnd = moviesEnabled)\n        selectLists()\n      }\n      fragmentListsCreateListButton.run {\n        if (!isFabHidden) fadeIn()\n        onClick { openCreateList() }\n      }\n      fragmentListsFilters.onSortClickListener = { sortOrder, sortType ->\n        showSortOrderDialog(sortOrder, sortType)\n      }\n      fragmentListsSearchButton.run {\n        onClick { if (!isSearching) enterSearch() else exitSearch() }\n      }\n      fragmentListsSearchView.onClick { openMainSearch() }\n\n      fragmentListsSearchView.translationY = searchViewTranslation\n      fragmentListsModeTabs.translationY = tabsTranslation\n      fragmentListsIcons.translationY = tabsTranslation\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      fragmentListsRoot.doOnApplyWindowInsets { _, insets, _, _ ->\n        val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n        val statusBarSize = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n        fragmentListsRecycler\n          .updatePadding(top = statusBarSize + dimenToPx(R.dimen.listsRecyclerPaddingTop))\n        fragmentListsSearchView.applyWindowInsetBehaviour(dimenToPx(R.dimen.spaceNormal) + statusBarSize)\n        fragmentListsSearchView.updateTopMargin(dimenToPx(R.dimen.spaceMedium) + statusBarSize)\n        fragmentListsModeTabs.updateTopMargin(dimenToPx(R.dimen.collectionTabsMargin) + statusBarSize)\n        fragmentListsIcons.updateTopMargin(dimenToPx(R.dimen.listsIconsPadding) + statusBarSize)\n        fragmentListsSearchLocalView.updateTopMargin(dimenToPx(R.dimen.listsSearchLocalViewPadding) + statusBarSize)\n        fragmentListsEmptyView.root.updateTopMargin(statusBarSize)\n      }\n    }\n  }\n\n  private fun setupRecycler() {\n    layoutManager = ListsLayoutManagerProvider.provideLayoutManger(requireContext(), settings)\n    adapter = ListsAdapter().apply {\n      stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY\n      itemClickListener = { openListDetails(it) }\n      itemsChangedListener = {\n        resetTranslations()\n        layoutManager?.scrollToPosition(0)\n      }\n      missingImageListener = { item, itemImage, force ->\n        viewModel.loadMissingImage(item, itemImage, force)\n      }\n    }\n    with(binding) {\n      fragmentListsRecycler.apply {\n        adapter = this@ListsFragment.adapter\n        layoutManager = this@ListsFragment.layoutManager\n        (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n        setHasFixedSize(true)\n        clearOnScrollListeners()\n        addOnScrollListener(object : RecyclerView.OnScrollListener() {\n          var isFading = false\n          override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {\n            if (isFading) return\n            val position = (this@ListsFragment.layoutManager as? LinearLayoutManager)?.findFirstCompletelyVisibleItemPosition()\n              ?: (this@ListsFragment.layoutManager as? GridLayoutManager)?.findFirstCompletelyVisibleItemPosition()\n            if ((position ?: 0) > 1) {\n              if (fragmentListsCreateListButton.visibility != VISIBLE) return\n              fragmentListsCreateListButton\n                .fadeOut(125, endAction = { isFading = false })\n                .add(animations)\n            } else {\n              if (fragmentListsCreateListButton.visibility != GONE) return\n              fragmentListsCreateListButton\n                .fadeIn(125, endAction = { isFading = false })\n                .add(animations)\n            }\n            isFading = true\n          }\n        })\n      }\n    }\n  }\n\n  override fun setupBackPressed() {\n    val dispatcher = requireActivity().onBackPressedDispatcher\n    dispatcher.addCallback(viewLifecycleOwner) {\n      if (isSearching) {\n        exitSearch()\n      } else {\n        isEnabled = false\n        activity?.onBackPressed()\n      }\n    }\n  }\n\n  private fun enterSearch() {\n    resetTranslations()\n    with(binding) {\n      fragmentListsSearchLocalView.fadeIn(150)\n      fragmentListsIcons.gone()\n      fragmentListsRecycler.smoothScrollToPosition(0)\n      with(fragmentListsSearchLocalView.binding.searchViewLocalInput) {\n        setText(\"\")\n        doAfterTextChanged {\n          viewModel.loadItems(\n            searchQuery = it.toString().trim(),\n            resetScroll = true\n          )\n        }\n        visible()\n        showKeyboard()\n        requestFocus()\n      }\n    }\n    isSearching = true\n  }\n\n  private fun exitSearch() {\n    with(binding) {\n      isSearching = false\n      resetTranslations()\n      fragmentListsSearchLocalView.gone()\n      fragmentListsIcons.visible()\n      fragmentListsRecycler.translationY = 0F\n      fragmentListsRecycler.postDelayed(200) { layoutManager?.scrollToPosition(0) }\n      with(fragmentListsSearchLocalView.binding.searchViewLocalInput) {\n        setText(\"\")\n        gone()\n        hideKeyboard()\n        clearFocus()\n      }\n    }\n  }\n\n  private fun showSortOrderDialog(sortOrder: SortOrder, sortType: SortType) {\n    val options = listOf(NAME, NEWEST, DATE_UPDATED)\n    val args = SortOrderBottomSheet.createBundle(options, sortOrder, sortType)\n\n    setFragmentResultListener(NavigationArgs.REQUEST_SORT_ORDER) { _, bundle ->\n      val order = bundle.getSerializable(NavigationArgs.ARG_SELECTED_SORT_ORDER) as SortOrder\n      val type = bundle.getSerializable(NavigationArgs.ARG_SELECTED_SORT_TYPE) as SortType\n      viewModel.setSortOrder(order, type)\n    }\n\n    navigateTo(R.id.actionListsFragmentToSortOrder, args)\n  }\n\n  private fun render(uiState: ListsUiState) {\n    uiState.run {\n      with(binding) {\n        items?.let {\n          fragmentListsEmptyView.root.fadeIf(it.isEmpty() && !isSearching)\n          fragmentListsSearchButton.visibleIf(it.isNotEmpty() || isSearching)\n\n          val resetScroll = resetScroll.consume() == true\n          adapter?.setItems(it, resetScroll)\n        }\n        sortOrder?.let {\n          fragmentListsFilters.setSorting(it.first, it.second)\n        }\n        isSyncing?.let {\n          fragmentListsSearchView.setTraktProgress(it)\n          fragmentListsSearchView.isEnabled = !it\n        }\n      }\n    }\n  }\n\n  private fun openMainSearch() {\n    disableUi()\n    hideNavigation()\n    with(binding) {\n      fragmentListsModeTabs.fadeOut(duration = 200).add(animations)\n      fragmentListsIcons.fadeOut(duration = 200).add(animations)\n      fragmentListsRecycler.fadeOut(duration = 200) {\n        super.navigateTo(R.id.actionListsFragmentToSearch, null)\n      }.add(animations)\n    }\n  }\n\n  private fun openListDetails(listItem: ListsItem) {\n    disableUi()\n    hideNavigation()\n    binding.fragmentListsRoot.fadeOut(150) {\n      val bundle = bundleOf(ARG_LIST to listItem.list)\n      navigateTo(R.id.actionListsFragmentToDetailsFragment, bundle)\n      exitSearch()\n    }.add(animations)\n  }\n\n  private fun openSettings() {\n    hideNavigation()\n    exitSearch()\n    navigateTo(R.id.actionListsFragmentToSettingsFragment)\n  }\n\n  private fun openCreateList() {\n    setFragmentResultListener(REQUEST_CREATE_LIST) { _, _ -> viewModel.loadItems(resetScroll = true) }\n    navigateTo(R.id.actionListsFragmentToCreateListDialog, bundleOf())\n  }\n\n  private fun resetTranslations(duration: Long = TRANSLATION_DURATION) {\n    if (view == null) return\n    with(binding) {\n      arrayOf(\n        fragmentListsSearchView,\n        fragmentListsModeTabs,\n        fragmentListsIcons,\n        fragmentListsSearchLocalView\n      ).forEach {\n        it.animate().translationY(0F).setDuration(duration).add(animations)?.start()\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event) {\n    when (event) {\n      is TraktListQuickSyncSuccess -> {\n        val text = resources.getQuantityString(R.plurals.textTraktQuickSyncComplete, 1, 1)\n        binding.fragmentListsSnackHost.showInfoSnackbar(text)\n      }\n\n      is TraktQuickSyncSuccess -> {\n        val text = resources.getQuantityString(R.plurals.textTraktQuickSyncComplete, event.count, event.count)\n        binding.fragmentListsSnackHost.showInfoSnackbar(text)\n      }\n\n      else -> Unit\n    }\n  }\n\n  override fun onTabReselected() {\n    if (view == null) return\n    resetTranslations()\n    binding.fragmentListsRecycler.smoothScrollToPosition(0)\n  }\n\n  override fun onDestroyView() {\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/lists/ListsUiState.kt",
    "content": "package com.michaldrabik.ui_lists.lists\n\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_lists.lists.recycler.ListsItem\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\n\ndata class ListsUiState(\n  val items: List<ListsItem>? = null,\n  val resetScroll: Event<Boolean> = Event(false),\n  val sortOrder: Pair<SortOrder, SortType>? = null,\n  val isSyncing: Boolean? = null,\n)\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/lists/ListsViewModel.kt",
    "content": "package com.michaldrabik.ui_lists.lists\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkInfo\nimport androidx.work.WorkManager\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.TraktSyncAuthError\nimport com.michaldrabik.ui_base.events.TraktSyncError\nimport com.michaldrabik.ui_base.events.TraktSyncSuccess\nimport com.michaldrabik.ui_base.trakt.TraktSyncWorker\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_lists.lists.cases.MainListsCase\nimport com.michaldrabik.ui_lists.lists.cases.SortOrderListsCase\nimport com.michaldrabik.ui_lists.lists.helpers.ListsItemImage\nimport com.michaldrabik.ui_lists.lists.recycler.ListsItem\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\nimport com.michaldrabik.ui_base.events.Event as EventSync\n\n@HiltViewModel\nclass ListsViewModel @Inject constructor(\n  private val mainCase: MainListsCase,\n  private val sortCase: SortOrderListsCase,\n  private val showImagesProvider: ShowImagesProvider,\n  private val movieImagesProvider: MovieImagesProvider,\n  private val eventsManager: EventsManager,\n  workManager: WorkManager,\n) : ViewModel() {\n\n  private var loadItemsJob: Job? = null\n\n  private val itemsState = MutableStateFlow<List<ListsItem>?>(null)\n  private val scrollState = MutableStateFlow(Event(false))\n  private val sortOrderState = MutableStateFlow<Pair<SortOrder, SortType>?>(null)\n  private val syncingState = MutableStateFlow(false)\n\n  init {\n    viewModelScope.launch {\n      eventsManager.events.collect { onEvent(it) }\n    }\n    workManager.getWorkInfosByTagLiveData(TraktSyncWorker.TAG_ID).observeForever { work ->\n      syncingState.value = work.any { it.state == WorkInfo.State.RUNNING }\n    }\n  }\n\n  fun loadItems(\n    resetScroll: Boolean,\n    searchQuery: String? = null,\n  ) {\n    loadItemsJob?.cancel()\n    loadItemsJob = viewModelScope.launch {\n      sortOrderState.value = sortCase.loadSortOrder()\n      itemsState.value = mainCase.loadLists(searchQuery)\n      scrollState.value = Event(resetScroll)\n    }\n  }\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType) {\n    viewModelScope.launch {\n      sortCase.setSortOrder(sortOrder, sortType)\n      loadItems(resetScroll = true)\n    }\n  }\n\n  fun loadMissingImage(item: ListsItem, itemImage: ListsItemImage, force: Boolean) {\n    viewModelScope.launch {\n      try {\n        val imageType = itemImage.image.type\n\n        val image =\n          when {\n            itemImage.isShow() -> showImagesProvider.loadRemoteImage(itemImage.show!!, imageType, force)\n            itemImage.isMovie() -> movieImagesProvider.loadRemoteImage(itemImage.movie!!, imageType, force)\n            else -> throw IllegalStateException()\n          }\n\n        val updateItemImage = itemImage.copy(image = image)\n        val updateImages = item.images.toMutableList()\n        updateImages.findReplace(updateItemImage) { it.getIds()?.trakt == updateItemImage.getIds()?.trakt }\n        updateItem(item.copy(images = updateImages))\n      } catch (t: Throwable) {\n        val updateItemImage = itemImage.copy(image = Image.createUnavailable(itemImage.image.type))\n        val updateImages = item.images.toMutableList()\n        updateImages.findReplace(updateItemImage) { it.getIds()?.trakt == updateItemImage.getIds()?.trakt }\n        updateItem(item.copy(images = updateImages))\n      }\n    }\n  }\n\n  private fun updateItem(newItem: ListsItem) {\n    val currentItems = uiState.value.items?.toMutableList() ?: mutableListOf()\n    currentItems.findReplace(newItem) { it.list.id == newItem.list.id }\n    itemsState.value = currentItems\n  }\n\n  private fun onEvent(event: EventSync) {\n    if (event in arrayOf(TraktSyncError, TraktSyncAuthError, TraktSyncSuccess)) {\n      loadItems(resetScroll = true)\n    }\n  }\n\n  val uiState = combine(\n    itemsState,\n    scrollState,\n    sortOrderState,\n    syncingState\n  ) { s1, s2, s3, s4 ->\n    ListsUiState(\n      items = s1,\n      resetScroll = s2,\n      sortOrder = s3,\n      isSyncing = s4\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ListsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/lists/cases/MainListsCase.kt",
    "content": "package com.michaldrabik.ui_lists.lists.cases\n\nimport com.michaldrabik.common.Mode.MOVIES\nimport com.michaldrabik.common.Mode.SHOWS\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.CustomListItem\nimport com.michaldrabik.repository.ListsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_lists.lists.helpers.ListsItemImage\nimport com.michaldrabik.ui_lists.lists.helpers.ListsSorter\nimport com.michaldrabik.ui_lists.lists.recycler.ListsItem\nimport com.michaldrabik.ui_model.CustomList\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MainListsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n  private val listsRepository: ListsRepository,\n  private val dateProvider: DateFormatProvider,\n  private val settingsRepository: SettingsRepository,\n  private val showImagesProvider: ShowImagesProvider,\n  private val movieImagesProvider: MovieImagesProvider,\n  private val sorter: ListsSorter,\n) {\n\n  companion object {\n    private const val IMAGES_LIMIT = 3\n  }\n\n  suspend fun loadLists(searchQuery: String?) = withContext(dispatchers.IO) {\n    val lists = listsRepository.loadAll()\n    val dateFormat = dateProvider.loadFullDayFormat()\n    val sorting = Pair(\n      settingsRepository.sorting.listsAllSortOrder,\n      settingsRepository.sorting.listsAllSortType\n    )\n\n    lists\n      .filterByQuery(searchQuery)\n      .sortedWith(sorter.sort(sorting.first, sorting.second))\n      .map {\n        async {\n          val items = localSource.customListsItems.getItemsForListImages(it.id, IMAGES_LIMIT)\n          val images = mutableListOf<ListsItemImage>()\n          val unavailable = ListsItemImage(Image.createUnavailable(POSTER))\n          items.forEach { item ->\n            images.add(findImage(item) ?: unavailable)\n          }\n          if (images.size < IMAGES_LIMIT) {\n            (images.size..IMAGES_LIMIT).forEach { _ -> images.add(unavailable) }\n          }\n          ListsItem(it, images, sorting, dateFormat)\n        }\n      }.awaitAll()\n  }\n\n  private fun List<CustomList>.filterByQuery(query: String?) = when {\n    query.isNullOrBlank() -> this\n    else -> this.filter {\n      it.name.contains(query, ignoreCase = true) ||\n        it.description?.contains(query, ignoreCase = true) == true\n    }\n  }\n\n  private suspend fun findImage(item: CustomListItem) =\n    when (item.type) {\n      SHOWS.type -> {\n        val showDb = localSource.shows.getById(item.idTrakt)\n        showDb?.let {\n          val show = mappers.show.fromDatabase(it)\n          val image = showImagesProvider.findCachedImage(show, POSTER)\n          ListsItemImage(image, show = show)\n        }\n      }\n      MOVIES.type -> {\n        val movieDb = localSource.movies.getById(item.idTrakt)\n        movieDb?.let {\n          val movie = mappers.movie.fromDatabase(movieDb)\n          val image = movieImagesProvider.findCachedImage(movie, POSTER)\n          ListsItemImage(image, movie = movie)\n        }\n      }\n      else -> throw IllegalStateException()\n    }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/lists/cases/SortOrderListsCase.kt",
    "content": "package com.michaldrabik.ui_lists.lists.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SortOrderListsCase @Inject constructor(\n  private val settingsRepository: SettingsRepository\n) {\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType) {\n    settingsRepository.sorting.listsAllSortOrder = sortOrder\n    settingsRepository.sorting.listsAllSortType = sortType\n  }\n\n  fun loadSortOrder() = Pair(\n    settingsRepository.sorting.listsAllSortOrder,\n    settingsRepository.sorting.listsAllSortType\n  )\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/lists/helpers/ListsItemImage.kt",
    "content": "package com.michaldrabik.ui_lists.lists.helpers\n\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\n\ndata class ListsItemImage(\n  val image: Image,\n  val show: Show? = null,\n  val movie: Movie? = null\n) {\n\n  fun getIds(): Ids? {\n    if (show != null) return show.ids\n    if (movie != null) return movie.ids\n    return null\n  }\n\n  fun isShow() = show != null\n\n  fun isMovie() = movie != null\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/lists/helpers/ListsLayoutManagerProvider.kt",
    "content": "package com.michaldrabik.ui_lists.lists.helpers\n\nimport android.content.Context\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\n\ninternal object ListsLayoutManagerProvider {\n\n  fun provideLayoutManger(\n    context: Context,\n    settings: SettingsViewModeRepository,\n  ): RecyclerView.LayoutManager {\n    return if (context.isTablet()) {\n      GridLayoutManager(context, settings.tabletGridSpanSize)\n    } else {\n      LinearLayoutManager(context, VERTICAL, false)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/lists/helpers/ListsSorter.kt",
    "content": "package com.michaldrabik.ui_lists.lists.helpers\n\nimport com.michaldrabik.ui_model.CustomList\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_UPDATED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SortType.ASCENDING\nimport com.michaldrabik.ui_model.SortType.DESCENDING\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass ListsSorter @Inject constructor() {\n\n  fun sort(sortOrder: SortOrder, sortType: SortType) = when (sortType) {\n    ASCENDING -> sortAscending(sortOrder)\n    DESCENDING -> sortDescending(sortOrder)\n  }\n\n  private fun sortAscending(sortOrder: SortOrder): Comparator<CustomList> =\n    when (sortOrder) {\n      NAME -> compareBy { it.name }\n      NEWEST -> compareBy { it.createdAt }\n      DATE_UPDATED -> compareBy { it.updatedAt }\n      else -> throw IllegalStateException(\"Invalid sort order\")\n    }\n\n  private fun sortDescending(sortOrder: SortOrder): Comparator<CustomList> =\n    when (sortOrder) {\n      NAME -> compareByDescending { it.name }\n      NEWEST -> compareByDescending { it.createdAt }\n      DATE_UPDATED -> compareByDescending { it.updatedAt }\n      else -> throw IllegalStateException(\"Invalid sort order\")\n    }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/lists/recycler/ListsAdapter.kt",
    "content": "package com.michaldrabik.ui_lists.lists.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_lists.lists.helpers.ListsItemImage\nimport com.michaldrabik.ui_lists.lists.views.ListsItemView\n\nclass ListsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>(), AsyncListDiffer.ListListener<ListsItem> {\n\n  private val asyncDiffer = AsyncListDiffer(this, ListsItemDiffCallback())\n\n  var itemClickListener: ((ListsItem) -> Unit)? = null\n  var itemsChangedListener: (() -> Unit)? = null\n  var missingImageListener: ((ListsItem, ListsItemImage, Boolean) -> Unit)? = null\n\n  private var notifyItemsChange = false\n\n  init {\n    asyncDiffer.addListListener(this)\n  }\n\n  fun setItems(newItems: List<ListsItem>, notifyItemsChange: Boolean = false) {\n    this.notifyItemsChange = notifyItemsChange\n    asyncDiffer.submitList(newItems)\n  }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    ListsItemViewHolder(\n      ListsItemView(parent.context).apply {\n        itemClickListener = { this@ListsAdapter.itemClickListener?.invoke(it) }\n        missingImageListener = { item, itemImage, force -> this@ListsAdapter.missingImageListener?.invoke(item, itemImage, force) }\n      }\n    )\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    (holder.itemView as ListsItemView).bind(item)\n  }\n\n  override fun getItemCount() = asyncDiffer.currentList.size\n\n  override fun onCurrentListChanged(oldList: MutableList<ListsItem>, newList: MutableList<ListsItem>) {\n    if (notifyItemsChange) itemsChangedListener?.invoke()\n  }\n\n  class ListsItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/lists/recycler/ListsItem.kt",
    "content": "package com.michaldrabik.ui_lists.lists.recycler\n\nimport com.michaldrabik.ui_lists.lists.helpers.ListsItemImage\nimport com.michaldrabik.ui_model.CustomList\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport java.time.format.DateTimeFormatter\n\ndata class ListsItem(\n  val list: CustomList,\n  val images: List<ListsItemImage>,\n  val sortOrder: Pair<SortOrder, SortType>,\n  val dateFormat: DateTimeFormatter? = null\n)\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/lists/recycler/ListsItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_lists.lists.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass ListsItemDiffCallback : DiffUtil.ItemCallback<ListsItem>() {\n\n  override fun areItemsTheSame(oldItem: ListsItem, newItem: ListsItem) =\n    oldItem.list.id == newItem.list.id\n\n  override fun areContentsTheSame(oldItem: ListsItem, newItem: ListsItem) =\n    oldItem.list == newItem.list &&\n      oldItem.sortOrder == newItem.sortOrder &&\n      oldItem.images.size == newItem.images.size &&\n      oldItem.images.toTypedArray().contentDeepEquals(newItem.images.toTypedArray()) &&\n      oldItem.dateFormat?.toString() == newItem.dateFormat?.toString()\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/lists/views/ListsFiltersView.kt",
    "content": "package com.michaldrabik.ui_lists.lists.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.forEach\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_lists.R\nimport com.michaldrabik.ui_lists.databinding.ViewListsFiltersBinding\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\n\nclass ListsFiltersView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewListsFiltersBinding.inflate(LayoutInflater.from(context), this)\n\n  var onSortClickListener: ((SortOrder, SortType) -> Unit)? = null\n\n  override fun setEnabled(enabled: Boolean) {\n    binding.viewListsFiltersChipGroup.forEach {\n      it.isEnabled = enabled\n    }\n  }\n\n  fun setSorting(sortOrder: SortOrder, sortType: SortType) {\n    with(binding) {\n      viewListsFilterSortChip.text = context.getString(sortOrder.displayString)\n      viewListsFilterSortChip.onClick {\n        onSortClickListener?.invoke(sortOrder, sortType)\n      }\n      val sortIcon = when (sortType) {\n        SortType.ASCENDING -> R.drawable.ic_arrow_alt_up\n        SortType.DESCENDING -> R.drawable.ic_arrow_alt_down\n      }\n      viewListsFilterSortChip.closeIcon = ContextCompat.getDrawable(context, sortIcon)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/lists/views/ListsItemView.kt",
    "content": "package com.michaldrabik.ui_lists.lists.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_lists.databinding.ViewListsItemBinding\nimport com.michaldrabik.ui_lists.lists.helpers.ListsItemImage\nimport com.michaldrabik.ui_lists.lists.recycler.ListsItem\nimport com.michaldrabik.ui_model.SortOrder.DATE_UPDATED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\n\n@SuppressLint(\"SetTextI18n\")\nclass ListsItemView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewListsItemBinding.inflate(LayoutInflater.from(context), this)\n\n  var itemClickListener: ((ListsItem) -> Unit)? = null\n  var missingImageListener: ((ListsItem, ListsItemImage, Boolean) -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    with(binding) {\n      listsItemRoot.onClick { itemClickListener?.invoke(item) }\n      listsItemImages.missingImageListener = { itemImage, force ->\n        missingImageListener?.invoke(item, itemImage, force)\n      }\n    }\n  }\n\n  private lateinit var item: ListsItem\n\n  fun bind(item: ListsItem) {\n    this.item = item\n    with(binding) {\n      listsItemTitle.text = item.list.name\n      with(listsItemDescription) {\n        text = item.list.description\n        visibleIf(!item.list.description.isNullOrBlank())\n      }\n\n      val sortOrder = item.sortOrder.first\n      listsItemHeader.visibleIf(sortOrder != NAME)\n      listsItemHeader.text = when (sortOrder) {\n        NAME -> \"\"\n        NEWEST -> item.dateFormat?.format(item.list.createdAt)?.capitalizeWords()\n        DATE_UPDATED -> item.dateFormat?.format(item.list.updatedAt)?.capitalizeWords()\n        else -> throw IllegalStateException()\n      }\n\n      listsItemImages.bind(item.images)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/lists/views/ListsTripleImageView.kt",
    "content": "package com.michaldrabik.ui_lists.lists.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.invisible\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_lists.R\nimport com.michaldrabik.ui_lists.databinding.ViewTripleImageBinding\nimport com.michaldrabik.ui_lists.lists.helpers.ListsItemImage\nimport com.michaldrabik.ui_model.ImageStatus\n\n@SuppressLint(\"SetTextI18n\")\nclass ListsTripleImageView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewTripleImageBinding.inflate(LayoutInflater.from(context), this)\n\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.mediaTileCorner) }\n\n  var missingImageListener: ((ListsItemImage, Boolean) -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n  }\n\n  fun bind(images: List<ListsItemImage>) {\n    clear()\n    with(binding) {\n      if (images.all { it.image.status == ImageStatus.UNAVAILABLE }) {\n        viewTripleImagePlaceholder1.visible()\n        viewTripleImagePlaceholder1.visible()\n        viewTripleImagePlaceholder2.visible()\n        viewTripleImagePlaceholder3.visible()\n        viewTripleImage1.gone()\n        viewTripleImage2.gone()\n        viewTripleImage3.gone()\n        return\n      }\n      // List is guaranteed to always have exact 3 items.\n      loadImage(images[0], viewTripleImage1, viewTripleImagePlaceholder1)\n      loadImage(images[1], viewTripleImage2, viewTripleImagePlaceholder2)\n      loadImage(images[2], viewTripleImage3, viewTripleImagePlaceholder3)\n    }\n  }\n\n  private fun loadImage(\n    itemImage: ListsItemImage,\n    imageView: ImageView,\n    placeholderView: ImageView,\n  ) {\n    if (itemImage.image.status == ImageStatus.UNAVAILABLE) {\n      imageView.invisible()\n      placeholderView.visible()\n      return\n    }\n    if (itemImage.image.status == ImageStatus.UNKNOWN) {\n      missingImageListener?.invoke(itemImage, true)\n      return\n    }\n\n    Glide.with(this)\n      .load(itemImage.image.fullFileUrl)\n      .transform(CenterCrop(), RoundedCorners(cornerRadius))\n      .transition(DrawableTransitionOptions.withCrossFade(Config.IMAGE_FADE_DURATION_MS))\n      .withSuccessListener {\n        imageView.visible()\n        placeholderView.invisible()\n      }\n      .withFailListener {\n        if (itemImage.image.status == ImageStatus.AVAILABLE) {\n          imageView.invisible()\n          placeholderView.visible()\n          return@withFailListener\n        }\n        val force = (itemImage.image.status == ImageStatus.UNKNOWN)\n        missingImageListener?.invoke(itemImage, force)\n      }\n      .into(imageView)\n  }\n\n  private fun clear() {\n    with(binding) {\n      Glide.with(this@ListsTripleImageView).run {\n        clear(viewTripleImage1)\n        clear(viewTripleImage2)\n        clear(viewTripleImage3)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/manage/ManageListsBottomSheet.kt",
    "content": "package com.michaldrabik.ui_lists.manage\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.events.Event\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.TraktQuickSyncSuccess\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.requireLong\nimport com.michaldrabik.ui_base.utilities.extensions.requireString\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_lists.R\nimport com.michaldrabik.ui_lists.databinding.ViewManageListsBinding\nimport com.michaldrabik.ui_lists.manage.recycler.ManageListsAdapter\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_TYPE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_CREATE_LIST\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_MANAGE_LISTS\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ManageListsBottomSheet : BaseBottomSheetFragment(R.layout.view_manage_lists) {\n\n  private val viewModel by viewModels<ManageListsViewModel>()\n  private val binding by viewBinding(ViewManageListsBinding::bind)\n\n  private val itemId by lazy { IdTrakt(requireLong(ARG_ID)) }\n  private val itemType by lazy { requireString(ARG_TYPE) }\n\n  private var adapter: ManageListsAdapter? = null\n  private var layoutManager: LinearLayoutManager? = null\n\n  @Inject lateinit var eventsManager: EventsManager\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupRecycler()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { eventsManager.events.collect { handleEvent(it) } },\n      doAfterLaunch = { viewModel.loadLists(itemId, itemType) }\n    )\n    viewLifecycleOwner.lifecycleScope.launch {\n      repeatOnLifecycle(Lifecycle.State.STARTED) {\n        with(viewModel) {\n          launch { uiState.collect { render(it) } }\n          loadLists(itemId, itemType)\n        }\n      }\n    }\n  }\n\n  private fun setupRecycler() {\n    layoutManager = LinearLayoutManager(context, VERTICAL, false)\n    adapter = ManageListsAdapter(\n      itemCheckListener = { item, isChecked ->\n        viewModel.onListItemChecked(itemId, itemType, item, isChecked)\n      }\n    )\n    binding.viewManageListsRecycler.apply {\n      adapter = this@ManageListsBottomSheet.adapter\n      layoutManager = this@ManageListsBottomSheet.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n    }\n  }\n\n  private fun setupView() {\n    with(binding) {\n      viewManageListsButton.onClick { closeSheet() }\n      viewManageListsCreateButton.onClick {\n        setFragmentResultListener(REQUEST_CREATE_LIST) { _, _ -> viewModel.loadLists(itemId, itemType) }\n        navigateTo(R.id.actionManageListsDialogToCreateListDialog, Bundle.EMPTY)\n      }\n      if (itemType == Mode.MOVIES.type) {\n        viewManageListsSubtitle.setText(R.string.textManageListsMovies)\n      }\n    }\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun render(uiState: ManageListsUiState) {\n    uiState.run {\n      items?.let {\n        adapter?.setItems(it)\n        binding.viewManageListsEmptyView.layoutManageListsEmpty.visibleIf(it.isEmpty())\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event) {\n    if (event is TraktQuickSyncSuccess) {\n      val text = resources.getQuantityString(R.plurals.textTraktQuickSyncComplete, event.count, event.count)\n      binding.viewManageListsSnackHost.showInfoSnackbar(text)\n    }\n  }\n\n  override fun onDestroyView() {\n    setFragmentResult(REQUEST_MANAGE_LISTS, bundleOf())\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/manage/ManageListsUiState.kt",
    "content": "package com.michaldrabik.ui_lists.manage\n\nimport com.michaldrabik.ui_lists.manage.recycler.ManageListsItem\n\ndata class ManageListsUiState(\n  val items: List<ManageListsItem>? = null,\n)\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/manage/ManageListsViewModel.kt",
    "content": "package com.michaldrabik.ui_lists.manage\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_lists.manage.cases.ManageListsCase\nimport com.michaldrabik.ui_lists.manage.recycler.ManageListsItem\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ManageListsViewModel @Inject constructor(\n  private val manageListsCase: ManageListsCase,\n) : ViewModel() {\n\n  private val loadingState = MutableStateFlow(false)\n  private val itemsState = MutableStateFlow<List<ManageListsItem>?>(null)\n\n  fun loadLists(itemId: IdTrakt, itemType: String) {\n    viewModelScope.launch {\n      val loadingJob = launch {\n        delay(500)\n        loadingState.value = true\n      }\n      val items = manageListsCase.loadLists(itemId, itemType)\n      itemsState.value = items\n      loadingState.value = false\n      loadingJob.cancel()\n    }\n  }\n\n  fun onListItemChecked(\n    itemId: IdTrakt,\n    itemType: String,\n    listItem: ManageListsItem,\n    isChecked: Boolean,\n  ) {\n    viewModelScope.launch {\n      if (isChecked) {\n        updateItem(listItem.copy(isEnabled = false, isChecked = true))\n        manageListsCase.addToList(itemId, itemType, listItem)\n        updateItem(listItem.copy(isEnabled = true, isChecked = true))\n      } else {\n        updateItem(listItem.copy(isEnabled = false, isChecked = false))\n        manageListsCase.removeFromList(itemId, itemType, listItem)\n        updateItem(listItem.copy(isEnabled = true, isChecked = false))\n      }\n    }\n  }\n\n  private fun updateItem(listItem: ManageListsItem) {\n    val currentItems = uiState.value.items?.toMutableList()\n    currentItems?.findReplace(listItem) { it.list.id == listItem.list.id }\n    itemsState.value = currentItems\n  }\n\n  val uiState = combine(\n    loadingState,\n    itemsState\n  ) { _, itemsState ->\n    ManageListsUiState(\n      items = itemsState\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ManageListsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/manage/cases/ManageListsCase.kt",
    "content": "package com.michaldrabik.ui_lists.manage.cases\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.ListsRepository\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_lists.manage.recycler.ManageListsItem\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ManageListsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val listsRepository: ListsRepository,\n  private val quickSyncManager: QuickSyncManager,\n) {\n\n  suspend fun loadLists(itemId: IdTrakt, itemType: String) = withContext(dispatchers.IO) {\n    val listsAsync = async { listsRepository.loadAll() }\n    val listsWithItemAsync = async { listsRepository.loadListIdsForItem(itemId, itemType) }\n    val (lists, listsWithItem) = Pair(listsAsync.await(), listsWithItemAsync.await())\n    lists\n      .sortedBy { it.name }\n      .map {\n        val isChecked = listsWithItem.contains(it.id)\n        ManageListsItem(it, isChecked, true)\n      }\n  }\n\n  suspend fun addToList(\n    itemId: IdTrakt,\n    itemType: String,\n    listItem: ManageListsItem,\n  ) = withContext(dispatchers.IO) {\n    listsRepository.addToList(listItem.list.id, itemId, itemType)\n    quickSyncManager.scheduleAddToList(itemId.id, listItem.list.id, Mode.fromType(itemType))\n  }\n\n  suspend fun removeFromList(\n    itemId: IdTrakt,\n    itemType: String,\n    listItem: ManageListsItem,\n  ) = withContext(dispatchers.IO) {\n    listsRepository.removeFromList(listItem.list.id, itemId, itemType)\n    quickSyncManager.scheduleRemoveFromList(itemId.id, listItem.list.id, Mode.fromType(itemType))\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/manage/recycler/ManageListsAdapter.kt",
    "content": "package com.michaldrabik.ui_lists.manage.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_lists.manage.views.ManageListsItemView\n\nclass ManageListsAdapter(\n  val itemCheckListener: ((ManageListsItem, Boolean) -> Unit)\n) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n  private val asyncDiffer = AsyncListDiffer(this, ManageListsItemDiffCallback())\n\n  fun setItems(newItems: List<ManageListsItem>) {\n    asyncDiffer.submitList(newItems)\n  }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    ManageListsItemViewHolder(\n      ManageListsItemView(parent.context).apply {\n        itemCheckListener = { item, isChecked ->\n          this@ManageListsAdapter.itemCheckListener.invoke(item, isChecked)\n        }\n      }\n    )\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    (holder.itemView as ManageListsItemView).bind(item)\n  }\n\n  override fun getItemCount() = asyncDiffer.currentList.size\n\n  class ManageListsItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/manage/recycler/ManageListsItem.kt",
    "content": "package com.michaldrabik.ui_lists.manage.recycler\n\nimport com.michaldrabik.ui_model.CustomList\n\ndata class ManageListsItem(\n  val list: CustomList,\n  val isChecked: Boolean,\n  val isEnabled: Boolean\n)\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/manage/recycler/ManageListsItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_lists.manage.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass ManageListsItemDiffCallback : DiffUtil.ItemCallback<ManageListsItem>() {\n\n  override fun areItemsTheSame(oldItem: ManageListsItem, newItem: ManageListsItem) =\n    oldItem.list.id == newItem.list.id\n\n  override fun areContentsTheSame(oldItem: ManageListsItem, newItem: ManageListsItem) =\n    oldItem.list == newItem.list &&\n      oldItem.isChecked == newItem.isChecked &&\n      oldItem.isEnabled == newItem.isEnabled\n}\n"
  },
  {
    "path": "ui-lists/src/main/java/com/michaldrabik/ui_lists/manage/views/ManageListsItemView.kt",
    "content": "package com.michaldrabik.ui_lists.manage.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_lists.databinding.ViewManageListsItemBinding\nimport com.michaldrabik.ui_lists.manage.recycler.ManageListsItem\n\n@SuppressLint(\"SetTextI18n\")\nclass ManageListsItemView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewManageListsItemBinding.inflate(LayoutInflater.from(context), this)\n\n  var itemCheckListener: ((ManageListsItem, Boolean) -> Unit)? = null\n  private var isCheckEnabled = false\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    binding.manageListsItemCheckbox.setOnCheckedChangeListener { _, isChecked ->\n      if (isCheckEnabled) itemCheckListener?.invoke(item, isChecked)\n    }\n  }\n\n  private lateinit var item: ManageListsItem\n\n  fun bind(item: ManageListsItem) {\n    this.item = item\n    isCheckEnabled = false\n    with(binding) {\n      manageListsItemCheckbox.text = \" ${item.list.name}\"\n      manageListsItemCheckbox.isChecked = item.isChecked\n      manageListsItemCheckbox.isEnabled = item.isEnabled\n    }\n    isCheckEnabled = true\n  }\n}\n"
  },
  {
    "path": "ui-lists/src/main/res/color/selector_create_list_button.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"@color/colorGrayLight\" android:state_enabled=\"false\" />\n  <item android:color=\"?attr/colorAccent\" />\n</selector>"
  },
  {
    "path": "ui-lists/src/main/res/color/selector_create_list_input.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?android:attr/textColorPrimary\" android:state_focused=\"true\" />\n  <item android:color=\"?android:attr/textColorPrimary\" android:state_selected=\"true\" />\n  <item android:color=\"?android:attr/textColorSecondary\" />\n</selector>"
  },
  {
    "path": "ui-lists/src/main/res/color/selector_list_chip_background.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?android:windowBackground\" android:state_checked=\"true\" />\n  <item android:color=\"?android:windowBackground\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "ui-lists/src/main/res/color/selector_list_chip_text.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?attr/textColorChipSelected\" android:state_checked=\"true\" />\n  <item android:color=\"?attr/textColorChip\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "ui-lists/src/main/res/color-notnight/selector_list_chip_background.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?attr/colorBackgroundChipSelected\" android:state_checked=\"true\" />\n  <item android:color=\"?android:windowBackground\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "ui-lists/src/main/res/drawable/bg_rank.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"100dp\" />\n  <solid android:color=\"@color/colorBlackTranslucent\" />\n</shape>"
  },
  {
    "path": "ui-lists/src/main/res/drawable/ic_edit.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z\"/>\n</vector>\n"
  },
  {
    "path": "ui-lists/src/main/res/drawable/ic_handle.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M20,9H4v2h16V9zM4,15h16v-2H4V15z\"/>\n</vector>\n"
  },
  {
    "path": "ui-lists/src/main/res/drawable/ic_list_create.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M14,10L2,10v2h12v-2zM14,6L2,6v2h12L14,6zM18,14v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2,16h8v-2L2,14v2z\"/>\n</vector>\n"
  },
  {
    "path": "ui-lists/src/main/res/drawable/ic_more.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z\"/>\n</vector>\n"
  },
  {
    "path": "ui-lists/src/main/res/drawable/ic_reorder.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n  android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n  android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M14,5h8v2h-8zM14,10.5h8v2h-8zM14,16h8v2h-8zM2,11.5C2,15.08 4.92,18 8.5,18L9,18v2l3,-3 -3,-3v2h-0.5C6.02,16 4,13.98 4,11.5S6.02,7 8.5,7L12,7L12,5L8.5,5C4.92,5 2,7.92 2,11.5z\"/>\n</vector>\n"
  },
  {
    "path": "ui-lists/src/main/res/layout/fragment_list_details.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  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/fragmentListDetailsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/fragmentListDetailsRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginTop=\"?android:attr/actionBarSize\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/screenMarginHorizontal\"\n    android:paddingTop=\"@dimen/listDetailsRecyclerTopPadding\"\n    android:paddingEnd=\"@dimen/screenMarginHorizontal\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    />\n\n  <com.michaldrabik.ui_lists.details.views.ListDetailsFilterView\n    android:id=\"@+id/fragmentListDetailsFiltersView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/listDetailsChipsTopPadding\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    android:visibility=\"gone\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.google.android.material.appbar.MaterialToolbar\n    android:id=\"@+id/fragmentListDetailsToolbar\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:windowBackground\"\n    android:elevation=\"@dimen/elevationNormal\"\n    app:contentInsetStartWithNavigation=\"0dp\"\n    app:navigationIcon=\"@drawable/ic_arrow_back\"\n    app:subtitleTextAppearance=\"@style/ListDetailsToolbarSubtitleAppearance\"\n    app:titleTextAppearance=\"@style/ListDetailsToolbarTitleAppearance\"\n    >\n\n    <ImageView\n      android:id=\"@+id/fragmentListDetailsMoreButton\"\n      android:layout_width=\"40dp\"\n      android:layout_height=\"match_parent\"\n      android:layout_gravity=\"end\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:padding=\"8dp\"\n      app:srcCompat=\"@drawable/ic_more\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <ImageView\n      android:id=\"@+id/fragmentListDetailsManageButton\"\n      android:layout_width=\"40dp\"\n      android:layout_height=\"match_parent\"\n      android:layout_gravity=\"end\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:padding=\"8dp\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_reorder\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/fragmentListDetailsViewModeButton\"\n      android:layout_width=\"40dp\"\n      android:layout_height=\"match_parent\"\n      android:layout_gravity=\"end\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:padding=\"9dp\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_view_list\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:visibility=\"visible\"\n      />\n\n  </com.google.android.material.appbar.MaterialToolbar>\n\n  <include\n    android:id=\"@+id/fragmentListDetailsEmptyView\"\n    layout=\"@layout/layout_list_details_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <FrameLayout\n    android:id=\"@+id/fragmentListDetailsLoadingView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:clickable=\"true\"\n    android:focusable=\"true\"\n    android:visibility=\"gone\"\n    >\n\n    <View\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:background=\"@color/colorBlackTranslucent\"\n      />\n\n    <ProgressBar\n      style=\"@style/ProgressBar.Accent\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center\"\n      />\n\n  </FrameLayout>\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "ui-lists/src/main/res/layout/fragment_lists.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  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/fragmentListsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/fragmentListsRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/listsRecyclerPaddingHorizontal\"\n    android:paddingTop=\"@dimen/listsRecyclerPaddingTop\"\n    android:paddingEnd=\"@dimen/listsRecyclerPaddingHorizontal\"\n    android:paddingBottom=\"@dimen/listsBottomPadding\"\n    />\n\n  <include\n    android:id=\"@+id/fragmentListsEmptyView\"\n    layout=\"@layout/layout_lists_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <FrameLayout\n    android:id=\"@+id/fragmentListsIcons\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"end\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/listsIconsPadding\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    >\n\n    <com.michaldrabik.ui_lists.lists.views.ListsFiltersView\n      android:id=\"@+id/fragmentListsFilters\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center_vertical\"\n      />\n\n    <com.michaldrabik.ui_base.common.views.ScrollableImageView\n      android:id=\"@+id/fragmentListsSearchButton\"\n      android:layout_width=\"36dp\"\n      android:layout_height=\"36dp\"\n      android:layout_gravity=\"end\"\n      android:paddingStart=\"14dp\"\n      app:srcCompat=\"@drawable/ic_search\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n  </FrameLayout>\n\n  <com.michaldrabik.ui_base.common.views.ModeTabsView\n    android:id=\"@+id/fragmentListsModeTabs\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/collectionTabsMargin\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.SearchLocalView\n    android:id=\"@+id/fragmentListsSearchLocalView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/searchLocalViewHeight\"\n    android:layout_marginLeft=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/listsSearchLocalViewPadding\"\n    android:layout_marginRight=\"@dimen/screenMarginHorizontal\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.SearchView\n    android:id=\"@+id/fragmentListsSearchView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/searchViewHeight\"\n    android:layout_marginLeft=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginRight=\"@dimen/screenMarginHorizontal\"\n    />\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/fragmentListsSnackHost\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"bottom\"\n    android:layout_marginBottom=\"@dimen/listsFabBottomPadding\"\n    >\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n      android:id=\"@+id/fragmentListsCreateListButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"end\"\n      android:layout_margin=\"@dimen/listsFabPadding\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:visibility=\"gone\"\n      app:backgroundTint=\"?attr/colorAccent\"\n      app:elevation=\"@dimen/elevationSmall\"\n      app:layout_behavior=\"com.google.android.material.floatingactionbutton.FloatingActionButton$Behavior\"\n      app:srcCompat=\"@drawable/ic_list_create\"\n      app:tint=\"?attr/textColorOnSurface\"\n      tools:ignore=\"ContentDescription\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "ui-lists/src/main/res/layout/layout_list_details_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/menuList\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textListEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-lists/src/main/res/layout/layout_list_details_item_grid.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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  >\n\n  <ImageView\n    android:id=\"@+id/listDetailsGridItemImage\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n  <ImageView\n    android:id=\"@+id/listDetailsGridItemPlaceholder\"\n    android:layout_width=\"@dimen/showTilePlaceholder\"\n    android:layout_height=\"@dimen/showTilePlaceholder\"\n    android:layout_gravity=\"center\"\n    android:visibility=\"gone\"\n    app:srcCompat=\"@drawable/ic_television\"\n    app:tint=\"?attr/colorPlaceholderIcon\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/listDetailsGridItemProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"28dp\"\n    android:layout_height=\"28dp\"\n    android:layout_gravity=\"center\"\n    android:visibility=\"gone\"\n    />\n\n  <TextView\n    android:id=\"@+id/listDetailsGridItemRating\"\n    style=\"@style/ImageTitle\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"bottom|end\"\n    android:layout_margin=\"@dimen/spaceTiny\"\n    android:background=\"@drawable/bg_text_on_surface\"\n    android:drawablePadding=\"2dp\"\n    android:gravity=\"end\"\n    android:paddingStart=\"4dp\"\n    android:paddingTop=\"2dp\"\n    android:paddingEnd=\"4dp\"\n    android:paddingBottom=\"2dp\"\n    android:shadowColor=\"@color/colorTransparent\"\n    android:textColor=\"@color/colorWhite\"\n    android:textSize=\"11sp\"\n    android:visibility=\"gone\"\n    app:drawableStartCompat=\"@drawable/ic_star_small\"\n    app:drawableTint=\"@color/colorWhite\"\n    tools:text=\"9.9\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/listDetailsGridItemHandle\"\n    android:layout_width=\"36dp\"\n    android:layout_height=\"36dp\"\n    android:layout_gravity=\"center\"\n    android:background=\"@drawable/bg_rank\"\n    android:padding=\"4dp\"\n    android:visibility=\"gone\"\n    app:srcCompat=\"@drawable/ic_handle\"\n    app:tint=\"@color/colorWhite\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/listDetailsGridItemRank\"\n    android:layout_width=\"18dp\"\n    android:layout_height=\"18dp\"\n    android:layout_gravity=\"bottom|end\"\n    android:layout_margin=\"@dimen/spaceTiny\"\n    android:background=\"@drawable/bg_rank\"\n    android:gravity=\"center\"\n    android:includeFontPadding=\"false\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    android:textSize=\"10sp\"\n    android:textStyle=\"bold\"\n    android:translationZ=\"@dimen/elevationSmall\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/listDetailsShowImage\"\n    app:layout_constraintEnd_toEndOf=\"@id/listDetailsShowImage\"\n    tools:ignore=\"SmallSp\"\n    tools:text=\"10\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/listDetailsGridItemBadge\"\n    style=\"@style/Badge\"\n    android:layout_width=\"22dp\"\n    android:layout_height=\"22dp\"\n    android:layout_marginEnd=\"2dp\"\n    android:translationY=\"-3dp\"\n    app:layout_constraintEnd_toEndOf=\"@id/listDetailsShowImage\"\n    app:layout_constraintTop_toTopOf=\"@id/listDetailsShowImage\"\n    app:srcCompat=\"@drawable/ic_bookmark_full\"\n    tools:visibility=\"visible\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-lists/src/main/res/layout/layout_lists_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/menuLists\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textListsEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-lists/src/main/res/layout/layout_manage_lists_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/layoutManageListsEmpty\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center\"\n    android:text=\"@string/textListsEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-lists/src/main/res/layout/view_create_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/viewCreateListRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  android:paddingStart=\"@dimen/spaceNormal\"\n  android:paddingTop=\"@dimen/spaceMedium\"\n  android:paddingEnd=\"@dimen/spaceNormal\"\n  android:paddingBottom=\"@dimen/spaceNormal\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <TextView\n    android:id=\"@+id/viewCreateListTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"start\"\n    android:text=\"@string/textCreateList\"\n    android:textAlignment=\"viewStart\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewCreateListSubtitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"start\"\n    android:text=\"@string/textCreateListDescription\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewCreateListTitle\"\n    />\n\n  <com.google.android.material.textfield.TextInputLayout\n    android:id=\"@+id/viewCreateListNameInput\"\n    style=\"@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    app:boxStrokeColor=\"@color/selector_create_list_input\"\n    app:hintTextColor=\"@color/selector_create_list_input\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewCreateListDescriptionInput\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewCreateListSubtitle\"\n    >\n\n    <com.google.android.material.textfield.TextInputEditText\n      android:id=\"@+id/viewCreateListNameValue\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"start\"\n      android:hint=\"@string/textName\"\n      android:imeOptions=\"actionDone\"\n      android:inputType=\"text\"\n      android:lines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColorHint=\"@color/selector_create_list_input\"\n      android:textCursorDrawable=\"@null\"\n      android:textSize=\"14sp\"\n      />\n\n  </com.google.android.material.textfield.TextInputLayout>\n\n  <com.google.android.material.textfield.TextInputLayout\n    android:id=\"@+id/viewCreateListDescriptionInput\"\n    style=\"@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    app:boxStrokeColor=\"@color/selector_create_list_input\"\n    app:hintTextColor=\"@color/selector_create_list_input\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewCreateListButton\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewCreateListNameInput\"\n    >\n\n    <com.google.android.material.textfield.TextInputEditText\n      android:id=\"@+id/viewCreateListDescriptionValue\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"start\"\n      android:hint=\"@string/textDescription\"\n      android:inputType=\"textMultiLine\"\n      android:maxLines=\"8\"\n      android:minLines=\"4\"\n      android:textAlignment=\"viewStart\"\n      android:textColorHint=\"@color/selector_create_list_input\"\n      android:textCursorDrawable=\"@null\"\n      android:textSize=\"14sp\"\n      />\n\n  </com.google.android.material.textfield.TextInputLayout>\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewCreateListButton\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:backgroundTint=\"@color/selector_create_list_button\"\n    android:gravity=\"center\"\n    android:text=\"@string/textCreateList\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewCreateListDescriptionInput\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/viewCreateListSnackHost\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewCreateListButton\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-lists/src/main/res/layout/view_list_delete_confirm.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <com.google.android.material.checkbox.MaterialCheckBox\n    android:id=\"@+id/viewListDeleteConfirmCheckbox\"\n    style=\"@style/ShowlyCheckbox\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:checked=\"true\"\n    android:gravity=\"center\"\n    android:text=\"@string/textRemoveFromTrakt\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-lists/src/main/res/layout/view_list_details_filters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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=\"wrap_content\"\n  >\n\n  <com.google.android.material.chip.ChipGroup\n    android:id=\"@+id/chipsGroup\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    app:singleLine=\"true\"\n    >\n\n    <com.google.android.material.chip.Chip\n      android:id=\"@+id/sortingChip\"\n      style=\"@style/ShowlyChip.Sort\"\n      android:text=\"@string/textSortName\"\n      />\n\n    <com.google.android.material.chip.Chip\n      android:id=\"@+id/showsChip\"\n      style=\"@style/ShowlyChip.Filter\"\n      android:checkable=\"false\"\n      android:text=\"@string/textShows\"\n      app:chipBackgroundColor=\"@color/selector_chip_background\"\n      app:chipStrokeColor=\"@color/selector_chip_stroke\"\n      />\n\n    <com.google.android.material.chip.Chip\n      android:id=\"@+id/moviesChip\"\n      style=\"@style/ShowlyChip.Filter\"\n      android:checkable=\"false\"\n      android:text=\"@string/textMovies\"\n      android:textColor=\"@color/selector_chip_text\"\n      app:chipBackgroundColor=\"@color/selector_chip_background\"\n      app:chipStrokeColor=\"@color/selector_chip_stroke\"\n      />\n\n  </com.google.android.material.chip.ChipGroup>\n\n</merge>"
  },
  {
    "path": "ui-lists/src/main/res/layout/view_list_details_item_grid.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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=\"match_parent\"\n  android:clipChildren=\"false\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/listDetailsGridItemRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@drawable/bg_media_view_elevation\"\n    android:elevation=\"@dimen/elevationTiny\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <include layout=\"@layout/layout_list_details_item_grid\" />\n\n  </FrameLayout>\n\n</merge>"
  },
  {
    "path": "ui-lists/src/main/res/layout/view_list_details_item_grid_title.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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=\"match_parent\"\n  android:clipChildren=\"false\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/listDetailsGridItemRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginBottom=\"@dimen/listDetailsItemMarginBottom\"\n    android:background=\"@drawable/bg_media_view_elevation\"\n    android:elevation=\"@dimen/elevationTiny\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <include layout=\"@layout/layout_list_details_item_grid\" />\n\n  </FrameLayout>\n\n  <TextView\n    android:id=\"@+id/listDetailsGridItemTitle\"\n    style=\"@style/ImageTitleGrid\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"bottom|start\"\n    android:layout_marginBottom=\"@dimen/listDetailsItemTitleMarginBottom\"\n    android:gravity=\"start\"\n    android:maxLines=\"1\"\n    android:textAlignment=\"viewStart\"\n    android:textSize=\"@dimen/listDetailsItemTitleSize\"\n    tools:text=\"Game Of Thrones\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-lists/src/main/res/layout/view_list_details_movie_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/listDetailsMovieRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    >\n\n    <ImageView\n      android:id=\"@+id/listDetailsMovieImage\"\n      android:layout_width=\"@dimen/listDetailsItemImageWidth\"\n      android:layout_height=\"@dimen/listDetailsItemImageHeight\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsMoviePlaceholder\"\n      android:layout_width=\"@dimen/listDetailsItemImageWidth\"\n      android:layout_height=\"@dimen/listDetailsItemImageHeight\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"22dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_film\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/listDetailsMovieProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"@id/listDetailsMovieImage\"\n      app:layout_constraintStart_toStartOf=\"@id/listDetailsMovieImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsMovieRank\"\n      android:layout_width=\"18dp\"\n      android:layout_height=\"18dp\"\n      android:layout_margin=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_rank\"\n      android:gravity=\"center\"\n      android:includeFontPadding=\"false\"\n      android:textColor=\"?attr/textColorOnSurface\"\n      android:textSize=\"10sp\"\n      android:textStyle=\"bold\"\n      android:translationZ=\"@dimen/elevationSmall\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsMovieImage\"\n      app:layout_constraintEnd_toEndOf=\"@id/listDetailsMovieImage\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"10\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsMovieHeaderBadge\"\n      style=\"@style/Badge\"\n      android:layout_width=\"22dp\"\n      android:layout_height=\"22dp\"\n      android:layout_marginEnd=\"2dp\"\n      android:translationY=\"-4dp\"\n      app:layout_constraintEnd_toEndOf=\"@id/listDetailsMovieImage\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsMovieImage\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsMovieTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/listDetailsMovieDescription\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsMovieHandle\"\n      app:layout_constraintStart_toEndOf=\"@id/listDetailsMovieImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/listDetailsMovieHeader\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Game of Thrones\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsMovieHeaderIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:layout_marginStart=\"10dp\"\n      android:padding=\"1dp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsMovieHeader\"\n      app:layout_constraintStart_toEndOf=\"@id/listDetailsMovieImage\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsMovieHeader\"\n      app:srcCompat=\"@drawable/ic_film\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsMovieHeader\"\n      style=\"@style/CollectionItem.Header\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:layout_marginBottom=\"@dimen/spaceMicro\"\n      app:layout_constraintBottom_toTopOf=\"@id/listDetailsMovieTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsMovieStarIcon\"\n      app:layout_constraintStart_toEndOf=\"@id/listDetailsMovieHeaderIcon\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Netflix\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsMovieStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsMovieHeader\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsMovieRating\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsMovieHeader\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsMovieRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsMovieHeader\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsMovieHeader\"\n      tools:text=\"7.6\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsMovieUserStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/listDetailsMovieTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsMovieUserRating\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsMovieHeader\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsMovieUserRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/listDetailsMovieTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsMovieStarIcon\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsMovieHeader\"\n      tools:text=\"10\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsMovieDescription\"\n      style=\"@style/CollectionItem.Description\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsMovieHandle\"\n      app:layout_constraintStart_toEndOf=\"@id/listDetailsMovieImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/listDetailsMovieTitle\"\n      tools:text=\"Lorem Ipsum\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsMovieHandle\"\n      android:layout_width=\"40dp\"\n      android:layout_height=\"100dp\"\n      android:paddingStart=\"12dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_handle\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-lists/src/main/res/layout/view_list_details_movie_item_compact.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/listDetailsMovieRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    >\n\n    <ImageView\n      android:id=\"@+id/listDetailsMovieImage\"\n      android:layout_width=\"@dimen/listDetailsItemCompactImageWidth\"\n      android:layout_height=\"@dimen/listDetailsItemCompactImageHeight\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsMoviePlaceholder\"\n      android:layout_width=\"@dimen/listDetailsItemCompactImageWidth\"\n      android:layout_height=\"@dimen/listDetailsItemCompactImageHeight\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"22dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/listDetailsMovieProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"20dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"@id/listDetailsMovieImage\"\n      app:layout_constraintStart_toStartOf=\"@id/listDetailsMovieImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:progress=\"75\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsMovieRank\"\n      android:layout_width=\"18dp\"\n      android:layout_height=\"18dp\"\n      android:layout_margin=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_rank\"\n      android:gravity=\"center\"\n      android:includeFontPadding=\"false\"\n      android:textColor=\"?attr/textColorOnSurface\"\n      android:textSize=\"10sp\"\n      android:textStyle=\"bold\"\n      android:translationZ=\"@dimen/elevationSmall\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsMovieImage\"\n      app:layout_constraintEnd_toEndOf=\"@id/listDetailsMovieImage\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"10\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsMovieTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:textSize=\"16sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsMovieHandle\"\n      app:layout_constraintStart_toEndOf=\"@id/listDetailsMovieImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/listDetailsMovieHeader\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Game of Thrones\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsMovieHeaderIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:layout_marginStart=\"11dp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsMovieHeader\"\n      app:layout_constraintStart_toEndOf=\"@id/listDetailsMovieImage\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsMovieHeader\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsMovieHeaderBadge\"\n      style=\"@style/Badge\"\n      android:layout_width=\"18dp\"\n      android:layout_height=\"18dp\"\n      android:layout_marginEnd=\"2dp\"\n      android:translationY=\"-2dp\"\n      app:layout_constraintEnd_toEndOf=\"@id/listDetailsMovieImage\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsMovieImage\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsMovieHeader\"\n      style=\"@style/CollectionItem.Header\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:layout_marginBottom=\"@dimen/spaceMicro\"\n      android:textSize=\"11sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/listDetailsMovieTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsMovieStarIcon\"\n      app:layout_constraintStart_toEndOf=\"@id/listDetailsMovieHeaderIcon\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Netflix 2020\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsMovieStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsMovieHeader\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsMovieRating\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsMovieHeader\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsMovieRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"11sp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsMovieHeader\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsMovieHeader\"\n      tools:text=\"7.6\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsMovieUserStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/listDetailsMovieTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsMovieUserRating\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsMovieHeader\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsMovieUserRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"11sp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/listDetailsMovieTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsMovieStarIcon\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsMovieHeader\"\n      tools:text=\"10\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsMovieHandle\"\n      android:layout_width=\"40dp\"\n      android:layout_height=\"0dp\"\n      android:paddingStart=\"12dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_handle\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-lists/src/main/res/layout/view_list_details_show_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/listDetailsShowRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    >\n\n    <ImageView\n      android:id=\"@+id/listDetailsShowImage\"\n      android:layout_width=\"@dimen/listDetailsItemImageWidth\"\n      android:layout_height=\"@dimen/listDetailsItemImageHeight\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsShowPlaceholder\"\n      android:layout_width=\"@dimen/listDetailsItemImageWidth\"\n      android:layout_height=\"@dimen/listDetailsItemImageHeight\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"22dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/listDetailsShowProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"@id/listDetailsShowImage\"\n      app:layout_constraintStart_toStartOf=\"@id/listDetailsShowImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsShowRank\"\n      android:layout_width=\"18dp\"\n      android:layout_height=\"18dp\"\n      android:layout_margin=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_rank\"\n      android:gravity=\"center\"\n      android:includeFontPadding=\"false\"\n      android:textColor=\"?attr/textColorOnSurface\"\n      android:textSize=\"10sp\"\n      android:textStyle=\"bold\"\n      android:translationZ=\"@dimen/elevationSmall\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsShowImage\"\n      app:layout_constraintEnd_toEndOf=\"@id/listDetailsShowImage\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"10\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsShowTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/listDetailsShowDescription\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsShowHandle\"\n      app:layout_constraintStart_toEndOf=\"@id/listDetailsShowImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/listDetailsShowHeader\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Game of Thrones\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsShowHeaderIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:layout_marginStart=\"11dp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsShowHeader\"\n      app:layout_constraintStart_toEndOf=\"@id/listDetailsShowImage\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsShowHeader\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsShowHeaderBadge\"\n      style=\"@style/Badge\"\n      android:layout_width=\"22dp\"\n      android:layout_height=\"22dp\"\n      android:layout_marginEnd=\"2dp\"\n      android:translationY=\"-4dp\"\n      app:layout_constraintEnd_toEndOf=\"@id/listDetailsShowImage\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsShowImage\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsShowHeader\"\n      style=\"@style/CollectionItem.Header\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:layout_marginBottom=\"@dimen/spaceMicro\"\n      app:layout_constraintBottom_toTopOf=\"@id/listDetailsShowTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsShowStarIcon\"\n      app:layout_constraintStart_toEndOf=\"@id/listDetailsShowHeaderIcon\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Netflix 2020\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsShowStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsShowHeader\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsShowRating\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsShowHeader\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsShowRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsShowHeader\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsShowHeader\"\n      tools:text=\"7.6\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsShowUserStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/listDetailsShowTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsShowUserRating\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsShowHeader\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsShowUserRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/listDetailsShowTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsShowStarIcon\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsShowHeader\"\n      tools:text=\"10\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsShowDescription\"\n      style=\"@style/CollectionItem.Description\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsShowHandle\"\n      app:layout_constraintStart_toEndOf=\"@id/listDetailsShowImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/listDetailsShowTitle\"\n      tools:text=\"Lorem Ipsum\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsShowHandle\"\n      android:layout_width=\"40dp\"\n      android:layout_height=\"100dp\"\n      android:paddingStart=\"12dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_handle\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-lists/src/main/res/layout/view_list_details_show_item_compact.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/listDetailsShowRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    >\n\n    <ImageView\n      android:id=\"@+id/listDetailsShowImage\"\n      android:layout_width=\"@dimen/listDetailsItemCompactImageWidth\"\n      android:layout_height=\"@dimen/listDetailsItemCompactImageHeight\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsShowPlaceholder\"\n      android:layout_width=\"@dimen/listDetailsItemCompactImageWidth\"\n      android:layout_height=\"@dimen/listDetailsItemCompactImageHeight\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"22dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/listDetailsShowProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"20dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"@id/listDetailsShowImage\"\n      app:layout_constraintStart_toStartOf=\"@id/listDetailsShowImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:progress=\"75\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsShowRank\"\n      android:layout_width=\"18dp\"\n      android:layout_height=\"18dp\"\n      android:layout_margin=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_rank\"\n      android:gravity=\"center\"\n      android:includeFontPadding=\"false\"\n      android:textColor=\"?attr/textColorOnSurface\"\n      android:textSize=\"10sp\"\n      android:textStyle=\"bold\"\n      android:translationZ=\"@dimen/elevationSmall\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsShowImage\"\n      app:layout_constraintEnd_toEndOf=\"@id/listDetailsShowImage\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"10\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsShowTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:textSize=\"16sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsShowHandle\"\n      app:layout_constraintStart_toEndOf=\"@id/listDetailsShowImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/listDetailsShowHeader\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Game of Thrones\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsShowHeaderIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:layout_marginStart=\"11dp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsShowHeader\"\n      app:layout_constraintStart_toEndOf=\"@id/listDetailsShowImage\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsShowHeader\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsShowHeaderBadge\"\n      style=\"@style/Badge\"\n      android:layout_width=\"18dp\"\n      android:layout_height=\"18dp\"\n      android:layout_marginEnd=\"2dp\"\n      android:translationY=\"-2dp\"\n      app:layout_constraintEnd_toEndOf=\"@id/listDetailsShowImage\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsShowImage\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsShowHeader\"\n      style=\"@style/CollectionItem.Header\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:layout_marginBottom=\"@dimen/spaceMicro\"\n      android:textSize=\"11sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/listDetailsShowTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsShowStarIcon\"\n      app:layout_constraintStart_toEndOf=\"@id/listDetailsShowHeaderIcon\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Netflix 2020\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsShowStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsShowHeader\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsShowRating\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsShowHeader\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsShowRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"11sp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/listDetailsShowHeader\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsShowHeader\"\n      tools:text=\"7.6\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsShowUserStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/listDetailsShowTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsShowUserRating\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsShowHeader\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <TextView\n      android:id=\"@+id/listDetailsShowUserRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"11sp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/listDetailsShowTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/listDetailsShowStarIcon\"\n      app:layout_constraintTop_toTopOf=\"@id/listDetailsShowHeader\"\n      tools:text=\"10\"\n      />\n\n    <ImageView\n      android:id=\"@+id/listDetailsShowHandle\"\n      android:layout_width=\"40dp\"\n      android:layout_height=\"0dp\"\n      android:paddingStart=\"12dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_handle\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-lists/src/main/res/layout/view_lists_filters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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=\"wrap_content\"\n  >\n\n  <com.google.android.material.chip.ChipGroup\n    android:id=\"@+id/viewListsFiltersChipGroup\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    app:singleLine=\"true\"\n    >\n\n    <com.google.android.material.chip.Chip\n      android:id=\"@+id/viewListsFilterSortChip\"\n      style=\"@style/ShowlyChip.Sort\"\n      android:text=\"@string/textSortName\"\n      />\n\n  </com.google.android.material.chip.ChipGroup>\n\n</merge>"
  },
  {
    "path": "ui-lists/src/main/res/layout/view_lists_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/listsItemRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:paddingLeft=\"@dimen/listsMarginHorizontal\"\n    android:paddingTop=\"@dimen/spaceSmall\"\n    android:paddingRight=\"@dimen/listsMarginHorizontal\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <androidx.constraintlayout.widget.Guideline\n      android:id=\"@+id/listsItemGuideline\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"vertical\"\n      app:layout_constraintGuide_begin=\"@dimen/listsTripleImageWidth\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      />\n\n    <com.michaldrabik.ui_lists.lists.views.ListsTripleImageView\n      android:id=\"@+id/listsItemImages\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"@dimen/listsImageHeight\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/listsItemGuideline\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <TextView\n      android:id=\"@+id/listsItemTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/listsItemDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/listsItemGuideline\"\n      app:layout_constraintTop_toBottomOf=\"@id/listsItemHeader\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Game of Thrones\"\n      />\n\n    <TextView\n      android:id=\"@+id/listsItemHeader\"\n      style=\"@style/CollectionItem.Header\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/listsItemTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/listsItemGuideline\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Netflix\"\n      />\n\n    <TextView\n      android:id=\"@+id/listsItemDescription\"\n      style=\"@style/CollectionItem.Description\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/listsItemGuideline\"\n      app:layout_constraintTop_toBottomOf=\"@id/listsItemTitle\"\n      tools:text=\"Lorem Ipsum\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-lists/src/main/res/layout/view_manage_lists.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/viewManageListsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  android:paddingStart=\"@dimen/spaceNormal\"\n  android:paddingTop=\"@dimen/spaceMedium\"\n  android:paddingEnd=\"@dimen/spaceNormal\"\n  android:paddingBottom=\"@dimen/spaceNormal\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <ImageView\n    android:id=\"@+id/viewManageListsCreateButton\"\n    android:layout_width=\"26dp\"\n    android:layout_height=\"26dp\"\n    app:layout_constraintBottom_toBottomOf=\"@id/viewManageListsSubtitle\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/viewManageListsTitle\"\n    app:srcCompat=\"@drawable/ic_list_create\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewManageListsTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"start\"\n    android:text=\"@string/textManageLists\"\n    android:textAlignment=\"viewStart\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewManageListsSubtitle\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_constraintVertical_chainStyle=\"packed\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewManageListsSubtitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"start\"\n    android:text=\"@string/textManageListsShows\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewManageListsRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewManageListsTitle\"\n    />\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/viewManageListsRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:minHeight=\"64dp\"\n    android:overScrollMode=\"never\"\n    android:translationX=\"-6dp\"\n    app:layout_constrainedHeight=\"true\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewManageListsButton\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewManageListsSubtitle\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewManageListsButton\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:backgroundTint=\"?attr/colorAccent\"\n    android:gravity=\"center\"\n    android:text=\"@string/textClose\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewManageListsRecycler\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/viewManageListsSnackHost\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewManageListsButton\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    />\n\n  <include\n    android:id=\"@+id/viewManageListsEmptyView\"\n    layout=\"@layout/layout_manage_lists_empty\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/viewManageListsRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/viewManageListsRecycler\"\n    tools:visibility=\"visible\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-lists/src/main/res/layout/view_manage_lists_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <com.google.android.material.checkbox.MaterialCheckBox\n    android:id=\"@+id/manageListsItemCheckbox\"\n    style=\"@style/ShowlyCheckbox\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center\"\n    tools:text=\"Some list name\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-lists/src/main/res/layout/view_triple_image.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:layout_width=\"130dp\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <ImageView\n    android:id=\"@+id/viewTripleImagePlaceholder3\"\n    android:layout_width=\"@dimen/listsImageWidth\"\n    android:layout_height=\"@dimen/listsImageHeight\"\n    android:layout_gravity=\"start\"\n    android:background=\"@drawable/bg_media_view_placeholder\"\n    android:elevation=\"3dp\"\n    android:padding=\"@dimen/spaceBig\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_list_alt\"\n    app:tint=\"?attr/colorPlaceholderIcon\"\n    />\n\n\n  <ImageView\n    android:id=\"@+id/viewTripleImage3\"\n    android:layout_width=\"@dimen/listsImageWidth\"\n    android:layout_height=\"@dimen/listsImageHeight\"\n    android:layout_gravity=\"start\"\n    android:background=\"@drawable/bg_media_view_elevation\"\n    android:elevation=\"3dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/viewTripleImagePlaceholder2\"\n    android:layout_width=\"@dimen/listsImageWidth\"\n    android:layout_height=\"@dimen/listsImageHeight\"\n    android:layout_gravity=\"center\"\n    android:background=\"@drawable/bg_media_view_placeholder\"\n    android:elevation=\"3dp\"\n    android:padding=\"@dimen/spaceBig\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_list_alt\"\n    app:tint=\"?attr/colorPlaceholderIcon\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/viewTripleImage2\"\n    android:layout_width=\"@dimen/listsImageWidth\"\n    android:layout_height=\"@dimen/listsImageHeight\"\n    android:layout_gravity=\"center\"\n    android:background=\"@drawable/bg_media_view_elevation\"\n    android:elevation=\"3dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/viewTripleImagePlaceholder1\"\n    android:layout_width=\"@dimen/listsImageWidth\"\n    android:layout_height=\"@dimen/listsImageHeight\"\n    android:layout_gravity=\"end\"\n    android:background=\"@drawable/bg_media_view_placeholder\"\n    android:elevation=\"3dp\"\n    android:padding=\"@dimen/spaceBig\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_list_alt\"\n    app:tint=\"?attr/colorPlaceholderIcon\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/viewTripleImage1\"\n    android:layout_width=\"@dimen/listsImageWidth\"\n    android:layout_height=\"@dimen/listsImageHeight\"\n    android:layout_gravity=\"end\"\n    android:background=\"@drawable/bg_media_view_elevation\"\n    android:elevation=\"3dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-lists/src/main/res/menu/menu_list_details.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n  >\n  <item\n    android:id=\"@+id/menuListDetailsEdit\"\n    android:icon=\"@drawable/ic_edit\"\n    android:title=\"@string/textEditList\"\n    app:showAsAction=\"never\"\n    />\n  <item\n    android:id=\"@+id/menuListDetailsDelete\"\n    android:icon=\"@drawable/ic_delete\"\n    android:title=\"@string/textDeleteList\"\n    app:showAsAction=\"never\"\n    />\n</menu>"
  },
  {
    "path": "ui-lists/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"listsRecyclerPaddingTop\">156dp</dimen>\n  <dimen name=\"listsRecyclerPaddingHorizontal\">0dp</dimen>\n  <dimen name=\"listsIconsPadding\">112dp</dimen>\n  <dimen name=\"listsBottomPadding\">78dp</dimen>\n  <dimen name=\"listsFabBottomPadding\">68dp</dimen>\n  <dimen name=\"listsFabPadding\">16dp</dimen>\n\n  <dimen name=\"listsSearchLocalViewPadding\">114dp</dimen>\n\n  <dimen name=\"listsImageHeight\">120dp</dimen>\n  <dimen name=\"listsImageWidth\">80dp</dimen>\n  <dimen name=\"listsTripleImageWidth\">116dp</dimen>\n  <dimen name=\"listsMarginHorizontal\">12dp</dimen>\n\n  <dimen name=\"listDetailsItemMarginBottom\">23dp</dimen>\n  <dimen name=\"listDetailsItemTitleMarginBottom\">4dp</dimen>\n  <dimen name=\"listDetailsItemTitleSize\">12sp</dimen>\n\n  <dimen name=\"listDetailsItemImageWidth\">80dp</dimen>\n  <dimen name=\"listDetailsItemImageHeight\">120dp</dimen>\n  <dimen name=\"listDetailsItemCompactImageWidth\">43dp</dimen>\n  <dimen name=\"listDetailsItemCompactImageHeight\">64dp</dimen>\n\n  <dimen name=\"listDetailsRecyclerTopPadding\">54dp</dimen>\n  <dimen name=\"listDetailsRecyclerTopGridPadding\">56dp</dimen>\n  <dimen name=\"listDetailsChipsTopPadding\">66dp</dimen>\n\n</resources>\n"
  },
  {
    "path": "ui-lists/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuLists\">Lists</string>\n  <string name=\"menuList\">List</string>\n\n  <string name=\"textFilters\">Filters:</string>\n  <string name=\"textListsEmpty\">Your lists collection is currently empty.</string>\n  <string name=\"textListEmpty\">Your list is currently empty.</string>\n  <string name=\"textCreateList\">Create list</string>\n  <string name=\"textCreateListDescription\">Create a new custom list.</string>\n  <string name=\"textName\">Name</string>\n  <string name=\"textDescription\">Description (optional)</string>\n  <string name=\"textDeleteList\">Delete list</string>\n  <string name=\"textEditList\">Edit list</string>\n  <string name=\"textEditListDescription\">Update your custom list.</string>\n  <string name=\"textConfirmDeleteListTitle\">Delete List</string>\n  <string name=\"textConfirmDeleteListSubtitle\">Are you sure you want to delete this list?</string>\n  <string name=\"textChangeRanks\">Change Ranks</string>\n  <string name=\"textChangeRanksSubtitle\">Drag and drop list item to change its rank</string>\n\n  <string name=\"textManageLists\">Manage lists</string>\n  <string name=\"textManageListsShows\">Manage your lists for this show.</string>\n  <string name=\"textManageListsMovies\">Manage your lists for this movie.</string>\n\n  <string name=\"errorCouldNotCreateList\">We could not create new list in your Trakt account at this moment.\\nPlease check internet connection and try again.</string>\n  <string name=\"errorCouldNotUpdateList\">We could not update this list in your Trakt account at this moment.\\nPlease check internet connection and try again.</string>\n  <string name=\"errorCouldNotDeleteList\">We could not delete this list from your Trakt account at this moment.\\nPlease check internet connection and try again.</string>\n\n</resources>"
  },
  {
    "path": "ui-lists/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <style name=\"ListDetailsToolbarTitleAppearance\" parent=\"@style/TextAppearance.Widget.AppCompat.Toolbar.Title\">\n    <item name=\"android:textSize\">17sp</item>\n  </style>\n\n  <style name=\"ListDetailsToolbarSubtitleAppearance\" parent=\"@style/TextAppearance.Widget.AppCompat.Toolbar.Subtitle\">\n    <item name=\"android:textSize\">12sp</item>\n  </style>\n\n</resources>"
  },
  {
    "path": "ui-lists/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuLists\">قوائم</string>\n  <string name=\"menuList\">قائمة</string>\n  <string name=\"textFilters\">الفلاتر:</string>\n  <string name=\"textListsEmpty\">لا توجد قوائم حاليًا.</string>\n  <string name=\"textListEmpty\">قائمتك فارغة حاليًا.</string>\n  <string name=\"textCreateList\">إنشاء قائمة</string>\n  <string name=\"textCreateListDescription\">إنشاء قائمة جديدة.</string>\n  <string name=\"textName\">اسم القائمة</string>\n  <string name=\"textDescription\">وصف القائمة (اختياري)</string>\n  <string name=\"textDeleteList\">حذف القائمة</string>\n  <string name=\"textEditList\">تعديل القائمة</string>\n  <string name=\"textEditListDescription\">تحديث قائمتك الخاصة.</string>\n  <string name=\"textConfirmDeleteListTitle\">حذف القائمة</string>\n  <string name=\"textConfirmDeleteListSubtitle\">أتريد فعلًا حذف القائمة؟</string>\n  <string name=\"textChangeRanks\">تغيير ترتيب العناصر</string>\n  <string name=\"textChangeRanksSubtitle\">اِسحب وأسقط أحد العناصر لتغيير ترتيبه</string>\n  <string name=\"textManageLists\">إدارة القوائم</string>\n  <string name=\"textManageListsShows\">إدارة قوائمك المتعلقة بهذا المسلسل.</string>\n  <string name=\"textManageListsMovies\">إدارة قوائمك المتعلقة بهذا الفيلم.</string>\n  <string name=\"errorCouldNotCreateList\">تعذر إنشاء قائمة جديدة في حسابك على موقع Trakt.tv.\\nالرجاء التأكد من اتصالك بالإنترنت وحاول مرة أخرى.</string>\n  <string name=\"errorCouldNotUpdateList\">تعذر تحديث القائمة في حسابك على موقع Trakt.tv.\\nالرجاء التأكد من اتصالك بالإنترنت وحاول مرة أخرى.</string>\n  <string name=\"errorCouldNotDeleteList\">تعذر حذف القائمة من حسابك على موقع Trakt.tv.\\nالرجاء التأكد من اتصالك بالإنترنت وحاول مرة أخرى.</string>\n</resources>\n"
  },
  {
    "path": "ui-lists/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuLists\">Listen</string>\n  <string name=\"menuList\">Liste</string>\n  <string name=\"textFilters\">Filter:</string>\n  <string name=\"textListsEmpty\">Ihre Listensammlung ist derzeit leer.</string>\n  <string name=\"textListEmpty\">Die Liste ist aktuell leer.</string>\n  <string name=\"textCreateList\">Liste erstellen</string>\n  <string name=\"textCreateListDescription\">Erstelle eine neue Liste.</string>\n  <string name=\"textName\">Name</string>\n  <string name=\"textDescription\">Beschreibung (optional)</string>\n  <string name=\"textDeleteList\">Liste löschen</string>\n  <string name=\"textEditList\">Liste bearbeiten</string>\n  <string name=\"textEditListDescription\">Aktualisiere deine Liste.</string>\n  <string name=\"textConfirmDeleteListTitle\">Liste löschen</string>\n  <string name=\"textConfirmDeleteListSubtitle\">Bist du sicher, dass du diese Liste löschen möchtest?</string>\n  <string name=\"textChangeRanks\">Ränge ändern</string>\n  <string name=\"textChangeRanksSubtitle\">Ziehe ein Listenelement um seinen Rang zu ändern</string>\n  <string name=\"textManageLists\">Listen verwalten</string>\n  <string name=\"textManageListsShows\">Verwalte deine Listen für diese Serie.</string>\n  <string name=\"textManageListsMovies\">Verwalte deine Listen für diesen Film.</string>\n  <string name=\"errorCouldNotCreateList\">Wir konnten derzeit keine neue Liste in deinem Trakt Konto erstellen.\\nBitte überprüfe deine Internetverbindung und versuche es erneut.</string>\n  <string name=\"errorCouldNotUpdateList\">Wir konnten diese Liste derzeit nicht in deinem Trakt Konto aktualisieren.\\nBitte überprüfe deine Internetverbindung und versuche es erneut.</string>\n  <string name=\"errorCouldNotDeleteList\">Wir konnten diese Liste momentan nicht von deinem Trakt Konto löschen.\\nBitte überprüfe deine Internetverbindung und versuche es erneut.</string>\n</resources>\n"
  },
  {
    "path": "ui-lists/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuLists\">Listas</string>\n  <string name=\"menuList\">Lista</string>\n  <string name=\"textFilters\">Filtros:</string>\n  <string name=\"textListsEmpty\">La colección de tus listas está vacía.</string>\n  <string name=\"textListEmpty\">Tu lista está vacía.</string>\n  <string name=\"textCreateList\">Crear lista</string>\n  <string name=\"textCreateListDescription\">Crear una nueva lista personalizada.</string>\n  <string name=\"textName\">Nombre</string>\n  <string name=\"textDescription\">Descripción (opcional)</string>\n  <string name=\"textDeleteList\">Borrar lista</string>\n  <string name=\"textEditList\">Editar lista</string>\n  <string name=\"textEditListDescription\">Actualiza tu lista personalizada.</string>\n  <string name=\"textConfirmDeleteListTitle\">Borrar Lista</string>\n  <string name=\"textConfirmDeleteListSubtitle\">¿Seguro que quieres eliminar esta lista?</string>\n  <string name=\"textChangeRanks\">Cambiar Rangos</string>\n  <string name=\"textChangeRanksSubtitle\">Arrastra y suelta el elemento de la lista para cambiar su rango</string>\n  <string name=\"textManageLists\">Gestionar listas</string>\n  <string name=\"textManageListsShows\">Gestiona tus listas para esta serie.</string>\n  <string name=\"textManageListsMovies\">Gestiona tus listas para esta película.</string>\n  <string name=\"errorCouldNotCreateList\">No pudimos crear una nueva lista en tu cuenta de Trakt en este momento.\\nPor favor, comprueba la conexión a Internet e inténtalo de nuevo.</string>\n  <string name=\"errorCouldNotUpdateList\">No pudimos actualizar esta lista en tu cuenta de Trakt en este momento.\\nPor favor, comprueba la conexión a Internet e inténtalo de nuevo.</string>\n  <string name=\"errorCouldNotDeleteList\">No pudimos eliminar esta lista de tu cuenta de Trakt en este momento.\\nPor favor, comprueba la conexión a Internet e inténtalo de nuevo.</string>\n</resources>\n"
  },
  {
    "path": "ui-lists/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuLists\">Listat</string>\n  <string name=\"menuList\">Lista</string>\n  <string name=\"textFilters\">Suodattimet:</string>\n  <string name=\"textListsEmpty\">Listakokoelmasi on tyhjä.</string>\n  <string name=\"textListEmpty\">Listasi on tyhjä.</string>\n  <string name=\"textCreateList\">Luo lista</string>\n  <string name=\"textCreateListDescription\">Luo uusi mukautettu lista.</string>\n  <string name=\"textName\">Nimi</string>\n  <string name=\"textDescription\">Kuvaus (valinnainen)</string>\n  <string name=\"textDeleteList\">Poista lista</string>\n  <string name=\"textEditList\">Muokkaa listaa</string>\n  <string name=\"textEditListDescription\">Päivitä mukautettu listasi.</string>\n  <string name=\"textConfirmDeleteListTitle\">Poista lista</string>\n  <string name=\"textConfirmDeleteListSubtitle\">Haluatko varmasti poistaa listan?</string>\n  <string name=\"textChangeRanks\">Muuta sijoituksia</string>\n  <string name=\"textChangeRanksSubtitle\">Muuta listan kohteen sijoitusta vetämällä ja pudottamalla</string>\n  <string name=\"textManageLists\">Hallitse listoja</string>\n  <string name=\"textManageListsShows\">Hallitse sarjan listoja.</string>\n  <string name=\"textManageListsMovies\">Hallitse elokuvan listoja.</string>\n  <string name=\"errorCouldNotCreateList\">Listan luonti Trakt.tv-kokoelmaasi ei juuri nyt onnistunut.\\nTarkista Internet-yhteytesi ja yritä uudelleen.</string>\n  <string name=\"errorCouldNotUpdateList\">Listan päivitys Trakt.tv-kokoelmaasi ei juuri nyt onnistunut.\\nTarkista Internet-yhteytesi ja yritä uudelleen.</string>\n  <string name=\"errorCouldNotDeleteList\">Listan poisto Trakt.tv-kokoelmastasi ei juuri nyt onnistunut.\\nTarkista Internet-yhteytesi ja yritä uudelleen.</string>\n</resources>\n"
  },
  {
    "path": "ui-lists/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuLists\">Listes</string>\n  <string name=\"menuList\">Liste</string>\n  <string name=\"textFilters\">Filtres :</string>\n  <string name=\"textListsEmpty\">Votre collection de listes est vide pour le moment.</string>\n  <string name=\"textListEmpty\">Votre liste est vide pour le moment.</string>\n  <string name=\"textCreateList\">Créer une liste</string>\n  <string name=\"textCreateListDescription\">Créer une nouvelle liste personnalisée.</string>\n  <string name=\"textName\">Nom</string>\n  <string name=\"textDescription\">Description (optionnel)</string>\n  <string name=\"textDeleteList\">Supprimer la liste</string>\n  <string name=\"textEditList\">Modifier la liste</string>\n  <string name=\"textEditListDescription\">Mettre à jour votre liste personnalisée.</string>\n  <string name=\"textConfirmDeleteListTitle\">Supprimer la liste</string>\n  <string name=\"textConfirmDeleteListSubtitle\">Êtes-vous sûr de vouloir supprimer cette liste ?</string>\n  <string name=\"textChangeRanks\">Modifier le classement</string>\n  <string name=\"textChangeRanksSubtitle\">Glisser-déposer l\\'élément de la liste pour changer son rang</string>\n  <string name=\"textManageLists\">Gérer les listes</string>\n  <string name=\"textManageListsShows\">Gérer vos listes pour cette série.</string>\n  <string name=\"textManageListsMovies\">Gérer vos listes pour ce film.</string>\n  <string name=\"errorCouldNotCreateList\">Nous n\\'avons pas pu créer une nouvelle liste dans votre compte Trakt pour le moment.\\nVeuillez vérifier la connexion Internet et réessayer.</string>\n  <string name=\"errorCouldNotUpdateList\">Nous n\\'avons pas pu mettre à jour cette liste dans votre compte Trakt pour le moment.\\nVeuillez vérifier la connexion internet et réessayer.</string>\n  <string name=\"errorCouldNotDeleteList\">Nous n\\'avons pas pu supprimer cette liste de votre compte Trakt pour le moment.\\nVeuillez vérifier la connexion internet et réessayer.</string>\n</resources>\n"
  },
  {
    "path": "ui-lists/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuLists\">Liste</string>\n  <string name=\"menuList\">Lista</string>\n  <string name=\"textFilters\">Filtri:</string>\n  <string name=\"textListsEmpty\">La tua raccolta di liste è attualmente vuota.</string>\n  <string name=\"textListEmpty\">La tua lista è attualmente vuota.</string>\n  <string name=\"textCreateList\">Crea lista</string>\n  <string name=\"textCreateListDescription\">Crea una nuova lista personalizzata.</string>\n  <string name=\"textName\">Nome</string>\n  <string name=\"textDescription\">Descrizione (facoltativa)</string>\n  <string name=\"textDeleteList\">Elimina lista</string>\n  <string name=\"textEditList\">Modifica lista</string>\n  <string name=\"textEditListDescription\">Aggiorna la tua lista personalizzata.</string>\n  <string name=\"textConfirmDeleteListTitle\">Elimina lista</string>\n  <string name=\"textConfirmDeleteListSubtitle\">Sei sicuro di voler eliminare questa lista?</string>\n  <string name=\"textChangeRanks\">Cambia posizione</string>\n  <string name=\"textChangeRanksSubtitle\">Trascina e rilascia l\\'elemento per cambiare la sua posizione nella lista</string>\n  <string name=\"textManageLists\">Gestisci liste</string>\n  <string name=\"textManageListsShows\">Gestisci le tue liste per questo show.</string>\n  <string name=\"textManageListsMovies\">Gestisci le tue liste per questo film.</string>\n  <string name=\"errorCouldNotCreateList\">Al momento non è possibile creare una nuova lista nel tuo account Trakt.\\nControlla la connessione a internet e riprova.</string>\n  <string name=\"errorCouldNotUpdateList\">Al momento non è possibile aggiornare questa lista nel tuo account Trakt.\\nControlla la connessione internet e riprova.</string>\n  <string name=\"errorCouldNotDeleteList\">Al momento non è possibile eliminare questa lista dal tuo account Trakt.\\nControlla la connessione a internet e riprova.</string>\n</resources>\n"
  },
  {
    "path": "ui-lists/src/main/res/values-notnight/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"listDetailsRecyclerTopPadding\">52dp</dimen>\n  <dimen name=\"listDetailsRecyclerTopGridPadding\">54dp</dimen>\n  <dimen name=\"listDetailsChipsTopPadding\">66dp</dimen>\n</resources>"
  },
  {
    "path": "ui-lists/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuLists\">Listy</string>\n  <string name=\"menuList\">Lista</string>\n  <string name=\"textFilters\">Filtry:</string>\n  <string name=\"textListsEmpty\">Twoja kolekcja list jest obecnie pusta.</string>\n  <string name=\"textListEmpty\">Twoja lista jest obecnie pusta.</string>\n  <string name=\"textCreateList\">Utwórz listę</string>\n  <string name=\"textCreateListDescription\">Utwórz nową własną listę.</string>\n  <string name=\"textName\">Nazwa</string>\n  <string name=\"textDescription\">Opis (opcjonalny)</string>\n  <string name=\"textDeleteList\">Usuń listę</string>\n  <string name=\"textEditList\">Edytuj listę</string>\n  <string name=\"textEditListDescription\">Aktualizuj swoją listę.</string>\n  <string name=\"textConfirmDeleteListTitle\">Usuń listę</string>\n  <string name=\"textConfirmDeleteListSubtitle\">Czy na pewno chcesz usunąć tę listę?</string>\n  <string name=\"textChangeRanks\">Zmień rangi</string>\n  <string name=\"textChangeRanksSubtitle\">Przeciągnij element listy aby zmienić jego rangę</string>\n  <string name=\"textManageLists\">Zarządzaj listami</string>\n  <string name=\"textManageListsShows\">Zarządzaj listami dla tego serialu.</string>\n  <string name=\"textManageListsMovies\">Zarządzaj listami dla tego filmu.</string>\n  <string name=\"errorCouldNotCreateList\">W tej chwili nie mogliśmy utworzyć nowej listy na Twoim koncie Trakt.\\nSprawdź połączenie internetowe i spróbuj ponownie.</string>\n  <string name=\"errorCouldNotUpdateList\">W tej chwili nie udało się zaktualizować tej listy na Twoim koncie Trakt.\\nSprawdź połączenie internetowe i spróbuj ponownie.</string>\n  <string name=\"errorCouldNotDeleteList\">W tej chwili nie można usunąć tej listy z Twojego konta Trakt.\\nSprawdź połączenie internetowe i spróbuj ponownie.</string>\n</resources>\n"
  },
  {
    "path": "ui-lists/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuLists\">Listas</string>\n  <string name=\"menuList\">Lista</string>\n  <string name=\"textFilters\">Filtros:</string>\n  <string name=\"textListsEmpty\">A sua coleção de listas está vazia.</string>\n  <string name=\"textListEmpty\">Sua lista está vazia no momento.</string>\n  <string name=\"textCreateList\">Criar lista</string>\n  <string name=\"textCreateListDescription\">Criar uma nova lista personalizada.</string>\n  <string name=\"textName\">Nome</string>\n  <string name=\"textDescription\">Descrição (opcional)</string>\n  <string name=\"textDeleteList\">Excluir lista</string>\n  <string name=\"textEditList\">Editar lista</string>\n  <string name=\"textEditListDescription\">Atualize sua lista personalizada.</string>\n  <string name=\"textConfirmDeleteListTitle\">Excluir Lista</string>\n  <string name=\"textConfirmDeleteListSubtitle\">Tem certeza que deseja excluir esta lista?</string>\n  <string name=\"textChangeRanks\">Alterar Rank</string>\n  <string name=\"textChangeRanksSubtitle\">Arraste e solte o item da lista para alterar seu rank</string>\n  <string name=\"textManageLists\">Gerenciar listas</string>\n  <string name=\"textManageListsShows\">Gerencie suas listas para esta série.</string>\n  <string name=\"textManageListsMovies\">Gerencie suas listas para este filme.</string>\n  <string name=\"errorCouldNotCreateList\">Não foi possível criar uma nova lista na sua conta Trakt neste momento.\\nPor favor, verifique a conexão com a internet e tente novamente.</string>\n  <string name=\"errorCouldNotUpdateList\">Não foi possível atualizar esta lista na sua conta Trakt neste momento.\\nPor favor, verifique a conexão com a internet e tente novamente.</string>\n  <string name=\"errorCouldNotDeleteList\">Não foi possível excluir esta lista da sua conta Trakt neste momento.\\nPor favor, verifique a conexão com a internet e tente novamente.</string>\n</resources>\n"
  },
  {
    "path": "ui-lists/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuLists\">Списки</string>\n  <string name=\"menuList\">Список</string>\n  <string name=\"textFilters\">Фильтры:</string>\n  <string name=\"textListsEmpty\">Ваш список коллекций пуст.</string>\n  <string name=\"textListEmpty\">Ваш список пуст.</string>\n  <string name=\"textCreateList\">Создать список</string>\n  <string name=\"textCreateListDescription\">Создать новый пользовательский список.</string>\n  <string name=\"textName\">Название</string>\n  <string name=\"textDescription\">Описание (необязательно)</string>\n  <string name=\"textDeleteList\">Удалить список</string>\n  <string name=\"textEditList\">Изменить список</string>\n  <string name=\"textEditListDescription\">Обновить пользовательский список.</string>\n  <string name=\"textConfirmDeleteListTitle\">Удалить список</string>\n  <string name=\"textConfirmDeleteListSubtitle\">Вы уверены, что вы хотите удалить этот список?</string>\n  <string name=\"textChangeRanks\">Изменить ранг</string>\n  <string name=\"textChangeRanksSubtitle\">Перетащите элемент списка, чтобы изменить его ранг</string>\n  <string name=\"textManageLists\">Управление списками</string>\n  <string name=\"textManageListsShows\">Управление списками для этого шоу.</string>\n  <string name=\"textManageListsMovies\">Управление списками для этого фильма.</string>\n  <string name=\"errorCouldNotCreateList\">Мы не смогли создать новый список в вашей учетной записи Trakt.\\nПожалуйста, проверьте подключение к Интернету и повторите попытку.</string>\n  <string name=\"errorCouldNotUpdateList\">Мы не смогли обновить список в вашей учетной записи Trakt.\\nПожалуйста, проверьте подключение к Интернету и повторите попытку.</string>\n  <string name=\"errorCouldNotDeleteList\">Мы не смогли удалить список в вашей учетной записи Trakt.\\nПожалуйста, проверьте подключение к Интернету и повторите попытку.</string>\n</resources>\n"
  },
  {
    "path": "ui-lists/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"listsRecyclerPaddingHorizontal\">12dp</dimen>\n  <dimen name=\"listsFabPadding\">24dp</dimen>\n\n  <dimen name=\"listDetailsItemMarginBottom\">29dp</dimen>\n  <dimen name=\"listDetailsItemTitleMarginBottom\">8dp</dimen>\n  <dimen name=\"listDetailsItemTitleSize\">12sp</dimen>\n\n</resources>\n"
  },
  {
    "path": "ui-lists/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuLists\">Listeler</string>\n  <string name=\"menuList\">Liste</string>\n  <string name=\"textFilters\">Filtreler:</string>\n  <string name=\"textListsEmpty\">Listeler koleksiyonunuz şu anda boş.</string>\n  <string name=\"textListEmpty\">Listeniz şu anda boş.</string>\n  <string name=\"textCreateList\">Liste oluştur</string>\n  <string name=\"textCreateListDescription\">Yeni bir özel liste oluştur.</string>\n  <string name=\"textName\">İsim</string>\n  <string name=\"textDescription\">Açıklama (isteğe bağlı)</string>\n  <string name=\"textDeleteList\">Listeyi sil</string>\n  <string name=\"textEditList\">Listeyi düzenle</string>\n  <string name=\"textEditListDescription\">Özel listenizi güncelleyin.</string>\n  <string name=\"textConfirmDeleteListTitle\">Listeyi Sil</string>\n  <string name=\"textConfirmDeleteListSubtitle\">Bu listeyi silmek istediğinizden emin misiniz?</string>\n  <string name=\"textChangeRanks\">Dereceleri Değiştir</string>\n  <string name=\"textChangeRanksSubtitle\">Derecesini değiştirmek için liste öğesini sürükleyip bırakın</string>\n  <string name=\"textManageLists\">Listeleri yönet</string>\n  <string name=\"textManageListsShows\">Bu dizi için listelerinizi yönetin.</string>\n  <string name=\"textManageListsMovies\">Bu film için listelerinizi yönetin.</string>\n  <string name=\"errorCouldNotCreateList\">Şu anda Trakt hesabınızda yeni liste oluşturamıyoruz.\\nLütfen internet bağlantısını kontrol edin ve tekrar deneyin.</string>\n  <string name=\"errorCouldNotUpdateList\">Şu anda Trakt hesabınızdaki bu listeyi güncelleyemiyoruz.\\nLütfen internet bağlantısını kontrol edin ve tekrar deneyin.</string>\n  <string name=\"errorCouldNotDeleteList\">Şu anda Trakt hesabınızdaki bu listeyi silemiyoruz.\\nLütfen internet bağlantısını kontrol edin ve tekrar deneyin.</string>\n</resources>\n"
  },
  {
    "path": "ui-lists/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuLists\">Списки</string>\n  <string name=\"menuList\">Список</string>\n  <string name=\"textFilters\">Фільтри:</string>\n  <string name=\"textListsEmpty\">Ваші списки колекцій наразі порожні.</string>\n  <string name=\"textListEmpty\">Ваш список наразі порожній.</string>\n  <string name=\"textCreateList\">Створити список</string>\n  <string name=\"textCreateListDescription\">Створити новий користувацький список.</string>\n  <string name=\"textName\">Назва</string>\n  <string name=\"textDescription\">Опис (необов\\'язково)</string>\n  <string name=\"textDeleteList\">Видалити список</string>\n  <string name=\"textEditList\">Редагувати список</string>\n  <string name=\"textEditListDescription\">Оновити користувацький список.</string>\n  <string name=\"textConfirmDeleteListTitle\">Видалити список</string>\n  <string name=\"textConfirmDeleteListSubtitle\">Ви дійсно бажаєте видалити цей список?</string>\n  <string name=\"textChangeRanks\">Змінити ранги</string>\n  <string name=\"textChangeRanksSubtitle\">Перетягніть елемент списку, щоб змінити його ранг</string>\n  <string name=\"textManageLists\">Керування списками</string>\n  <string name=\"textManageListsShows\">Керування списками для цього серіалу.</string>\n  <string name=\"textManageListsMovies\">Керування списками для цього фільму.</string>\n  <string name=\"errorCouldNotCreateList\">Не вдалося створити новий список у вашому обліковому записі Trakt.\\nБудь ласка, перевірте підключення до Інтернету і спробуйте ще раз.</string>\n  <string name=\"errorCouldNotUpdateList\">Не вдалося оновити цей список у вашому обліковому записі Trakt.\\nБудь ласка, перевірте підключення до Інтернету і спробуйте ще раз.</string>\n  <string name=\"errorCouldNotDeleteList\">Не вдалося видалити цей список з вашого облікового запису Trakt.\\nБудь ласка, перевірте підключення до Інтернету і спробуйте ще раз.</string>\n</resources>\n"
  },
  {
    "path": "ui-lists/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuLists\">Các Danh sách</string>\n  <string name=\"menuList\">Danh sách</string>\n\n  <string name=\"textFilters\">Bộ lọc:</string>\n  <string name=\"textListsEmpty\">Bộ sưu tập các danh sách của bạn hiện đang trống.</string>\n  <string name=\"textListEmpty\">Danh sách của bạn hiện đang trống.</string>\n  <string name=\"textCreateList\">Tạo danh sách</string>\n  <string name=\"textCreateListDescription\">Tạo một danh sách tùy chỉnh mới.</string>\n  <string name=\"textName\">Tên</string>\n  <string name=\"textDescription\">Mô tả (tùy chọn)</string>\n  <string name=\"textDeleteList\">Xóa danh sách</string>\n  <string name=\"textEditList\">Chỉnh sửa danh sách</string>\n  <string name=\"textEditListDescription\">Cập nhật danh sách tùy chỉnh của bạn.</string>\n  <string name=\"textConfirmDeleteListTitle\">Xóa danh sách</string>\n  <string name=\"textConfirmDeleteListSubtitle\">Bạn có chắc chắn muốn xóa danh sách này?</string>\n  <string name=\"textChangeRanks\">Thay đổi thứ hạng</string>\n  <string name=\"textChangeRanksSubtitle\">Kéo và thả mục danh sách để thay đổi thứ hạng của nó</string>\n\n  <string name=\"textManageLists\">Quản lý danh sách</string>\n  <string name=\"textManageListsShows\">Quản lý danh sách của bạn cho chương trình này.</string>\n  <string name=\"textManageListsMovies\">Quản lý danh sách của bạn cho bộ phim này.</string>\n\n  <string name=\"errorCouldNotCreateList\">Chúng tôi không thể tạo danh sách mới trong tài khoản Trakt của bạn vào lúc này.\\nVui lòng kiểm tra kết nối Internet và thử lại.</string>\n  <string name=\"errorCouldNotUpdateList\">Chúng tôi không thể cập nhật danh sách này trong tài khoản Trakt của bạn vào lúc này.\\nVui lòng kiểm tra kết nối Internet và thử lại.</string>\n  <string name=\"errorCouldNotDeleteList\">Chúng tôi không thể xóa danh sách này khỏi tài khoản Trakt của bạn vào lúc này.\\nVui lòng kiểm tra kết nối Internet và thử lại.</string>\n\n</resources>"
  },
  {
    "path": "ui-lists/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuLists\">列表</string>\n  <string name=\"menuList\">列表</string>\n  <string name=\"textFilters\">筛选：</string>\n  <string name=\"textListsEmpty\">当前您的列表合集为空。</string>\n  <string name=\"textListEmpty\">当前您的列表为空。</string>\n  <string name=\"textCreateList\">创建列表</string>\n  <string name=\"textCreateListDescription\">创建一个新的自定义列表</string>\n  <string name=\"textName\">名称</string>\n  <string name=\"textDescription\">描述（选填）</string>\n  <string name=\"textDeleteList\">删除列表</string>\n  <string name=\"textEditList\">编辑列表</string>\n  <string name=\"textEditListDescription\">更新自定义列表</string>\n  <string name=\"textConfirmDeleteListTitle\">删除列表</string>\n  <string name=\"textConfirmDeleteListSubtitle\">您确定要删除此列表吗？</string>\n  <string name=\"textChangeRanks\">修改排行</string>\n  <string name=\"textChangeRanksSubtitle\">拖放列表项以更改排行</string>\n  <string name=\"textManageLists\">管理列表</string>\n  <string name=\"textManageListsShows\">管理这部剧所在的列表。</string>\n  <string name=\"textManageListsMovies\">管理这部电影所在的列表。</string>\n  <string name=\"errorCouldNotCreateList\">目前无法在您 Trakt 账号中创建新的列表。\\n请检查网络连接并重试。</string>\n  <string name=\"errorCouldNotUpdateList\">目前无法更新您 Trakt 账号中的此列表。\\n请检查网络连接并重试。</string>\n  <string name=\"errorCouldNotDeleteList\">目前无法删除您 Trakt 账号中的此列表。\\n请检查网络连接并重试。</string>\n</resources>\n"
  },
  {
    "path": "ui-model/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-model/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-parcelize'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_model'\n}\n\ndependencies {\n  implementation project(':common')\n\n  implementation libs.android.appcompat\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-model/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/AirTime.kt",
    "content": "package com.michaldrabik.ui_model\n\ndata class AirTime(\n  val day: String,\n  val time: String,\n  val timezone: String\n)\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/CalendarMode.kt",
    "content": "package com.michaldrabik.ui_model\n\nenum class CalendarMode {\n  PRESENT_FUTURE,\n  RECENTS\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/Comment.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\nimport java.time.ZonedDateTime\n\n@Parcelize\ndata class Comment(\n  val id: Long,\n  val parentId: Long,\n  val comment: String,\n  val userRating: Int,\n  val spoiler: Boolean,\n  val review: Boolean,\n  val likes: Long,\n  val replies: Long,\n  val createdAt: ZonedDateTime?,\n  val updatedAt: ZonedDateTime?,\n  val user: User,\n  val isMe: Boolean,\n  val isSignedIn: Boolean,\n  val isLoading: Boolean,\n  val hasRepliesLoaded: Boolean,\n) : Parcelable {\n\n  fun hasSpoilers() = spoiler || comment.contains(\"spoiler\", true)\n\n  fun isReply() = parentId > 0\n\n  fun getReplyId() = if (isReply()) parentId else id\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/CustomList.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport android.os.Parcelable\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.extensions.nowUtc\nimport kotlinx.parcelize.Parcelize\nimport java.time.ZonedDateTime\n\n@Parcelize\ndata class CustomList(\n  val id: Long,\n  val idTrakt: Long?,\n  val idSlug: String,\n  val name: String,\n  val description: String?,\n  val privacy: String,\n  val displayNumbers: Boolean,\n  val allowComments: Boolean,\n  val sortBy: SortOrder,\n  val sortHow: SortType,\n  val sortByLocal: SortOrder,\n  val sortHowLocal: SortType,\n  val filterTypeLocal: List<Mode>,\n  val itemCount: Long,\n  val commentCount: Long,\n  val likes: Long,\n  val createdAt: ZonedDateTime,\n  val updatedAt: ZonedDateTime\n) : Parcelable {\n\n  companion object {\n    fun create() = CustomList(\n      id = 0,\n      idTrakt = null,\n      idSlug = \"\",\n      name = \"\",\n      description = null,\n      privacy = \"private\",\n      displayNumbers = false,\n      allowComments = true,\n      sortBy = SortOrder.RANK,\n      sortHow = SortType.ASCENDING,\n      sortByLocal = SortOrder.RANK,\n      sortHowLocal = SortType.ASCENDING,\n      filterTypeLocal = Mode.getAll(),\n      itemCount = 0,\n      commentCount = 0,\n      likes = 0,\n      createdAt = nowUtc(),\n      updatedAt = nowUtc()\n    )\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/DiscoverFilters.kt",
    "content": "package com.michaldrabik.ui_model\n\ndata class DiscoverFilters(\n  val feedOrder: DiscoverSortOrder = DiscoverSortOrder.HOT,\n  val hideAnticipated: Boolean = true,\n  val hideCollection: Boolean = false,\n  val genres: List<Genre> = emptyList(),\n  val networks: List<Network> = emptyList(),\n)\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/DiscoverSortOrder.kt",
    "content": "package com.michaldrabik.ui_model\n\nenum class DiscoverSortOrder {\n  HOT,\n  RATING,\n  NEWEST\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/Episode.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport android.os.Parcelable\nimport com.michaldrabik.common.extensions.nowUtc\nimport kotlinx.parcelize.Parcelize\nimport java.time.ZonedDateTime\n\n@Parcelize\ndata class Episode(\n  val season: Int,\n  val number: Int,\n  val title: String,\n  val ids: Ids,\n  val overview: String,\n  val rating: Float,\n  val votes: Int,\n  val commentCount: Int,\n  val firstAired: ZonedDateTime?,\n  val runtime: Int,\n  val numberAbs: Int?,\n  val lastWatchedAt: ZonedDateTime?\n) : Parcelable {\n\n  companion object {\n    val EMPTY = Episode(-1, -1, \"\", Ids.EMPTY, \"\", -1F, -1, -1, null, -1, -1, null)\n  }\n\n  fun hasAired(season: Season): Boolean {\n    val nowUtc = nowUtc()\n    return when (firstAired) {\n      null -> season.episodes.any {\n        it.number > number && (it.firstAired != null && nowUtc.isAfter(it.firstAired))\n      }\n      else -> nowUtc.isAfter(firstAired)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/EpisodeBundle.kt",
    "content": "package com.michaldrabik.ui_model\n\ndata class EpisodeBundle(\n  val episode: Episode,\n  val season: Season,\n  val show: Show\n)\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/Genre.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport androidx.annotation.StringRes\n\nenum class Genre(\n  val slug: String,\n  @StringRes val displayName: Int\n) {\n  ACTION(\"action\", R.string.textGenreAction),\n  ADVENTURE(\"adventure\", R.string.textGenreAdventure),\n  ANIMATION(\"animation\", R.string.textGenreAnimation),\n  ANIME(\"anime\", R.string.textGenreAnime),\n  COMEDY(\"comedy\", R.string.textGenreComedy),\n  CRIME(\"crime\", R.string.textGenreCrime),\n  DOCUMENTARY(\"documentary\", R.string.textGenreDocumentary),\n  DRAMA(\"drama\", R.string.textGenreDrama),\n  FANTASY(\"fantasy\", R.string.textGenreFantasy),\n  HISTORY(\"history\", R.string.textGenreHistory),\n  HORROR(\"horror\", R.string.textGenreHorror),\n  SF(\"science-fiction\", R.string.textGenreScienceFiction),\n  THRILLER(\"thriller\", R.string.textGenreThriller),\n  WAR(\"war\", R.string.textGenreWar),\n  WESTERN(\"western\", R.string.textGenreWestern);\n\n  companion object {\n    fun fromSlug(slug: String) = values().find { it.slug.equals(slug, ignoreCase = true) }\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/Ids.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\ndata class Ids(\n  val trakt: IdTrakt,\n  val slug: IdSlug,\n  val tvdb: IdTvdb,\n  val imdb: IdImdb,\n  val tmdb: IdTmdb,\n  val tvrage: IdTvRage,\n) : Parcelable {\n\n  companion object {\n    val EMPTY = Ids(\n      IdTrakt(),\n      IdSlug(),\n      IdTvdb(),\n      IdImdb(),\n      IdTmdb(),\n      IdTvRage()\n    )\n  }\n}\n\nsealed interface Id : Parcelable\n\n@JvmInline\n@Parcelize\nvalue class IdTrakt(val id: Long = -1) : Id\n\n@JvmInline\n@Parcelize\nvalue class IdTvdb(val id: Long = -1) : Id\n\n@JvmInline\n@Parcelize\nvalue class IdImdb(val id: String = \"\") : Id\n\n@JvmInline\n@Parcelize\nvalue class IdTmdb(val id: Long = -1) : Id\n\n@JvmInline\n@Parcelize\nvalue class IdTvRage(val id: Long = -1) : Id\n\n@JvmInline\n@Parcelize\nvalue class IdSlug(val id: String = \"\") : Id\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/Image.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport com.michaldrabik.common.Config.AWS_IMAGE_BASE_URL\nimport com.michaldrabik.common.Config.TMDB_IMAGE_BASE_FANART_URL\nimport com.michaldrabik.common.Config.TMDB_IMAGE_BASE_POSTER_URL\nimport com.michaldrabik.common.Config.TMDB_IMAGE_BASE_PROFILE_URL\nimport com.michaldrabik.common.Config.TVDB_IMAGE_BASE_BANNERS_URL\nimport com.michaldrabik.ui_model.ImageFamily.SHOW\nimport com.michaldrabik.ui_model.ImageSource.AWS\nimport com.michaldrabik.ui_model.ImageSource.CUSTOM\nimport com.michaldrabik.ui_model.ImageSource.TMDB\nimport com.michaldrabik.ui_model.ImageSource.TVDB\nimport com.michaldrabik.ui_model.ImageStatus.AVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNAVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNKNOWN\nimport com.michaldrabik.ui_model.ImageType.FANART\nimport com.michaldrabik.ui_model.ImageType.FANART_WIDE\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport com.michaldrabik.ui_model.ImageType.PROFILE\n\ndata class Image(\n  val id: Long,\n  val idTvdb: IdTvdb,\n  val idTmdb: IdTmdb,\n  val type: ImageType,\n  val family: ImageFamily,\n  val fileUrl: String,\n  val thumbnailUrl: String,\n  val status: ImageStatus,\n  val source: ImageSource\n) {\n\n  val fullFileUrl = when (source) {\n    TVDB -> \"$TVDB_IMAGE_BASE_BANNERS_URL$fileUrl\"\n    TMDB -> when (type) {\n      POSTER -> \"${TMDB_IMAGE_BASE_POSTER_URL}$fileUrl\"\n      FANART, FANART_WIDE -> \"${TMDB_IMAGE_BASE_FANART_URL}$fileUrl\"\n      PROFILE -> \"${TMDB_IMAGE_BASE_PROFILE_URL}$fileUrl\"\n      else -> \"\"\n    }\n    AWS -> \"$AWS_IMAGE_BASE_URL$fileUrl\"\n    CUSTOM -> fileUrl\n  }\n\n  companion object {\n    fun createUnknown(\n      type: ImageType,\n      family: ImageFamily = SHOW,\n      source: ImageSource = TVDB\n    ) = Image(0, IdTvdb(0), IdTmdb(0), type, family, \"\", \"\", UNKNOWN, source)\n\n    fun createUnavailable(\n      type: ImageType,\n      family: ImageFamily = SHOW,\n      source: ImageSource = TVDB\n    ) = Image(0, IdTvdb(0), IdTmdb(0), type, family, \"\", \"\", UNAVAILABLE, source)\n\n    fun createAvailable(\n      ids: Ids,\n      type: ImageType,\n      family: ImageFamily,\n      path: String,\n      source: ImageSource\n    ) = Image(0, ids.tvdb, ids.tmdb, type, family, path, \"\", AVAILABLE, source)\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/ImageFamily.kt",
    "content": "package com.michaldrabik.ui_model\n\nenum class ImageFamily(val key: String) {\n  SHOW(\"show\"),\n  MOVIE(\"movie\"),\n  EPISODE(\"episode\"),\n  PROFILE(\"profile\"),\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/ImageSource.kt",
    "content": "package com.michaldrabik.ui_model\n\nenum class ImageSource(\n  val key: String\n) {\n  TVDB(\"tvdb\"),\n  TMDB(\"tmdb\"),\n  CUSTOM(\"custom\"),\n  AWS(\"aws\");\n\n  companion object {\n    fun fromKey(key: String) = values().first { it.key == key }\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/ImageStatus.kt",
    "content": "package com.michaldrabik.ui_model\n\n/**\n * AVAILABLE - image's web url is known to be valid when used the last time.\n * UNKNOWN - image's web url has not been yet checked with remote images service (TVDB).\n * UNAVAILABLE - remote images service does not contain any valid image url.\n */\nenum class ImageStatus {\n  AVAILABLE,\n  UNKNOWN,\n  UNAVAILABLE\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/ImageType.kt",
    "content": "package com.michaldrabik.ui_model\n\nenum class ImageType(\n  val id: Int,\n  val key: String\n) {\n  POSTER(1, \"poster\"),\n  FANART(2, \"fanart\"),\n  FANART_WIDE(3, \"fanart\"),\n  TWITTER(4, \"twitterAd\"),\n  PREMIUM(5, \"premiumAd\"),\n  PROFILE(6, \"profile\");\n\n  fun getSpan(isTablet: Boolean): Int {\n    return when (this) {\n      POSTER -> 1\n      FANART -> 2\n      FANART_WIDE -> if (isTablet) 3 else 3\n      TWITTER -> if (isTablet) 6 else 3\n      PREMIUM -> if (isTablet) 6 else 3\n      PROFILE -> 1\n    }\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/Movie.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport com.michaldrabik.common.extensions.nowUtcDay\nimport java.time.LocalDate\n\ndata class Movie(\n  val ids: Ids,\n  val title: String,\n  val year: Int,\n  val overview: String,\n  val released: LocalDate?,\n  val runtime: Int,\n  val country: String,\n  val trailer: String,\n  val homepage: String,\n  val language: String,\n  val status: MovieStatus,\n  val rating: Float,\n  val votes: Long,\n  val commentCount: Long,\n  val genres: List<String>,\n  val updatedAt: Long,\n  val createdAt: Long\n) {\n\n  val traktId = ids.trakt.id\n\n  val titleNoThe = title.removePrefix(\"The\").trim()\n\n  fun hasNoDate() = released == null && year <= 0\n\n  fun hasAired(): Boolean {\n    if (released == null) return false\n    val now = nowUtcDay()\n    return now.isEqual(released) || now.isAfter(released)\n  }\n\n  fun isToday(): Boolean {\n    if (released == null) return false\n    val now = nowUtcDay()\n    return now.isEqual(released)\n  }\n\n  companion object {\n    val EMPTY = Movie(\n      Ids.EMPTY,\n      \"\",\n      -1,\n      \"\",\n      null,\n      -1,\n      \"\",\n      \"\",\n      \"\",\n      \"\",\n      MovieStatus.UNKNOWN,\n      -1F,\n      -1L,\n      -1L,\n      emptyList(),\n      -1L,\n      -1L\n    )\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/MovieCollection.kt",
    "content": "package com.michaldrabik.ui_model\n\ndata class MovieCollection(\n  val id: IdTrakt,\n  val name: String,\n  val description: String,\n  val itemCount: Int,\n) {\n\n  companion object {\n    val EMPTY = MovieCollection(IdTrakt(-1), \"\", \"\", -1)\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/MovieStatus.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport androidx.annotation.StringRes\n\nenum class MovieStatus(\n  val key: String,\n  @StringRes val displayName: Int\n) {\n  RELEASED(\"released\", R.string.textMovieStatusReleased),\n  IN_PRODUCTION(\"in production\", R.string.textMovieStatusInProduction),\n  POST_PRODUCTION(\"post production\", R.string.textMovieStatusPostProduction),\n  PLANNED(\"planned\", R.string.textMovieStatusPlanned),\n  RUMORED(\"rumored\", R.string.textMovieStatusRumored),\n  CANCELED(\"canceled\", R.string.textMovieStatusCanceled),\n  UNKNOWN(\"unknown\", R.string.textMovieStatusUnknown);\n\n  fun isAnticipated() = this in arrayOf(IN_PRODUCTION, POST_PRODUCTION, PLANNED, RUMORED)\n\n  companion object {\n    fun fromKey(key: String?) =\n      enumValues<MovieStatus>().firstOrNull { it.key == key } ?: UNKNOWN\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/MyMoviesSection.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport androidx.annotation.StringRes\n\nenum class MyMoviesSection(\n  @StringRes val displayString: Int\n) {\n  RECENTS(\n    displayString = R.string.textHeaderRecentlyAdded\n  ),\n  ALL(\n    displayString = R.string.textHeaderAll\n  )\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/MyShowsSection.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport androidx.annotation.StringRes\nimport com.michaldrabik.ui_model.ShowStatus.CANCELED\nimport com.michaldrabik.ui_model.ShowStatus.ENDED\nimport com.michaldrabik.ui_model.ShowStatus.IN_PRODUCTION\nimport com.michaldrabik.ui_model.ShowStatus.PLANNED\nimport com.michaldrabik.ui_model.ShowStatus.RETURNING\n\nenum class MyShowsSection(\n  @StringRes val displayString: Int,\n  val allowedStatuses: List<ShowStatus> = emptyList(),\n) {\n  RECENTS(\n    displayString = R.string.textHeaderRecentlyAdded\n  ),\n  WATCHING(\n    allowedStatuses = listOf(RETURNING),\n    displayString = R.string.textHeaderWatching\n  ),\n  FINISHED(\n    allowedStatuses = listOf(CANCELED, ENDED),\n    displayString = R.string.textHeaderFinished\n  ),\n  UPCOMING(\n    allowedStatuses = listOf(IN_PRODUCTION, PLANNED, ShowStatus.UPCOMING),\n    displayString = R.string.textHeaderReturning\n  ),\n  ALL(\n    displayString = R.string.textHeaderAll\n  )\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/Network.kt",
    "content": "package com.michaldrabik.ui_model\n\nenum class Network(\n  vararg val channels: String,\n) {\n  ABC(\n    \"ABC\",\n    \"ABC (AU)\",\n    \"ABC (JA)\",\n    \"ABC (US)\",\n    \"ABC Comedy\",\n    \"ABC Family\",\n    \"ABC KIDS\",\n    \"ABC Me\",\n    \"ABC News\",\n    \"ABC News 24\",\n    \"ABC Spark\",\n    \"ABC TV\",\n    \"ABC TV Plus\",\n  ),\n  AMC(\n    \"AMC\",\n    \"AMC+\"\n  ),\n  APPLE(\n    \"Apple\",\n    \"Apple TV+\",\n    \"Apple Music\"\n  ),\n  AMAZON(\n    \"Amazon\",\n    \"Amazon (Japan)\",\n    \"Amazon Freevee\",\n    \"Amazon Kids+\",\n    \"Amazon Prime Video\",\n    \"Amazon Studios\",\n    \"Amazon miniTV\"\n  ),\n  BBC(\n    \"BBC\",\n    \"BBC America\",\n    \"BBC Canada\",\n    \"BBC Choice\",\n    \"BBC Earth\",\n    \"BBC First\",\n    \"BBC Four\",\n    \"BBC HD\",\n    \"BBC Kids\",\n    \"BBC Knowledge\",\n    \"BBC Music\",\n    \"BBC News\",\n    \"BBC One\",\n    \"BBC Prime\",\n    \"BBC Red Button\",\n    \"BBC Scotland\",\n    \"BBC Television\",\n    \"BBC Three\",\n    \"BBC Two\",\n    \"BBC UKTV\",\n    \"BBC Wales\",\n    \"BBC World News\",\n    \"BBC Worldwide\",\n    \"BBC iPlayer\",\n  ),\n  CBS(\n    \"CBS\",\n    \"CBS All Access\",\n    \"CBS News\",\n    \"CBS Reality\",\n    \"CBS Reality (UK)\",\n    \"CBS.com\",\n  ),\n  CW(\"CW\", \"The CW\"),\n  DISCOVERY(\n    \"Discovery\",\n    \"Discovery (NL)\",\n    \"Discovery (US)\",\n    \"Discovery Asia\",\n    \"Discovery Channel\",\n    \"Discovery Channel (AU)\",\n    \"Discovery Channel (Asia)\",\n    \"Discovery Channel (CA)\",\n    \"Discovery Channel (Canada)\",\n    \"Discovery Channel (PL)\",\n    \"Discovery Channel (SE)\",\n    \"Discovery Channel (UK)\",\n    \"Discovery Communications\",\n    \"Discovery Family\",\n    \"Discovery GO\",\n    \"Discovery HD World\",\n    \"Discovery Health Channel\",\n    \"Discovery History\",\n    \"Discovery JEET\",\n    \"Discovery Kids\",\n    \"Discovery Life\",\n    \"Discovery MAX\",\n    \"Discovery Real Time\",\n    \"Discovery Science\",\n    \"Discovery Shed\",\n    \"Discovery Turbo\",\n    \"Discovery Turbo UK\",\n    \"Discovery UK\",\n    \"Discovery+\"\n  ),\n  DISNEY(\n    \"Disney\",\n    \"Disney Channel\",\n    \"Disney Channel (DE)\",\n    \"Disney Channel (FR)\",\n    \"Disney Channel (IT)\",\n    \"Disney Channel (UK)\",\n    \"Disney Channel (US)\",\n    \"Disney Cinemagic\",\n    \"Disney Junior\",\n    \"Disney Junior (UK)\",\n    \"Disney Television Animation\",\n    \"Disney XD\",\n    \"Disney XD (Latin America)\",\n    \"Disney+\",\n    \"Disney+ Hotstar\"\n  ),\n  HBO(\n    \"HBO Max\",\n    \"HBO Asia\",\n    \"HBO Brasil\",\n    \"HBO Canada\",\n    \"HBO España\",\n    \"HBO Europe\",\n    \"HBO Family\",\n    \"HBO Latin\",\n    \"HBO America\",\n    \"HBO Magyarország\",\n    \"HBO Nordic\",\n    \"HBO\",\n    \"Max\",\n    \"MAX\",\n  ),\n  FOX(\n    \"FOX\",\n    \"FOX (US)\",\n    \"FOX España\",\n    \"FOX Reality\",\n    \"FOX SPORTS\",\n    \"FOX Traveller\",\n    \"FOX Türkiye\",\n    \"FOX Türkiye (TR)\",\n    \"FOX+\",\n  ),\n  HULU(\"Hulu\", \"Hulu Japan\"),\n  ITV(\n    \"ITV\",\n    \"ITV Encore\",\n    \"ITV Granada\",\n    \"ITV Wales\"\n  ),\n  NBC(\n    \"NBC\",\n    \"NBC News Studios\",\n    \"NBC Universo\",\n    \"NBCSN\",\n    \"NBCUniversal\"\n  ),\n  NETFLIX(\"Netflix\"),\n  PARAMOUNT(\n    \"Paramount\",\n    \"Paramount (GB)\",\n    \"Paramount Network\",\n    \"Paramount+\",\n    \"Paramount+ (AU)\",\n    \"Paramount+ (MX)\",\n  ),\n  PEACOCK(\n    \"Peacock\",\n  ),\n  RAKUTEN(\"Rakuten TV\"),\n  SHOWTIME(\"Showtime\"),\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/NewsItem.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport java.time.ZonedDateTime\n\ndata class NewsItem(\n  val id: String,\n  val title: String,\n  val url: String,\n  val type: Type,\n  val image: String?,\n  val score: Long,\n  val datedAt: ZonedDateTime,\n  val createdAt: ZonedDateTime,\n  val updatedAt: ZonedDateTime\n) {\n\n  enum class Type(val slug: String) {\n    SHOW(\"show\"),\n    MOVIE(\"movie\");\n\n    companion object {\n      fun fromSlug(slug: String) = Type.values().first { it.slug == slug }\n    }\n  }\n\n  val isVideo =\n    url.startsWith(\"https://www.youtu\") ||\n      url.startsWith(\"https://youtu\") ||\n      url.startsWith(\"www.youtu\")\n\n  val isWebLink =\n    url.startsWith(\"http\") ||\n      url.startsWith(\"www\")\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/NotificationDelay.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport androidx.annotation.StringRes\n\nconst val HOUR_MS = 3_600_000L\n\n@Suppress(\"unused\")\nenum class NotificationDelay(\n  @StringRes val stringRes: Int,\n  val delayMs: Long\n) {\n  HOURS_12_NEG(R.string.textSettingsShowsNotificationsWhen12HoursBefore, -HOUR_MS * 12),\n  HOURS_6_NEG(R.string.textSettingsShowsNotificationsWhen6HoursBefore, -HOUR_MS * 6),\n  HOURS_3_NEG(R.string.textSettingsShowsNotificationsWhen3HoursBefore, -HOUR_MS * 3),\n  HOURS_1_NEG(R.string.textSettingsShowsNotificationsWhen1HourBefore, -HOUR_MS),\n  WHEN_AVAILABLE(R.string.textSettingsShowsNotificationsWhenAvailable, 0),\n  HOURS_1(R.string.textSettingsShowsNotificationsWhen1Hour, HOUR_MS),\n  HOURS_3(R.string.textSettingsShowsNotificationsWhen3Hours, HOUR_MS * 3),\n  HOURS_6(R.string.textSettingsShowsNotificationsWhen6Hours, HOUR_MS * 6),\n  HOURS_12(R.string.textSettingsShowsNotificationsWhen12Hours, HOUR_MS * 12),\n  HOURS_24(R.string.textSettingsShowsNotificationsWhenNextDay, HOUR_MS * 24);\n\n  companion object {\n    fun fromDelay(delayMs: Long) =\n      enumValues<NotificationDelay>().first { it.delayMs == delayMs }\n  }\n\n  fun isBefore() = this in listOf(HOURS_1_NEG, HOURS_3_NEG, HOURS_6_NEG, HOURS_12_NEG)\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/Person.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport android.os.Parcelable\nimport com.michaldrabik.common.extensions.nowUtcDay\nimport kotlinx.parcelize.Parcelize\nimport java.time.LocalDate\nimport java.time.Period\n\n@Parcelize\ndata class Person(\n  val ids: Ids,\n  val name: String,\n  val department: Department,\n  val bio: String?,\n  val bioTranslation: String?,\n  val characters: List<String>,\n  val jobs: List<Job>,\n  val episodesCount: Int,\n  val birthplace: String?,\n  val imagePath: String?,\n  val homepage: String?,\n  val birthday: LocalDate?,\n  val deathday: LocalDate?,\n) : Parcelable {\n\n  fun getAge() = when {\n    birthday != null && deathday != null -> Period.between(birthday, deathday).years\n    birthday != null -> Period.between(birthday, nowUtcDay()).years\n    else -> null\n  }\n\n  enum class Department(val slug: String) {\n    ACTING(\"Acting\"),\n    DIRECTING(\"Directing\"),\n    WRITING(\"Writing\"),\n    SOUND(\"Sound\"),\n    UNKNOWN(\"-\")\n  }\n\n  enum class Job(val slug: String) {\n    DIRECTOR(\"Director\"),\n    WRITER(\"Writer\"),\n    STORY(\"Story\"),\n    SCREENPLAY(\"Screenplay\"),\n    MUSIC(\"Music\"),\n    ORIGINAL_MUSIC(\"Original Music Composer\"),\n    UNKNOWN(\"-\");\n\n    companion object {\n      fun fromSlug(slug: String?) = values().firstOrNull { it.slug == slug } ?: UNKNOWN\n    }\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/PersonCredit.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport com.michaldrabik.ui_model.MovieStatus.IN_PRODUCTION\nimport com.michaldrabik.ui_model.MovieStatus.PLANNED\nimport com.michaldrabik.ui_model.MovieStatus.POST_PRODUCTION\nimport com.michaldrabik.ui_model.MovieStatus.RUMORED\nimport java.time.LocalDate\nimport java.time.ZonedDateTime\n\ndata class PersonCredit(\n  val show: Show?,\n  val movie: Movie?,\n  val image: Image,\n  val translation: Translation?\n) {\n\n  fun requireShow() = show!!\n  fun requireMovie() = movie!!\n\n  val releaseDate: LocalDate?\n    get() = when {\n      show != null ->\n        if (show.firstAired.isNotBlank()) {\n          ZonedDateTime.parse(show.firstAired).toLocalDate()\n        } else {\n          null\n        }\n      movie != null -> movie.released\n      else -> null\n    }\n\n  val isUpcoming: Boolean\n    get() = when {\n      show != null -> {\n        show.status in arrayOf(ShowStatus.IN_PRODUCTION, ShowStatus.PLANNED, ShowStatus.UPCOMING)\n      }\n      movie != null -> {\n        !movie.hasAired() && movie.status in arrayOf(RUMORED, PLANNED, IN_PRODUCTION, POST_PRODUCTION)\n      }\n      else -> false\n    }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/PremiumFeature.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport android.content.Context\nimport androidx.annotation.StringRes\n\nenum class PremiumFeature(@StringRes val tag: Int) {\n  THEME(R.string.tagTheme),\n  NEWS(R.string.tagNews),\n  WIDGET_TRANSPARENCY(R.string.tagWidgetTransparency),\n  QUICK_RATING(R.string.tagQuickRating),\n  CUSTOM_IMAGES(R.string.tagCustomImages),\n  VIEW_TYPES(R.string.tagViewsTypes);\n\n  companion object {\n    fun fromTag(\n      context: Context,\n      tag: String\n    ) = values().firstOrNull { context.getString(it.tag) == tag }\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/ProgressNextEpisodeType.kt",
    "content": "package com.michaldrabik.ui_model\n\nenum class ProgressNextEpisodeType {\n  LAST_WATCHED,\n  OLDEST\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/ProgressType.kt",
    "content": "package com.michaldrabik.ui_model\n\nenum class ProgressType {\n  AIRED,\n  ALL\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/RatingState.kt",
    "content": "package com.michaldrabik.ui_model\n\ndata class RatingState(\n  val userRating: TraktRating? = null,\n  val rateAllowed: Boolean? = null,\n  val rateLoading: Boolean? = null\n) {\n\n  fun hasRating() = userRating != null && userRating.rating > 0\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/Ratings.kt",
    "content": "package com.michaldrabik.ui_model\n\ndata class Ratings(\n  val trakt: Value? = null,\n  val imdb: Value? = null,\n  val metascore: Value? = null,\n  val rottenTomatoes: Value? = null,\n  val rottenTomatoesUrl: String? = null,\n  val isHidden: Boolean = false,\n  val isTapToReveal: Boolean = false\n) {\n\n  fun isAnyLoading() =\n    trakt?.isLoading == true ||\n      imdb?.isLoading == true ||\n      metascore?.isLoading == true ||\n      rottenTomatoes?.isLoading == true\n\n  data class Value(\n    val value: String?,\n    val isLoading: Boolean,\n  )\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/RecentSearch.kt",
    "content": "package com.michaldrabik.ui_model\n\ndata class RecentSearch(\n  val text: String\n)\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/SearchResult.kt",
    "content": "package com.michaldrabik.ui_model\n\ndata class SearchResult(\n  val score: Float,\n  val show: Show,\n  val movie: Movie\n) {\n\n  val isShow = show != Show.EMPTY\n\n  val traktId = if (show != Show.EMPTY) show.traktId else movie.traktId\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/Season.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport java.time.ZonedDateTime\n\ndata class Season(\n  val ids: Ids,\n  val number: Int,\n  val episodeCount: Int,\n  val airedEpisodes: Int,\n  val title: String,\n  val firstAired: ZonedDateTime?,\n  val overview: String,\n  val rating: Float,\n  val episodes: List<Episode>\n) {\n\n  companion object {\n    val EMPTY = Season(\n      ids = Ids.EMPTY,\n      number = 0,\n      episodeCount = 0,\n      airedEpisodes = 0,\n      title = \"\",\n      firstAired = null,\n      overview = \"\",\n      rating = 0F,\n      episodes = listOf()\n    )\n  }\n\n  fun isSpecial() = number == 0\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/SeasonBundle.kt",
    "content": "package com.michaldrabik.ui_model\n\ndata class SeasonBundle(\n  val season: Season,\n  val show: Show\n)\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/SeasonTranslation.kt",
    "content": "package com.michaldrabik.ui_model\n\ndata class SeasonTranslation(\n  val ids: Ids,\n  val title: String,\n  val seasonNumber: Int,\n  val episodeNumber: Int,\n  val overview: String,\n  val language: String,\n  val isLocal: Boolean = false\n)\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/Settings.kt",
    "content": "package com.michaldrabik.ui_model\n\ndata class Settings(\n  val isInitialRun: Boolean,\n  val episodesNotificationsEnabled: Boolean,\n  val episodesNotificationsDelay: NotificationDelay,\n  val watchlistShowsSortBy: SortOrder,\n  val archiveShowsSortBy: SortOrder,\n  val myShowsWatchingSortBy: SortOrder,\n  val myShowsUpcomingSortBy: SortOrder,\n  val myShowsFinishedSortBy: SortOrder,\n  val myShowsAllSortBy: SortOrder,\n  val myShowsRunningIsCollapsed: Boolean,\n  val myShowsIncomingIsCollapsed: Boolean,\n  val myShowsEndedIsCollapsed: Boolean,\n  val myRecentsAmount: Int,\n  val showAnticipatedShows: Boolean,\n  val discoverFilterGenres: List<Genre>,\n  val discoverFilterNetworks: List<Network>,\n  val discoverFilterFeed: DiscoverSortOrder,\n  val traktSyncSchedule: TraktSyncSchedule,\n  val traktQuickSyncEnabled: Boolean,\n  val traktQuickRemoveEnabled: Boolean,\n  val progressSortOrder: SortOrder,\n  val archiveIncludeStatistics: Boolean,\n  val specialSeasonsEnabled: Boolean,\n  val showAnticipatedMovies: Boolean,\n  val discoverMoviesFilterGenres: List<Genre>,\n  val discoverMoviesFilterFeed: DiscoverSortOrder,\n  val watchlistMoviesSortBy: SortOrder,\n  val myMoviesAllSortBy: SortOrder,\n  val progressMoviesSortBy: SortOrder,\n  val showCollectionShows: Boolean,\n  val showCollectionMovies: Boolean,\n  val widgetsShowLabel: Boolean,\n  val traktQuickRateEnabled: Boolean,\n  val listsSortBy: SortOrder,\n  val progressUpcomingEnabled: Boolean,\n) {\n\n  companion object {\n    fun createInitial() = Settings(\n      isInitialRun = true,\n      episodesNotificationsEnabled = true,\n      episodesNotificationsDelay = NotificationDelay.WHEN_AVAILABLE,\n      myShowsFinishedSortBy = SortOrder.NAME,\n      myShowsUpcomingSortBy = SortOrder.NAME,\n      myShowsWatchingSortBy = SortOrder.NAME,\n      myShowsAllSortBy = SortOrder.NAME,\n      myShowsEndedIsCollapsed = true,\n      myShowsIncomingIsCollapsed = true,\n      myShowsRunningIsCollapsed = true,\n      myRecentsAmount = 4,\n      watchlistShowsSortBy = SortOrder.NAME,\n      archiveShowsSortBy = SortOrder.NAME,\n      showAnticipatedShows = true,\n      discoverFilterFeed = DiscoverSortOrder.HOT,\n      discoverFilterGenres = emptyList(),\n      discoverFilterNetworks = emptyList(),\n      traktSyncSchedule = TraktSyncSchedule.OFF,\n      traktQuickSyncEnabled = false,\n      traktQuickRemoveEnabled = false,\n      progressSortOrder = SortOrder.NAME,\n      archiveIncludeStatistics = true,\n      specialSeasonsEnabled = false,\n      discoverMoviesFilterFeed = DiscoverSortOrder.HOT,\n      discoverMoviesFilterGenres = emptyList(),\n      showAnticipatedMovies = true,\n      watchlistMoviesSortBy = SortOrder.NAME,\n      myMoviesAllSortBy = SortOrder.NAME,\n      progressMoviesSortBy = SortOrder.NAME,\n      showCollectionMovies = true,\n      showCollectionShows = true,\n      widgetsShowLabel = true,\n      traktQuickRateEnabled = false,\n      listsSortBy = SortOrder.NEWEST,\n      progressUpcomingEnabled = true\n    )\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/Show.kt",
    "content": "package com.michaldrabik.ui_model\n\ndata class Show(\n  val ids: Ids,\n  val title: String,\n  val year: Int,\n  val overview: String,\n  val firstAired: String,\n  val runtime: Int,\n  val airTime: AirTime,\n  val certification: String,\n  val network: String,\n  val country: String,\n  val trailer: String,\n  val homepage: String,\n  val status: ShowStatus,\n  val rating: Float,\n  val votes: Long,\n  val commentCount: Long,\n  val genres: List<String>,\n  val airedEpisodes: Int,\n  val createdAt: Long,\n  val updatedAt: Long\n) {\n\n  val traktId = ids.trakt.id\n\n  val titleNoThe = title.removePrefix(\"The\").trim()\n\n  val isAnime get() = genres.contains(Genre.ANIME.slug)\n\n  companion object {\n    val EMPTY = Show(\n      ids = Ids(\n        trakt = IdTrakt(id = 0),\n        slug = IdSlug(id = \"\"),\n        tvdb = IdTvdb(id = 0),\n        imdb = IdImdb(id = \"\"),\n        tmdb = IdTmdb(id = 0),\n        tvrage = IdTvRage(id = 0)\n      ),\n      title = \"\",\n      year = 0,\n      overview = \"\",\n      firstAired = \"\",\n      runtime = 0,\n      airTime = AirTime(day = \"\", time = \"\", timezone = \"\"),\n      certification = \"\",\n      network = \"\",\n      country = \"\",\n      trailer = \"\",\n      homepage = \"\",\n      status = ShowStatus.UNKNOWN,\n      rating = 0.0f,\n      votes = 0,\n      commentCount = 0,\n      genres = listOf(),\n      airedEpisodes = 0,\n      createdAt = 0,\n      updatedAt = 0\n    )\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/ShowStatus.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport androidx.annotation.StringRes\n\nenum class ShowStatus(\n  val key: String,\n  @StringRes val displayName: Int\n) {\n  RETURNING(\"returning series\", R.string.textShowStatusReturning),\n  UPCOMING(\"upcoming\", R.string.textShowStatusUpcoming),\n  IN_PRODUCTION(\"in production\", R.string.textShowStatusInProduction),\n  PLANNED(\"planned\", R.string.textShowStatusPlanned),\n  CANCELED(\"canceled\", R.string.textShowStatusCanceled),\n  ENDED(\"ended\", R.string.textShowStatusEnded),\n  UNKNOWN(\"unknown\", R.string.textShowStatusUnknown);\n\n  fun isAnticipated() = this in arrayOf(UPCOMING, IN_PRODUCTION, PLANNED)\n\n  companion object {\n    fun fromKey(key: String?) =\n      enumValues<ShowStatus>().firstOrNull { it.key == key } ?: UNKNOWN\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/SortOrder.kt",
    "content": "package com.michaldrabik.ui_model\n\nenum class SortOrder(\n  val slug: String,\n  val displayString: Int\n) {\n  RANK(\"rank\", R.string.textSortRank),\n  NAME(\"title\", R.string.textSortName),\n  NEWEST(\"released\", R.string.textSortNewest),\n  RATING(\"percentage\", R.string.textSortRated),\n  USER_RATING(\"user_rating\", R.string.textSortRatedUser),\n  DATE_ADDED(\"added\", R.string.textSortDateAdded),\n  DATE_UPDATED(\"updated\", R.string.textSortDateUpdated),\n  RECENTLY_WATCHED(\"recently_watched\", R.string.textSortRecentlyWatched),\n  EPISODES_LEFT(\"episodes_left\", R.string.textSortEpisodesLeft);\n\n  companion object {\n    fun fromSlug(slug: String) = values().firstOrNull { it.slug == slug }\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/SortType.kt",
    "content": "package com.michaldrabik.ui_model\n\nenum class SortType(\n  val slug: String\n) {\n  ASCENDING(\"asc\"),\n  DESCENDING(\"desc\");\n\n  companion object {\n    fun fromSlug(slug: String) = SortType.values().first { it.slug == slug }\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/SpoilersSettings.kt",
    "content": "package com.michaldrabik.ui_model\n\ndata class SpoilersSettings(\n  val isNotCollectedShowsHidden: Boolean,\n  val isNotCollectedShowsRatingsHidden: Boolean,\n  val isMyShowsHidden: Boolean,\n  val isMyShowsRatingsHidden: Boolean,\n  val isWatchlistShowsHidden: Boolean,\n  val isWatchlistShowsRatingsHidden: Boolean,\n  val isHiddenShowsHidden: Boolean,\n  val isHiddenShowsRatingsHidden: Boolean,\n  val isNotCollectedMoviesHidden: Boolean,\n  val isNotCollectedMoviesRatingsHidden: Boolean,\n  val isMyMoviesHidden: Boolean,\n  val isMyMoviesRatingsHidden: Boolean,\n  val isWatchlistMoviesHidden: Boolean,\n  val isWatchlistMoviesRatingsHidden: Boolean,\n  val isHiddenMoviesHidden: Boolean,\n  val isHiddenMoviesRatingsHidden: Boolean,\n  val isEpisodeTitleHidden: Boolean,\n  val isEpisodeDescriptionHidden: Boolean,\n  val isEpisodeRatingHidden: Boolean,\n  val isEpisodeImageHidden: Boolean,\n  val isTapToReveal: Boolean,\n) {\n\n  companion object {\n    val INITIAL = SpoilersSettings(\n      isNotCollectedShowsHidden = false,\n      isNotCollectedShowsRatingsHidden = false,\n      isMyShowsHidden = false,\n      isMyShowsRatingsHidden = false,\n      isWatchlistShowsHidden = false,\n      isWatchlistShowsRatingsHidden = false,\n      isHiddenShowsHidden = false,\n      isHiddenShowsRatingsHidden = false,\n      isNotCollectedMoviesHidden = false,\n      isNotCollectedMoviesRatingsHidden = false,\n      isMyMoviesHidden = false,\n      isMyMoviesRatingsHidden = false,\n      isWatchlistMoviesHidden = false,\n      isWatchlistMoviesRatingsHidden = false,\n      isHiddenMoviesHidden = false,\n      isHiddenMoviesRatingsHidden = false,\n      isEpisodeTitleHidden = false,\n      isEpisodeDescriptionHidden = false,\n      isEpisodeRatingHidden = false,\n      isEpisodeImageHidden = false,\n      isTapToReveal = false,\n    )\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/StreamingService.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport androidx.annotation.StringRes\n\ndata class StreamingService(\n  val imagePath: String,\n  val name: String,\n  val options: List<Option>,\n  val mediaName: String,\n  val countryCode: String,\n  val link: String,\n) {\n\n  enum class Option(@StringRes val resId: Int) {\n    FLATRATE(R.string.textStreamingStream),\n    BUY(R.string.textStreamingBuy),\n    RENT(R.string.textStreamingRent),\n    ADS(R.string.textStreamingAds),\n    FREE(R.string.textStreamingFree)\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/Tip.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport androidx.annotation.StringRes\n\nenum class Tip(\n  @StringRes val textResId: Int,\n) {\n  MENU_MODES(R.string.textTipBottomModeMenu),\n  MENU_DISCOVER(R.string.textTipDiscover),\n  MENU_MY_SHOWS(R.string.textTipMyShows),\n  SHOW_DETAILS_GALLERY(R.string.textTipShowDetailsGallery),\n  PERSON_DETAILS_GALLERY(R.string.textTipShowDetailsGallery),\n  WATCHLIST_ITEM_PIN(R.string.textTipWatchlistPinItem),\n  LIST_ITEM_SWIPE_DELETE(R.string.textTipListSwipeToDelete)\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/TraktRating.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport com.michaldrabik.common.extensions.nowUtc\nimport java.time.ZonedDateTime\n\ndata class TraktRating(\n  val idTrakt: IdTrakt,\n  val rating: Int,\n  val ratedAt: ZonedDateTime = nowUtc()\n) {\n  companion object {\n    val EMPTY = TraktRating(IdTrakt(-1), 0)\n  }\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/TraktSyncSchedule.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport androidx.annotation.StringRes\nimport java.util.concurrent.TimeUnit\nimport java.util.concurrent.TimeUnit.DAYS\nimport java.util.concurrent.TimeUnit.HOURS\nimport java.util.concurrent.TimeUnit.SECONDS\n\nenum class TraktSyncSchedule(\n  val duration: Long,\n  val durationUnit: TimeUnit,\n  @StringRes val stringRes: Int,\n  @StringRes val confirmationStringRes: Int,\n  @StringRes val buttonStringRes: Int\n) {\n  OFF(0, SECONDS, R.string.textTraktSyncOptionOff, R.string.textTraktSyncOptionOffMessage, R.string.textTraktSyncSchedule),\n  EVERY_HOUR(1, HOURS, R.string.textTraktSyncOption1Hour, R.string.textTraktSyncOptionConfirmMessage, R.string.textTraktSyncOptionHourButton),\n  EVERY_3_HOURS(3, HOURS, R.string.textTraktSyncOption3Hours, R.string.textTraktSyncOptionConfirmMessage, R.string.textTraktSyncOption3HoursButton),\n  EVERY_6_HOURS(6, HOURS, R.string.textTraktSyncOption6Hours, R.string.textTraktSyncOptionConfirmMessage, R.string.textTraktSyncOption6HoursButton),\n  EVERY_DAY(1, DAYS, R.string.textTraktSyncOptionDaily, R.string.textTraktSyncOptionConfirmMessage, R.string.textTraktSyncOptionDailyButton),\n  EVERY_3_DAYS(3, DAYS, R.string.textTraktSyncOption3Day, R.string.textTraktSyncOptionConfirmMessage, R.string.textTraktSyncOption3DayButton),\n  EVERY_WEEK(7, DAYS, R.string.textTraktSyncOptionWeekly, R.string.textTraktSyncOptionConfirmMessage, R.string.textTraktSyncOptionWeeklyButton)\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/Translation.kt",
    "content": "package com.michaldrabik.ui_model\n\ndata class Translation(\n  val title: String,\n  val overview: String,\n  val language: String\n) {\n\n  companion object {\n    val EMPTY = Translation(\"\", \"\", \"\")\n  }\n\n  val hasTitle = title.isNotBlank()\n}\n"
  },
  {
    "path": "ui-model/src/main/java/com/michaldrabik/ui_model/User.kt",
    "content": "package com.michaldrabik.ui_model\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\ndata class User(\n  val username: String,\n  val avatarUrl: String,\n) : Parcelable\n"
  },
  {
    "path": "ui-model/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortRank\">Rank</string>\n  <string name=\"textSortName\">Title</string>\n  <string name=\"textSortNewest\">Most Recent</string>\n  <string name=\"textSortRated\">Top Rated</string>\n  <string name=\"textSortRatedUser\">My Rating</string>\n  <string name=\"textSortDateAdded\">Recently Added</string>\n  <string name=\"textSortDateUpdated\">Recently Updated</string>\n  <string name=\"textSortRecentlyWatched\">Recently Watched</string>\n  <string name=\"textSortEpisodesLeft\">Episodes Left</string>\n\n  <string name=\"textHeaderRecentlyAdded\">Recently Added</string>\n  <string name=\"textHeaderWatching\">Watching</string>\n  <string name=\"textHeaderFinished\">Finished</string>\n  <string name=\"textHeaderReturning\">Returning &amp; Upcoming</string>\n  <string name=\"textHeaderAll\">All</string>\n\n  <string name=\"textSettingsShowsNotificationsWhenTitle\">When To Notify</string>\n  <string name=\"textSettingsShowsNotificationsWhenSummary\">When would you like to receive episodes announcements.</string>\n  <string name=\"textSettingsShowsNotificationsWhenAvailable\">When Available</string>\n  <string name=\"textSettingsShowsNotificationsWhen1Hour\">1 Hour After</string>\n  <string name=\"textSettingsShowsNotificationsWhen3Hours\">3 Hours After</string>\n  <string name=\"textSettingsShowsNotificationsWhen6Hours\">6 Hours After</string>\n  <string name=\"textSettingsShowsNotificationsWhen1HourBefore\">1 Hour Before</string>\n  <string name=\"textSettingsShowsNotificationsWhen3HoursBefore\">3 Hours Before</string>\n  <string name=\"textSettingsShowsNotificationsWhen6HoursBefore\">6 Hours Before</string>\n  <string name=\"textSettingsShowsNotificationsWhen12HoursBefore\">12 Hours Before</string>\n  <string name=\"textSettingsShowsNotificationsWhen12Hours\">12 Hours After</string>\n  <string name=\"textSettingsShowsNotificationsWhenNextDay\">1 Day After</string>\n\n  <string name=\"textTipDiscover\">Double tap \\'Discover\\' menu button to quickly open search screen.</string>\n  <string name=\"textTipMyShows\">Double tap \\'Collection\\' menu button to quickly switch between tabs.</string>\n  <string name=\"textTipShowDetailsGallery\">Tap image to open gallery.</string>\n  <string name=\"textTipWatchlistPinItem\">Tap and hold list item to pin it to the top.</string>\n  <string name=\"textTipBottomModeMenu\">Swipe bottom menu left or right to quickly change mode.</string>\n  <string name=\"textTipListSwipeToDelete\">Swipe list items to remove them from the list.</string>\n\n  <string name=\"textGenreAction\">Action</string>\n  <string name=\"textGenreAdventure\">Adventure</string>\n  <string name=\"textGenreAnimation\">Animation</string>\n  <string name=\"textGenreAnime\">Anime</string>\n  <string name=\"textGenreComedy\">Comedy</string>\n  <string name=\"textGenreCrime\">Crime</string>\n  <string name=\"textGenreDocumentary\">Documentary</string>\n  <string name=\"textGenreDrama\">Drama</string>\n  <string name=\"textGenreFantasy\">Fantasy</string>\n  <string name=\"textGenreHistory\">History</string>\n  <string name=\"textGenreHorror\">Horror</string>\n  <string name=\"textGenreScienceFiction\">Science-Fiction</string>\n  <string name=\"textGenreThriller\">Thriller</string>\n  <string name=\"textGenreWar\">War</string>\n  <string name=\"textGenreWestern\">Western</string>\n\n  <string name=\"textTraktSyncSchedule\">Schedule</string>\n  <string name=\"textTraktSyncOptionOff\">Off</string>\n  <string name=\"textTraktSyncOption1Hour\">Every Hour</string>\n  <string name=\"textTraktSyncOption3Hours\">Every 3 Hours</string>\n  <string name=\"textTraktSyncOption6Hours\">Every 6 Hours</string>\n  <string name=\"textTraktSyncOptionDaily\">Every Day</string>\n  <string name=\"textTraktSyncOption3Day\">Every 3 Days</string>\n  <string name=\"textTraktSyncOptionWeekly\">Every Week</string>\n  <string name=\"textTraktSyncOptionOffMessage\">Sync will not run automatically.</string>\n  <string name=\"textTraktSyncOptionConfirmMessage\">Automatic sync has been scheduled.</string>\n\n  <string name=\"textTraktSyncOptionHourButton\">Scheduled to run every hour</string>\n  <string name=\"textTraktSyncOption3HoursButton\">Scheduled to run every 3 hours</string>\n  <string name=\"textTraktSyncOption6HoursButton\">Scheduled to run every 6 hours</string>\n  <string name=\"textTraktSyncOptionDailyButton\">Scheduled to run every day</string>\n  <string name=\"textTraktSyncOption3DayButton\">Scheduled to run every 3 days</string>\n  <string name=\"textTraktSyncOptionWeeklyButton\">Scheduled to run every week</string>\n\n  <string name=\"textShowStatusReturning\">Returning Series</string>\n  <string name=\"textShowStatusUpcoming\">Upcoming</string>\n  <string name=\"textShowStatusInProduction\">In Production</string>\n  <string name=\"textShowStatusPlanned\">Planned</string>\n  <string name=\"textShowStatusCanceled\">Canceled</string>\n  <string name=\"textShowStatusEnded\">Ended</string>\n  <string name=\"textShowStatusUnknown\" translatable=\"false\" />\n\n  <string name=\"textMovieStatusReleased\">Released</string>\n  <string name=\"textMovieStatusInProduction\">In Production</string>\n  <string name=\"textMovieStatusPostProduction\">Post Production</string>\n  <string name=\"textMovieStatusPlanned\">Planned</string>\n  <string name=\"textMovieStatusCanceled\">Canceled</string>\n  <string name=\"textMovieStatusRumored\">Rumored</string>\n  <string name=\"textMovieStatusUnknown\" translatable=\"false\" />\n\n  <string name=\"textStreamingStream\">Stream</string>\n  <string name=\"textStreamingAds\">Ads</string>\n  <string name=\"textStreamingBuy\">Buy</string>\n  <string name=\"textStreamingRent\">Rent</string>\n  <string name=\"textStreamingFree\">Free</string>\n\n  <string name=\"tagQuickRating\" translatable=\"false\">Quick Rating</string>\n  <string name=\"tagCustomImages\" translatable=\"false\">Custom Images</string>\n  <string name=\"tagTheme\" translatable=\"false\">Theme</string>\n  <string name=\"tagWidgetTransparency\" translatable=\"false\">Widget Transparency</string>\n  <string name=\"tagNews\" translatable=\"false\">News</string>\n  <string name=\"tagViewsTypes\" translatable=\"false\">List View Types</string>\n</resources>"
  },
  {
    "path": "ui-model/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortRank\">ترتيب العناصر</string>\n  <string name=\"textSortName\">الاسم</string>\n  <string name=\"textSortNewest\">الأحدث</string>\n  <string name=\"textSortRated\">الأعلى تقييمًا</string>\n  <string name=\"textSortRatedUser\">تقييمي</string>\n  <string name=\"textSortDateAdded\">أُضيف مؤخرًا</string>\n  <string name=\"textSortDateUpdated\">حُدث مؤخرًا</string>\n  <string name=\"textSortRecentlyWatched\">شوهد مؤخرًا</string>\n  <string name=\"textSortEpisodesLeft\">عدد الحلقات المتبقية</string>\n  <string name=\"textHeaderRecentlyAdded\">أضيف مؤخراً</string>\n  <string name=\"textHeaderWatching\">يُشاهد حاليًا</string>\n  <string name=\"textHeaderFinished\">تمت مشاهدته</string>\n  <string name=\"textHeaderReturning\">قادم بموسم جديد &amp; سيُعرض قريبًا</string>\n  <string name=\"textHeaderAll\">الكل</string>\n  <string name=\"textSettingsShowsNotificationsWhenTitle\">وقت استلام الإشعار</string>\n  <string name=\"textSettingsShowsNotificationsWhenSummary\">متى ترغب بتلقي إشعارات الحلقات الجديدة؟</string>\n  <string name=\"textSettingsShowsNotificationsWhenAvailable\">فور توفرها</string>\n  <string name=\"textSettingsShowsNotificationsWhen1Hour\">بعد توفرها بساعة</string>\n  <string name=\"textSettingsShowsNotificationsWhen3Hours\">بعد توفرها ب 3 ساعات</string>\n  <string name=\"textSettingsShowsNotificationsWhen6Hours\">بعد توفرها ب 6 ساعات</string>\n  <string name=\"textSettingsShowsNotificationsWhen1HourBefore\">قبل توفرها بساعة</string>\n  <string name=\"textSettingsShowsNotificationsWhen3HoursBefore\">قبل توفرها ب 3 ساعات</string>\n  <string name=\"textSettingsShowsNotificationsWhen6HoursBefore\">قبل توفرها ب 6 ساعات</string>\n  <string name=\"textSettingsShowsNotificationsWhen12HoursBefore\">قبل توفرها ب 12 ساعة</string>\n  <string name=\"textSettingsShowsNotificationsWhen12Hours\">بعد توفرها ب 12 ساعة</string>\n  <string name=\"textSettingsShowsNotificationsWhenNextDay\">بعد توفرها بيوم</string>\n  <string name=\"textTipDiscover\">اُنقر نقرًا مزدوجًا على زر صفحة \\\"اكتشف\\\" لِفتح صفحة البحث بسرعة.</string>\n  <string name=\"textTipMyShows\">اُنقر نقرًا مزدوجًا على زر قائمة \\\"المجموعة\\\" للتنقل بين النوافذ بسرعة.</string>\n  <string name=\"textTipShowDetailsGallery\">انقر على أي صورة لفتح معرض الصور.</string>\n  <string name=\"textTipWatchlistPinItem\">اضغط مطولًا على أي عنصر لتثبيته في الأعلى.</string>\n  <string name=\"textTipBottomModeMenu\">مرر أسفل القائمة لليسار أو لليمين للتنقل بين المسلسلات والأفلام.</string>\n  <string name=\"textTipListSwipeToDelete\">اسحب أي عنصر لحذفه من القائمة.</string>\n  <string name=\"textGenreAction\">أكشن</string>\n  <string name=\"textGenreAdventure\">مغامرات</string>\n  <string name=\"textGenreAnimation\">أنیميشن</string>\n  <string name=\"textGenreAnime\">أنمي</string>\n  <string name=\"textGenreComedy\">كوميديا</string>\n  <string name=\"textGenreCrime\">جريمة</string>\n  <string name=\"textGenreDocumentary\">وثائقي</string>\n  <string name=\"textGenreDrama\">دراما</string>\n  <string name=\"textGenreFantasy\">خيال</string>\n  <string name=\"textGenreHistory\">تاريخ</string>\n  <string name=\"textGenreHorror\"> رعب</string>\n  <string name=\"textGenreScienceFiction\">خيال علمي</string>\n  <string name=\"textGenreThriller\">إثارة</string>\n  <string name=\"textGenreWar\">حرب</string>\n  <string name=\"textGenreWestern\">ويسترن</string>\n  <string name=\"textTraktSyncSchedule\">جدولة المزامنة</string>\n  <string name=\"textTraktSyncOptionOff\">تعطيل</string>\n  <string name=\"textTraktSyncOption1Hour\">كُل ساعة</string>\n  <string name=\"textTraktSyncOption3Hours\">كُل 3 ساعات</string>\n  <string name=\"textTraktSyncOption6Hours\">كُل 6 ساعات</string>\n  <string name=\"textTraktSyncOptionDaily\">كُل يوم</string>\n  <string name=\"textTraktSyncOption3Day\">كُل 3 أيام</string>\n  <string name=\"textTraktSyncOptionWeekly\">كُل أسبوع</string>\n  <string name=\"textTraktSyncOptionOffMessage\">لن تعمل المزامنة بشكل تلقائي.</string>\n  <string name=\"textTraktSyncOptionConfirmMessage\">جُدولت المزامنة التلقائية.</string>\n  <string name=\"textTraktSyncOptionHourButton\">وُقتت الجدولة لتعمل كُل ساعة</string>\n  <string name=\"textTraktSyncOption3HoursButton\">تم توقيت الجدولة لتعمل كُل 3 ساعات</string>\n  <string name=\"textTraktSyncOption6HoursButton\">تم توقيت الجدولة لتعمل كُل 6 ساعات</string>\n  <string name=\"textTraktSyncOptionDailyButton\">تم توقيت الجدولة لتعمل كُل يوم</string>\n  <string name=\"textTraktSyncOption3DayButton\">تم توقيت الجدولة لتعمل كُل 3 أيام</string>\n  <string name=\"textTraktSyncOptionWeeklyButton\">تم توقيت الجدولة لتعمل كُل إسبوع</string>\n  <string name=\"textShowStatusReturning\">مسلسل مستمر</string>\n  <string name=\"textShowStatusUpcoming\">يُعرض قريباً</string>\n  <string name=\"textShowStatusInProduction\">قيد الإنتاج</string>\n  <string name=\"textShowStatusPlanned\">مُخطط له</string>\n  <string name=\"textShowStatusCanceled\">تم إلغاءه</string>\n  <string name=\"textShowStatusEnded\">إنتهى</string>\n  <string name=\"textMovieStatusReleased\">توفر</string>\n  <string name=\"textMovieStatusInProduction\">قيد الإنتاج</string>\n  <string name=\"textMovieStatusPostProduction\">في مرحلة ما بعد الإنتاج</string>\n  <string name=\"textMovieStatusPlanned\">مُخطط له</string>\n  <string name=\"textMovieStatusCanceled\">تم إلغاءه</string>\n  <string name=\"textMovieStatusRumored\">يُشاع</string>\n  <string name=\"textStreamingStream\">بث</string>\n  <string name=\"textStreamingAds\">إعلانات</string>\n  <string name=\"textStreamingBuy\">شراء</string>\n  <string name=\"textStreamingRent\">إيجار</string>\n  <string name=\"textStreamingFree\">مجانًا</string>\n</resources>\n"
  },
  {
    "path": "ui-model/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortRank\">Rang</string>\n  <string name=\"textSortName\">Titel</string>\n  <string name=\"textSortNewest\">Neuste</string>\n  <string name=\"textSortRated\">Top Bewertung</string>\n  <string name=\"textSortRatedUser\">Meine Bewertungen</string>\n  <string name=\"textSortDateAdded\">Zuletzt Hinzugefügt</string>\n  <string name=\"textSortDateUpdated\">Kürzlich Aktualisiert</string>\n  <string name=\"textSortRecentlyWatched\">Zuletzt Geguckt</string>\n  <string name=\"textSortEpisodesLeft\">Folgen Übrig</string>\n  <string name=\"textHeaderRecentlyAdded\">Zuletzt Hinzugefügt</string>\n  <string name=\"textHeaderWatching\">Watching</string>\n  <string name=\"textHeaderFinished\">Fertig</string>\n  <string name=\"textHeaderReturning\">Wiederkehrend &amp; Demnächst</string>\n  <string name=\"textHeaderAll\">Alle</string>\n  <string name=\"textSettingsShowsNotificationsWhenTitle\">Wann soll die Benachrichtigung erscheinen?</string>\n  <string name=\"textSettingsShowsNotificationsWhenSummary\">Wann möchtest du Episodenankündigungen erhalten?</string>\n  <string name=\"textSettingsShowsNotificationsWhenAvailable\">Wenn Verfügbar</string>\n  <string name=\"textSettingsShowsNotificationsWhen1Hour\">Nach 1 Stunde</string>\n  <string name=\"textSettingsShowsNotificationsWhen3Hours\">Nach 3 Stunden</string>\n  <string name=\"textSettingsShowsNotificationsWhen6Hours\">6 Stunden später</string>\n  <string name=\"textSettingsShowsNotificationsWhen1HourBefore\">Vor 1 Stunde</string>\n  <string name=\"textSettingsShowsNotificationsWhen3HoursBefore\">Vor 3 Stunden</string>\n  <string name=\"textSettingsShowsNotificationsWhen6HoursBefore\">6 Stunden vorher</string>\n  <string name=\"textSettingsShowsNotificationsWhen12HoursBefore\">Vor 12 Stunden</string>\n  <string name=\"textSettingsShowsNotificationsWhen12Hours\">Nach 12 Stunden</string>\n  <string name=\"textSettingsShowsNotificationsWhenNextDay\">1 Tag danach</string>\n  <string name=\"textTipDiscover\">Tippe zweimal auf \\'Entdecken\\', um eine Schnellsuche zu starten.</string>\n  <string name=\"textTipMyShows\">Tippe zweimal auf \\'Sammlung\\', um schnell zwischen den Registerkarten zu wechseln.</string>\n  <string name=\"textTipShowDetailsGallery\">Bild antippen, um Galerie zu öffnen.</string>\n  <string name=\"textTipWatchlistPinItem\">Tippe &amp; Halte um oben anzupinnen.</string>\n  <string name=\"textTipBottomModeMenu\">Wische im Menü unten nach links oder rechts um den Modus zu wechseln.</string>\n  <string name=\"textTipListSwipeToDelete\">Wische Listenelemente zur Seite, um sie aus der Liste zu entfernen.</string>\n  <string name=\"textGenreAction\">Action</string>\n  <string name=\"textGenreAdventure\">Abenteuer</string>\n  <string name=\"textGenreAnimation\">Animation</string>\n  <string name=\"textGenreAnime\">Anime</string>\n  <string name=\"textGenreComedy\">Komödie</string>\n  <string name=\"textGenreCrime\">Crime</string>\n  <string name=\"textGenreDocumentary\">Dokumentation</string>\n  <string name=\"textGenreDrama\">Drama</string>\n  <string name=\"textGenreFantasy\">Fantasy</string>\n  <string name=\"textGenreHistory\">Geschichte</string>\n  <string name=\"textGenreHorror\">Horror</string>\n  <string name=\"textGenreScienceFiction\">Science-Fiction</string>\n  <string name=\"textGenreThriller\">Thriller</string>\n  <string name=\"textGenreWar\">Krieg</string>\n  <string name=\"textGenreWestern\">Western</string>\n  <string name=\"textTraktSyncSchedule\">Zeitplan</string>\n  <string name=\"textTraktSyncOptionOff\">Aus</string>\n  <string name=\"textTraktSyncOption1Hour\">Jede Stunde</string>\n  <string name=\"textTraktSyncOption3Hours\">Alle 3 Stunden</string>\n  <string name=\"textTraktSyncOption6Hours\">Alle 6 Stunden</string>\n  <string name=\"textTraktSyncOptionDaily\">Jeden Tag</string>\n  <string name=\"textTraktSyncOption3Day\">Alle 3 Tage</string>\n  <string name=\"textTraktSyncOptionWeekly\">Jede Woche</string>\n  <string name=\"textTraktSyncOptionOffMessage\">Sync wird nicht automatisch starten.</string>\n  <string name=\"textTraktSyncOptionConfirmMessage\">Die automatische Sync wurde geplant.</string>\n  <string name=\"textTraktSyncOptionHourButton\">Geplant für jede Stunde</string>\n  <string name=\"textTraktSyncOption3HoursButton\">Geplant für alle 3 Stunden</string>\n  <string name=\"textTraktSyncOption6HoursButton\">Geplant für alle 6 Stunden</string>\n  <string name=\"textTraktSyncOptionDailyButton\">Geplant für jeden Tag</string>\n  <string name=\"textTraktSyncOption3DayButton\">Geplant für alle 3 Tage.</string>\n  <string name=\"textTraktSyncOptionWeeklyButton\">Geplant für jede Woche</string>\n  <string name=\"textShowStatusReturning\">Zurückkehrende Serien</string>\n  <string name=\"textShowStatusUpcoming\">Demnächst</string>\n  <string name=\"textShowStatusInProduction\">In Produktion</string>\n  <string name=\"textShowStatusPlanned\">Geplant</string>\n  <string name=\"textShowStatusCanceled\">Abgebrochen</string>\n  <string name=\"textShowStatusEnded\">Beendet</string>\n  <string name=\"textMovieStatusReleased\">Veröffentlicht</string>\n  <string name=\"textMovieStatusInProduction\">In Produktion</string>\n  <string name=\"textMovieStatusPostProduction\">Postproduktion</string>\n  <string name=\"textMovieStatusPlanned\">Geplant</string>\n  <string name=\"textMovieStatusCanceled\">Abgebrochen</string>\n  <string name=\"textMovieStatusRumored\">Gerüchten zufolge</string>\n  <string name=\"textStreamingStream\">Stream</string>\n  <string name=\"textStreamingAds\">Werbung</string>\n  <string name=\"textStreamingBuy\">Kaufen</string>\n  <string name=\"textStreamingRent\">Leihen</string>\n  <string name=\"textStreamingFree\">Frei</string>\n</resources>\n"
  },
  {
    "path": "ui-model/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortRank\">Clasificación</string>\n  <string name=\"textSortName\">Título</string>\n  <string name=\"textSortNewest\">Más Reciente</string>\n  <string name=\"textSortRated\">Mejores Valorados</string>\n  <string name=\"textSortRatedUser\">Mi valoración</string>\n  <string name=\"textSortDateAdded\">Añadidos Recientemente</string>\n  <string name=\"textSortDateUpdated\">Actualizado Recientemente</string>\n  <string name=\"textSortRecentlyWatched\">Visto Recientemente</string>\n  <string name=\"textSortEpisodesLeft\">Episodios Restantes</string>\n  <string name=\"textHeaderRecentlyAdded\">Añadidos Recientemente</string>\n  <string name=\"textHeaderWatching\">Viendo</string>\n  <string name=\"textHeaderFinished\">Terminados</string>\n  <string name=\"textHeaderReturning\">Regresan &amp; Próximamente</string>\n  <string name=\"textHeaderAll\">Todos</string>\n  <string name=\"textSettingsShowsNotificationsWhenTitle\">Cuándo Notificar</string>\n  <string name=\"textSettingsShowsNotificationsWhenSummary\">¿Cuándo te gustaría recibir anuncios sobre episodios?</string>\n  <string name=\"textSettingsShowsNotificationsWhenAvailable\">Cuando Estén Disponibles</string>\n  <string name=\"textSettingsShowsNotificationsWhen1Hour\">1 Hora Después</string>\n  <string name=\"textSettingsShowsNotificationsWhen3Hours\">3 Horas Después</string>\n  <string name=\"textSettingsShowsNotificationsWhen6Hours\">6 Horas Después</string>\n  <string name=\"textSettingsShowsNotificationsWhen1HourBefore\">1 Hora Antes</string>\n  <string name=\"textSettingsShowsNotificationsWhen3HoursBefore\">3 Horas Antes</string>\n  <string name=\"textSettingsShowsNotificationsWhen6HoursBefore\">6 Horas Antes</string>\n  <string name=\"textSettingsShowsNotificationsWhen12HoursBefore\">12 Horas Antes</string>\n  <string name=\"textSettingsShowsNotificationsWhen12Hours\">12 Horas Después</string>\n  <string name=\"textSettingsShowsNotificationsWhenNextDay\">1 Día Después</string>\n  <string name=\"textTipDiscover\">Toca dos veces el botón de menú \\'Descubrir\\' para abrir rápidamente la pantalla de búsqueda.</string>\n  <string name=\"textTipMyShows\">Toca dos veces el botón de menú \\'Colección\\' para cambiar rápidamente entre pestañas.</string>\n  <string name=\"textTipShowDetailsGallery\">Toca la imagen para abrir la galería.</string>\n  <string name=\"textTipWatchlistPinItem\">Mantén pulsado el elemento de la lista para fijarlo en la parte superior.</string>\n  <string name=\"textTipBottomModeMenu\">Desliza el menú inferior izquierda o derecha para cambiar el modo rápidamente.</string>\n  <string name=\"textTipListSwipeToDelete\">Desliza los elementos de la lista para eliminarlos de ella.</string>\n  <string name=\"textGenreAction\">Acción</string>\n  <string name=\"textGenreAdventure\">Aventura</string>\n  <string name=\"textGenreAnimation\">Animación</string>\n  <string name=\"textGenreAnime\">Anime</string>\n  <string name=\"textGenreComedy\">Comedia</string>\n  <string name=\"textGenreCrime\">Crimen</string>\n  <string name=\"textGenreDocumentary\">Documental</string>\n  <string name=\"textGenreDrama\">Drama</string>\n  <string name=\"textGenreFantasy\">Fantasía</string>\n  <string name=\"textGenreHistory\">Historia</string>\n  <string name=\"textGenreHorror\">Terror</string>\n  <string name=\"textGenreScienceFiction\">Ciencia-Ficción</string>\n  <string name=\"textGenreThriller\">Thriller</string>\n  <string name=\"textGenreWar\">Guerra</string>\n  <string name=\"textGenreWestern\">Western</string>\n  <string name=\"textTraktSyncSchedule\">Programar</string>\n  <string name=\"textTraktSyncOptionOff\">Apagado</string>\n  <string name=\"textTraktSyncOption1Hour\">Cada Hora</string>\n  <string name=\"textTraktSyncOption3Hours\">Cada 3 Horas</string>\n  <string name=\"textTraktSyncOption6Hours\">Cada 6 Horas</string>\n  <string name=\"textTraktSyncOptionDaily\">Cada Día</string>\n  <string name=\"textTraktSyncOption3Day\">Cada 3 Días</string>\n  <string name=\"textTraktSyncOptionWeekly\">Cada Semana</string>\n  <string name=\"textTraktSyncOptionOffMessage\">La sincronización no se ejecutará automáticamente.</string>\n  <string name=\"textTraktSyncOptionConfirmMessage\">Se ha programado la sincronización automática.</string>\n  <string name=\"textTraktSyncOptionHourButton\">Programada para ejecutarse cada hora</string>\n  <string name=\"textTraktSyncOption3HoursButton\">Programada para ejecutarse cada 3 horas</string>\n  <string name=\"textTraktSyncOption6HoursButton\">Programada para ejecutarse cada 6 horas</string>\n  <string name=\"textTraktSyncOptionDailyButton\">Programada para ejecutarse cada día</string>\n  <string name=\"textTraktSyncOption3DayButton\">Programada para ejecutarse cada 3 días</string>\n  <string name=\"textTraktSyncOptionWeeklyButton\">Programada para ejecutarse cada semana</string>\n  <string name=\"textShowStatusReturning\">Serie que Regresa</string>\n  <string name=\"textShowStatusUpcoming\">Próximamente</string>\n  <string name=\"textShowStatusInProduction\">En Producción</string>\n  <string name=\"textShowStatusPlanned\">Planificada</string>\n  <string name=\"textShowStatusCanceled\">Cancelada</string>\n  <string name=\"textShowStatusEnded\">Terminada</string>\n  <string name=\"textMovieStatusReleased\">Estrenada</string>\n  <string name=\"textMovieStatusInProduction\">En Producción</string>\n  <string name=\"textMovieStatusPostProduction\">Post-Producción</string>\n  <string name=\"textMovieStatusPlanned\">Planificada</string>\n  <string name=\"textMovieStatusCanceled\">Cancelada</string>\n  <string name=\"textMovieStatusRumored\">Rumoreada</string>\n  <string name=\"textStreamingStream\">Stream</string>\n  <string name=\"textStreamingAds\">Anuncios</string>\n  <string name=\"textStreamingBuy\">Comprar</string>\n  <string name=\"textStreamingRent\">Alquilar</string>\n  <string name=\"textStreamingFree\">Gratis</string>\n</resources>\n"
  },
  {
    "path": "ui-model/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortRank\">Sijoitus</string>\n  <string name=\"textSortName\">Nimi</string>\n  <string name=\"textSortNewest\">Viimeisimmät</string>\n  <string name=\"textSortRated\">Arvostetuimmat</string>\n  <string name=\"textSortRatedUser\">Oma arvio</string>\n  <string name=\"textSortDateAdded\">Lisätty hiljattain</string>\n  <string name=\"textSortDateUpdated\">Hiljattain päivitetyt</string>\n  <string name=\"textSortRecentlyWatched\">Katsottu hiljattain</string>\n  <string name=\"textSortEpisodesLeft\">Jaksoja jäljellä</string>\n  <string name=\"textHeaderRecentlyAdded\">Lisätty hiljattain</string>\n  <string name=\"textHeaderWatching\">Katsottavana</string>\n  <string name=\"textHeaderFinished\">Katsellut</string>\n  <string name=\"textHeaderReturning\">Jatkuvat ja tulevat</string>\n  <string name=\"textHeaderAll\">Kaikki</string>\n  <string name=\"textSettingsShowsNotificationsWhenTitle\">Milloin ilmoitetaan</string>\n  <string name=\"textSettingsShowsNotificationsWhenSummary\">Milloin haluat vastaanottaa ilmoituksia jaksoista.</string>\n  <string name=\"textSettingsShowsNotificationsWhenAvailable\">Julkaisuhetkellä</string>\n  <string name=\"textSettingsShowsNotificationsWhen1Hour\">Tunnin kuluttua</string>\n  <string name=\"textSettingsShowsNotificationsWhen3Hours\">3 tunnin kuluttua</string>\n  <string name=\"textSettingsShowsNotificationsWhen6Hours\">6 tunnin kuluttua</string>\n  <string name=\"textSettingsShowsNotificationsWhen1HourBefore\">Tuntia ennen</string>\n  <string name=\"textSettingsShowsNotificationsWhen3HoursBefore\">3 tuntia ennen</string>\n  <string name=\"textSettingsShowsNotificationsWhen6HoursBefore\">6 tuntia ennen</string>\n  <string name=\"textSettingsShowsNotificationsWhen12HoursBefore\">12 tuntia ennen</string>\n  <string name=\"textSettingsShowsNotificationsWhen12Hours\">12 tunnin kuluttua</string>\n  <string name=\"textSettingsShowsNotificationsWhenNextDay\">Päivän kuluttua</string>\n  <string name=\"textTipDiscover\">Napauta Etsi-valikkopainiketta uudelleen avataksesi haun nopeasti.</string>\n  <string name=\"textTipMyShows\">Napauta Kokoelma-valikkopainiketta uudelleen siirtyäksesi alueiden välillä nopeasti.</string>\n  <string name=\"textTipShowDetailsGallery\">Avaa galleria napauttamalla kuvaa.</string>\n  <string name=\"textTipWatchlistPinItem\">Kiinnitä kohde listan ylälaitaan painamalla sitä pitkään.</string>\n  <string name=\"textTipBottomModeMenu\">Vaihda aluetta nopeasti pyyhkäisemällä alareunan valikkoa vasemmalle tai oikealle.</string>\n  <string name=\"textTipListSwipeToDelete\">Poista kohde listalta pyyhkäisemällä se sivuun.</string>\n  <string name=\"textGenreAction\">Toiminta</string>\n  <string name=\"textGenreAdventure\">Seikkailu</string>\n  <string name=\"textGenreAnimation\">Animaatio</string>\n  <string name=\"textGenreAnime\">Anime</string>\n  <string name=\"textGenreComedy\">Komedia</string>\n  <string name=\"textGenreCrime\">Rikos</string>\n  <string name=\"textGenreDocumentary\">Dokumentti</string>\n  <string name=\"textGenreDrama\">Draama</string>\n  <string name=\"textGenreFantasy\">Fantasia</string>\n  <string name=\"textGenreHistory\">Historia</string>\n  <string name=\"textGenreHorror\">Kauhu</string>\n  <string name=\"textGenreScienceFiction\">Scifi</string>\n  <string name=\"textGenreThriller\">Trilleri</string>\n  <string name=\"textGenreWar\">Sota</string>\n  <string name=\"textGenreWestern\">Länkkäri</string>\n  <string name=\"textTraktSyncSchedule\">Ajoitus</string>\n  <string name=\"textTraktSyncOptionOff\">Ei käytössä</string>\n  <string name=\"textTraktSyncOption1Hour\">Kerran tunnissa</string>\n  <string name=\"textTraktSyncOption3Hours\">3 tunnin välein</string>\n  <string name=\"textTraktSyncOption6Hours\">6 tunnin välein</string>\n  <string name=\"textTraktSyncOptionDaily\">Kerran päivässä</string>\n  <string name=\"textTraktSyncOption3Day\">3 päivän välein</string>\n  <string name=\"textTraktSyncOptionWeekly\">Kerran viikossa</string>\n  <string name=\"textTraktSyncOptionOffMessage\">Synkronointia ei suoriteta automaattisesti.</string>\n  <string name=\"textTraktSyncOptionConfirmMessage\">Automaattinen synkronointi on ajastettu.</string>\n  <string name=\"textTraktSyncOptionHourButton\">Ajoitettu suoritettavaksi kerran tunnissa</string>\n  <string name=\"textTraktSyncOption3HoursButton\">Ajoitettu suoritettavaksi 3 tunnin välein</string>\n  <string name=\"textTraktSyncOption6HoursButton\">Ajoitettu suoritettavaksi 6 tunnin välein</string>\n  <string name=\"textTraktSyncOptionDailyButton\">Ajoitettu suoritettavaksi kerran päivässä</string>\n  <string name=\"textTraktSyncOption3DayButton\">Ajoitettu suoritettavaksi 3 päivän välein</string>\n  <string name=\"textTraktSyncOptionWeeklyButton\">Ajoitettu suoritettavaksi kerran viikossa</string>\n  <string name=\"textShowStatusReturning\">Jatkuva sarja</string>\n  <string name=\"textShowStatusUpcoming\">Tulossa</string>\n  <string name=\"textShowStatusInProduction\">Tuotannossa</string>\n  <string name=\"textShowStatusPlanned\">Suunniteltu</string>\n  <string name=\"textShowStatusCanceled\">Peruttu</string>\n  <string name=\"textShowStatusEnded\">Päättynyt</string>\n  <string name=\"textMovieStatusReleased\">Julkaistu</string>\n  <string name=\"textMovieStatusInProduction\">Tuotannossa</string>\n  <string name=\"textMovieStatusPostProduction\">Jälkituotannossa</string>\n  <string name=\"textMovieStatusPlanned\">Suunniteltu</string>\n  <string name=\"textMovieStatusCanceled\">Peruttu</string>\n  <string name=\"textMovieStatusRumored\">Huhuttu</string>\n  <string name=\"textStreamingStream\">Suoratoista</string>\n  <string name=\"textStreamingAds\">Mainoksilla</string>\n  <string name=\"textStreamingBuy\">Osta</string>\n  <string name=\"textStreamingRent\">Vuokraa</string>\n  <string name=\"textStreamingFree\">Ilmaiseksi</string>\n</resources>\n"
  },
  {
    "path": "ui-model/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortRank\">Classement</string>\n  <string name=\"textSortName\">Titre</string>\n  <string name=\"textSortNewest\">Les plus récents</string>\n  <string name=\"textSortRated\">Les mieux notés</string>\n  <string name=\"textSortRatedUser\">Ma note</string>\n  <string name=\"textSortDateAdded\">Ajoutés récemment</string>\n  <string name=\"textSortDateUpdated\">Récemment mis à jour</string>\n  <string name=\"textSortRecentlyWatched\">Vu récemment</string>\n  <string name=\"textSortEpisodesLeft\">Épisodes restants</string>\n  <string name=\"textHeaderRecentlyAdded\">Ajoutés récemment</string>\n  <string name=\"textHeaderWatching\">En cours de lecture</string>\n  <string name=\"textHeaderFinished\">Terminé</string>\n  <string name=\"textHeaderReturning\">De retour et à venir</string>\n  <string name=\"textHeaderAll\">Tous</string>\n  <string name=\"textSettingsShowsNotificationsWhenTitle\">Quand notifier</string>\n  <string name=\"textSettingsShowsNotificationsWhenSummary\">Quand souhaitez-vous recevoir des annonces d\\'épisodes.</string>\n  <string name=\"textSettingsShowsNotificationsWhenAvailable\">Lorsque disponible</string>\n  <string name=\"textSettingsShowsNotificationsWhen1Hour\">1 heure après</string>\n  <string name=\"textSettingsShowsNotificationsWhen3Hours\">3 heures après</string>\n  <string name=\"textSettingsShowsNotificationsWhen6Hours\">6 heures après</string>\n  <string name=\"textSettingsShowsNotificationsWhen1HourBefore\">1 heure avant</string>\n  <string name=\"textSettingsShowsNotificationsWhen3HoursBefore\">3 heures avant</string>\n  <string name=\"textSettingsShowsNotificationsWhen6HoursBefore\">6 heures avant</string>\n  <string name=\"textSettingsShowsNotificationsWhen12HoursBefore\">12 heures avant</string>\n  <string name=\"textSettingsShowsNotificationsWhen12Hours\">12 heures après</string>\n  <string name=\"textSettingsShowsNotificationsWhenNextDay\">1 jour après</string>\n  <string name=\"textTipDiscover\">Appuyez deux fois sur le bouton « Découvrir » pour ouvrir rapidement l\\'écran de recherche.</string>\n  <string name=\"textTipMyShows\">Appuyez deux fois sur le bouton « Collection » pour basculer rapidement entre les onglets.</string>\n  <string name=\"textTipShowDetailsGallery\">Tapotez sur l\\'image pour ouvrir la galerie.</string>\n  <string name=\"textTipWatchlistPinItem\">Appuyez et maintenez la liste enfoncée pour l\\'épingler en haut.</string>\n  <string name=\"textTipBottomModeMenu\">Balayez le menu inférieur vers la gauche ou vers la droite pour changer rapidement de mode.</string>\n  <string name=\"textTipListSwipeToDelete\">Glisser les éléments pour les retirer de la liste.</string>\n  <string name=\"textGenreAction\">Action</string>\n  <string name=\"textGenreAdventure\">Aventure</string>\n  <string name=\"textGenreAnimation\">Animation</string>\n  <string name=\"textGenreAnime\">Anime</string>\n  <string name=\"textGenreComedy\">Comédie</string>\n  <string name=\"textGenreCrime\">Crime</string>\n  <string name=\"textGenreDocumentary\">Documentaire</string>\n  <string name=\"textGenreDrama\">Drame</string>\n  <string name=\"textGenreFantasy\">Fantastique</string>\n  <string name=\"textGenreHistory\">Historique</string>\n  <string name=\"textGenreHorror\">Horreur</string>\n  <string name=\"textGenreScienceFiction\">Science-Fiction</string>\n  <string name=\"textGenreThriller\">Thriller</string>\n  <string name=\"textGenreWar\">Guerre</string>\n  <string name=\"textGenreWestern\">Western</string>\n  <string name=\"textTraktSyncSchedule\">Planifier</string>\n  <string name=\"textTraktSyncOptionOff\">Off</string>\n  <string name=\"textTraktSyncOption1Hour\">Toutes les Heures</string>\n  <string name=\"textTraktSyncOption3Hours\">Toutes les 3 heures</string>\n  <string name=\"textTraktSyncOption6Hours\">Toutes les 6 heures</string>\n  <string name=\"textTraktSyncOptionDaily\">Quotidiennement</string>\n  <string name=\"textTraktSyncOption3Day\">Tous les 3 jours</string>\n  <string name=\"textTraktSyncOptionWeekly\">Chaque semaine</string>\n  <string name=\"textTraktSyncOptionOffMessage\">La synchronisation ne s\\'exécutera pas automatiquement.</string>\n  <string name=\"textTraktSyncOptionConfirmMessage\">La synchronisation automatique a été planifiée.</string>\n  <string name=\"textTraktSyncOptionHourButton\">Programmé pour s\\'exécuter toutes les heures</string>\n  <string name=\"textTraktSyncOption3HoursButton\">Programmé pour s\\'exécuter toutes les 3 heures</string>\n  <string name=\"textTraktSyncOption6HoursButton\">Programmé pour s\\'exécuter toutes les 6 heures</string>\n  <string name=\"textTraktSyncOptionDailyButton\">Programmé pour s\\'exécuter tout les jours</string>\n  <string name=\"textTraktSyncOption3DayButton\">Programmé pour s\\'exécuter tout les 3 jours</string>\n  <string name=\"textTraktSyncOptionWeeklyButton\">Programmé pour s\\'exécuter chaque semaine</string>\n  <string name=\"textShowStatusReturning\">Séries de retour</string>\n  <string name=\"textShowStatusUpcoming\">À venir</string>\n  <string name=\"textShowStatusInProduction\">En Production</string>\n  <string name=\"textShowStatusPlanned\">Programmé</string>\n  <string name=\"textShowStatusCanceled\">Annulé</string>\n  <string name=\"textShowStatusEnded\">Terminé</string>\n  <string name=\"textMovieStatusReleased\">Fait par</string>\n  <string name=\"textMovieStatusInProduction\">En Production</string>\n  <string name=\"textMovieStatusPostProduction\">En Production</string>\n  <string name=\"textMovieStatusPlanned\">Programmé</string>\n  <string name=\"textMovieStatusCanceled\">Annulé</string>\n  <string name=\"textMovieStatusRumored\">Rumeur</string>\n  <string name=\"textStreamingStream\">Stream</string>\n  <string name=\"textStreamingAds\">Publicités</string>\n  <string name=\"textStreamingBuy\">Achat</string>\n  <string name=\"textStreamingRent\">Location</string>\n  <string name=\"textStreamingFree\">Gratuit</string>\n</resources>\n"
  },
  {
    "path": "ui-model/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortRank\">Posizione</string>\n  <string name=\"textSortName\">Titolo</string>\n  <string name=\"textSortNewest\">Più recenti</string>\n  <string name=\"textSortRated\">Più votati</string>\n  <string name=\"textSortRatedUser\">La mia valutazione</string>\n  <string name=\"textSortDateAdded\">Aggiunti di recente</string>\n  <string name=\"textSortDateUpdated\">Aggiornati di recente</string>\n  <string name=\"textSortRecentlyWatched\">Visti di recente</string>\n  <string name=\"textSortEpisodesLeft\">Episodi rimasti</string>\n  <string name=\"textHeaderRecentlyAdded\">Aggiunti di Recente</string>\n  <string name=\"textHeaderWatching\">Visione in corso</string>\n  <string name=\"textHeaderFinished\">Finiti</string>\n  <string name=\"textHeaderReturning\">Rinnovate &amp; in arrivo</string>\n  <string name=\"textHeaderAll\">Tutto</string>\n  <string name=\"textSettingsShowsNotificationsWhenTitle\">Quando ricevere notifiche</string>\n  <string name=\"textSettingsShowsNotificationsWhenSummary\">Quando ti piacerebbe ricevere notifiche sui nuovi episodi.</string>\n  <string name=\"textSettingsShowsNotificationsWhenAvailable\">Quando disponibile</string>\n  <string name=\"textSettingsShowsNotificationsWhen1Hour\">1 ora dopo</string>\n  <string name=\"textSettingsShowsNotificationsWhen3Hours\">3 ore dopo</string>\n  <string name=\"textSettingsShowsNotificationsWhen6Hours\">6 ore dopo</string>\n  <string name=\"textSettingsShowsNotificationsWhen1HourBefore\">1 ora prima</string>\n  <string name=\"textSettingsShowsNotificationsWhen3HoursBefore\">3 ore prima</string>\n  <string name=\"textSettingsShowsNotificationsWhen6HoursBefore\">6 ore prima</string>\n  <string name=\"textSettingsShowsNotificationsWhen12HoursBefore\">12 ore prima</string>\n  <string name=\"textSettingsShowsNotificationsWhen12Hours\">12 ore dopo</string>\n  <string name=\"textSettingsShowsNotificationsWhenNextDay\">1 giorno dopo</string>\n  <string name=\"textTipDiscover\">Doppio tocco sul tasto \\'Scopri\\' per iniziare la ricerca.</string>\n  <string name=\"textTipMyShows\">Doppio tocco sul tasto \\'Raccolta\\' per navigare velocemente fra le pagine.</string>\n  <string name=\"textTipShowDetailsGallery\">Tocca l\\'immagine per aprire la galleria.</string>\n  <string name=\"textTipWatchlistPinItem\">Tocca e mantieni un elemento della lista per fissarlo in cima.</string>\n  <string name=\"textTipBottomModeMenu\">Scorri a sinistra o a destra il menu inferiore per cambiare modalità rapidamente.</string>\n  <string name=\"textTipListSwipeToDelete\">Scorri gli elementi della lista per rimuoverli.</string>\n  <string name=\"textGenreAction\">Azione</string>\n  <string name=\"textGenreAdventure\">Avventura</string>\n  <string name=\"textGenreAnimation\">Animazione</string>\n  <string name=\"textGenreAnime\">Anime</string>\n  <string name=\"textGenreComedy\">Commedia</string>\n  <string name=\"textGenreCrime\">Giallo</string>\n  <string name=\"textGenreDocumentary\">Documentario</string>\n  <string name=\"textGenreDrama\">Drammatico</string>\n  <string name=\"textGenreFantasy\">Fantasy</string>\n  <string name=\"textGenreHistory\">Storia</string>\n  <string name=\"textGenreHorror\">Horror</string>\n  <string name=\"textGenreScienceFiction\">Fantascienza</string>\n  <string name=\"textGenreThriller\">Thriller</string>\n  <string name=\"textGenreWar\">Guerra</string>\n  <string name=\"textGenreWestern\">Western</string>\n  <string name=\"textTraktSyncSchedule\">Programma</string>\n  <string name=\"textTraktSyncOptionOff\">Disattivo</string>\n  <string name=\"textTraktSyncOption1Hour\">Ogni ora</string>\n  <string name=\"textTraktSyncOption3Hours\">Ogni 3 ore</string>\n  <string name=\"textTraktSyncOption6Hours\">Ogni 6 ore</string>\n  <string name=\"textTraktSyncOptionDaily\">Ogni giorno</string>\n  <string name=\"textTraktSyncOption3Day\">Ogni 3 giorni</string>\n  <string name=\"textTraktSyncOptionWeekly\">Ogni settimana</string>\n  <string name=\"textTraktSyncOptionOffMessage\">La sincronizzazione non verrà eseguita in automatico.</string>\n  <string name=\"textTraktSyncOptionConfirmMessage\">La sincronizzazione automatica è stata programmata.</string>\n  <string name=\"textTraktSyncOptionHourButton\">Programmata per ogni ora</string>\n  <string name=\"textTraktSyncOption3HoursButton\">Programmata per ogni 3 ore</string>\n  <string name=\"textTraktSyncOption6HoursButton\">Programmata per ogni 6 ore</string>\n  <string name=\"textTraktSyncOptionDailyButton\">Programmata per ogni giorno</string>\n  <string name=\"textTraktSyncOption3DayButton\">Programmata per ogni 3 giorni</string>\n  <string name=\"textTraktSyncOptionWeeklyButton\">Programmata per ogni settimana</string>\n  <string name=\"textShowStatusReturning\">Show rinnovato</string>\n  <string name=\"textShowStatusUpcoming\">In arrivo</string>\n  <string name=\"textShowStatusInProduction\">In produzione</string>\n  <string name=\"textShowStatusPlanned\">Pianificata</string>\n  <string name=\"textShowStatusCanceled\">Cancellata</string>\n  <string name=\"textShowStatusEnded\">Conclusa</string>\n  <string name=\"textMovieStatusReleased\">Trasmessa</string>\n  <string name=\"textMovieStatusInProduction\">In Produzione</string>\n  <string name=\"textMovieStatusPostProduction\">Non in produzione</string>\n  <string name=\"textMovieStatusPlanned\">Pianificata</string>\n  <string name=\"textMovieStatusCanceled\">Cancellata</string>\n  <string name=\"textMovieStatusRumored\">Si vocifera</string>\n  <string name=\"textStreamingStream\">Streaming</string>\n  <string name=\"textStreamingAds\">Pubblicità</string>\n  <string name=\"textStreamingBuy\">Acquisto</string>\n  <string name=\"textStreamingRent\">Noleggio</string>\n  <string name=\"textStreamingFree\">Gratuito</string>\n</resources>\n"
  },
  {
    "path": "ui-model/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortRank\">Ranga</string>\n  <string name=\"textSortName\">Tytuł</string>\n  <string name=\"textSortNewest\">Najnowsze</string>\n  <string name=\"textSortRated\">Docenione</string>\n  <string name=\"textSortRatedUser\">Moja Ocena</string>\n  <string name=\"textSortDateAdded\">Ostatnio Dodane</string>\n  <string name=\"textSortDateUpdated\">Ostatnio aktualizowane</string>\n  <string name=\"textSortRecentlyWatched\">Ostatnio Obejrzane</string>\n  <string name=\"textSortEpisodesLeft\">Pozostałe Odcinki</string>\n  <string name=\"textHeaderRecentlyAdded\">Ostatnio Dodane</string>\n  <string name=\"textHeaderWatching\">Oglądane</string>\n  <string name=\"textHeaderFinished\">Skończone</string>\n  <string name=\"textHeaderReturning\">Powracające</string>\n  <string name=\"textHeaderAll\">Wszystkie</string>\n  <string name=\"textSettingsShowsNotificationsWhenTitle\">Czas Powiadomień</string>\n  <string name=\"textSettingsShowsNotificationsWhenSummary\">Kiedy chciałbyś otrzymać powiadomienie o odcinku?</string>\n  <string name=\"textSettingsShowsNotificationsWhenAvailable\">Jak Najszybciej</string>\n  <string name=\"textSettingsShowsNotificationsWhen1Hour\">1 Godzinę Po</string>\n  <string name=\"textSettingsShowsNotificationsWhen3Hours\">3 Godziny Po</string>\n  <string name=\"textSettingsShowsNotificationsWhen6Hours\">6 Godzin Po</string>\n  <string name=\"textSettingsShowsNotificationsWhen1HourBefore\">1 Godzinę Przed</string>\n  <string name=\"textSettingsShowsNotificationsWhen3HoursBefore\">3 Godziny Przed</string>\n  <string name=\"textSettingsShowsNotificationsWhen6HoursBefore\">6 Godzin Przed</string>\n  <string name=\"textSettingsShowsNotificationsWhen12HoursBefore\">12 Godzin Przed</string>\n  <string name=\"textSettingsShowsNotificationsWhen12Hours\">12 Godzin Po</string>\n  <string name=\"textSettingsShowsNotificationsWhenNextDay\">1 Dzień Po</string>\n  <string name=\"textTipDiscover\">Kliknij \\'Odkrywaj\\' dwa razy aby szybko przejść do wyszukiwania.</string>\n  <string name=\"textTipMyShows\">Kliknij \\'Kolekcja\\' dwa razy aby szybko przełączyć się między sekcjami.</string>\n  <string name=\"textTipShowDetailsGallery\">Kliknij obrazek aby otworzyć galerię.</string>\n  <string name=\"textTipWatchlistPinItem\">Kliknij i przytrzymaj element aby przypiąć go u góry listy.</string>\n  <string name=\"textTipBottomModeMenu\">Kliknij i przesuń dolne menu w lewo lub prawo aby szybko przełączyć się między serialami i filmami.</string>\n  <string name=\"textTipListSwipeToDelete\">Przeciągnij elementy listy aby je usunąć.</string>\n  <string name=\"textGenreAction\">Akcja</string>\n  <string name=\"textGenreAdventure\">Przygoda</string>\n  <string name=\"textGenreAnimation\">Animacja</string>\n  <string name=\"textGenreAnime\">Anime</string>\n  <string name=\"textGenreComedy\">Komedia</string>\n  <string name=\"textGenreCrime\">Kryminał</string>\n  <string name=\"textGenreDocumentary\">Dokument</string>\n  <string name=\"textGenreDrama\">Dramat</string>\n  <string name=\"textGenreFantasy\">Fantasy</string>\n  <string name=\"textGenreHistory\">Historia</string>\n  <string name=\"textGenreHorror\">Horror</string>\n  <string name=\"textGenreScienceFiction\">Science-Fiction</string>\n  <string name=\"textGenreThriller\">Thriller</string>\n  <string name=\"textGenreWar\">Wojna</string>\n  <string name=\"textGenreWestern\">Western</string>\n  <string name=\"textTraktSyncSchedule\">Zaplanuj</string>\n  <string name=\"textTraktSyncOptionOff\">Wyłączone</string>\n  <string name=\"textTraktSyncOption1Hour\">Co Godzinę</string>\n  <string name=\"textTraktSyncOption3Hours\">Co 3 Godziny</string>\n  <string name=\"textTraktSyncOption6Hours\">Co 6 Godzin</string>\n  <string name=\"textTraktSyncOptionDaily\">Każdego Dnia</string>\n  <string name=\"textTraktSyncOption3Day\">Co 3 Dni</string>\n  <string name=\"textTraktSyncOptionWeekly\">Każdego Tygodnia</string>\n  <string name=\"textTraktSyncOptionOffMessage\">Synchronizacja automatyczna wyłączona.</string>\n  <string name=\"textTraktSyncOptionConfirmMessage\">Synchronizacja automatyczna włączona.</string>\n  <string name=\"textTraktSyncOptionHourButton\">Zaplanowano co godzinę</string>\n  <string name=\"textTraktSyncOption3HoursButton\">Zaplanowano co 3 godziny</string>\n  <string name=\"textTraktSyncOption6HoursButton\">Zaplanowano co 6 godzin</string>\n  <string name=\"textTraktSyncOptionDailyButton\">Zaplanowano raz każdego dnia</string>\n  <string name=\"textTraktSyncOption3DayButton\">Zaplanowano co 3 dni</string>\n  <string name=\"textTraktSyncOptionWeeklyButton\">Zaplanowano raz każdego tygodnia</string>\n  <string name=\"textShowStatusReturning\">Powracająca Seria</string>\n  <string name=\"textShowStatusUpcoming\">Nadchodzące</string>\n  <string name=\"textShowStatusInProduction\">W Produkcji</string>\n  <string name=\"textShowStatusPlanned\">Zaplanowany</string>\n  <string name=\"textShowStatusCanceled\">Anulowany</string>\n  <string name=\"textShowStatusEnded\">Zakończony</string>\n  <string name=\"textMovieStatusReleased\">Dostępny</string>\n  <string name=\"textMovieStatusInProduction\">W Produkcji</string>\n  <string name=\"textMovieStatusPostProduction\">Post Produkcja</string>\n  <string name=\"textMovieStatusPlanned\">Planowany</string>\n  <string name=\"textMovieStatusCanceled\">Anulowany</string>\n  <string name=\"textMovieStatusRumored\">Spodziewany</string>\n  <string name=\"textStreamingStream\">Stream</string>\n  <string name=\"textStreamingAds\">Reklamy</string>\n  <string name=\"textStreamingBuy\">Kup</string>\n  <string name=\"textStreamingRent\">Wypożycz</string>\n  <string name=\"textStreamingFree\">Darmowy</string>\n</resources>\n"
  },
  {
    "path": "ui-model/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortRank\">Rank</string>\n  <string name=\"textSortName\">Título</string>\n  <string name=\"textSortNewest\">Mais recentes</string>\n  <string name=\"textSortRated\">Melhor avaliação</string>\n  <string name=\"textSortRatedUser\">Minha avaliação</string>\n  <string name=\"textSortDateAdded\">Adicionado recentemente</string>\n  <string name=\"textSortDateUpdated\">Atualizadas recentemente</string>\n  <string name=\"textSortRecentlyWatched\">Assistidos recentemente</string>\n  <string name=\"textSortEpisodesLeft\">Episódios restantes</string>\n  <string name=\"textHeaderRecentlyAdded\">Adicionado recentemente</string>\n  <string name=\"textHeaderWatching\">Assistindo</string>\n  <string name=\"textHeaderFinished\">Finalizado</string>\n  <string name=\"textHeaderReturning\">Aguardando lançamento</string>\n  <string name=\"textHeaderAll\">Todos</string>\n  <string name=\"textSettingsShowsNotificationsWhenTitle\">Quando notificar</string>\n  <string name=\"textSettingsShowsNotificationsWhenSummary\">Quando você gostaria de receber avisos de episódios.</string>\n  <string name=\"textSettingsShowsNotificationsWhenAvailable\">Quando disponível</string>\n  <string name=\"textSettingsShowsNotificationsWhen1Hour\">1 hora depois</string>\n  <string name=\"textSettingsShowsNotificationsWhen3Hours\">3 horas depois</string>\n  <string name=\"textSettingsShowsNotificationsWhen6Hours\">6 horas depois</string>\n  <string name=\"textSettingsShowsNotificationsWhen1HourBefore\">1 hora antes</string>\n  <string name=\"textSettingsShowsNotificationsWhen3HoursBefore\">3 horas antes</string>\n  <string name=\"textSettingsShowsNotificationsWhen6HoursBefore\">6 horas antes</string>\n  <string name=\"textSettingsShowsNotificationsWhen12HoursBefore\">12 horas antes</string>\n  <string name=\"textSettingsShowsNotificationsWhen12Hours\">12 horas depois</string>\n  <string name=\"textSettingsShowsNotificationsWhenNextDay\">1 dia depois</string>\n  <string name=\"textTipDiscover\">Toque duas vezes no botão do menu \\'Explorar\\' para abrir rapidamente a tela de pesquisa.</string>\n  <string name=\"textTipMyShows\">Toque duas vezes no botão de menu \\'Coleção\\' para alternar rapidamente entre as abas.</string>\n  <string name=\"textTipShowDetailsGallery\">Toque na imagem para abrir a galeria.</string>\n  <string name=\"textTipWatchlistPinItem\">Toque e segure o item da lista para fixá-lo no topo.</string>\n  <string name=\"textTipBottomModeMenu\">Deslize para a esquerda ou para a direita para alterar o modo rapidamente.</string>\n  <string name=\"textTipListSwipeToDelete\">Passe os itens para removê-los da lista.</string>\n  <string name=\"textGenreAction\">Ação</string>\n  <string name=\"textGenreAdventure\">Aventura</string>\n  <string name=\"textGenreAnimation\">Animação</string>\n  <string name=\"textGenreAnime\">Anime</string>\n  <string name=\"textGenreComedy\">Comédia</string>\n  <string name=\"textGenreCrime\">Crime</string>\n  <string name=\"textGenreDocumentary\">Documentário</string>\n  <string name=\"textGenreDrama\">Drama</string>\n  <string name=\"textGenreFantasy\">Fantasia</string>\n  <string name=\"textGenreHistory\">Histórico</string>\n  <string name=\"textGenreHorror\">Terror</string>\n  <string name=\"textGenreScienceFiction\">Ficção científica</string>\n  <string name=\"textGenreThriller\">Suspense</string>\n  <string name=\"textGenreWar\">Guerra</string>\n  <string name=\"textGenreWestern\">Faroeste</string>\n  <string name=\"textTraktSyncSchedule\">Programação</string>\n  <string name=\"textTraktSyncOptionOff\">Desligado</string>\n  <string name=\"textTraktSyncOption1Hour\">A cada hora</string>\n  <string name=\"textTraktSyncOption3Hours\">A cada 3 horas</string>\n  <string name=\"textTraktSyncOption6Hours\">A cada 6 horas</string>\n  <string name=\"textTraktSyncOptionDaily\">Todos os dias</string>\n  <string name=\"textTraktSyncOption3Day\">A cada 3 dias</string>\n  <string name=\"textTraktSyncOptionWeekly\">Toda semana</string>\n  <string name=\"textTraktSyncOptionOffMessage\">Sincronização não será executada automaticamente.</string>\n  <string name=\"textTraktSyncOptionConfirmMessage\">A sincronização automática foi programada.</string>\n  <string name=\"textTraktSyncOptionHourButton\">Programado para ser executado a cada hora</string>\n  <string name=\"textTraktSyncOption3HoursButton\">Programado para ser executado a cada 3 horas</string>\n  <string name=\"textTraktSyncOption6HoursButton\">Programado para ser executado a cada 6 horas</string>\n  <string name=\"textTraktSyncOptionDailyButton\">Programado para ser executado todos os dias</string>\n  <string name=\"textTraktSyncOption3DayButton\">Programado para ser executado a cada 3 dias</string>\n  <string name=\"textTraktSyncOptionWeeklyButton\">Programado para ser executado todas as semanas</string>\n  <string name=\"textShowStatusReturning\">Renovada</string>\n  <string name=\"textShowStatusUpcoming\">Em breve</string>\n  <string name=\"textShowStatusInProduction\">Em produção</string>\n  <string name=\"textShowStatusPlanned\">Planejada</string>\n  <string name=\"textShowStatusCanceled\">Cancelada</string>\n  <string name=\"textShowStatusEnded\">Finalizada</string>\n  <string name=\"textMovieStatusReleased\">Lançado</string>\n  <string name=\"textMovieStatusInProduction\">Em produção</string>\n  <string name=\"textMovieStatusPostProduction\">Em pós produção</string>\n  <string name=\"textMovieStatusPlanned\">Planejada</string>\n  <string name=\"textMovieStatusCanceled\">Cancelada</string>\n  <string name=\"textMovieStatusRumored\">Apenas rumor</string>\n  <string name=\"textStreamingStream\">Stream</string>\n  <string name=\"textStreamingAds\">Anúncios</string>\n  <string name=\"textStreamingBuy\">Comprar</string>\n  <string name=\"textStreamingRent\">Aluguel</string>\n  <string name=\"textStreamingFree\">Gratuito</string>\n</resources>\n"
  },
  {
    "path": "ui-model/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortRank\">Ранг</string>\n  <string name=\"textSortName\">Название</string>\n  <string name=\"textSortNewest\">Новизне</string>\n  <string name=\"textSortRated\">Оценкам</string>\n  <string name=\"textSortRatedUser\">Моя оценка</string>\n  <string name=\"textSortDateAdded\">Недавно добавленные</string>\n  <string name=\"textSortDateUpdated\">Недавно обновленные</string>\n  <string name=\"textSortRecentlyWatched\">Недавно просмотренные</string>\n  <string name=\"textSortEpisodesLeft\">Оставшиеся эпизоды</string>\n  <string name=\"textHeaderRecentlyAdded\">Недавно добавленные</string>\n  <string name=\"textHeaderWatching\">Смотрю</string>\n  <string name=\"textHeaderFinished\">Завершенные</string>\n  <string name=\"textHeaderReturning\">Продолжающиеся &amp; Предстоящие</string>\n  <string name=\"textHeaderAll\">Все</string>\n  <string name=\"textSettingsShowsNotificationsWhenTitle\">Когда уведомлять</string>\n  <string name=\"textSettingsShowsNotificationsWhenSummary\">Когда вы хотели бы получать уведомления о эпизодах.</string>\n  <string name=\"textSettingsShowsNotificationsWhenAvailable\">Когда доступны</string>\n  <string name=\"textSettingsShowsNotificationsWhen1Hour\">Через 1 час</string>\n  <string name=\"textSettingsShowsNotificationsWhen3Hours\">Через 3 часа</string>\n  <string name=\"textSettingsShowsNotificationsWhen6Hours\">Через 6 часов</string>\n  <string name=\"textSettingsShowsNotificationsWhen1HourBefore\">За 1 час</string>\n  <string name=\"textSettingsShowsNotificationsWhen3HoursBefore\">За 3 часа</string>\n  <string name=\"textSettingsShowsNotificationsWhen6HoursBefore\">За 6 часов</string>\n  <string name=\"textSettingsShowsNotificationsWhen12HoursBefore\">За 12 часов</string>\n  <string name=\"textSettingsShowsNotificationsWhen12Hours\">Через 12 часов</string>\n  <string name=\"textSettingsShowsNotificationsWhenNextDay\">Через 1 день</string>\n  <string name=\"textTipDiscover\">Чтобы быстро открыть окно поиска, дважды нажмите кнопку \\\"Открытия\\\".</string>\n  <string name=\"textTipMyShows\">Чтобы быстро переключиться между вкладками, дважды нажмите на кнопку \\\"Коллекция\\\".</string>\n  <string name=\"textTipShowDetailsGallery\">Нажмите на изображение, чтобы открыть галерею.</string>\n  <string name=\"textTipWatchlistPinItem\">Нажмите и удерживайте элемент списка, чтобы закрепить его вверху.</string>\n  <string name=\"textTipBottomModeMenu\">Проведите по нижнему меню влево или вправо, чтобы быстро изменить режим.</string>\n  <string name=\"textTipListSwipeToDelete\">Смахните элементы списка, чтобы удалить их из списка.</string>\n  <string name=\"textGenreAction\">Экшн</string>\n  <string name=\"textGenreAdventure\">Приключения</string>\n  <string name=\"textGenreAnimation\">Анимация</string>\n  <string name=\"textGenreAnime\">Аниме</string>\n  <string name=\"textGenreComedy\">Комедия</string>\n  <string name=\"textGenreCrime\">Криминал</string>\n  <string name=\"textGenreDocumentary\">Документальный фильм</string>\n  <string name=\"textGenreDrama\">Драма</string>\n  <string name=\"textGenreFantasy\">Фэнтези</string>\n  <string name=\"textGenreHistory\">История</string>\n  <string name=\"textGenreHorror\">Ужасы</string>\n  <string name=\"textGenreScienceFiction\">Научная фантастика</string>\n  <string name=\"textGenreThriller\">Триллер</string>\n  <string name=\"textGenreWar\">Военный</string>\n  <string name=\"textGenreWestern\">Вестерн</string>\n  <string name=\"textTraktSyncSchedule\">Расписание</string>\n  <string name=\"textTraktSyncOptionOff\">Выкл</string>\n  <string name=\"textTraktSyncOption1Hour\">Каждый час</string>\n  <string name=\"textTraktSyncOption3Hours\">Каждые 3 часа</string>\n  <string name=\"textTraktSyncOption6Hours\">Каждые 6 часов</string>\n  <string name=\"textTraktSyncOptionDaily\">Каждый день</string>\n  <string name=\"textTraktSyncOption3Day\">Каждые 3 дня</string>\n  <string name=\"textTraktSyncOptionWeekly\">Каждую неделю</string>\n  <string name=\"textTraktSyncOptionOffMessage\">Синхронизация не будет запускаться автоматически.</string>\n  <string name=\"textTraktSyncOptionConfirmMessage\">Автоматическая синхронизация запланирована.</string>\n  <string name=\"textTraktSyncOptionHourButton\">Запланировано запускать каждый час</string>\n  <string name=\"textTraktSyncOption3HoursButton\">Запланировано запускать каждые 3 часа</string>\n  <string name=\"textTraktSyncOption6HoursButton\">Запланировано запускать каждые 6 часов</string>\n  <string name=\"textTraktSyncOptionDailyButton\">Запланировано запускать ежедневно</string>\n  <string name=\"textTraktSyncOption3DayButton\">Запланировано запускать каждые 3 дня</string>\n  <string name=\"textTraktSyncOptionWeeklyButton\">Запланировано запускать каждую неделю</string>\n  <string name=\"textShowStatusReturning\">Продолжающиеся сериалы</string>\n  <string name=\"textShowStatusUpcoming\">Предстоящие</string>\n  <string name=\"textShowStatusInProduction\">В производстве</string>\n  <string name=\"textShowStatusPlanned\">Запланировано</string>\n  <string name=\"textShowStatusCanceled\">Отменен</string>\n  <string name=\"textShowStatusEnded\">Завершен</string>\n  <string name=\"textMovieStatusReleased\">Выпущен</string>\n  <string name=\"textMovieStatusInProduction\">В производстве</string>\n  <string name=\"textMovieStatusPostProduction\">Пост-продакшен</string>\n  <string name=\"textMovieStatusPlanned\">Запланировано</string>\n  <string name=\"textMovieStatusCanceled\">Отменен</string>\n  <string name=\"textMovieStatusRumored\">Ходят слухи</string>\n  <string name=\"textStreamingStream\">Стриминг</string>\n  <string name=\"textStreamingAds\">Реклама</string>\n  <string name=\"textStreamingBuy\">Покупка</string>\n  <string name=\"textStreamingRent\">Аренда</string>\n  <string name=\"textStreamingFree\">Бесплатно</string>\n</resources>\n"
  },
  {
    "path": "ui-model/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortRank\">Derece</string>\n  <string name=\"textSortName\">Başlık</string>\n  <string name=\"textSortNewest\">En Yeni</string>\n  <string name=\"textSortRated\">En Yüksek Puan Alan</string>\n  <string name=\"textSortRatedUser\">Benim Puanlamam</string>\n  <string name=\"textSortDateAdded\">Son Eklenen</string>\n  <string name=\"textSortDateUpdated\">Son Güncellenen</string>\n  <string name=\"textSortRecentlyWatched\">Son İzlenen</string>\n  <string name=\"textSortEpisodesLeft\">Kalan Bölümler</string>\n  <string name=\"textHeaderRecentlyAdded\">Son Eklenen</string>\n  <string name=\"textHeaderWatching\">İzlemeye Devam Edilen</string>\n  <string name=\"textHeaderFinished\">Tamamlanan</string>\n  <string name=\"textHeaderReturning\">Yakında Devam Edecek</string>\n  <string name=\"textHeaderAll\">Tümü</string>\n  <string name=\"textSettingsShowsNotificationsWhenTitle\">Bildirim Zamanı</string>\n  <string name=\"textSettingsShowsNotificationsWhenSummary\">Bölüm duyurularını ne zaman almak istersiniz?</string>\n  <string name=\"textSettingsShowsNotificationsWhenAvailable\">Yayınlandığında</string>\n  <string name=\"textSettingsShowsNotificationsWhen1Hour\">1 Saat Sonra</string>\n  <string name=\"textSettingsShowsNotificationsWhen3Hours\">3 Saat Sonra</string>\n  <string name=\"textSettingsShowsNotificationsWhen6Hours\">6 Saat Sonra</string>\n  <string name=\"textSettingsShowsNotificationsWhen1HourBefore\">1 Saat Önce</string>\n  <string name=\"textSettingsShowsNotificationsWhen3HoursBefore\">3 Saat Önce</string>\n  <string name=\"textSettingsShowsNotificationsWhen6HoursBefore\">6 Saat Önce</string>\n  <string name=\"textSettingsShowsNotificationsWhen12HoursBefore\">12 Saat Önce</string>\n  <string name=\"textSettingsShowsNotificationsWhen12Hours\">12 Saat Sonra</string>\n  <string name=\"textSettingsShowsNotificationsWhenNextDay\">1 Gün Sonra</string>\n  <string name=\"textTipDiscover\">Arama ekranını hızlı bir şekilde açmak için \\'Keşfet\\' menü düğmesine iki kez dokunun.</string>\n  <string name=\"textTipMyShows\">Sekmeler arasında hızlıca geçiş yapmak için \\'Koleksiyon\\' menü düğmesine iki kez dokunun.</string>\n  <string name=\"textTipShowDetailsGallery\">Galeriyi açmak için resme dokunun.</string>\n  <string name=\"textTipWatchlistPinItem\">En üste sabitlemek için liste ögesine dokunun ve basılı tutun.</string>\n  <string name=\"textTipBottomModeMenu\">Modu hızlı bir şekilde değiştirmek için alt menüyü sola veya sağa kaydırın.</string>\n  <string name=\"textTipListSwipeToDelete\">Listeden çıkarmak için liste ögelerini kaydırın.</string>\n  <string name=\"textGenreAction\">Aksiyon</string>\n  <string name=\"textGenreAdventure\">Macera</string>\n  <string name=\"textGenreAnimation\">Animasyon</string>\n  <string name=\"textGenreAnime\">Çizgi Film</string>\n  <string name=\"textGenreComedy\">Komedi</string>\n  <string name=\"textGenreCrime\">Polisiye</string>\n  <string name=\"textGenreDocumentary\">Belgesel</string>\n  <string name=\"textGenreDrama\">Dram</string>\n  <string name=\"textGenreFantasy\">Fantastik</string>\n  <string name=\"textGenreHistory\">Tarih</string>\n  <string name=\"textGenreHorror\">Korku</string>\n  <string name=\"textGenreScienceFiction\">Bilim Kurgu</string>\n  <string name=\"textGenreThriller\">Gerilim</string>\n  <string name=\"textGenreWar\">Savaş</string>\n  <string name=\"textGenreWestern\">Western</string>\n  <string name=\"textTraktSyncSchedule\">Zamanla</string>\n  <string name=\"textTraktSyncOptionOff\">Kapalı</string>\n  <string name=\"textTraktSyncOption1Hour\">Saatte Bir</string>\n  <string name=\"textTraktSyncOption3Hours\">Her 3 Saatte Bir</string>\n  <string name=\"textTraktSyncOption6Hours\">Her 6 Saatte Bir</string>\n  <string name=\"textTraktSyncOptionDaily\">Her Gün</string>\n  <string name=\"textTraktSyncOption3Day\">Her 3 Günde Bir</string>\n  <string name=\"textTraktSyncOptionWeekly\">Her Hafta</string>\n  <string name=\"textTraktSyncOptionOffMessage\">Eşitleme otomatik olarak çalışmayacaktır.</string>\n  <string name=\"textTraktSyncOptionConfirmMessage\">Otomatik eşitleme zamanlandı.</string>\n  <string name=\"textTraktSyncOptionHourButton\">Her saat çalışacak şekilde zamanlandı</string>\n  <string name=\"textTraktSyncOption3HoursButton\">Her üç saatte bir çalışacak şekilde zamanlandı</string>\n  <string name=\"textTraktSyncOption6HoursButton\">Her altı saatte bir çalışacak şekilde zamanlandı</string>\n  <string name=\"textTraktSyncOptionDailyButton\">Her gün çalışacak şekilde zamanlandı</string>\n  <string name=\"textTraktSyncOption3DayButton\">Her üç güne bir çalışacak şekilde zamanlandı</string>\n  <string name=\"textTraktSyncOptionWeeklyButton\">Her hafta çalışacak şekilde zamanlandı</string>\n  <string name=\"textShowStatusReturning\">Devam Ediyor</string>\n  <string name=\"textShowStatusUpcoming\">Yakında</string>\n  <string name=\"textShowStatusInProduction\">Yapım Aşamasında</string>\n  <string name=\"textShowStatusPlanned\">Planlanan</string>\n  <string name=\"textShowStatusCanceled\">İptal Edildi</string>\n  <string name=\"textShowStatusEnded\">Sona Erdi</string>\n  <string name=\"textMovieStatusReleased\">Yayınlandı</string>\n  <string name=\"textMovieStatusInProduction\">Yapım Aşamasında</string>\n  <string name=\"textMovieStatusPostProduction\">Yapım Sonrası</string>\n  <string name=\"textMovieStatusPlanned\">Planlanan</string>\n  <string name=\"textMovieStatusCanceled\">İptal Edildi</string>\n  <string name=\"textMovieStatusRumored\">Söylenti</string>\n  <string name=\"textStreamingStream\">Yayın</string>\n  <string name=\"textStreamingAds\">Reklamlı</string>\n  <string name=\"textStreamingBuy\">Satın Al</string>\n  <string name=\"textStreamingRent\">Kirala</string>\n  <string name=\"textStreamingFree\">Ücretsiz</string>\n</resources>\n"
  },
  {
    "path": "ui-model/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortRank\">За рангом</string>\n  <string name=\"textSortName\">За назвою</string>\n  <string name=\"textSortNewest\">За датою випуску</string>\n  <string name=\"textSortRated\">За оцінкою</string>\n  <string name=\"textSortRatedUser\">За моєю оцінкою</string>\n  <string name=\"textSortDateAdded\">За датою додавання</string>\n  <string name=\"textSortDateUpdated\">За датою оновлення</string>\n  <string name=\"textSortRecentlyWatched\">За датою перегляду</string>\n  <string name=\"textSortEpisodesLeft\">За кількістю непереглянутих серій</string>\n  <string name=\"textHeaderRecentlyAdded\">За датою додавання</string>\n  <string name=\"textHeaderWatching\">Дивлюся</string>\n  <string name=\"textHeaderFinished\">Завершено</string>\n  <string name=\"textHeaderReturning\">Продовжується &amp; Незабаром</string>\n  <string name=\"textHeaderAll\">Всі</string>\n  <string name=\"textSettingsShowsNotificationsWhenTitle\">Коли сповіщати</string>\n  <string name=\"textSettingsShowsNotificationsWhenSummary\">Коли б ви хотіли отримувати оголошення про серії.</string>\n  <string name=\"textSettingsShowsNotificationsWhenAvailable\">Одразу, як буде доступно</string>\n  <string name=\"textSettingsShowsNotificationsWhen1Hour\">Через 1 годину</string>\n  <string name=\"textSettingsShowsNotificationsWhen3Hours\">Через 3 години</string>\n  <string name=\"textSettingsShowsNotificationsWhen6Hours\">Через 6 годин</string>\n  <string name=\"textSettingsShowsNotificationsWhen1HourBefore\">За 1 годину</string>\n  <string name=\"textSettingsShowsNotificationsWhen3HoursBefore\">За 3 години</string>\n  <string name=\"textSettingsShowsNotificationsWhen6HoursBefore\">За 6 годин</string>\n  <string name=\"textSettingsShowsNotificationsWhen12HoursBefore\">За 12 годин</string>\n  <string name=\"textSettingsShowsNotificationsWhen12Hours\">Через 12 годин</string>\n  <string name=\"textSettingsShowsNotificationsWhenNextDay\">Через 1 день</string>\n  <string name=\"textTipDiscover\">Двічі торкніться кнопки меню \\\"Огляд\\\", щоб швидко відкрити панель пошуку.</string>\n  <string name=\"textTipMyShows\">Двічі торкніться кнопки меню \\\"Колекція\\\", щоб швидко переходити між вкладками.</string>\n  <string name=\"textTipShowDetailsGallery\">Торкніться зображення для відкриття галереї.</string>\n  <string name=\"textTipWatchlistPinItem\">Торкніться та утримуйте предмет списку для закріплення його вгорі.</string>\n  <string name=\"textTipBottomModeMenu\">Проведіть по нижньому меню ліворуч або праворуч, щоб швидко змінити режим.</string>\n  <string name=\"textTipListSwipeToDelete\">Проводьте по елементах, щоб видаляти їх зі списку.</string>\n  <string name=\"textGenreAction\">Екшн</string>\n  <string name=\"textGenreAdventure\">Пригоди</string>\n  <string name=\"textGenreAnimation\">Анімація</string>\n  <string name=\"textGenreAnime\">Аніме</string>\n  <string name=\"textGenreComedy\">Комедія</string>\n  <string name=\"textGenreCrime\">Кримінал</string>\n  <string name=\"textGenreDocumentary\">Документальний</string>\n  <string name=\"textGenreDrama\">Драма</string>\n  <string name=\"textGenreFantasy\">Фентезі</string>\n  <string name=\"textGenreHistory\">Історія</string>\n  <string name=\"textGenreHorror\">Жахи</string>\n  <string name=\"textGenreScienceFiction\">Наукова фантастика</string>\n  <string name=\"textGenreThriller\">Трилер</string>\n  <string name=\"textGenreWar\">Військовий</string>\n  <string name=\"textGenreWestern\">Вестерн</string>\n  <string name=\"textTraktSyncSchedule\">Розклад</string>\n  <string name=\"textTraktSyncOptionOff\">Вимкнено</string>\n  <string name=\"textTraktSyncOption1Hour\">Щогодини</string>\n  <string name=\"textTraktSyncOption3Hours\">Кожні 3 години</string>\n  <string name=\"textTraktSyncOption6Hours\">Кожні 6 годин</string>\n  <string name=\"textTraktSyncOptionDaily\">Щодня</string>\n  <string name=\"textTraktSyncOption3Day\">Кожні 3 дні</string>\n  <string name=\"textTraktSyncOptionWeekly\">Щотижня</string>\n  <string name=\"textTraktSyncOptionOffMessage\">Синхронізація не буде запущена автоматично.</string>\n  <string name=\"textTraktSyncOptionConfirmMessage\">Автоматична синхронізація була запланована.</string>\n  <string name=\"textTraktSyncOptionHourButton\">Заплановано запускати кожну годину</string>\n  <string name=\"textTraktSyncOption3HoursButton\">Заплановано запускати кожні 3 години</string>\n  <string name=\"textTraktSyncOption6HoursButton\">Заплановано запускати кожні 6 годин</string>\n  <string name=\"textTraktSyncOptionDailyButton\">Заплановано запускати кожен день</string>\n  <string name=\"textTraktSyncOption3DayButton\">Заплановано запускати кожні 3 дні</string>\n  <string name=\"textTraktSyncOptionWeeklyButton\">Заплановано запускати кожен тиждень</string>\n  <string name=\"textShowStatusReturning\">Продовжується</string>\n  <string name=\"textShowStatusUpcoming\">Незабаром</string>\n  <string name=\"textShowStatusInProduction\">У виробництві</string>\n  <string name=\"textShowStatusPlanned\">Заплановано</string>\n  <string name=\"textShowStatusCanceled\">Скасовано</string>\n  <string name=\"textShowStatusEnded\">Завершено</string>\n  <string name=\"textMovieStatusReleased\">Випущено</string>\n  <string name=\"textMovieStatusInProduction\">У виробництві</string>\n  <string name=\"textMovieStatusPostProduction\">Постпродукція</string>\n  <string name=\"textMovieStatusPlanned\">Заплановано</string>\n  <string name=\"textMovieStatusCanceled\">Скасовано</string>\n  <string name=\"textMovieStatusRumored\">За чутками</string>\n  <string name=\"textStreamingStream\">Стрімінг</string>\n  <string name=\"textStreamingAds\">Реклама</string>\n  <string name=\"textStreamingBuy\">Придбати</string>\n  <string name=\"textStreamingRent\">Оренда</string>\n  <string name=\"textStreamingFree\">Безплатно</string>\n</resources>\n"
  },
  {
    "path": "ui-model/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortRank\">Thứ hạng</string>\n  <string name=\"textSortName\">Tiêu đề</string>\n  <string name=\"textSortNewest\">Gần đây nhất</string>\n  <string name=\"textSortRated\">Được xếp hạng cao nhất</string>\n  <string name=\"textSortRatedUser\">Đánh giá của tôi</string>\n  <string name=\"textSortDateAdded\">Đã thêm gần đây</string>\n  <string name=\"textSortDateUpdated\">Đã cập nhật gần đây</string>\n  <string name=\"textSortRecentlyWatched\">Đã xem gần đây</string>\n  <string name=\"textSortEpisodesLeft\">Các tập còn lại</string>\n\n  <string name=\"textHeaderRecentlyAdded\">Đã thêm gần đây</string>\n  <string name=\"textHeaderWatching\">Đang xem</string>\n  <string name=\"textHeaderFinished\">Đã hoàn tất</string>\n  <string name=\"textHeaderReturning\">Trở lại &amp; Sắp tới</string>\n  <string name=\"textHeaderAll\">Tất cả</string>\n\n  <string name=\"textSettingsShowsNotificationsWhenTitle\">Khi nào cần thông báo</string>\n  <string name=\"textSettingsShowsNotificationsWhenSummary\">Khi nào bạn muốn nhận thông báo về các tập.</string>\n  <string name=\"textSettingsShowsNotificationsWhenAvailable\">Khi có sẵn</string>\n  <string name=\"textSettingsShowsNotificationsWhen1Hour\">1 giờ sau đó</string>\n  <string name=\"textSettingsShowsNotificationsWhen3Hours\">3 giờ sau đó</string>\n  <string name=\"textSettingsShowsNotificationsWhen6Hours\">6 giờ sau đó</string>\n  <string name=\"textSettingsShowsNotificationsWhen1HourBefore\">1 giờ trước đó</string>\n  <string name=\"textSettingsShowsNotificationsWhen3HoursBefore\">3 giờ trước đó</string>\n  <string name=\"textSettingsShowsNotificationsWhen6HoursBefore\">6 giờ trước đó</string>\n  <string name=\"textSettingsShowsNotificationsWhen12HoursBefore\">12 giờ trước đó</string>\n  <string name=\"textSettingsShowsNotificationsWhen12Hours\">12 giờ sau đó</string>\n  <string name=\"textSettingsShowsNotificationsWhenNextDay\">1 ngày sau đó</string>\n\n  <string name=\"textTipDiscover\">Nhấn đúp vào nút menu \\'Khám phá\\' để mở nhanh màn hình tìm kiếm.</string>\n  <string name=\"textTipMyShows\">Nhấn đúp vào nút menu \\'Bộ sưu tập\\' để chuyển nhanh giữa các tab.</string>\n  <string name=\"textTipShowDetailsGallery\">Nhấn vào hình ảnh để mở thư viện.</string>\n  <string name=\"textTipWatchlistPinItem\">Nhấn và giữ mục danh sách để ghim mục đó lên đầu.</string>\n  <string name=\"textTipBottomModeMenu\">Vuốt menu dưới cùng sang trái hoặc phải để nhanh chóng thay đổi chế độ.</string>\n  <string name=\"textTipListSwipeToDelete\">Vuốt các mục trong danh sách để xóa chúng khỏi danh sách.</string>\n\n  <string name=\"textGenreAction\">Hành động</string>\n  <string name=\"textGenreAdventure\">Phiêu lưu</string>\n  <string name=\"textGenreAnimation\">Hoạt hình</string>\n  <string name=\"textGenreAnime\">Anime</string>\n  <string name=\"textGenreComedy\">Hài kịch</string>\n  <string name=\"textGenreCrime\">Tội ác</string>\n  <string name=\"textGenreDocumentary\">Tài liệu</string>\n  <string name=\"textGenreDrama\">Kịch</string>\n  <string name=\"textGenreFantasy\">Ảo mộng</string>\n  <string name=\"textGenreHistory\">Lịch sử</string>\n  <string name=\"textGenreHorror\">Kinh dị</string>\n  <string name=\"textGenreScienceFiction\">Khoa học viễn tưởng</string>\n  <string name=\"textGenreThriller\">Hồi hộp</string>\n  <string name=\"textGenreWar\">Chiến tranh</string>\n  <string name=\"textGenreWestern\">Viễn Tây</string>\n\n  <string name=\"textTraktSyncSchedule\">Lịch trình</string>\n  <string name=\"textTraktSyncOptionOff\">Tắt</string>\n  <string name=\"textTraktSyncOption1Hour\">Mỗi giờ</string>\n  <string name=\"textTraktSyncOption3Hours\">Cứ sau 3 giờ</string>\n  <string name=\"textTraktSyncOption6Hours\">Cứ sau 6 giờ</string>\n  <string name=\"textTraktSyncOptionDaily\">Mỗi ngày</string>\n  <string name=\"textTraktSyncOption3Day\">Cứ sau 3 ngày</string>\n  <string name=\"textTraktSyncOptionWeekly\">Hàng tuần</string>\n  <string name=\"textTraktSyncOptionOffMessage\">Đồng bộ hóa sẽ không tự động chạy.</string>\n  <string name=\"textTraktSyncOptionConfirmMessage\">Đồng bộ hóa tự động đã được lên lịch.</string>\n\n  <string name=\"textTraktSyncOptionHourButton\">Dự kiến chạy mỗi giờ</string>\n  <string name=\"textTraktSyncOption3HoursButton\">Dự kiến chạy 3 giờ một lần</string>\n  <string name=\"textTraktSyncOption6HoursButton\">Dự kiến chạy 6 giờ một lần</string>\n  <string name=\"textTraktSyncOptionDailyButton\">Dự kiến chạy hàng ngày</string>\n  <string name=\"textTraktSyncOption3DayButton\">Dự kiến chạy 3 ngày một lần</string>\n  <string name=\"textTraktSyncOptionWeeklyButton\">Dự kiến chạy hàng tuần</string>\n\n  <string name=\"textShowStatusReturning\">Loạt phim tái chiếu</string>\n  <string name=\"textShowStatusUpcoming\">Sắp tới</string>\n  <string name=\"textShowStatusInProduction\">Đang sản xuất</string>\n  <string name=\"textShowStatusPlanned\">Đã lên kế hoạch</string>\n  <string name=\"textShowStatusCanceled\">Đã hủy</string>\n  <string name=\"textShowStatusEnded\">Đã kết thúc</string>\n  <string name=\"textShowStatusUnknown\" translatable=\"false\" />\n\n  <string name=\"textMovieStatusReleased\">Đã phát hành</string>\n  <string name=\"textMovieStatusInProduction\">Đang sản xuất</string>\n  <string name=\"textMovieStatusPostProduction\">Hậu kỳ</string>\n  <string name=\"textMovieStatusPlanned\">Đã lên kế hoạch</string>\n  <string name=\"textMovieStatusCanceled\">Đã hủy</string>\n  <string name=\"textMovieStatusRumored\">Có tin đồn</string>\n  <string name=\"textMovieStatusUnknown\" translatable=\"false\" />\n\n  <string name=\"textStreamingStream\">Truyền phát</string>\n  <string name=\"textStreamingAds\">Quảng cáo</string>\n  <string name=\"textStreamingBuy\">Mua</string>\n  <string name=\"textStreamingRent\">Thuê</string>\n  <string name=\"textStreamingFree\">Miễn phí</string>\n\n  <string name=\"tagQuickRating\" translatable=\"false\">Đánh giá nhanh</string>\n  <string name=\"tagCustomImages\" translatable=\"false\">Hình ảnh tùy chỉnh</string>\n  <string name=\"tagTheme\" translatable=\"false\">Chủ đề</string>\n  <string name=\"tagWidgetTransparency\" translatable=\"false\">Độ trong suốt của tiện ích</string>\n  <string name=\"tagNews\" translatable=\"false\">Tin tức</string>\n  <string name=\"tagViewsTypes\" translatable=\"false\">Các kiểu xem danh sách</string>\n</resources>\n"
  },
  {
    "path": "ui-model/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSortRank\">（我的）排行</string>\n  <string name=\"textSortName\">标题</string>\n  <string name=\"textSortNewest\">最新的</string>\n  <string name=\"textSortRated\">最受好评</string>\n  <string name=\"textSortRatedUser\">我的评分</string>\n  <string name=\"textSortDateAdded\">最近添加</string>\n  <string name=\"textSortDateUpdated\">最近更新</string>\n  <string name=\"textSortRecentlyWatched\">最近观看</string>\n  <string name=\"textSortEpisodesLeft\">剩余未看集数</string>\n  <string name=\"textHeaderRecentlyAdded\">最近添加</string>\n  <string name=\"textHeaderWatching\">正在观看</string>\n  <string name=\"textHeaderFinished\">已完结</string>\n  <string name=\"textHeaderReturning\">即将回归 &amp; 首播</string>\n  <string name=\"textHeaderAll\">全部</string>\n  <string name=\"textSettingsShowsNotificationsWhenTitle\">显示通知时间</string>\n  <string name=\"textSettingsShowsNotificationsWhenSummary\">您想要何时收到剧集更新通知。</string>\n  <string name=\"textSettingsShowsNotificationsWhenAvailable\">当播出/上映时</string>\n  <string name=\"textSettingsShowsNotificationsWhen1Hour\">1 小时后</string>\n  <string name=\"textSettingsShowsNotificationsWhen3Hours\">3 小时后</string>\n  <string name=\"textSettingsShowsNotificationsWhen6Hours\">6 小时后</string>\n  <string name=\"textSettingsShowsNotificationsWhen1HourBefore\">1 小时前</string>\n  <string name=\"textSettingsShowsNotificationsWhen3HoursBefore\">3 小时前</string>\n  <string name=\"textSettingsShowsNotificationsWhen6HoursBefore\">6 小时前</string>\n  <string name=\"textSettingsShowsNotificationsWhen12HoursBefore\">12 小时前</string>\n  <string name=\"textSettingsShowsNotificationsWhen12Hours\">12 小时后</string>\n  <string name=\"textSettingsShowsNotificationsWhenNextDay\">1 天后</string>\n  <string name=\"textTipDiscover\">双击「发现」菜单按钮可快速打开搜索页面。</string>\n  <string name=\"textTipMyShows\">双击「合集」菜单按钮可快速切换标签页。</string>\n  <string name=\"textTipShowDetailsGallery\">点击图像打开图库。</string>\n  <string name=\"textTipWatchlistPinItem\">点击并长按列表项可将其置顶。</string>\n  <string name=\"textTipBottomModeMenu\">在底部菜单区域向左或向右滑动可快速切换（剧集/电影）模式。</string>\n  <string name=\"textTipListSwipeToDelete\">滑动列表项可将其移除。</string>\n  <string name=\"textGenreAction\">动作</string>\n  <string name=\"textGenreAdventure\">冒险</string>\n  <string name=\"textGenreAnimation\">动画</string>\n  <string name=\"textGenreAnime\">日漫</string>\n  <string name=\"textGenreComedy\">喜剧</string>\n  <string name=\"textGenreCrime\">犯罪</string>\n  <string name=\"textGenreDocumentary\">纪录片</string>\n  <string name=\"textGenreDrama\">剧情</string>\n  <string name=\"textGenreFantasy\">奇幻</string>\n  <string name=\"textGenreHistory\">历史</string>\n  <string name=\"textGenreHorror\">恐怖</string>\n  <string name=\"textGenreScienceFiction\">科幻</string>\n  <string name=\"textGenreThriller\">惊悚</string>\n  <string name=\"textGenreWar\">战争</string>\n  <string name=\"textGenreWestern\">西部</string>\n  <string name=\"textTraktSyncSchedule\">定时同步时间</string>\n  <string name=\"textTraktSyncOptionOff\">关闭</string>\n  <string name=\"textTraktSyncOption1Hour\">每隔 1 小时</string>\n  <string name=\"textTraktSyncOption3Hours\">每隔 3 小时</string>\n  <string name=\"textTraktSyncOption6Hours\">每隔 6 小时</string>\n  <string name=\"textTraktSyncOptionDaily\">每隔 1 天</string>\n  <string name=\"textTraktSyncOption3Day\">每隔 3 天</string>\n  <string name=\"textTraktSyncOptionWeekly\">每隔 1 周</string>\n  <string name=\"textTraktSyncOptionOffMessage\">不会自动进行同步。</string>\n  <string name=\"textTraktSyncOptionConfirmMessage\">已设置自动定时同步。</string>\n  <string name=\"textTraktSyncOptionHourButton\">已设置每隔 1 小时运行</string>\n  <string name=\"textTraktSyncOption3HoursButton\">已设置每隔 3 小时运行</string>\n  <string name=\"textTraktSyncOption6HoursButton\">已设置每隔 6 小时运行</string>\n  <string name=\"textTraktSyncOptionDailyButton\">已设置每隔 1 天运行</string>\n  <string name=\"textTraktSyncOption3DayButton\">已设置每隔 3 天运行</string>\n  <string name=\"textTraktSyncOptionWeeklyButton\">已设置每隔 1 周运行</string>\n  <string name=\"textShowStatusReturning\">回归中</string>\n  <string name=\"textShowStatusUpcoming\">即将到来</string>\n  <string name=\"textShowStatusInProduction\">制片中</string>\n  <string name=\"textShowStatusPlanned\">已规划</string>\n  <string name=\"textShowStatusCanceled\">已取消</string>\n  <string name=\"textShowStatusEnded\">已完结</string>\n  <string name=\"textMovieStatusReleased\">已发行</string>\n  <string name=\"textMovieStatusInProduction\">制片中</string>\n  <string name=\"textMovieStatusPostProduction\">后期制作中</string>\n  <string name=\"textMovieStatusPlanned\">已规划</string>\n  <string name=\"textMovieStatusCanceled\">已取消</string>\n  <string name=\"textMovieStatusRumored\">有传闻</string>\n  <string name=\"textStreamingStream\">串流</string>\n  <string name=\"textStreamingAds\">含广告</string>\n  <string name=\"textStreamingBuy\">购买</string>\n  <string name=\"textStreamingRent\">租看</string>\n  <string name=\"textStreamingFree\">免费</string>\n</resources>\n"
  },
  {
    "path": "ui-movie/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-movie/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_movie'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-local')\n  implementation project(':data-remote')\n  implementation project(':repository')\n  implementation project(':ui-base')\n  implementation project(':ui-model')\n  implementation project(':ui-people')\n  implementation project(':ui-navigation')\n  implementation project(':ui-comments')\n  implementation project(':ui-streamings')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  implementation libs.circleIndicator\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-movie/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n  <application android:theme=\"@style/AppTheme\" />\n\n</manifest>\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/MovieDetailsFragment.kt",
    "content": "package com.michaldrabik.ui_movie\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT\nimport android.content.res.Configuration.ORIENTATION_PORTRAIT\nimport android.graphics.Typeface.BOLD\nimport android.graphics.Typeface.NORMAL\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.animation.DecelerateInterpolator\nimport androidx.annotation.IdRes\nimport androidx.constraintlayout.widget.ConstraintSet\nimport androidx.core.os.bundleOf\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateMargins\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.transition.AutoTransition\nimport androidx.transition.TransitionManager\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade\nimport com.google.android.material.snackbar.Snackbar\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Config.IMAGE_FADE_DURATION_MS\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_base.Analytics\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.WidgetsProvider\nimport com.michaldrabik.ui_base.common.sheets.links.LinksBottomSheet\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Operation\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Type\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet\nimport com.michaldrabik.ui_base.utilities.SnackbarHost\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.copyToClipboard\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.openWebUrl\nimport com.michaldrabik.ui_base.utilities.extensions.requireLong\nimport com.michaldrabik.ui_base.utilities.extensions.screenHeight\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_comments.fragment.CommentsFragment\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageFamily.MOVIE\nimport com.michaldrabik.ui_model.ImageStatus.UNAVAILABLE\nimport com.michaldrabik.ui_model.ImageType.FANART\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.PremiumFeature\nimport com.michaldrabik.ui_model.RatingState\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_movie.MovieDetailsEvent.Finish\nimport com.michaldrabik.ui_movie.MovieDetailsEvent.RemoveFromTrakt\nimport com.michaldrabik.ui_movie.MovieDetailsEvent.RequestWidgetsUpdate\nimport com.michaldrabik.ui_movie.databinding.FragmentMovieDetailsBinding\nimport com.michaldrabik.ui_movie.views.AddToMoviesButton.State.ADD\nimport com.michaldrabik.ui_movie.views.AddToMoviesButton.State.IN_HIDDEN\nimport com.michaldrabik.ui_movie.views.AddToMoviesButton.State.IN_MY_MOVIES\nimport com.michaldrabik.ui_movie.views.AddToMoviesButton.State.IN_WATCHLIST\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_CUSTOM_IMAGE_CLEARED\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_FAMILY\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ITEM\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_MOVIE_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_TYPE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_CUSTOM_IMAGE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_MANAGE_LISTS\nimport dagger.hilt.android.AndroidEntryPoint\nimport timber.log.Timber\nimport java.util.Locale.ENGLISH\nimport java.util.Locale.ROOT\n\n@SuppressLint(\"SetTextI18n\", \"DefaultLocale\", \"SourceLockedOrientationActivity\")\n@AndroidEntryPoint\nclass MovieDetailsFragment : BaseFragment<MovieDetailsViewModel>(R.layout.fragment_movie_details) {\n\n  override val navigationId = R.id.movieDetailsFragment\n  val binding by viewBinding(FragmentMovieDetailsBinding::bind)\n\n  override val viewModel by viewModels<MovieDetailsViewModel>()\n\n  private val movieId by lazy { IdTrakt(requireLong(ARG_MOVIE_ID)) }\n\n  private val imageHeight by lazy {\n    if (resources.configuration.orientation == ORIENTATION_PORTRAIT) screenHeight()\n    else screenWidth()\n  }\n  private val imageRatio by lazy { resources.getString(R.string.detailsImageRatio).toFloat() }\n  private val imagePadded by lazy { resources.getBoolean(R.bool.detailsImagePadded) }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    requireActivity().requestedOrientation = SCREEN_ORIENTATION_PORTRAIT\n    setupView()\n    setupStatusBar()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.messageFlow.collect { renderSnack(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = {\n        if (!isInitialized) {\n          viewModel.loadDetails(movieId)\n          isInitialized = true\n        }\n        viewModel.loadPremium()\n      }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      hideNavigation()\n      movieDetailsImageGuideline.setGuidelineBegin((imageHeight * imageRatio).toInt())\n      movieDetailsBackArrow.onClick { requireActivity().onBackPressed() }\n      movieDetailsImage.onClick {\n        val bundle = bundleOf(\n          ARG_MOVIE_ID to movieId.id,\n          ARG_FAMILY to MOVIE,\n          ARG_TYPE to FANART\n        )\n        navigateToSafe(R.id.actionMovieDetailsFragmentToArtGallery, bundle)\n        Analytics.logMovieGalleryClick(movieId.id)\n      }\n      movieDetailsAddButton.run {\n        isEnabled = false\n        onAddMyMoviesClickListener = {\n          viewModel.addFollowedMovie()\n        }\n        onAddWatchLaterClickListener = { viewModel.addWatchlistMovie() }\n        onRemoveClickListener = { viewModel.removeFromFollowed() }\n      }\n      movieDetailsManageListsLabel.onClick { openListsDialog() }\n      movieDetailsHideLabel.onClick { viewModel.addHiddenMovie() }\n      movieDetailsTitle.onClick {\n        requireContext().copyToClipboard(movieDetailsTitle.text.toString())\n        showSnack(MessageEvent.Info(R.string.textCopiedToClipboard))\n      }\n      movieDetailsDescription.onLongClick {\n        requireContext().copyToClipboard(movieDetailsDescription.text.toString())\n        showSnack(MessageEvent.Info(R.string.textCopiedToClipboard))\n      }\n      movieDetailsPremiumAd.onClick {\n        navigateTo(R.id.actionMovieDetailsFragmentToPremium)\n      }\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      movieDetailsBackArrow.doOnApplyWindowInsets { view, insets, _, _ ->\n        val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top\n        if (imagePadded) {\n          movieDetailsMainLayout\n            .updatePadding(top = inset)\n        } else {\n          (movieDetailsShareButton.layoutParams as ViewGroup.MarginLayoutParams)\n            .updateMargins(top = inset)\n        }\n        (view.layoutParams as ViewGroup.MarginLayoutParams).updateMargins(top = inset)\n      }\n    }\n  }\n\n  private fun render(uiState: MovieDetailsUiState) {\n    uiState.run {\n      with(binding) {\n        movie?.let { movie ->\n          renderTitleDescription(movie, translation, followedState, spoilers)\n          movieDetailsStatus.text = getString(movie.status.displayName)\n\n          val releaseDate =\n            when {\n              movie.released != null -> String.format(ENGLISH, \"%s\", meta?.dateFormat?.format(movie.released)?.capitalizeWords())\n              movie.year > 0 -> movie.year.toString()\n              else -> \"\"\n            }\n\n          val country = if (movie.country.isNotBlank()) String.format(ENGLISH, \"(%s)\", movie.country) else \"\"\n          movieDetailsExtraInfo.text = getString(\n            R.string.textMovieExtraInfo,\n            releaseDate,\n            country.uppercase(ROOT),\n            movie.runtime.toString(),\n            getString(R.string.textMinutesShort),\n            renderGenres(movie.genres)\n          )\n          movieDetailsCommentsButton.visible()\n          movieDetailsShareButton.run {\n            isEnabled = movie.ids.imdb.id.isNotBlank()\n            alpha = if (isEnabled) 1.0F else 0.35F\n            onClick { openShareSheet(movie) }\n          }\n          movieDetailsTrailerButton.run {\n            isEnabled = movie.trailer.isNotBlank()\n            alpha = if (isEnabled) 1.0F else 0.35F\n            onClick {\n              openWebUrl(movie.trailer) ?: showSnack(MessageEvent.Info(R.string.errorCouldNotFindApp))\n              Analytics.logMovieTrailerClick(movie)\n            }\n          }\n          movieDetailsLinksButton.run {\n            onClick {\n              val args = LinksBottomSheet.createBundle(movie)\n              navigateTo(R.id.actionMovieDetailsFragmentToLinks, args)\n            }\n          }\n          separator5.visible()\n          movieDetailsCustomImagesButton.visibleIf(Config.SHOW_PREMIUM)\n          movieDetailsCustomImagesButton.onClick { openCustomImagesSheet(movie.traktId, meta?.isPremium) }\n          movieDetailsCommentsButton.onClick {\n            val bundle = CommentsFragment.createBundle(movie)\n            navigateToSafe(R.id.actionMovieDetailsFragmentToComments, bundle)\n          }\n          movieDetailsAddButton.isEnabled = true\n        }\n        movieLoading?.let {\n          movieDetailsMainLayout.fadeIf(!it, hardware = true)\n          movieDetailsMainProgress.visibleIf(it)\n        }\n        followedState?.let {\n          when {\n            it.isMyMovie -> movieDetailsAddButton.setState(IN_MY_MOVIES, it.withAnimation)\n            it.isWatchlist -> movieDetailsAddButton.setState(IN_WATCHLIST, it.withAnimation)\n            it.isHidden -> movieDetailsAddButton.setState(IN_HIDDEN, it.withAnimation)\n            else -> movieDetailsAddButton.setState(ADD, it.withAnimation)\n          }\n          movieDetailsHideLabel.visibleIf(!it.isHidden)\n        }\n        image?.let { renderImage(it) }\n        listsCount?.let {\n          val text =\n            if (it > 0) getString(R.string.textMovieManageListsCount, it)\n            else getString(R.string.textMovieManageLists)\n          movieDetailsManageListsLabel.text = text\n        }\n        ratingState?.let { renderRating(it) }\n        meta?.isPremium.let {\n          movieDetailsPremiumAd.visibleIf(it != true)\n        }\n      }\n    }\n  }\n\n  private fun renderTitleDescription(\n    movie: Movie,\n    translation: Translation?,\n    followedState: MovieDetailsUiState.FollowedState?,\n    spoilersSettings: SpoilersSettings?,\n  ) {\n    with(binding) {\n      var title = movie.title\n      var description = movie.overview\n\n      if (translation?.title?.isNotBlank() == true) {\n        title = translation.title\n      }\n      if (translation?.overview?.isNotBlank() == true) {\n        description = translation.overview\n      }\n\n      if (followedState == null || spoilersSettings == null) {\n        movieDetailsTitle.text = title\n        movieDetailsDescription.text = description\n        return\n      }\n\n      val isMyMovieHidden = spoilersSettings.isMyMoviesHidden && followedState.isMyMovie\n      val isWatchlistHidden = spoilersSettings.isWatchlistMoviesHidden && followedState.isWatchlist\n      val isHiddenMovieHidden = spoilersSettings.isHiddenMoviesHidden && followedState.isHidden\n      val isNotCollectedHidden = spoilersSettings.isNotCollectedMoviesHidden && (!followedState.isInCollection())\n\n      if (isMyMovieHidden || isWatchlistHidden || isHiddenMovieHidden || isNotCollectedHidden) {\n        movieDetailsDescription.tag = description\n        description = SPOILERS_REGEX.replace(description, SPOILERS_HIDE_SYMBOL)\n\n        if (spoilersSettings.isTapToReveal) {\n          with(movieDetailsDescription) {\n            onClick {\n              tag?.let { text = it.toString() }\n              enableFoldOnClick()\n            }\n          }\n        }\n      }\n\n      movieDetailsTitle.text = title\n      movieDetailsDescription.text = description\n    }\n  }\n\n  private fun renderGenres(genres: List<String>) =\n    genres\n      .take(3)\n      .mapNotNull { Genre.fromSlug(it) }\n      .joinToString(\", \") { getString(it.displayName) }\n\n  private fun renderRating(rating: RatingState) {\n    with(binding) {\n      movieDetailsRateButton.visibleIf(rating.rateLoading == false, gone = false)\n      movieDetailsRateProgress.visibleIf(rating.rateLoading == true)\n\n      movieDetailsRateButton.text =\n        if (rating.hasRating()) \"${rating.userRating?.rating}/10\"\n        else getString(R.string.textMovieRate)\n\n      val typeFace = if (rating.hasRating()) BOLD else NORMAL\n      movieDetailsRateButton.setTypeface(null, typeFace)\n\n      movieDetailsRateButton.onClick {\n        if (rating.rateAllowed == true) {\n          openRateDialog()\n        } else {\n          showSnack(MessageEvent.Info(R.string.textSignBeforeRateMovie))\n        }\n      }\n    }\n  }\n\n  private fun renderImage(image: Image) {\n    with(binding) {\n      if (image.status == UNAVAILABLE) {\n        movieDetailsImageProgress.gone()\n        movieDetailsPlaceholder.visible()\n        movieDetailsImage.isClickable = false\n        movieDetailsImage.isEnabled = false\n        return\n      }\n      Glide.with(this@MovieDetailsFragment)\n        .load(image.fullFileUrl)\n        .transform(CenterCrop())\n        .transition(withCrossFade(IMAGE_FADE_DURATION_MS))\n        .withFailListener {\n          movieDetailsImageProgress.gone()\n          movieDetailsPlaceholder.visible()\n          movieDetailsImage.isClickable = true\n          movieDetailsImage.isEnabled = true\n        }\n        .withSuccessListener {\n          movieDetailsImageProgress.gone()\n          movieDetailsPlaceholder.gone()\n        }\n        .into(movieDetailsImage)\n    }\n  }\n\n  private fun renderSnack(event: MessageEvent) {\n    if (event.textResId == R.string.errorMalformedMovie) {\n      event.consume()?.let {\n        val host = (requireActivity() as SnackbarHost).provideSnackbarLayout()\n        val snack = host.showInfoSnackbar(getString(it), length = Snackbar.LENGTH_INDEFINITE) {\n          viewModel.removeMalformedMovie(movieId)\n        }\n        snackbars.add(snack)\n      }\n      return\n    }\n    showSnack(event)\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is RemoveFromTrakt -> openRemoveTraktSheet(event.navigationId)\n      is RequestWidgetsUpdate -> (requireAppContext() as WidgetsProvider).requestMoviesWidgetsUpdate()\n      is Finish -> requireActivity().onBackPressed()\n    }\n  }\n\n  private fun openRemoveTraktSheet(@IdRes action: Int) {\n    setFragmentResultListener(NavigationArgs.REQUEST_REMOVE_TRAKT) { _, bundle ->\n      if (bundle.getBoolean(NavigationArgs.RESULT, false)) {\n        val text = resources.getString(R.string.textTraktSyncMovieRemovedFromTrakt)\n        (requireActivity() as SnackbarHost).provideSnackbarLayout().showInfoSnackbar(text)\n      }\n    }\n    val args = RemoveTraktBottomSheet.createBundle(movieId, RemoveTraktBottomSheet.Mode.MOVIE)\n    navigateTo(action, args)\n  }\n\n  private fun openShareSheet(movie: Movie) {\n    val intent = Intent().apply {\n      val text = \"Hey! Check out ${movie.title}:\\nhttps://trakt.tv/movies/${movie.ids.slug.id}\\nhttps://www.imdb.com/title/${movie.ids.imdb.id}\"\n      action = Intent.ACTION_SEND\n      putExtra(Intent.EXTRA_TEXT, text)\n      type = \"text/plain\"\n    }\n\n    val shareIntent = Intent.createChooser(intent, \"Share ${movie.title}\")\n    startActivity(shareIntent)\n\n    Analytics.logMovieShareClick(movie)\n  }\n\n  private fun openRateDialog() {\n    setFragmentResultListener(NavigationArgs.REQUEST_RATING) { _, bundle ->\n      when (bundle.getParcelable<Operation>(NavigationArgs.RESULT)) {\n        Operation.SAVE -> renderSnack(MessageEvent.Info(R.string.textRateSaved))\n        Operation.REMOVE -> renderSnack(MessageEvent.Info(R.string.textRateRemoved))\n        else -> Timber.w(\"Unknown result.\")\n      }\n      viewModel.loadUserRating()\n    }\n    val bundle = RatingsBottomSheet.createBundle(movieId, Type.MOVIE)\n    navigateTo(R.id.actionMovieDetailsFragmentToRating, bundle)\n  }\n\n  private fun openListsDialog() {\n    setFragmentResultListener(REQUEST_MANAGE_LISTS) { _, _ -> viewModel.loadListsCount() }\n    val bundle = bundleOf(\n      ARG_ID to movieId.id,\n      ARG_TYPE to Mode.MOVIES.type\n    )\n    navigateToSafe(R.id.actionMovieDetailsFragmentToManageLists, bundle)\n  }\n\n  private fun openCustomImagesSheet(movieId: Long, isPremium: Boolean?) {\n    if (isPremium == false) {\n      val args = bundleOf(ARG_ITEM to PremiumFeature.CUSTOM_IMAGES)\n      navigateTo(R.id.actionMovieDetailsFragmentToPremium, args)\n      return\n    }\n\n    setFragmentResultListener(REQUEST_CUSTOM_IMAGE) { _, bundle ->\n      viewModel.loadBackgroundImage()\n      if (!bundle.getBoolean(ARG_CUSTOM_IMAGE_CLEARED)) openCustomImagesSheet(movieId, true)\n    }\n\n    val bundle = bundleOf(\n      ARG_MOVIE_ID to movieId,\n      ARG_FAMILY to MOVIE\n    )\n    navigateToSafe(R.id.actionMovieDetailsFragmentToCustomImages, bundle)\n  }\n\n  fun showStreamingsView(animate: Boolean) {\n    with(binding) {\n      if (!animate) {\n        movieDetailsStreamingsFragment.visible()\n        return\n      }\n      val animation = ConstraintSet().apply {\n        clone(movieDetailsMainContent)\n        setVisibility(movieDetailsStreamingsFragment.id, View.VISIBLE)\n      }\n      val transition = AutoTransition().apply {\n        interpolator = DecelerateInterpolator(1.5F)\n        duration = 200\n      }\n      TransitionManager.beginDelayedTransition(movieDetailsMainContent, transition)\n      animation.applyTo(movieDetailsMainContent)\n    }\n  }\n\n  fun showCollectionsView(animate: Boolean) {\n    with(binding) {\n      if (!animate) {\n        movieDetailsCollectionsFragment.visible()\n        return\n      }\n      val animation = ConstraintSet().apply {\n        clone(movieDetailsMainContent)\n        setVisibility(movieDetailsCollectionsFragment.id, View.VISIBLE)\n      }\n      val transition = AutoTransition().apply {\n        interpolator = DecelerateInterpolator(1.5F)\n        duration = 200\n      }\n      TransitionManager.beginDelayedTransition(movieDetailsMainContent, transition)\n      animation.applyTo(movieDetailsMainContent)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/MovieDetailsUiEvents.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_movie\n\nimport androidx.annotation.IdRes\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.MovieCollection\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_people.details.PersonDetailsArgs\n\nsealed class MovieDetailsEvent<T>(action: T) : Event<T>(action) {\n\n  data class OpenPersonSheet(\n    val movie: Movie,\n    val person: Person,\n    val personArgs: PersonDetailsArgs?,\n  ) : MovieDetailsEvent<Movie>(movie)\n\n  data class OpenPeopleSheet(\n    val movie: Movie,\n    val people: List<Person>,\n    val department: Person.Department,\n  ) : MovieDetailsEvent<Movie>(movie)\n\n  data class OpenCollectionSheet(\n    val movie: Movie,\n    val collection: MovieCollection,\n  ) : MovieDetailsEvent<Movie>(movie)\n\n  data class RemoveFromTrakt(\n    @IdRes val navigationId: Int,\n  ) : MovieDetailsEvent<Int>(navigationId)\n\n  object RequestWidgetsUpdate : MovieDetailsEvent<Unit>(Unit)\n\n  object Finish : MovieDetailsEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/MovieDetailsUiState.kt",
    "content": "package com.michaldrabik.ui_movie\n\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.RatingState\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_movie.helpers.MovieDetailsMeta\n\ndata class MovieDetailsUiState(\n  val movie: Movie? = null,\n  val movieLoading: Boolean? = null,\n  val image: Image? = null,\n  val listsCount: Int? = null,\n  val followedState: FollowedState? = null,\n  val ratingState: RatingState? = null,\n  val translation: Translation? = null,\n  val meta: MovieDetailsMeta? = null,\n  val spoilers: SpoilersSettings? = null\n) {\n\n  data class FollowedState(\n    val isMyMovie: Boolean,\n    val isWatchlist: Boolean,\n    val isHidden: Boolean,\n    val withAnimation: Boolean,\n  ) {\n\n    fun isInCollection() = isMyMovie || isWatchlist || isHidden\n\n    companion object {\n      fun idle() = FollowedState(isMyMovie = false, isWatchlist = false, isHidden = false, withAnimation = true)\n      fun inMyMovies() = FollowedState(isMyMovie = true, isWatchlist = false, isHidden = false, withAnimation = true)\n      fun inHidden() = FollowedState(isMyMovie = false, isWatchlist = false, isHidden = true, withAnimation = true)\n      fun inWatchlist() = FollowedState(isMyMovie = false, isWatchlist = true, isHidden = false, withAnimation = true)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/MovieDetailsViewModel.kt",
    "content": "package com.michaldrabik.ui_movie\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError.CoroutineCancellation\nimport com.michaldrabik.common.errors.ShowlyError.ResourceNotFoundError\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.Analytics\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.combine\nimport com.michaldrabik.ui_base.utilities.extensions.launchDelayed\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.RatingState\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.TraktRating\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_movie.MovieDetailsEvent.Finish\nimport com.michaldrabik.ui_movie.MovieDetailsEvent.RemoveFromTrakt\nimport com.michaldrabik.ui_movie.MovieDetailsEvent.RequestWidgetsUpdate\nimport com.michaldrabik.ui_movie.MovieDetailsUiState.FollowedState\nimport com.michaldrabik.ui_movie.cases.MovieDetailsHiddenCase\nimport com.michaldrabik.ui_movie.cases.MovieDetailsListsCase\nimport com.michaldrabik.ui_movie.cases.MovieDetailsMainCase\nimport com.michaldrabik.ui_movie.cases.MovieDetailsMyMoviesCase\nimport com.michaldrabik.ui_movie.cases.MovieDetailsTranslationCase\nimport com.michaldrabik.ui_movie.cases.MovieDetailsWatchlistCase\nimport com.michaldrabik.ui_movie.helpers.MovieDetailsMeta\nimport com.michaldrabik.ui_movie.sections.ratings.cases.MovieDetailsRatingCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\nimport kotlin.properties.Delegates.notNull\n\n@HiltViewModel\nclass MovieDetailsViewModel @Inject constructor(\n  private val mainCase: MovieDetailsMainCase,\n  private val translationCase: MovieDetailsTranslationCase,\n  private val myMoviesCase: MovieDetailsMyMoviesCase,\n  private val ratingsCase: MovieDetailsRatingCase,\n  private val watchlistCase: MovieDetailsWatchlistCase,\n  private val hiddenCase: MovieDetailsHiddenCase,\n  private val listsCase: MovieDetailsListsCase,\n  private val settingsRepository: SettingsRepository,\n  private val userManager: UserTraktManager,\n  private val imagesProvider: MovieImagesProvider,\n  private val dateFormatProvider: DateFormatProvider,\n  private val announcementManager: AnnouncementManager,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private var movie by notNull<Movie>()\n\n  private val movieState = MutableStateFlow<Movie?>(null)\n  private val movieLoadingState = MutableStateFlow<Boolean?>(null)\n  private val imageState = MutableStateFlow<Image?>(null)\n  private val followedState = MutableStateFlow<FollowedState?>(null)\n  private val ratingState = MutableStateFlow<RatingState?>(null)\n  private val translationState = MutableStateFlow<Translation?>(null)\n  private val metaState = MutableStateFlow<MovieDetailsMeta?>(null)\n  private val spoilersState = MutableStateFlow<SpoilersSettings?>(null)\n  private val listsCountState = MutableStateFlow(0)\n\n  val parentMovieState = movieState.asStateFlow()\n  val parentFollowedState = followedState.asStateFlow()\n\n  fun loadDetails(id: IdTrakt) {\n    viewModelScope.launch {\n      val progressJob = launchDelayed(700) {\n        movieLoadingState.value = true\n      }\n      try {\n        movie = mainCase.loadDetails(id)\n        Analytics.logMovieDetailsDisplay(movie)\n\n        val isSignedIn = userManager.isAuthorized()\n        val isMyMovie = async { myMoviesCase.isMyMovie(movie) }\n        val isWatchlist = async { watchlistCase.isWatchlist(movie) }\n        val isHidden = async { hiddenCase.isHidden(movie) }\n        val isFollowed = FollowedState(\n          isMyMovie = isMyMovie.await(),\n          isWatchlist = isWatchlist.await(),\n          isHidden = isHidden.await(),\n          withAnimation = false\n        )\n\n        progressJob.cancel()\n\n        movieState.value = movie\n        movieLoadingState.value = false\n        followedState.value = isFollowed\n        ratingState.value = RatingState(rateAllowed = isSignedIn, rateLoading = false)\n        spoilersState.value = settingsRepository.spoilers.getAll()\n        metaState.value = MovieDetailsMeta(\n          dateFormat = dateFormatProvider.loadShortDayFormat(),\n          commentsDateFormat = dateFormatProvider.loadFullHourFormat(),\n          isSignedIn = isSignedIn,\n          isPremium = settingsRepository.isPremium\n        )\n\n        loadBackgroundImage(movie)\n        loadListsCount(movie)\n        loadUserRating()\n        loadTranslation()\n\n        eventChannel.send(RequestWidgetsUpdate)\n      } catch (error: Throwable) {\n        Timber.e(error)\n        progressJob.cancel()\n        when (ErrorHelper.parse(error)) {\n          is CoroutineCancellation -> rethrowCancellation(error)\n          is ResourceNotFoundError -> {\n            // Malformed Trakt data or duplicate show.\n            messageChannel.send(MessageEvent.Info(R.string.errorMalformedMovie))\n            Logger.record(error, \"MovieDetailsViewModel::loadDetails(${id.id})\")\n          }\n          else -> {\n            messageChannel.send(MessageEvent.Error(R.string.errorCouldNotLoadMovie))\n            Logger.record(error, \"MovieDetailsViewModel::loadDetails(${id.id})\")\n          }\n        }\n      }\n    }\n  }\n\n  fun loadBackgroundImage(movie: Movie? = null) {\n    viewModelScope.launch {\n      try {\n        val backgroundImage = imagesProvider.loadRemoteImage(movie ?: this@MovieDetailsViewModel.movie, ImageType.FANART)\n        imageState.value = backgroundImage\n      } catch (error: Throwable) {\n        imageState.value = Image.createUnavailable(ImageType.FANART)\n        Timber.e(error)\n        rethrowCancellation(error)\n      }\n    }\n  }\n\n  private fun loadTranslation() {\n    viewModelScope.launch {\n      try {\n        translationCase.loadTranslation(movie)?.let {\n          translationState.value = it\n        }\n      } catch (error: Throwable) {\n        rethrowCancellation(error)\n      }\n    }\n  }\n\n  fun loadListsCount(movie: Movie? = null) {\n    viewModelScope.launch {\n      val count = listsCase.countLists(movie ?: this@MovieDetailsViewModel.movie)\n      listsCountState.value = count\n    }\n  }\n\n  fun loadPremium() {\n    metaState.update { it?.copy(isPremium = settingsRepository.isPremium) }\n  }\n\n  fun loadUserRating() {\n    if (!userManager.isAuthorized()) {\n      return\n    }\n    viewModelScope.launch {\n      try {\n        ratingState.value = RatingState(rateLoading = true, rateAllowed = true)\n        val rating = ratingsCase.loadRating(movie)\n        ratingState.value = RatingState(rateLoading = false, rateAllowed = true, userRating = rating ?: TraktRating.EMPTY)\n      } catch (error: Throwable) {\n        ratingState.value = RatingState(rateLoading = false, rateAllowed = true)\n        rethrowCancellation(error)\n      }\n    }\n  }\n\n  fun addFollowedMovie() {\n    viewModelScope.launch {\n      myMoviesCase.addToMyMovies(movie)\n      followedState.value = FollowedState.inMyMovies()\n      eventChannel.send(RequestWidgetsUpdate)\n      Analytics.logMovieAddToMyMovies(movie)\n    }\n  }\n\n  fun addWatchlistMovie() {\n    viewModelScope.launch {\n      watchlistCase.addToWatchlist(movie)\n      followedState.value = FollowedState.inWatchlist()\n      eventChannel.send(RequestWidgetsUpdate)\n      Analytics.logMovieAddToWatchlistMovies(movie)\n    }\n  }\n\n  fun addHiddenMovie() {\n    viewModelScope.launch {\n      hiddenCase.addToHidden(movie)\n      followedState.value = FollowedState.inHidden()\n      eventChannel.send(RequestWidgetsUpdate)\n      Analytics.logMovieAddToArchive(movie)\n    }\n  }\n\n  fun removeFromFollowed() {\n    viewModelScope.launch {\n      val isMyMovie = myMoviesCase.isMyMovie(movie)\n      val isWatchlist = watchlistCase.isWatchlist(movie)\n      val isHidden = hiddenCase.isHidden(movie)\n\n      when {\n        isMyMovie -> myMoviesCase.removeFromMyMovies(movie)\n        isWatchlist -> watchlistCase.removeFromWatchlist(movie)\n        isHidden -> hiddenCase.removeFromHidden(movie)\n      }\n\n      val traktQuickRemoveEnabled = settingsRepository.load().traktQuickRemoveEnabled\n      val showRemoveTrakt = userManager.isAuthorized() && traktQuickRemoveEnabled\n\n      val state = FollowedState.idle()\n      when {\n        isMyMovie -> {\n          followedState.value = state\n          if (showRemoveTrakt) {\n            eventChannel.send(RemoveFromTrakt(R.id.actionMovieDetailsFragmentToRemoveTraktProgress))\n          }\n        }\n        isWatchlist -> {\n          followedState.value = state\n          if (showRemoveTrakt) {\n            eventChannel.send(RemoveFromTrakt(R.id.actionMovieDetailsFragmentToRemoveTraktWatchlist))\n          }\n        }\n        isHidden -> {\n          followedState.value = state\n          if (showRemoveTrakt) {\n            eventChannel.send(RemoveFromTrakt(R.id.actionMovieDetailsFragmentToRemoveTraktHidden))\n          }\n        }\n        else -> error(\"Unexpected movie state.\")\n      }\n      eventChannel.send(RequestWidgetsUpdate)\n      announcementManager.refreshMoviesAnnouncements()\n    }\n  }\n\n  fun removeMalformedMovie(id: IdTrakt) {\n    viewModelScope.launch {\n      try {\n        mainCase.removeMalformedMovie(id)\n      } catch (error: Throwable) {\n        Timber.e(error)\n        rethrowCancellation(error)\n      } finally {\n        eventChannel.send(Finish)\n      }\n    }\n  }\n\n  val uiState = combine(\n    movieState,\n    movieLoadingState,\n    imageState,\n    followedState,\n    ratingState,\n    translationState,\n    listsCountState,\n    metaState,\n    spoilersState\n  ) { s1, s2, s3, s4, s5, s6, s7, s8, s9 ->\n    MovieDetailsUiState(\n      movie = s1,\n      movieLoading = s2,\n      image = s3,\n      followedState = s4,\n      ratingState = s5,\n      translation = s6,\n      listsCount = s7,\n      meta = s8,\n      spoilers = s9\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = MovieDetailsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/cases/MovieDetailsHiddenCase.kt",
    "content": "package com.michaldrabik.ui_movie.cases\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.Movie\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsHiddenCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val moviesRepository: MoviesRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val quickSyncManager: QuickSyncManager\n) {\n\n  suspend fun isHidden(movie: Movie) = withContext(dispatchers.IO) {\n    moviesRepository.hiddenMovies.exists(movie.ids.trakt)\n  }\n\n  suspend fun addToHidden(movie: Movie) {\n    withContext(dispatchers.IO) {\n      moviesRepository.hiddenMovies.insert(movie.ids.trakt)\n      pinnedItemsRepository.removePinnedItem(movie)\n      quickSyncManager.scheduleHidden(movie.traktId, Mode.MOVIES, TraktSyncQueue.Operation.ADD)\n    }\n  }\n\n  suspend fun removeFromHidden(movie: Movie) {\n    withContext(dispatchers.IO) {\n      moviesRepository.hiddenMovies.delete(movie.ids.trakt)\n      pinnedItemsRepository.removePinnedItem(movie)\n      quickSyncManager.clearHiddenMovies(listOf(movie.traktId))\n    }\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/cases/MovieDetailsListsCase.kt",
    "content": "package com.michaldrabik.ui_movie.cases\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.ListsRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Movie\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsListsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val listsRepository: ListsRepository\n) {\n\n  suspend fun countLists(movie: Movie) = withContext(dispatchers.IO) {\n    listsRepository.loadListIdsForItem(IdTrakt(movie.traktId), Mode.MOVIES.type).size\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/cases/MovieDetailsMainCase.kt",
    "content": "package com.michaldrabik.ui_movie.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsMainCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val moviesRepository: MoviesRepository,\n) {\n\n  suspend fun loadDetails(idTrakt: IdTrakt) = withContext(dispatchers.IO) {\n    moviesRepository.movieDetails.load(idTrakt)\n  }\n\n  suspend fun removeMalformedMovie(idTrakt: IdTrakt) {\n    withContext(dispatchers.IO) {\n      with(moviesRepository) {\n        myMovies.delete(idTrakt)\n        watchlistMovies.delete(idTrakt)\n        movieDetails.delete(idTrakt)\n      }\n    }\n    Timber.d(\"Removing malformed movie...\")\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/cases/MovieDetailsMyMoviesCase.kt",
    "content": "package com.michaldrabik.ui_movie.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.Movie\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsMyMoviesCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val moviesRepository: MoviesRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val quickSyncManager: QuickSyncManager,\n  private val announcementManager: AnnouncementManager\n) {\n\n  suspend fun getAllIds() = withContext(dispatchers.IO) {\n    val (myMovies, watchlistMovies) = awaitAll(\n      async { moviesRepository.myMovies.loadAllIds() },\n      async { moviesRepository.watchlistMovies.loadAllIds() }\n    )\n    Pair(myMovies, watchlistMovies)\n  }\n\n  suspend fun isMyMovie(movie: Movie) = withContext(dispatchers.IO) {\n    moviesRepository.myMovies.load(movie.ids.trakt) != null\n  }\n\n  suspend fun addToMyMovies(movie: Movie) {\n    withContext(dispatchers.IO) {\n      moviesRepository.myMovies.insert(movie.ids.trakt)\n      quickSyncManager.scheduleMovies(listOf(movie.traktId))\n      pinnedItemsRepository.removePinnedItem(movie)\n      announcementManager.refreshMoviesAnnouncements()\n    }\n  }\n\n  suspend fun removeFromMyMovies(movie: Movie) {\n    withContext(dispatchers.IO) {\n      moviesRepository.myMovies.delete(movie.ids.trakt)\n      pinnedItemsRepository.removePinnedItem(movie)\n      quickSyncManager.clearMovies(listOf(movie.traktId))\n    }\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/cases/MovieDetailsTranslationCase.kt",
    "content": "package com.michaldrabik.ui_movie.cases\n\nimport com.michaldrabik.common.Config.DEFAULT_LANGUAGE\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Translation\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsTranslationCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val translationsRepository: TranslationsRepository\n) {\n\n  suspend fun loadTranslation(movie: Movie): Translation? = withContext(dispatchers.IO) {\n    val language = translationsRepository.getLanguage()\n    if (language == DEFAULT_LANGUAGE) {\n      return@withContext null\n    }\n    translationsRepository.loadTranslation(movie, language)\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/cases/MovieDetailsWatchlistCase.kt",
    "content": "package com.michaldrabik.ui_movie.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.Movie\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsWatchlistCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val moviesRepository: MoviesRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val quickSyncManager: QuickSyncManager,\n  private val announcementManager: AnnouncementManager\n) {\n\n  suspend fun isWatchlist(movie: Movie) = withContext(dispatchers.IO) {\n    moviesRepository.watchlistMovies.load(movie.ids.trakt) != null\n  }\n\n  suspend fun addToWatchlist(movie: Movie) {\n    withContext(dispatchers.IO) {\n      moviesRepository.watchlistMovies.insert(movie.ids.trakt)\n      pinnedItemsRepository.removePinnedItem(movie)\n      quickSyncManager.scheduleMoviesWatchlist(listOf(movie.traktId))\n      announcementManager.refreshMoviesAnnouncements()\n    }\n  }\n\n  suspend fun removeFromWatchlist(movie: Movie) {\n    withContext(dispatchers.IO) {\n      moviesRepository.watchlistMovies.delete(movie.ids.trakt)\n      pinnedItemsRepository.removePinnedItem(movie)\n      quickSyncManager.clearWatchlistMovies(listOf(movie.traktId))\n    }\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/helpers/MovieDetailsMeta.kt",
    "content": "package com.michaldrabik.ui_movie.helpers\n\nimport java.time.format.DateTimeFormatter\n\ndata class MovieDetailsMeta(\n  val dateFormat: DateTimeFormatter,\n  val commentsDateFormat: DateTimeFormatter,\n  val isSignedIn: Boolean,\n  val isPremium: Boolean\n)\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/helpers/MovieLink.kt",
    "content": "package com.michaldrabik.ui_movie.helpers\n\nimport android.net.Uri\nimport com.michaldrabik.ui_base.common.AppCountry\n\nenum class MovieLink {\n  IMDB,\n  TRAKT,\n  TMDB,\n  METACRITIC,\n  ROTTEN,\n  JUST_WATCH;\n\n  fun getUri(\n    id: String,\n    country: AppCountry,\n  ) = when (this) {\n    IMDB -> \"https://www.imdb.com/title/$id\"\n    TRAKT -> \"https://trakt.tv/search/trakt/$id?id_type=movie\"\n    TMDB -> \"https://www.themoviedb.org/movie/$id\"\n    METACRITIC -> \"https://www.metacritic.com/search/$id?category=2\"\n    ROTTEN -> \"https://www.rottentomatoes.com/search?search=$id\"\n    JUST_WATCH -> \"https://www.justwatch.com/${country.code}/${country.justWatchQuery}?content_type=movie&q=${Uri.encode(id)}\"\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/details/MovieDetailsCollectionBottomSheet.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.details\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.clearFragmentResultListener\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.navigation.fragment.findNavController\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetDialog\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.common.FastLinearLayoutManager\nimport com.michaldrabik.ui_base.common.sheets.context_menu.ContextMenuBottomSheet\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.requireParcelable\nimport com.michaldrabik.ui_base.utilities.extensions.screenHeight\nimport com.michaldrabik.ui_base.utilities.extensions.showErrorSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_movie.R\nimport com.michaldrabik.ui_movie.databinding.ViewMovieCollectionDetailsBinding\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionAdapter\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_COLLECTION_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_MOVIE_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_DETAILS\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass MovieDetailsCollectionBottomSheet : BaseBottomSheetFragment(R.layout.view_movie_collection_details) {\n\n  companion object {\n    const val SHOW_BACK_UP_BUTTON_THRESHOLD = 25\n\n    fun createBundle(\n      collectionId: IdTrakt,\n      sourceMovieId: IdTrakt,\n    ) = bundleOf(\n      ARG_ID to collectionId,\n      ARG_MOVIE_ID to sourceMovieId\n    )\n  }\n\n  private val viewModel by viewModels<MovieDetailsCollectionViewModel>()\n  private val binding by viewBinding(ViewMovieCollectionDetailsBinding::bind)\n\n  private val collectionId by lazy { requireParcelable<IdTrakt>(ARG_ID) }\n  private val sourceMovieId by lazy { requireParcelable<IdTrakt>(ARG_MOVIE_ID) }\n\n  private var adapter: MovieDetailsCollectionAdapter? = null\n  private var layoutManager: LinearLayoutManager? = null\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    setupView()\n    setupRecycler()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.messageFlow.collect { renderSnackbar(it) } },\n      doAfterLaunch = {\n        viewModel.loadCollection(collectionId)\n      }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior\n      with(behavior) {\n        peekHeight = (screenHeight() * 0.55).toInt()\n        skipCollapsed = true\n        state = BottomSheetBehavior.STATE_COLLAPSED\n      }\n      backToTopButton.onClick {\n        backToTopButton.fadeOut(150)\n        itemsRecycler.smoothScrollToPosition(0)\n      }\n    }\n  }\n\n  private fun setupRecycler() {\n    layoutManager = FastLinearLayoutManager(context, VERTICAL, false)\n    adapter = MovieDetailsCollectionAdapter(\n      onItemClickListener = ::openDetails,\n      onItemLongClickListener = ::openContextDetails,\n      onMissingImageListener = viewModel::loadMissingImage,\n      onMissingTranslationListener = viewModel::loadMissingTranslation,\n    )\n    with(binding.itemsRecycler) {\n      adapter = this@MovieDetailsCollectionBottomSheet.adapter\n      layoutManager = this@MovieDetailsCollectionBottomSheet.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      removeOnScrollListener(recyclerScrollListener)\n      addOnScrollListener(recyclerScrollListener)\n    }\n  }\n\n  private fun openDetails(item: MovieDetailsCollectionItem) {\n    if (item !is MovieDetailsCollectionItem.MovieItem) return\n    if (item.movie.ids.trakt == sourceMovieId) {\n      dismiss()\n      return\n    }\n\n    val resultBundle = bundleOf(ARG_COLLECTION_ID to collectionId)\n    setFragmentResult(REQUEST_DETAILS, resultBundle)\n\n    val argsBundle = bundleOf(ARG_MOVIE_ID to item.movie.traktId)\n    requireParentFragment()\n      .findNavController()\n      .navigate(R.id.actionMovieCollectionDialogToMovie, argsBundle)\n  }\n\n  private fun openContextDetails(item: MovieDetailsCollectionItem) {\n    if (item !is MovieDetailsCollectionItem.MovieItem) return\n    if (item.movie.ids.trakt == sourceMovieId) return\n\n    setFragmentResultListener(NavigationArgs.REQUEST_ITEM_MENU) { requestKey, _ ->\n      if (requestKey == NavigationArgs.REQUEST_ITEM_MENU) {\n        viewModel.loadCollection(collectionId)\n      }\n      clearFragmentResultListener(NavigationArgs.REQUEST_ITEM_MENU)\n    }\n\n    val bundle = ContextMenuBottomSheet.createBundle(\n      idTrakt = item.movie.ids.trakt,\n      detailsEnabled = false\n    )\n    navigateTo(R.id.actionMovieCollectionDialogToContextDialog, bundle)\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun render(uiState: MovieDetailsCollectionUiState) {\n    uiState.run {\n      items?.let { adapter?.setItems(it) }\n    }\n  }\n\n  private fun renderSnackbar(message: MessageEvent) {\n    when (message) {\n      is MessageEvent.Info -> binding.rootLayout.showInfoSnackbar(getString(message.textRestId))\n      is MessageEvent.Error -> binding.rootLayout.showErrorSnackbar(getString(message.textRestId))\n    }\n  }\n\n  override fun onDestroyView() {\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n\n  private val recyclerScrollListener = object : RecyclerView.OnScrollListener() {\n    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {\n      if (newState != SCROLL_STATE_IDLE) {\n        return\n      }\n      if ((layoutManager?.findFirstVisibleItemPosition() ?: 0) >= SHOW_BACK_UP_BUTTON_THRESHOLD) {\n        binding.backToTopButton.fadeIn(150)\n      } else {\n        binding.backToTopButton.fadeOut(150)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/details/MovieDetailsCollectionUiState.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.details\n\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem\n\ndata class MovieDetailsCollectionUiState(\n  val items: List<MovieDetailsCollectionItem>? = null,\n)\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/details/MovieDetailsCollectionViewModel.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.details\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_movie.sections.collections.details.cases.MovieDetailsCollectionDetailsCase\nimport com.michaldrabik.ui_movie.sections.collections.details.cases.MovieDetailsCollectionImagesCase\nimport com.michaldrabik.ui_movie.sections.collections.details.cases.MovieDetailsCollectionMoviesCase\nimport com.michaldrabik.ui_movie.sections.collections.details.cases.MovieDetailsCollectionTranslationsCase\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem.LoadingItem\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem.MovieItem\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass MovieDetailsCollectionViewModel @Inject constructor(\n  private val collectionDetailsCase: MovieDetailsCollectionDetailsCase,\n  private val collectionMoviesCase: MovieDetailsCollectionMoviesCase,\n  private val collectionMoviesImagesCase: MovieDetailsCollectionImagesCase,\n  private val collectionMoviesTranslationsCase: MovieDetailsCollectionTranslationsCase,\n  private val settingsRepository: SettingsRepository\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val itemsState = MutableStateFlow<MutableList<MovieDetailsCollectionItem>?>(null)\n\n  private var imagesJobs = mutableMapOf<String, Boolean>()\n  private var translationsJobs = mutableMapOf<String, Boolean>()\n\n  fun loadCollection(collectionId: IdTrakt) {\n    viewModelScope.launch {\n      try {\n        val headerItem = collectionDetailsCase.loadCollection(collectionId)\n        itemsState.value = mutableListOf(headerItem)\n        loadCollectionMovies(collectionId)\n      } catch (error: Throwable) {\n        // TODO Collection not available\n        rethrowCancellation(error)\n      }\n    }\n  }\n\n  private fun loadCollectionMovies(collectionId: IdTrakt) {\n    viewModelScope.launch {\n      val loadingJob = launch {\n        delay(500)\n        itemsState.update {\n          it?.toMutableList()?.apply { add(LoadingItem) }\n        }\n      }\n      try {\n        val moviesItems = collectionMoviesCase.loadCollectionMovies(\n          collectionId = collectionId,\n          language = settingsRepository.language\n        )\n        itemsState.update {\n          it?.toMutableList()?.apply {\n            remove(LoadingItem)\n            addAll(moviesItems)\n          }\n        }\n      } catch (error: Throwable) {\n        // TODO Error\n        rethrowCancellation(error)\n      } finally {\n        loadingJob.cancel()\n      }\n    }\n  }\n\n  fun loadMissingImage(item: MovieDetailsCollectionItem, force: Boolean) {\n    if (item.id in imagesJobs.keys) {\n      return\n    }\n    imagesJobs[item.id] = true\n    viewModelScope.launch {\n      (item as? MovieItem)?.let {\n        updateItem(it.copy(isLoading = true))\n        val updatedItem = collectionMoviesImagesCase.loadMissingImage(it, force)\n        updateItem(updatedItem.copy(isLoading = false))\n      }\n      imagesJobs.remove(item.id)\n    }\n  }\n\n  fun loadMissingTranslation(item: MovieDetailsCollectionItem) {\n    val language = settingsRepository.language\n    if (item.id in translationsJobs.keys || language == Config.DEFAULT_LANGUAGE) {\n      return\n    }\n    translationsJobs[item.id] = true\n    viewModelScope.launch {\n      (item as? MovieItem)?.let {\n        val updatedItem = collectionMoviesTranslationsCase.loadMissingTranslation(it, language)\n        updateItem(updatedItem.copy(isLoading = false))\n      }\n      translationsJobs.remove(item.id)\n    }\n  }\n\n  private fun updateItem(newItem: MovieDetailsCollectionItem) {\n    val currentItems = itemsState.value?.toMutableList()\n    currentItems?.findReplace(newItem) { it.id == newItem.id }\n    itemsState.value = currentItems\n  }\n\n  val uiState = combine(\n    itemsState\n  ) { s1 ->\n    MovieDetailsCollectionUiState(\n      items = s1[0]\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = MovieDetailsCollectionUiState()\n  )\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/details/cases/MovieDetailsCollectionDetailsCase.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.details.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.movies.MovieCollectionsRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsCollectionDetailsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val collectionsRepository: MovieCollectionsRepository,\n) {\n\n  suspend fun loadCollection(collectionId: IdTrakt): MovieDetailsCollectionItem.HeaderItem =\n    withContext(dispatchers.IO) {\n      val collection = collectionsRepository.loadCollection(collectionId)\n        ?: throw Error(\"Requested collection must be available at this point\")\n\n      return@withContext MovieDetailsCollectionItem.HeaderItem(\n        title = collection.name,\n        description = collection.description\n      )\n    }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/details/cases/MovieDetailsCollectionImagesCase.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.details.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem.MovieItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsCollectionImagesCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val movieImagesProvider: MovieImagesProvider\n) {\n\n  suspend fun loadMissingImage(\n    item: MovieItem,\n    force: Boolean\n  ): MovieItem = withContext(dispatchers.IO) {\n    try {\n      val image = movieImagesProvider.loadRemoteImage(item.movie, item.image.type, force)\n      return@withContext item.copy(image = image)\n    } catch (error: Throwable) {\n      Timber.w(error)\n      return@withContext item.copy(image = Image.createUnavailable(item.image.type))\n    }\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/details/cases/MovieDetailsCollectionMoviesCase.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.details.cases\n\nimport com.michaldrabik.common.Config.DEFAULT_LANGUAGE\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.movies.MovieCollectionsRepository\nimport com.michaldrabik.repository.movies.MyMoviesRepository\nimport com.michaldrabik.repository.movies.WatchlistMoviesRepository\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsCollectionMoviesCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val collectionsRepository: MovieCollectionsRepository,\n  private val myMoviesRepository: MyMoviesRepository,\n  private val watchlistMoviesRepository: WatchlistMoviesRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val settingsSpoilersRepository: SettingsSpoilersRepository,\n  private val imagesProvider: MovieImagesProvider,\n) {\n\n  suspend fun loadCollectionMovies(\n    collectionId: IdTrakt,\n    language: String\n  ): List<MovieDetailsCollectionItem.MovieItem> = withContext(dispatchers.IO) {\n    val movies = collectionsRepository.loadCollectionItems(collectionId)\n    movies.mapIndexed { index, movie ->\n      async {\n        MovieDetailsCollectionItem.MovieItem(\n          rank = index + 1,\n          movie = movie,\n          image = imagesProvider.findCachedImage(movie, POSTER),\n          isMyMovie = myMoviesRepository.exists(movie.ids.trakt),\n          isWatchlist = watchlistMoviesRepository.exists(movie.ids.trakt),\n          translation = loadTranslation(movie, language),\n          spoilers = settingsSpoilersRepository.getAll(),\n          isLoading = false\n        )\n      }\n    }.awaitAll()\n  }\n\n  private suspend fun loadTranslation(movie: Movie, language: String): Translation? {\n    if (language == DEFAULT_LANGUAGE) return null\n    return translationsRepository.loadTranslation(\n      movie = movie,\n      language = language,\n      onlyLocal = true\n    )\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/details/cases/MovieDetailsCollectionTranslationsCase.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.details.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsCollectionTranslationsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val translationsRepository: TranslationsRepository,\n) {\n\n  suspend fun loadMissingTranslation(\n    item: MovieDetailsCollectionItem.MovieItem,\n    language: String\n  ) = withContext(dispatchers.IO) {\n    try {\n      val translation = translationsRepository.loadTranslation(item.movie, language) ?: Translation.EMPTY\n      return@withContext item.copy(translation = translation)\n    } catch (error: Throwable) {\n      Timber.w(error)\n      return@withContext item.copy(translation = Translation.EMPTY)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/details/recycler/MovieDetailsCollectionAdapter.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.details.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem.HeaderItem\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem.LoadingItem\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem.MovieItem\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.views.MovieDetailsCollectionHeaderView\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.views.MovieDetailsCollectionItemView\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.views.MovieDetailsCollectionLoadingView\n\nclass MovieDetailsCollectionAdapter(\n  var onItemClickListener: (MovieDetailsCollectionItem) -> Unit,\n  var onItemLongClickListener: (MovieDetailsCollectionItem) -> Unit,\n  val onMissingImageListener: (MovieDetailsCollectionItem, Boolean) -> Unit,\n  val onMissingTranslationListener: (MovieDetailsCollectionItem) -> Unit,\n) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n  companion object {\n    private const val VIEW_TYPE_ITEM = 1\n    private const val VIEW_TYPE_HEADER = 2\n    private const val VIEW_TYPE_LOADING = 3\n  }\n\n  private val asyncDiffer = AsyncListDiffer(this, MovieDetailsCollectionItemDiffCallback())\n\n  fun setItems(items: List<MovieDetailsCollectionItem>) = asyncDiffer.submitList(items)\n\n  override fun getItemViewType(position: Int) =\n    when (asyncDiffer.currentList[position]) {\n      is HeaderItem -> VIEW_TYPE_HEADER\n      is MovieItem -> VIEW_TYPE_ITEM\n      is LoadingItem -> VIEW_TYPE_LOADING\n      else -> throw IllegalStateException()\n    }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    when (viewType) {\n      VIEW_TYPE_ITEM -> BaseViewHolder(\n        MovieDetailsCollectionItemView(parent.context).apply {\n          onItemClickListener = this@MovieDetailsCollectionAdapter.onItemClickListener\n          onItemLongClickListener = this@MovieDetailsCollectionAdapter.onItemLongClickListener\n          onMissingImageListener = this@MovieDetailsCollectionAdapter.onMissingImageListener\n          onMissingTranslationListener = this@MovieDetailsCollectionAdapter.onMissingTranslationListener\n        }\n      )\n      VIEW_TYPE_HEADER -> BaseViewHolder(MovieDetailsCollectionHeaderView(parent.context))\n      VIEW_TYPE_LOADING -> BaseViewHolder(MovieDetailsCollectionLoadingView(parent.context))\n      else -> throw IllegalStateException()\n    }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) =\n    when (val item = asyncDiffer.currentList[position]) {\n      is HeaderItem -> (holder.itemView as MovieDetailsCollectionHeaderView).bind(item)\n      is MovieItem -> (holder.itemView as MovieDetailsCollectionItemView).bind(item)\n      is LoadingItem -> Unit\n    }\n\n  override fun getItemCount() = asyncDiffer.currentList.size\n\n  class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/details/recycler/MovieDetailsCollectionItem.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.details.recycler\n\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.Translation\nimport java.util.UUID\n\nsealed class MovieDetailsCollectionItem {\n\n  open val id: String\n    get() = UUID.randomUUID().toString()\n\n  data class HeaderItem(\n    val title: String,\n    val description: String,\n  ) : MovieDetailsCollectionItem()\n\n  data class MovieItem(\n    val rank: Int,\n    val movie: Movie,\n    val image: Image,\n    val isMyMovie: Boolean,\n    val isWatchlist: Boolean,\n    val translation: Translation?,\n    val spoilers: SpoilersSettings,\n    val isLoading: Boolean,\n  ) : MovieDetailsCollectionItem() {\n    override val id get() = \"${movie.traktId}\"\n  }\n\n  object LoadingItem : MovieDetailsCollectionItem()\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/details/recycler/MovieDetailsCollectionItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.details.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem.HeaderItem\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem.LoadingItem\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem.MovieItem\n\nclass MovieDetailsCollectionItemDiffCallback : DiffUtil.ItemCallback<MovieDetailsCollectionItem>() {\n\n  override fun areItemsTheSame(oldItem: MovieDetailsCollectionItem, newItem: MovieDetailsCollectionItem) =\n    when {\n      oldItem is HeaderItem && newItem is HeaderItem -> true\n      oldItem is MovieItem && newItem is MovieItem && oldItem.id == newItem.id -> true\n      oldItem is LoadingItem && newItem is LoadingItem -> true\n      else -> false\n    }\n\n  override fun areContentsTheSame(oldItem: MovieDetailsCollectionItem, newItem: MovieDetailsCollectionItem) =\n    when {\n      oldItem is HeaderItem && newItem is HeaderItem -> oldItem == newItem\n      oldItem is MovieItem && newItem is MovieItem -> oldItem == newItem\n      else -> false\n    }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/details/recycler/views/MovieDetailsCollectionHeaderView.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.details.recycler.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_movie.databinding.ViewMovieCollectionListHeaderBinding\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem\n\nclass MovieDetailsCollectionHeaderView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewMovieCollectionListHeaderBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    binding.subtitleText.setInitialLines(3)\n  }\n\n  fun bind(item: MovieDetailsCollectionItem.HeaderItem) {\n    with(binding) {\n      titleText.text = item.title\n      subtitleText.text = item.description.ifBlank { item.title }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/details/recycler/views/MovieDetailsCollectionItemView.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.details.recycler.views\n\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport androidx.core.content.ContextCompat\nimport androidx.core.widget.ImageViewCompat\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions\nimport com.michaldrabik.common.Config.IMAGE_FADE_DURATION_MS\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.invisible\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_model.ImageStatus\nimport com.michaldrabik.ui_movie.R\nimport com.michaldrabik.ui_movie.databinding.ViewMovieCollectionListItemBinding\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem\nimport com.michaldrabik.ui_movie.sections.collections.details.recycler.MovieDetailsCollectionItem.MovieItem\n\nclass MovieDetailsCollectionItemView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewMovieCollectionListItemBinding.inflate(LayoutInflater.from(context), this)\n\n  var onItemClickListener: ((MovieDetailsCollectionItem) -> Unit)? = null\n  var onItemLongClickListener: ((MovieDetailsCollectionItem) -> Unit)? = null\n  var onMissingImageListener: ((MovieDetailsCollectionItem, Boolean) -> Unit)? = null\n  var onMissingTranslationListener: ((MovieDetailsCollectionItem) -> Unit)? = null\n\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.mediaTileCorner) }\n  private val centerCropTransformation by lazy { CenterCrop() }\n  private val cornersTransformation by lazy { RoundedCorners(cornerRadius) }\n\n  private val colorAccent by lazy { ContextCompat.getColor(context, R.color.colorAccent) }\n  private val colorGray by lazy { ContextCompat.getColor(context, R.color.colorGrayLight) }\n\n  private lateinit var item: MovieDetailsCollectionItem\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    with(binding.rootLayout) {\n      onClick { onItemClickListener?.invoke(item) }\n      onLongClick { onItemLongClickListener?.invoke(item) }\n    }\n  }\n\n  fun bind(item: MovieItem) {\n    clear()\n    this.item = item\n\n    bindTitleDescription(item)\n    bindBadge(item.isMyMovie, item.isWatchlist)\n\n    binding.headerText.text = String.format(\"%s\", item.movie.released?.year ?: \"TBA\")\n    binding.rankText.text = item.rank.toString()\n\n    if (!item.isLoading) loadImage(item)\n  }\n\n  private fun bindTitleDescription(item: MovieItem) {\n    with(binding) {\n      titleText.text = when {\n        item.translation?.title?.isNotBlank() == true -> item.translation.title\n        else -> item.movie.title\n      }\n\n      var description = when {\n        item.translation?.overview?.isNotBlank() == true -> item.translation.overview\n        item.movie.overview.isNotBlank() -> item.movie.overview\n        else -> context.getString(R.string.textNoDescription)\n      }\n\n      val isMyMovieHidden = item.spoilers.isMyMoviesHidden && item.isMyMovie\n      val isWatchlistHidden = item.spoilers.isWatchlistMoviesHidden && item.isWatchlist\n      val isNotCollectedHidden = item.spoilers.isNotCollectedMoviesHidden && (!item.isMyMovie && !item.isWatchlist)\n\n      if (isMyMovieHidden || isWatchlistHidden || isNotCollectedHidden) {\n        descriptionText.tag = description\n        description = SPOILERS_REGEX.replace(description, SPOILERS_HIDE_SYMBOL)\n\n        if (item.spoilers.isTapToReveal) {\n          with(descriptionText) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      }\n\n      descriptionText.text = description\n    }\n  }\n\n  private fun bindBadge(isMyMovie: Boolean, isWatchlist: Boolean) {\n    with(binding) {\n      badgeImage.visibleIf(isMyMovie || isWatchlist)\n      ImageViewCompat.setImageTintList(\n        badgeImage,\n        ColorStateList.valueOf(if (isMyMovie) colorAccent else colorGray)\n      )\n    }\n  }\n\n  private fun loadImage(item: MovieDetailsCollectionItem) {\n    val image = when (item) {\n      is MovieItem -> item.image\n      else -> throw IllegalArgumentException()\n    }\n\n    if (image.status == ImageStatus.UNAVAILABLE) {\n      binding.movieImage.invisible()\n      binding.placeholderImage.fadeIn(IMAGE_FADE_DURATION_MS.toLong())\n      return\n    }\n\n    if (image.status == ImageStatus.UNKNOWN) {\n      onMissingImageListener?.invoke(item, true)\n      return\n    }\n\n    Glide.with(this)\n      .load(image.fullFileUrl)\n      .transform(centerCropTransformation, cornersTransformation)\n      .transition(DrawableTransitionOptions.withCrossFade(IMAGE_FADE_DURATION_MS))\n      .withSuccessListener {\n        binding.placeholderImage.invisible()\n        loadTranslation(item)\n      }\n      .withFailListener {\n        if (image.status == ImageStatus.AVAILABLE) {\n          binding.movieImage.invisible()\n          binding.placeholderImage.fadeIn(IMAGE_FADE_DURATION_MS.toLong())\n          loadTranslation(item)\n          return@withFailListener\n        }\n        onMissingImageListener?.invoke(item, false)\n      }\n      .into(binding.movieImage)\n  }\n\n  private fun loadTranslation(item: MovieDetailsCollectionItem) {\n    if (item is MovieItem && item.translation == null) {\n      onMissingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      badgeImage.invisible()\n      placeholderImage.invisible()\n      movieImage.visible()\n      Glide.with(this@MovieDetailsCollectionItemView).clear(movieImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/details/recycler/views/MovieDetailsCollectionLoadingView.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.details.recycler.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_movie.R\n\nclass MovieDetailsCollectionLoadingView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  init {\n    inflate(context, R.layout.view_movie_collection_list_loading, this)\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/list/MovieDetailsCollectionsFragment.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.list\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView.HORIZONTAL\nimport com.michaldrabik.repository.movies.MovieCollectionsRepository.Source\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.addDivider\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.MovieCollection\nimport com.michaldrabik.ui_movie.MovieDetailsEvent.OpenCollectionSheet\nimport com.michaldrabik.ui_movie.MovieDetailsFragment\nimport com.michaldrabik.ui_movie.MovieDetailsViewModel\nimport com.michaldrabik.ui_movie.R\nimport com.michaldrabik.ui_movie.databinding.FragmentMovieDetailsCollectionBinding\nimport com.michaldrabik.ui_movie.sections.collections.details.MovieDetailsCollectionBottomSheet\nimport com.michaldrabik.ui_movie.sections.collections.list.recycler.MovieCollectionAdapter\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_COLLECTION_ID\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass MovieDetailsCollectionsFragment : BaseFragment<MovieDetailsCollectionsViewModel>(R.layout.fragment_movie_details_collection) {\n\n  override val navigationId = R.id.movieDetailsFragment\n\n  private val parentViewModel by viewModels<MovieDetailsViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<MovieDetailsCollectionsViewModel>()\n\n  private val binding by viewBinding(FragmentMovieDetailsCollectionBinding::bind)\n\n  private var collectionsAdapter: MovieCollectionAdapter? = null\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    launchAndRepeatStarted(\n      { parentViewModel.parentMovieState.collect { it?.let { viewModel.loadCollections(it) } } },\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = { viewModel.loadLastOpenedCollection() }\n    )\n  }\n\n  private fun setupView() {\n    collectionsAdapter = MovieCollectionAdapter(\n      itemClickListener = { viewModel.loadCollection(it) }\n    )\n    binding.movieDetailsCollectionRecycler.apply {\n      setHasFixedSize(true)\n      adapter = collectionsAdapter\n      layoutManager = LinearLayoutManager(requireContext(), HORIZONTAL, false)\n      addDivider(R.drawable.divider_horizontal_list, HORIZONTAL)\n    }\n  }\n\n  private fun render(uiState: MovieDetailsCollectionsUiState) {\n    with(uiState) {\n      collections?.let { (collections, source) ->\n        collectionsAdapter?.setItems(collections)\n        if (collections.isNotEmpty()) {\n          (requireParentFragment() as MovieDetailsFragment).showCollectionsView(animate = source == Source.REMOTE)\n        }\n      }\n      isLoading.let {\n        binding.movieDetailsCollectionProgress.visibleIf(it)\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is OpenCollectionSheet -> openCollectionDetails(event.movie, event.collection)\n    }\n  }\n\n  @Suppress(\"DEPRECATION\")\n  private fun openCollectionDetails(\n    movie: Movie,\n    collection: MovieCollection,\n  ) {\n    requireParentFragment()\n      .setFragmentResultListener(NavigationArgs.REQUEST_DETAILS) { _, bundle ->\n        bundle.getParcelable<IdTrakt>(ARG_COLLECTION_ID)?.let {\n          viewModel.saveLastOpenedCollection(it)\n        }\n      }\n    val bundle = MovieDetailsCollectionBottomSheet.createBundle(\n      collectionId = collection.id,\n      sourceMovieId = movie.ids.trakt\n    )\n    navigateToSafe(R.id.actionMovieDetailsFragmentToCollection, bundle)\n  }\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    collectionsAdapter = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/list/MovieDetailsCollectionsUiState.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.list\n\nimport com.michaldrabik.repository.movies.MovieCollectionsRepository.Source\nimport com.michaldrabik.ui_model.MovieCollection\n\ndata class MovieDetailsCollectionsUiState(\n  val isLoading: Boolean = true,\n  val collections: Pair<List<MovieCollection>, Source>? = null,\n)\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/list/MovieDetailsCollectionsViewModel.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.list\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.movies.MovieCollectionsRepository.Source\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.MovieCollection\nimport com.michaldrabik.ui_movie.MovieDetailsEvent.OpenCollectionSheet\nimport com.michaldrabik.ui_movie.sections.collections.list.cases.MovieDetailsCollectionsCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@HiltViewModel\nclass MovieDetailsCollectionsViewModel @Inject constructor(\n  private val collectionsCase: MovieDetailsCollectionsCase,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private lateinit var movie: Movie\n  private var lastOpenedCollection: IdTrakt? = null\n\n  private val loadingState = MutableStateFlow(true)\n  private val movieCollectionState = MutableStateFlow<Pair<List<MovieCollection>, Source>?>(null)\n\n  fun loadCollections(movie: Movie) {\n    if (this::movie.isInitialized) {\n      return\n    }\n    this.movie = movie\n\n    viewModelScope.launch {\n      try {\n        movieCollectionState.value = collectionsCase.loadMovieCollections(movie)\n      } catch (error: Throwable) {\n        Timber.e(error)\n        rethrowCancellation(error)\n      } finally {\n        loadingState.value = false\n      }\n    }\n    Timber.d(\"Loading movie collections...\")\n  }\n\n  fun loadCollection(collection: MovieCollection) {\n    viewModelScope.launch {\n      eventChannel.send(OpenCollectionSheet(movie, collection))\n    }\n  }\n\n  fun loadLastOpenedCollection() {\n    lastOpenedCollection?.let { id ->\n      val collection = movieCollectionState.value?.first?.find { it.id == id }\n      collection?.let {\n        loadCollection(collection)\n        lastOpenedCollection = null\n      }\n    }\n  }\n\n  fun saveLastOpenedCollection(collectionId: IdTrakt) {\n    lastOpenedCollection = collectionId\n  }\n\n  val uiState = combine(\n    loadingState,\n    movieCollectionState\n  ) { s1, s2 ->\n    MovieDetailsCollectionsUiState(\n      isLoading = s1,\n      collections = s2\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = MovieDetailsCollectionsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/list/cases/MovieDetailsCollectionsCase.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.list.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.movies.MovieCollectionsRepository\nimport com.michaldrabik.repository.movies.MovieCollectionsRepository.Source\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.MovieCollection\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsCollectionsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val repository: MovieCollectionsRepository,\n) {\n\n  suspend fun loadMovieCollections(movie: Movie): Pair<List<MovieCollection>, Source> =\n    withContext(dispatchers.IO) {\n      try {\n        val (collections, source) = repository.loadCollections(movie.ids.trakt)\n        return@withContext Pair(\n          collections.filter { it.itemCount != -1 },\n          source\n        )\n      } catch (error: Throwable) {\n        return@withContext Pair(\n          emptyList(),\n          Source.LOCAL\n        )\n      }\n    }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/list/recycler/MovieCollectionAdapter.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.list.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_model.MovieCollection\n\nclass MovieCollectionAdapter(\n  private val itemClickListener: (MovieCollection) -> Unit,\n) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n  private val asyncDiffer = AsyncListDiffer(this, MovieCollectionDiffCallback())\n\n  fun setItems(newItems: List<MovieCollection>) {\n    with(asyncDiffer) {\n      submitList(newItems)\n    }\n  }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {\n    val view = MovieCollectionItemView(parent.context).apply {\n      itemClickListener = this@MovieCollectionAdapter.itemClickListener\n    }\n    return ViewHolder(view)\n  }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    (holder.itemView as MovieCollectionItemView).bind(item)\n  }\n\n  override fun getItemCount() = asyncDiffer.currentList.size\n\n  class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/list/recycler/MovieCollectionDiffCallback.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.list.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\nimport com.michaldrabik.ui_model.MovieCollection\n\nclass MovieCollectionDiffCallback : DiffUtil.ItemCallback<MovieCollection>() {\n\n  override fun areItemsTheSame(oldItem: MovieCollection, newItem: MovieCollection) =\n    oldItem.id == newItem.id\n\n  override fun areContentsTheSame(oldItem: MovieCollection, newItem: MovieCollection) =\n    oldItem == newItem\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/collections/list/recycler/MovieCollectionItemView.kt",
    "content": "package com.michaldrabik.ui_movie.sections.collections.list.recycler\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_model.MovieCollection\nimport com.michaldrabik.ui_movie.databinding.ViewMovieCollectionBinding\n\nclass MovieCollectionItemView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewMovieCollectionBinding.inflate(LayoutInflater.from(context), this)\n\n  private lateinit var item: MovieCollection\n  var itemClickListener: ((MovieCollection) -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)\n    clipChildren = false\n    binding.root.onClick {\n      itemClickListener?.invoke(item)\n    }\n  }\n\n  fun bind(item: MovieCollection) {\n    this.item = item\n    with(binding) {\n      title.text = item.name\n      description.text = item.description.ifBlank { item.name }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/people/MovieDetailsPeopleFragment.kt",
    "content": "package com.michaldrabik.ui_movie.sections.people\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.TextView\nimport androidx.fragment.app.clearFragmentResultListener\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.addDivider\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.trimWithSuffix\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_model.Person.Department\nimport com.michaldrabik.ui_movie.MovieDetailsEvent.OpenPeopleSheet\nimport com.michaldrabik.ui_movie.MovieDetailsEvent.OpenPersonSheet\nimport com.michaldrabik.ui_movie.MovieDetailsFragment\nimport com.michaldrabik.ui_movie.MovieDetailsViewModel\nimport com.michaldrabik.ui_movie.R\nimport com.michaldrabik.ui_movie.databinding.FragmentMovieDetailsPeopleBinding\nimport com.michaldrabik.ui_movie.sections.people.recycler.ActorsAdapter\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_PERSON\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_PERSON_ARGS\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_DETAILS\nimport com.michaldrabik.ui_people.details.PersonDetailsArgs\nimport com.michaldrabik.ui_people.details.PersonDetailsBottomSheet\nimport com.michaldrabik.ui_people.list.PeopleListBottomSheet\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass MovieDetailsPeopleFragment : BaseFragment<MovieDetailsPeopleViewModel>(R.layout.fragment_movie_details_people) {\n\n  override val navigationId = R.id.movieDetailsFragment\n\n  private val parentViewModel by viewModels<MovieDetailsViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<MovieDetailsPeopleViewModel>()\n  private val binding by viewBinding(FragmentMovieDetailsPeopleBinding::bind)\n\n  private var actorsAdapter: ActorsAdapter? = null\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    launchAndRepeatStarted(\n      { parentViewModel.parentMovieState.collect { it?.let { viewModel.loadPeople(it) } } },\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = { viewModel.loadLastPerson() }\n    )\n  }\n\n  private fun setupView() {\n    actorsAdapter = ActorsAdapter().apply {\n      itemClickListener = { viewModel.loadPersonDetails(it) }\n    }\n    binding.movieDetailsActorsRecycler.apply {\n      setHasFixedSize(true)\n      adapter = actorsAdapter\n      layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)\n      addDivider(R.drawable.divider_horizontal_list, LinearLayoutManager.HORIZONTAL)\n    }\n  }\n\n  private fun openPersonSheet(movie: Movie, person: Person, personArgs: PersonDetailsArgs?) {\n    handleSheetResult()\n    val bundle = PersonDetailsBottomSheet.createBundle(person, movie.ids.trakt, personArgs)\n    (requireParentFragment() as BaseFragment<*>)\n      .navigateToSafe(R.id.actionMovieDetailsFragmentToPerson, bundle)\n  }\n\n  private fun openPeopleSheet(event: OpenPeopleSheet) {\n    val (movie, people, department) = event\n\n    if (people.isEmpty()) return\n    if (people.size == 1) {\n      viewModel.loadPersonDetails(people.first())\n      return\n    }\n\n    handleSheetResult()\n\n    val title = (requireParentFragment() as MovieDetailsFragment).binding.movieDetailsTitle.text.toString()\n    val bundle = PeopleListBottomSheet.createBundle(movie.ids.trakt, title, Mode.MOVIES, department)\n    navigateToSafe(R.id.actionMovieDetailsFragmentToPeopleList, bundle)\n  }\n\n  private fun render(uiState: MovieDetailsPeopleUiState) {\n    with(uiState) {\n      with(binding) {\n        actors?.let {\n          if (actorsAdapter?.itemCount != 0) return@let\n          actorsAdapter?.setItems(it)\n          movieDetailsActorsRecycler.visibleIf(actors.isNotEmpty(), gone = false)\n          movieDetailsActorsEmptyView.visibleIf(actors.isEmpty())\n        }\n        crew?.let { renderCrew(it) }\n        isLoading.let {\n          movieDetailsActorsProgress.visibleIf(it)\n        }\n      }\n    }\n  }\n\n  private fun renderCrew(crew: Map<Department, List<Person>>) {\n\n    fun renderPeople(\n      labelView: View,\n      valueView: TextView,\n      people: List<Person>,\n      department: Department,\n    ) {\n      labelView.visibleIf(people.isNotEmpty())\n      valueView.visibleIf(people.isNotEmpty())\n      valueView.text = people\n        .take(2)\n        .joinToString(\"\\n\") { it.name.trimWithSuffix(20, \"…\") }\n        .plus(if (people.size > 2) \"\\n…\" else \"\")\n      valueView.onClick { viewModel.loadPeopleList(people, department) }\n    }\n\n    if (!crew.containsKey(Department.DIRECTING)) {\n      return\n    }\n\n    val directors = crew[Department.DIRECTING] ?: emptyList()\n    val writers = crew[Department.WRITING] ?: emptyList()\n    val sound = crew[Department.SOUND] ?: emptyList()\n\n    with(binding) {\n      renderPeople(movieDetailsDirectingLabel, movieDetailsDirectingValue, directors, Department.DIRECTING)\n      renderPeople(movieDetailsWritingLabel, movieDetailsWritingValue, writers, Department.WRITING)\n      renderPeople(movieDetailsMusicLabel, movieDetailsMusicValue, sound, Department.SOUND)\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is OpenPersonSheet -> openPersonSheet(event.movie, event.person, event.personArgs)\n      is OpenPeopleSheet -> openPeopleSheet(event)\n    }\n  }\n\n  @Suppress(\"DEPRECATION\")\n  private fun handleSheetResult() {\n    requireParentFragment()\n      .setFragmentResultListener(REQUEST_DETAILS) { _, bundle ->\n        val person = bundle.getParcelable<Person>(ARG_PERSON)\n        val personArgs = bundle.getParcelable<PersonDetailsArgs>(ARG_PERSON_ARGS)\n        person?.let {\n          viewModel.saveLastPerson(it, personArgs)\n          bundle.clear()\n        }\n        requireParentFragment().clearFragmentResultListener(REQUEST_DETAILS)\n      }\n  }\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    actorsAdapter = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/people/MovieDetailsPeopleUiState.kt",
    "content": "package com.michaldrabik.ui_movie.sections.people\n\nimport com.michaldrabik.ui_model.Person\n\ndata class MovieDetailsPeopleUiState(\n  val isLoading: Boolean = true,\n  val actors: List<Person>? = null,\n  val crew: Map<Person.Department, List<Person>>? = null,\n)\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/people/MovieDetailsPeopleViewModel.kt",
    "content": "package com.michaldrabik.ui_movie.sections.people\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_model.Person.Department\nimport com.michaldrabik.ui_movie.MovieDetailsEvent.OpenPeopleSheet\nimport com.michaldrabik.ui_movie.MovieDetailsEvent.OpenPersonSheet\nimport com.michaldrabik.ui_movie.sections.people.cases.MovieDetailsPeopleCase\nimport com.michaldrabik.ui_people.details.PersonDetailsArgs\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@HiltViewModel\nclass MovieDetailsPeopleViewModel @Inject constructor(\n  private val actorsCase: MovieDetailsPeopleCase,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private lateinit var movie: Movie\n\n  private var lastOpenedPerson: Person? = null\n  private var lastOpenedPersonArgs: PersonDetailsArgs? = null\n\n  private val loadingState = MutableStateFlow(true)\n  private val actorsState = MutableStateFlow<List<Person>?>(null)\n  private val crewState = MutableStateFlow<Map<Department, List<Person>>?>(null)\n\n  fun loadPeople(movie: Movie) {\n    if (this::movie.isInitialized) return\n    this.movie = movie\n\n    viewModelScope.launch {\n      try {\n        val people = actorsCase.loadPeople(movie)\n\n        val actors = people.getOrDefault(Department.ACTING, emptyList())\n        val crew = people.filter { it.key !in arrayOf(Department.ACTING, Department.UNKNOWN) }\n\n        loadingState.value = false\n        actorsState.value = actors\n        crewState.value = crew\n\n        actorsCase.preloadDetails(actors)\n      } catch (error: Throwable) {\n        loadingState.value = false\n        actorsState.value = emptyList()\n        crewState.value = emptyMap()\n        rethrowCancellation(error)\n      }\n    }\n    Timber.d(\"Loading people...\")\n  }\n\n  fun loadPersonDetails(\n    person: Person,\n    personArgs: PersonDetailsArgs? = null,\n  ) {\n    viewModelScope.launch {\n      eventChannel.send(OpenPersonSheet(movie, person, personArgs))\n    }\n  }\n\n  fun loadPeopleList(people: List<Person>, department: Department) {\n    viewModelScope.launch {\n      eventChannel.send(OpenPeopleSheet(movie, people, department))\n    }\n  }\n\n  fun loadLastPerson() {\n    lastOpenedPerson?.let {\n      loadPersonDetails(it, lastOpenedPersonArgs)\n      lastOpenedPerson = null\n      lastOpenedPersonArgs = null\n    }\n  }\n\n  fun saveLastPerson(\n    person: Person,\n    personArgs: PersonDetailsArgs?,\n  ) {\n    lastOpenedPerson = person\n    lastOpenedPersonArgs = personArgs\n  }\n\n  val uiState = combine(\n    loadingState,\n    actorsState,\n    crewState\n  ) { s1, s2, s3 ->\n    MovieDetailsPeopleUiState(\n      isLoading = s1,\n      actors = s2,\n      crew = s3,\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = MovieDetailsPeopleUiState()\n  )\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/people/cases/MovieDetailsPeopleCase.kt",
    "content": "package com.michaldrabik.ui_movie.sections.people.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.PeopleRepository\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Person\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.CoroutineExceptionHandler\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.supervisorScope\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsPeopleCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val peopleRepository: PeopleRepository\n) {\n\n  suspend fun loadPeople(movie: Movie) =\n    withContext(dispatchers.IO) {\n      peopleRepository.loadAllForMovie(movie.ids)\n    }\n\n  suspend fun preloadDetails(people: List<Person>) {\n    supervisorScope {\n      val errorHandler = CoroutineExceptionHandler { _, _ -> Timber.d(\"Failed to preload details.\") }\n      people.take(5).forEach {\n        launch(errorHandler) {\n          withContext(dispatchers.IO) {\n            peopleRepository.loadDetails(it)\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/people/recycler/ActorView.kt",
    "content": "package com.michaldrabik.ui_movie.sections.people.recycler\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.engine.DiskCacheStrategy.DATA\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade\nimport com.michaldrabik.common.Config.IMAGE_FADE_DURATION_MS\nimport com.michaldrabik.common.Config.TMDB_IMAGE_BASE_ACTOR_URL\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_movie.R\nimport com.michaldrabik.ui_movie.databinding.ViewActorMovieBinding\n\nclass ActorView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewActorMovieBinding.inflate(LayoutInflater.from(context), this)\n\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.actorMovieTileCorner) }\n\n  init {\n    layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)\n    clipChildren = false\n  }\n\n  fun bind(item: Person, clickListener: (Person) -> Unit) {\n    clear()\n    tag = item.ids.tmdb.id\n    onClick { clickListener(item) }\n    binding.actorMovieName.text = item.name.split(\" \").joinToString(\"\\n\")\n    loadImage(item)\n  }\n\n  private fun loadImage(actor: Person) {\n    with(binding) {\n      if (actor.imagePath.isNullOrBlank()) {\n        actorMoviePlaceholder.visible()\n        actorMovieImage.gone()\n        return\n      }\n      Glide.with(this@ActorView)\n        .load(\"$TMDB_IMAGE_BASE_ACTOR_URL${actor.imagePath}\")\n        .diskCacheStrategy(DATA)\n        .transform(CenterCrop(), RoundedCorners(cornerRadius))\n        .transition(withCrossFade(IMAGE_FADE_DURATION_MS))\n        .withFailListener {\n          actorMoviePlaceholder.visible()\n          actorMovieImage.gone()\n        }\n        .into(actorMovieImage)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      actorMovieImage.visible()\n      actorMoviePlaceholder.gone()\n      Glide.with(this@ActorView).clear(actorMovieImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/people/recycler/ActorsAdapter.kt",
    "content": "package com.michaldrabik.ui_movie.sections.people.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_model.Person\n\nclass ActorsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n  private val items: MutableList<Person> = mutableListOf()\n\n  var itemClickListener: (Person) -> Unit = {}\n\n  fun setItems(items: List<Person>) {\n    this.items.apply {\n      clear()\n      addAll(items)\n    }\n    notifyDataSetChanged()\n  }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    ViewHolderShow(ActorView(parent.context))\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    (holder.itemView as ActorView).bind(items[position], itemClickListener)\n  }\n\n  override fun getItemCount() = items.size\n\n  class ViewHolderShow(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/ratings/MovieDetailsRatingsFragment.kt",
    "content": "package com.michaldrabik.ui_movie.sections.ratings\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.AppCountry\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.openImdbUrl\nimport com.michaldrabik.ui_base.utilities.extensions.openWebUrl\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.IdImdb\nimport com.michaldrabik.ui_movie.MovieDetailsViewModel\nimport com.michaldrabik.ui_movie.R\nimport com.michaldrabik.ui_movie.databinding.FragmentMovieDetailsRatingsBinding\nimport com.michaldrabik.ui_movie.helpers.MovieLink\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass MovieDetailsRatingsFragment : BaseFragment<MovieDetailsRatingsViewModel>(R.layout.fragment_movie_details_ratings) {\n\n  private val parentViewModel by viewModels<MovieDetailsViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<MovieDetailsRatingsViewModel>()\n  private val binding by viewBinding(FragmentMovieDetailsRatingsBinding::bind)\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    launchAndRepeatStarted(\n      { parentViewModel.parentMovieState.collect { it?.let { viewModel.loadRatings(it) } } },\n      { parentViewModel.parentFollowedState.collect { it?.let { viewModel.refreshRatings() } } },\n      { viewModel.uiState.collect { render(it) } }\n    )\n  }\n\n  private fun render(uiState: MovieDetailsRatingsUiState) {\n    with(uiState) {\n      with(binding) {\n        ratings?.let {\n          if (movieDetailsRatings.isBound() && !isRefreshingRatings) {\n            return\n          }\n          movieDetailsRatings.bind(ratings)\n          movie?.let {\n            movieDetailsRatings.onTraktClick = { openMovieLink(MovieLink.TRAKT, movie.traktId.toString()) }\n            movieDetailsRatings.onImdbClick = { openMovieLink(MovieLink.IMDB, movie.ids.imdb.id) }\n            movieDetailsRatings.onMetaClick = { openMovieLink(MovieLink.METACRITIC, movie.title) }\n            movieDetailsRatings.onRottenClick = {\n              val url = it.rottenTomatoesUrl\n              if (!url.isNullOrBlank()) {\n                openWebUrl(url) ?: openMovieLink(MovieLink.ROTTEN, \"${movie.title} ${movie.year}\")\n              } else {\n                openMovieLink(MovieLink.ROTTEN, \"${movie.title} ${movie.year}\")\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  private fun openMovieLink(\n    link: MovieLink,\n    id: String,\n    country: AppCountry = AppCountry.UNITED_STATES,\n  ) {\n    if (link == MovieLink.IMDB) {\n      openImdbUrl(IdImdb(id)) ?: showSnack(MessageEvent.Info(R.string.errorCouldNotFindApp))\n    } else {\n      openWebUrl(link.getUri(id, country)) ?: showSnack(MessageEvent.Info(R.string.errorCouldNotFindApp))\n    }\n  }\n\n  override fun setupBackPressed() = Unit\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/ratings/MovieDetailsRatingsUiState.kt",
    "content": "package com.michaldrabik.ui_movie.sections.ratings\n\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Ratings\n\ndata class MovieDetailsRatingsUiState(\n  val movie: Movie? = null,\n  val ratings: Ratings? = null,\n  val isRefreshingRatings: Boolean = false\n)\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/ratings/MovieDetailsRatingsViewModel.kt",
    "content": "package com.michaldrabik.ui_movie.sections.ratings\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Ratings\nimport com.michaldrabik.ui_movie.sections.ratings.cases.MovieDetailsRatingCase\nimport com.michaldrabik.ui_movie.sections.ratings.cases.MovieDetailsRatingSpoilersCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport java.util.Locale\nimport javax.inject.Inject\n\n@HiltViewModel\nclass MovieDetailsRatingsViewModel @Inject constructor(\n  private val ratingsCase: MovieDetailsRatingCase,\n  private val ratingsSpoilersCase: MovieDetailsRatingSpoilersCase,\n) : ViewModel() {\n\n  private lateinit var movie: Movie\n\n  private val movieState = MutableStateFlow<Movie?>(null)\n  private val ratingsState = MutableStateFlow<Ratings?>(null)\n  private val isRefreshingRatingsState = MutableStateFlow(false)\n\n  fun loadRatings(movie: Movie) {\n    if (this::movie.isInitialized) return\n    this.movie = movie\n\n    viewModelScope.launch {\n      movieState.value = movie\n\n      val traktRatings = Ratings(\n        trakt = Ratings.Value(String.format(Locale.ENGLISH, \"%.1f\", movie.rating), false),\n        imdb = Ratings.Value(null, true),\n        metascore = Ratings.Value(null, true),\n        rottenTomatoes = Ratings.Value(null, true)\n      )\n\n      try {\n        ratingsState.value = ratingsSpoilersCase.hideSpoilerRatings(movie, traktRatings)\n        val ratings = ratingsCase.loadExternalRatings(movie)\n        ratingsState.value = ratingsSpoilersCase.hideSpoilerRatings(movie, ratings)\n      } catch (error: Throwable) {\n        ratingsState.value = ratingsSpoilersCase.hideSpoilerRatings(movie, traktRatings)\n        rethrowCancellation(error)\n      }\n    }\n  }\n\n  fun refreshRatings() {\n    val movie = movieState.value\n    val ratings = ratingsState.value\n    viewModelScope.launch {\n      if (movie != null && ratings != null) {\n        isRefreshingRatingsState.value = true\n        ratingsState.value = ratingsSpoilersCase.hideSpoilerRatings(movie, ratings)\n      }\n    }\n  }\n\n  val uiState = combine(\n    ratingsState,\n    movieState,\n    isRefreshingRatingsState\n  ) { s1, s2, s3 ->\n    MovieDetailsRatingsUiState(\n      ratings = s1,\n      movie = s2,\n      isRefreshingRatings = s3\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = MovieDetailsRatingsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/ratings/cases/MovieDetailsRatingCase.kt",
    "content": "package com.michaldrabik.ui_movie.sections.ratings.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.ui_model.Movie\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsRatingCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val ratingsRepository: RatingsRepository,\n) {\n\n  suspend fun loadRating(movie: Movie) = withContext(dispatchers.IO) {\n    ratingsRepository.movies.loadRatings(listOf(movie)).firstOrNull()\n  }\n\n  suspend fun loadExternalRatings(movie: Movie) = withContext(dispatchers.IO) {\n    ratingsRepository.movies.external.loadRatings(movie)\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/ratings/cases/MovieDetailsRatingSpoilersCase.kt",
    "content": "package com.michaldrabik.ui_movie.sections.ratings.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Ratings\nimport com.michaldrabik.ui_movie.MovieDetailsUiState.FollowedState\nimport com.michaldrabik.ui_movie.cases.MovieDetailsHiddenCase\nimport com.michaldrabik.ui_movie.cases.MovieDetailsMyMoviesCase\nimport com.michaldrabik.ui_movie.cases.MovieDetailsWatchlistCase\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsRatingSpoilersCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val myMoviesCase: MovieDetailsMyMoviesCase,\n  private val watchlistCase: MovieDetailsWatchlistCase,\n  private val hiddenCase: MovieDetailsHiddenCase,\n  private val settingsSpoilersRepository: SettingsSpoilersRepository,\n) {\n\n  suspend fun hideSpoilerRatings(\n    movie: Movie,\n    ratings: Ratings\n  ): Ratings = withContext(dispatchers.IO) {\n    val spoilers = settingsSpoilersRepository.getAll()\n\n    val isMy = async { myMoviesCase.isMyMovie(movie) }\n    val isWatchlist = async { watchlistCase.isWatchlist(movie) }\n    val isHidden = async { hiddenCase.isHidden(movie) }\n\n    val state = FollowedState(\n      isMyMovie = isMy.await(),\n      isWatchlist = isWatchlist.await(),\n      isHidden = isHidden.await(),\n      withAnimation = false\n    )\n\n    val isMyHidden = spoilers.isMyMoviesRatingsHidden && state.isMyMovie\n    val isWatchlistHidden = spoilers.isWatchlistMoviesRatingsHidden && state.isWatchlist\n    val isHiddenHidden = spoilers.isHiddenMoviesRatingsHidden && state.isHidden\n    val isNotCollectedHidden = spoilers.isNotCollectedMoviesRatingsHidden && !state.isInCollection()\n\n    return@withContext ratings.copy(\n      isHidden = isMyHidden || isWatchlistHidden || isHiddenHidden || isNotCollectedHidden\n    )\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/related/MovieDetailsRelatedFragment.kt",
    "content": "package com.michaldrabik.ui_movie.sections.related\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.clearFragmentResultListener\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView.HORIZONTAL\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.sheets.context_menu.ContextMenuBottomSheet\nimport com.michaldrabik.ui_base.utilities.extensions.addDivider\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_movie.MovieDetailsViewModel\nimport com.michaldrabik.ui_movie.R\nimport com.michaldrabik.ui_movie.databinding.FragmentMovieDetailsRelatedBinding\nimport com.michaldrabik.ui_movie.sections.related.recycler.RelatedListItem\nimport com.michaldrabik.ui_movie.sections.related.recycler.RelatedMovieAdapter\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_MOVIE_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_ITEM_MENU\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass MovieDetailsRelatedFragment : BaseFragment<MovieDetailsRelatedViewModel>(R.layout.fragment_movie_details_related) {\n\n  private val parentViewModel by viewModels<MovieDetailsViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<MovieDetailsRelatedViewModel>()\n  private val binding by viewBinding(FragmentMovieDetailsRelatedBinding::bind)\n\n  private var relatedAdapter: RelatedMovieAdapter? = null\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    launchAndRepeatStarted(\n      { parentViewModel.parentMovieState.collect { it?.let { viewModel.initRelatedMovies(it) } } },\n      { viewModel.uiState.collect { render(it) } }\n    )\n  }\n\n  private fun setupView() {\n    relatedAdapter = RelatedMovieAdapter(\n      itemClickListener = ::openDetails,\n      itemLongClickListener = ::openContextMenu,\n      missingImageListener = { ids, force -> viewModel.loadMissingImage(ids, force) }\n    )\n    binding.movieDetailsRelatedRecycler.apply {\n      setHasFixedSize(true)\n      adapter = relatedAdapter\n      layoutManager = LinearLayoutManager(requireContext(), HORIZONTAL, false)\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      addDivider(R.drawable.divider_horizontal_list, HORIZONTAL)\n    }\n  }\n\n  private fun openDetails(item: RelatedListItem) {\n    val bundle = Bundle().apply { putLong(ARG_MOVIE_ID, item.movie.traktId) }\n    navigateTo(R.id.actionMovieDetailsFragmentToSelf, bundle)\n  }\n\n  private fun openContextMenu(item: RelatedListItem) {\n    requireParentFragment()\n      .setFragmentResultListener(REQUEST_ITEM_MENU) { requestKey, _ ->\n        if (requestKey == REQUEST_ITEM_MENU) {\n          viewModel.loadRelatedMovies()\n        }\n        requireParentFragment().clearFragmentResultListener(REQUEST_ITEM_MENU)\n      }\n\n    val bundle = ContextMenuBottomSheet.createBundle(item.movie.ids.trakt)\n    navigateTo(R.id.actionMovieDetailsFragmentToContext, bundle)\n  }\n\n  private fun render(uiState: MovieDetailsRelatedUiState) {\n    with(uiState) {\n      with(binding) {\n        relatedMovies?.let {\n          relatedAdapter?.setItems(it)\n          movieDetailsRelatedRecycler.visibleIf(it.isNotEmpty())\n          movieDetailsRelatedLabel.fadeIf(it.isNotEmpty(), hardware = true)\n        }\n        isLoading.let {\n          movieDetailsRelatedProgress.visibleIf(it)\n        }\n      }\n    }\n  }\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    relatedAdapter = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/related/MovieDetailsRelatedUiState.kt",
    "content": "package com.michaldrabik.ui_movie.sections.related\n\nimport com.michaldrabik.ui_movie.sections.related.recycler.RelatedListItem\n\ndata class MovieDetailsRelatedUiState(\n  val isLoading: Boolean = true,\n  val relatedMovies: List<RelatedListItem>? = null,\n)\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/related/MovieDetailsRelatedViewModel.kt",
    "content": "package com.michaldrabik.ui_movie.sections.related\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_movie.cases.MovieDetailsMyMoviesCase\nimport com.michaldrabik.ui_movie.sections.related.cases.MovieDetailsRelatedCase\nimport com.michaldrabik.ui_movie.sections.related.recycler.RelatedListItem\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@HiltViewModel\nclass MovieDetailsRelatedViewModel @Inject constructor(\n  private val relatedCase: MovieDetailsRelatedCase,\n  private val myMoviesCase: MovieDetailsMyMoviesCase,\n  private val imagesProvider: MovieImagesProvider,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private lateinit var movie: Movie\n\n  private val loadingState = MutableStateFlow(true)\n  private val relatedItemsState = MutableStateFlow<List<RelatedListItem>?>(null)\n\n  fun initRelatedMovies(movie: Movie) {\n    if (this::movie.isInitialized) return\n    this.movie = movie\n    loadRelatedMovies()\n  }\n\n  fun loadRelatedMovies() {\n    if (!this::movie.isInitialized) return\n    viewModelScope.launch {\n      try {\n        val (myMovies, watchlistMovies) = myMoviesCase.getAllIds()\n        val related = relatedCase.loadRelatedMovies(movie).map {\n          val image = imagesProvider.findCachedImage(it, ImageType.POSTER)\n          RelatedListItem(\n            movie = it,\n            image = image,\n            isFollowed = it.traktId in myMovies,\n            isWatchlist = it.traktId in watchlistMovies\n          )\n        }\n        relatedItemsState.value = related\n      } catch (error: Throwable) {\n        relatedItemsState.value = emptyList()\n        Timber.e(error)\n        rethrowCancellation(error)\n      } finally {\n        loadingState.value = false\n      }\n    }\n    Timber.d(\"Loading related movies...\")\n  }\n\n  fun loadMissingImage(item: RelatedListItem, force: Boolean) {\n\n    fun updateItem(new: RelatedListItem) {\n      val currentItems = uiState.value.relatedMovies?.toMutableList()\n      currentItems?.findReplace(new) { it isSameAs new }\n      relatedItemsState.value = currentItems\n    }\n\n    viewModelScope.launch {\n      updateItem(item.copy(isLoading = true))\n      try {\n        val image = imagesProvider.loadRemoteImage(item.movie, item.image.type, force)\n        updateItem(item.copy(isLoading = false, image = image))\n      } catch (t: Throwable) {\n        updateItem(item.copy(isLoading = false, image = Image.createUnavailable(item.image.type)))\n      }\n    }\n  }\n\n  val uiState = combine(\n    loadingState,\n    relatedItemsState\n  ) { s1, s2 ->\n    MovieDetailsRelatedUiState(\n      isLoading = s1,\n      relatedMovies = s2\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = MovieDetailsRelatedUiState()\n  )\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/related/cases/MovieDetailsRelatedCase.kt",
    "content": "package com.michaldrabik.ui_movie.sections.related.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.ui_model.Movie\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsRelatedCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val moviesRepository: MoviesRepository\n) {\n\n  // TODO Add Hidden items\n  suspend fun loadRelatedMovies(movie: Movie): List<Movie> = withContext(dispatchers.IO) {\n    moviesRepository.relatedMovies.loadAll(movie)\n      .sortedWith(compareBy({ it.votes }, { it.rating }))\n      .reversed()\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/related/recycler/RelatedItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_movie.sections.related.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass RelatedItemDiffCallback : DiffUtil.ItemCallback<RelatedListItem>() {\n\n  override fun areItemsTheSame(oldItem: RelatedListItem, newItem: RelatedListItem) =\n    oldItem.movie.ids.trakt == newItem.movie.ids.trakt\n\n  override fun areContentsTheSame(oldItem: RelatedListItem, newItem: RelatedListItem) =\n    oldItem.image == newItem.image &&\n      oldItem.isLoading == newItem.isLoading &&\n      oldItem.isFollowed == newItem.isFollowed &&\n      oldItem.isWatchlist == newItem.isWatchlist\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/related/recycler/RelatedListItem.kt",
    "content": "package com.michaldrabik.ui_movie.sections.related.recycler\n\nimport com.michaldrabik.ui_base.common.MovieListItem\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Movie\n\ndata class RelatedListItem(\n  override val movie: Movie,\n  override val image: Image,\n  override var isLoading: Boolean = false,\n  val isFollowed: Boolean = false,\n  val isWatchlist: Boolean = false\n) : MovieListItem\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/related/recycler/RelatedMovieAdapter.kt",
    "content": "package com.michaldrabik.ui_movie.sections.related.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseMovieAdapter\n\nclass RelatedMovieAdapter(\n  private val itemClickListener: (RelatedListItem) -> Unit,\n  private val itemLongClickListener: (RelatedListItem) -> Unit,\n  private val missingImageListener: (RelatedListItem, Boolean) -> Unit,\n) : BaseMovieAdapter<RelatedListItem>() {\n\n  override val asyncDiffer = AsyncListDiffer(this, RelatedItemDiffCallback())\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    ViewHolderShow(\n      RelatedMovieView(parent.context).apply {\n        itemClickListener = this@RelatedMovieAdapter.itemClickListener\n        itemLongClickListener = this@RelatedMovieAdapter.itemLongClickListener\n        missingImageListener = this@RelatedMovieAdapter.missingImageListener\n      }\n    )\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    (holder.itemView as RelatedMovieView).bind(item)\n  }\n\n  class ViewHolderShow(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/related/recycler/RelatedMovieView.kt",
    "content": "package com.michaldrabik.ui_movie.sections.related.recycler\n\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport androidx.core.content.ContextCompat\nimport androidx.core.widget.ImageViewCompat\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.ui_base.common.views.MovieView\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.ImageStatus.AVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNAVAILABLE\nimport com.michaldrabik.ui_movie.R\nimport com.michaldrabik.ui_movie.databinding.ViewRelatedMovieBinding\n\nclass RelatedMovieView : MovieView<RelatedListItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewRelatedMovieBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)\n    clipChildren = false\n    onClick { itemClickListener?.invoke(item) }\n    onLongClick { itemLongClickListener?.invoke(item) }\n  }\n\n  private val colorAccent by lazy { ContextCompat.getColor(context, R.color.colorAccent) }\n  private val colorGray by lazy { ContextCompat.getColor(context, R.color.colorGrayLight) }\n\n  override val imageView: ImageView = binding.relatedMovieImage\n  override val placeholderView: ImageView = binding.relatedMoviePlaceholder\n\n  private lateinit var item: RelatedListItem\n\n  override fun bind(item: RelatedListItem) {\n    clear()\n    this.item = item\n\n    binding.relatedMovieTitle.text = item.movie.title\n    binding.relatedMovieBadge.visibleIf(item.isFollowed || item.isWatchlist)\n\n    ImageViewCompat.setImageTintList(\n      binding.relatedMovieBadge,\n      ColorStateList.valueOf(if (item.isFollowed) colorAccent else colorGray)\n    )\n\n    loadImage(item)\n  }\n\n  override fun loadImage(item: RelatedListItem) {\n    if (item.image.status == UNAVAILABLE) {\n      binding.relatedMovieTitle.visible()\n    }\n    super.loadImage(item)\n  }\n\n  override fun onImageLoadFail(item: RelatedListItem) {\n    super.onImageLoadFail(item)\n    if (item.image.status == AVAILABLE) {\n      binding.relatedMovieTitle.visible()\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      relatedMoviePlaceholder.gone()\n      relatedMovieTitle.gone()\n      Glide.with(this@RelatedMovieView)\n        .clear(relatedMovieImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/streamings/MovieDetailsStreamingsFragment.kt",
    "content": "package com.michaldrabik.ui_movie.sections.streamings\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.extensions.addDivider\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_movie.MovieDetailsFragment\nimport com.michaldrabik.ui_movie.MovieDetailsViewModel\nimport com.michaldrabik.ui_movie.R\nimport com.michaldrabik.ui_movie.databinding.FragmentMovieDetailsStreamingsBinding\nimport com.michaldrabik.ui_streamings.recycler.StreamingAdapter\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass MovieDetailsStreamingsFragment : BaseFragment<MovieDetailsStreamingsViewModel>(R.layout.fragment_movie_details_streamings) {\n\n  private val parentViewModel by viewModels<MovieDetailsViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<MovieDetailsStreamingsViewModel>()\n  private val binding by viewBinding(FragmentMovieDetailsStreamingsBinding::bind)\n\n  private var streamingAdapter: StreamingAdapter? = null\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    launchAndRepeatStarted(\n      { parentViewModel.parentMovieState.collect { it?.let { viewModel.loadStreamings(it) } } },\n      { viewModel.uiState.collect { render(it) } }\n    )\n  }\n\n  private fun setupView() {\n    streamingAdapter = StreamingAdapter()\n    binding.movieDetailsStreamingsRecycler.apply {\n      setHasFixedSize(true)\n      adapter = streamingAdapter\n      layoutManager = LinearLayoutManager(requireContext(), HORIZONTAL, false)\n      addDivider(R.drawable.divider_horizontal_list, HORIZONTAL)\n    }\n  }\n\n  private fun render(uiState: MovieDetailsStreamingsUiState) {\n    with(uiState) {\n      streamings?.let {\n        if (streamingAdapter?.itemCount != 0) return@let\n        val (items, isLocal) = it\n        streamingAdapter?.setItems(items)\n        if (items.isNotEmpty()) {\n          (requireParentFragment() as MovieDetailsFragment).showStreamingsView(animate = !isLocal)\n        }\n      }\n    }\n  }\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    streamingAdapter = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/streamings/MovieDetailsStreamingsUiState.kt",
    "content": "package com.michaldrabik.ui_movie.sections.streamings\n\nimport com.michaldrabik.ui_model.StreamingService\n\ndata class MovieDetailsStreamingsUiState(\n  val streamings: StreamingsState? = null,\n) {\n\n  data class StreamingsState(\n    val streamings: List<StreamingService>,\n    val isLocal: Boolean,\n  )\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/streamings/MovieDetailsStreamingsViewModel.kt",
    "content": "package com.michaldrabik.ui_movie.sections.streamings\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_movie.sections.streamings.MovieDetailsStreamingsUiState.StreamingsState\nimport com.michaldrabik.ui_movie.sections.streamings.cases.MovieDetailsStreamingCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass MovieDetailsStreamingsViewModel @Inject constructor(\n  private val streamingCase: MovieDetailsStreamingCase,\n) : ViewModel() {\n\n  private lateinit var movie: Movie\n\n  private val streamingsState = MutableStateFlow<StreamingsState?>(null)\n  private val loadingState = MutableStateFlow(false)\n\n  fun loadStreamings(movie: Movie) {\n    if (this::movie.isInitialized) return\n    this.movie = movie\n\n    viewModelScope.launch {\n      loadingState.value = true\n      try {\n        val localStreamings = streamingCase.getLocalStreamingServices(movie)\n        streamingsState.value = StreamingsState(localStreamings, isLocal = true)\n\n        val remoteStreamings = streamingCase.loadStreamingServices(movie)\n        streamingsState.value = StreamingsState(remoteStreamings, isLocal = false)\n      } catch (error: Throwable) {\n        streamingsState.value = StreamingsState(emptyList(), isLocal = false)\n        rethrowCancellation(error)\n      } finally {\n        loadingState.value = false\n      }\n    }\n  }\n\n  val uiState = combine(\n    streamingsState,\n    loadingState\n  ) { s1, _ ->\n    MovieDetailsStreamingsUiState(\n      streamings = s1\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = MovieDetailsStreamingsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/sections/streamings/cases/MovieDetailsStreamingCase.kt",
    "content": "package com.michaldrabik.ui_movie.sections.streamings.cases\n\nimport com.michaldrabik.common.ConfigVariant\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.repository.movies.MovieStreamingsRepository\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.AppCountry\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.StreamingService\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MovieDetailsStreamingCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val streamingsRepository: MovieStreamingsRepository,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun getLocalStreamingServices(movie: Movie): List<StreamingService> =\n    withContext(dispatchers.IO) {\n      if (!settingsRepository.streamingsEnabled) {\n        return@withContext emptyList()\n      }\n      val country = AppCountry.fromCode(settingsRepository.country)\n      val localData = streamingsRepository.getLocalStreamings(movie, country.code)\n      localData.first\n    }\n\n  suspend fun loadStreamingServices(movie: Movie): List<StreamingService> =\n    withContext(dispatchers.IO) {\n      if (!settingsRepository.streamingsEnabled) {\n        return@withContext emptyList()\n      }\n      val country = AppCountry.fromCode(settingsRepository.country)\n      val (localItems, timestamp) = streamingsRepository.getLocalStreamings(movie, country.code)\n      if (timestamp != null && timestamp.plusSeconds(ConfigVariant.STREAMINGS_CACHE_DURATION / 1000).isAfter(nowUtc())) {\n        return@withContext localItems\n      }\n      streamingsRepository.loadRemoteStreamings(movie, country.code)\n    }\n}\n"
  },
  {
    "path": "ui-movie/src/main/java/com/michaldrabik/ui_movie/views/AddToMoviesButton.kt",
    "content": "package com.michaldrabik.ui_movie.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport androidx.core.view.isVisible\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.colorStateListFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_movie.R\nimport com.michaldrabik.ui_movie.databinding.ViewAddToMoviesButtonBinding\nimport com.michaldrabik.ui_movie.views.AddToMoviesButton.State.ADD\nimport com.michaldrabik.ui_movie.views.AddToMoviesButton.State.IN_MY_MOVIES\nimport com.michaldrabik.ui_movie.views.AddToMoviesButton.State.IN_WATCHLIST\n\nclass AddToMoviesButton : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewAddToMoviesButtonBinding.inflate(LayoutInflater.from(context), this)\n\n  var onAddMyMoviesClickListener: (() -> Unit)? = null\n  var onAddWatchLaterClickListener: (() -> Unit)? = null\n  var onRemoveClickListener: (() -> Unit)? = null\n\n  private var state: State = ADD\n  private var isAnimating = false\n\n  init {\n    with(binding) {\n      addToMyMoviesButton.onClick { if (!isAnimating) onAddMyMoviesClickListener?.invoke() }\n      watchlistButton.onClick { if (!isAnimating) onAddWatchLaterClickListener?.invoke() }\n      addedToButton.onClick { if (!isAnimating) onRemoveClickListener?.invoke() }\n      checkButton.onClick { if (!isAnimating) onAddMyMoviesClickListener?.invoke() }\n    }\n  }\n\n  fun setState(state: State, animate: Boolean = false) {\n    if (state == this.state) return\n    this.state = state\n\n    val duration = if (animate) 175L else 0\n    val startDelay = if (animate) 200L else 0\n    if (animate) isAnimating = true\n\n    with(binding) {\n      when (state) {\n        ADD -> {\n          addToMyMoviesButton.setText(R.string.textAddToMyMovies)\n          checkButton.fadeOut(duration, withHardware = true)\n          addedToButton.fadeOut(duration, withHardware = true)\n          addToMyMoviesButton.fadeIn(duration, startDelay = startDelay, withHardware = true)\n          watchlistButton.fadeIn(duration, startDelay = startDelay, withHardware = true) { isAnimating = false }\n        }\n        IN_MY_MOVIES -> {\n          val color = context.colorFromAttr(R.attr.colorAccent)\n          val colorState = context.colorStateListFromAttr(R.attr.colorAccent)\n\n          addToMyMoviesButton.fadeOut(duration, withHardware = true)\n          watchlistButton.fadeOut(duration, withHardware = true)\n          checkButton.fadeOut(duration, withHardware = true)\n          addedToButton.fadeOut(duration, withHardware = true) {\n            addedToButton.run {\n              setIconResource(R.drawable.ic_bookmark_full)\n              setText(R.string.textInMyMovies)\n              setTextColor(color)\n              iconTint = colorState\n              strokeColor = colorState\n              rippleColor = colorState\n              fadeIn(duration, withHardware = true) { isAnimating = false }\n            }\n          }\n        }\n        IN_WATCHLIST -> {\n          val color = context.colorFromAttr(android.R.attr.textColorSecondary)\n          val colorState = context.colorStateListFromAttr(android.R.attr.textColorSecondary)\n\n          addToMyMoviesButton.fadeOut(duration, withHardware = true)\n          watchlistButton.fadeOut(duration, withHardware = true)\n          checkButton.fadeIn(duration, startDelay = startDelay, withHardware = true)\n          addedToButton.run {\n            setIconResource(R.drawable.ic_bookmark_full)\n            setText(R.string.textInMoviesWatchlist)\n            setTextColor(color)\n            iconTint = colorState\n            strokeColor = colorState\n            rippleColor = colorState\n            fadeIn(duration, startDelay = startDelay, withHardware = true) { isAnimating = false }\n          }\n        }\n        State.IN_HIDDEN -> {\n          val delay = if (addToMyMoviesButton.isVisible) startDelay else 0\n          addToMyMoviesButton.fadeOut(duration, withHardware = true)\n          watchlistButton.fadeOut(duration, withHardware = true)\n          checkButton.fadeOut(duration, withHardware = true)\n          with(addedToButton) {\n            fadeOut(duration, withHardware = true) {\n              val color = context.colorFromAttr(android.R.attr.textColorSecondary)\n              val colorState = context.colorStateListFromAttr(android.R.attr.textColorSecondary)\n              setIconResource(R.drawable.ic_eye_no)\n              setText(R.string.textInHidden)\n              setTextColor(color)\n              iconTint = colorState\n              strokeColor = colorState\n              rippleColor = colorState\n              fadeIn(duration, startDelay = delay, withHardware = true) { isAnimating = false }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  override fun setEnabled(enabled: Boolean) {\n    with(binding) {\n      addToMyMoviesButton.isEnabled = enabled\n      addToMyMoviesButton.isClickable = enabled\n      watchlistButton.isEnabled = enabled\n      watchlistButton.isClickable = enabled\n      addedToButton.isEnabled = enabled\n      addedToButton.isClickable = enabled\n      checkButton.isEnabled = enabled\n      checkButton.isClickable = enabled\n    }\n  }\n\n  enum class State {\n    ADD,\n    IN_MY_MOVIES,\n    IN_WATCHLIST,\n    IN_HIDDEN\n  }\n}\n"
  },
  {
    "path": "ui-movie/src/main/res/drawable/bg_check_ripple.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:color=\"?android:attr/colorControlHighlight\"\n  >\n  <item android:id=\"@android:id/mask\">\n    <shape android:shape=\"oval\">\n      <solid android:color=\"#000000\" />\n      <size\n        android:width=\"48dp\"\n        android:height=\"48dp\"\n        />\n    </shape>\n  </item>\n</ripple>\n"
  },
  {
    "path": "ui-movie/src/main/res/drawable/bg_collection.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"6dp\" />\n  <solid android:color=\"?attr/colorBadgeBackground\" />\n</shape>"
  },
  {
    "path": "ui-movie/src/main/res/drawable/bg_collection_ripple.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:color=\"?android:attr/colorControlHighlight\"\n  >\n  <item android:id=\"@android:id/mask\">\n    <shape android:shape=\"rectangle\">\n      <solid android:color=\"#000000\" />\n      <corners android:radius=\"6dp\" />\n    </shape>\n  </item>\n  <item android:drawable=\"@drawable/bg_collection\" />\n</ripple>"
  },
  {
    "path": "ui-movie/src/main/res/drawable/bg_rank.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"100dp\" />\n  <solid android:color=\"@color/colorBlackTranslucent\" />\n</shape>"
  },
  {
    "path": "ui-movie/src/main/res/drawable/divider_horizontal_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <size\n    android:width=\"@dimen/dividerHorizontalList\"\n    android:height=\"1dp\"\n    />\n</shape>"
  },
  {
    "path": "ui-movie/src/main/res/layout/fragment_movie_details.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  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/movieDetailsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:layout_marginTop=\"-0dp\"\n  >\n\n  <androidx.core.widget.NestedScrollView\n    android:id=\"@+id/movieDetailsMainLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:scrollbars=\"none\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    >\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:id=\"@+id/movieDetailsMainContent\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:clipChildren=\"false\"\n      android:clipToPadding=\"false\"\n      android:descendantFocusability=\"blocksDescendants\"\n      android:paddingBottom=\"@dimen/spaceNormal\"\n      >\n\n      <androidx.constraintlayout.widget.Guideline\n        android:id=\"@+id/movieDetailsImageGuideline\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:orientation=\"horizontal\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator1\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:layout_constraintGuide_begin=\"230dp\"\n        />\n\n      <ImageView\n        android:id=\"@+id/movieDetailsImage\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsImageGuideline\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n      <ImageView\n        android:id=\"@+id/movieDetailsPlaceholder\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"?attr/colorPlaceholderBackground\"\n        android:padding=\"74dp\"\n        android:src=\"@drawable/ic_film\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsImageGuideline\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:tint=\"?attr/colorPlaceholderIcon\"\n        tools:visibility=\"visible\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsTitle\"\n        style=\"@style/ImageTitle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"@dimen/spaceNormal\"\n        android:gravity=\"start\"\n        android:maxLines=\"2\"\n        android:paddingStart=\"@dimen/spaceNormal\"\n        android:paddingEnd=\"@dimen/spaceNormal\"\n        android:textAlignment=\"viewStart\"\n        android:textSize=\"25sp\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsImageGuideline\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        tools:text=\"Lord Of The Rings\"\n        />\n\n      <ImageView\n        android:id=\"@+id/movieDetailsShareButton\"\n        android:layout_width=\"@dimen/backArrowSize\"\n        android:layout_height=\"@dimen/backArrowSize\"\n        android:background=\"?android:attr/selectableItemBackground\"\n        android:padding=\"@dimen/backArrowPadding\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:srcCompat=\"@drawable/ic_share\"\n        />\n\n      <View\n        android:id=\"@+id/separator1\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:background=\"?attr/colorSeparator\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsExtraInfo\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsImageGuideline\"\n        />\n\n      <ProgressBar\n        android:id=\"@+id/movieDetailsImageProgress\"\n        style=\"@style/ProgressBar.Dark\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/movieDetailsImage\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/movieDetailsImage\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsExtraInfo\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginTop=\"@dimen/spaceMedium\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:ellipsize=\"end\"\n        android:gravity=\"start\"\n        android:maxLines=\"1\"\n        android:textAlignment=\"viewStart\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"14sp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsStatus\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator1\"\n        tools:text=\"2019 (PL) | 126 min | Drama, SF, Horror\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsStatus\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceSmall\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"13sp\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsAddButton\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/movieDetailsExtraInfo\"\n        tools:text=\"Post Production\"\n        />\n\n      <com.michaldrabik.ui_movie.views.AddToMoviesButton\n        android:id=\"@+id/movieDetailsAddButton\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsManageListsLabel\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsStatus\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsManageListsLabel\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginTop=\"@dimen/spaceMedium\"\n        android:drawablePadding=\"@dimen/spaceSmall\"\n        android:fontFamily=\"sans-serif-medium\"\n        android:gravity=\"start|center_vertical\"\n        android:text=\"@string/textMovieManageLists\"\n        android:textAlignment=\"viewStart\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"13sp\"\n        app:drawableStartCompat=\"@drawable/ic_lists\"\n        app:drawableTint=\"?android:attr/textColorPrimary\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator4\"\n        app:layout_constraintEnd_toStartOf=\"@id/movieDetailsHideLabel\"\n        app:layout_constraintHorizontal_bias=\"0\"\n        app:layout_constraintHorizontal_chainStyle=\"packed\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsAddButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsHideLabel\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"20dp\"\n        android:drawablePadding=\"6dp\"\n        android:fontFamily=\"sans-serif-medium\"\n        android:gravity=\"start|center_vertical\"\n        android:text=\"@string/textHide\"\n        android:textAlignment=\"viewStart\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"13sp\"\n        app:drawableStartCompat=\"@drawable/ic_eye_no\"\n        app:drawableTint=\"?android:attr/textColorPrimary\"\n        app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsManageListsLabel\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/movieDetailsManageListsLabel\"\n        app:layout_constraintTop_toTopOf=\"@id/movieDetailsManageListsLabel\"\n        />\n\n      <View\n        android:id=\"@+id/separator4\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginTop=\"18dp\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:background=\"?attr/colorSeparator\"\n        app:layout_constraintBottom_toTopOf=\"@+id/movieDetailsRatingsFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.0\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsManageListsLabel\"\n        />\n\n      <com.michaldrabik.ui_base.common.views.FoldableTextView\n        android:id=\"@+id/movieDetailsDescription\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceTiny\"\n        android:ellipsize=\"end\"\n        android:gravity=\"start\"\n        android:textAlignment=\"viewStart\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"14sp\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsActorsFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsStreamingsFragment\"\n        app:layout_goneMarginBottom=\"@dimen/spaceHuge\"\n        tools:targetApi=\"o\"\n        tools:text=\"Description\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/movieDetailsRatingsFragment\"\n        android:name=\"com.michaldrabik.ui_movie.sections.ratings.MovieDetailsRatingsFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceTiny\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceNormal\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsStreamingsFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/separator4\"\n        tools:layout=\"@layout/fragment_movie_details_ratings\"\n        />\n\n      <View\n        android:id=\"@+id/separator2\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginTop=\"14dp\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceNormal\"\n        android:background=\"?attr/colorSeparator\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsCommentsButton\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.0\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsActorsFragment\"\n        app:layout_goneMarginBottom=\"@dimen/spaceBig\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/movieDetailsStreamingsFragment\"\n        android:name=\"com.michaldrabik.ui_movie.sections.streamings.MovieDetailsStreamingsFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsDescription\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/movieDetailsRatingsFragment\"\n        tools:layout_height=\"40dp\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/movieDetailsActorsFragment\"\n        android:name=\"com.michaldrabik.ui_movie.sections.people.MovieDetailsPeopleFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceMedium\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator2\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsDescription\"\n        tools:layout=\"@layout/fragment_movie_details_people\"\n        tools:layout_height=\"100dp\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsTrailerButton\"\n        style=\"@style/MovieDetails.ExtraButton\"\n        android:text=\"@string/textMovieTrailer\"\n        app:drawableTopCompat=\"@drawable/ic_play\"\n        app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsCommentsButton\"\n        app:layout_constraintEnd_toStartOf=\"@+id/movieDetailsRateButton\"\n        app:layout_constraintHorizontal_chainStyle=\"spread\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@id/movieDetailsCommentsButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsLinksButton\"\n        style=\"@style/MovieDetails.ExtraButton\"\n        android:text=\"@string/textLink\"\n        app:drawableTopCompat=\"@drawable/ic_link\"\n        app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsCommentsButton\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@+id/movieDetailsCommentsButton\"\n        app:layout_constraintTop_toTopOf=\"@id/movieDetailsCommentsButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsRateButton\"\n        style=\"@style/MovieDetails.ExtraButton\"\n        android:text=\"@string/textMovieRate\"\n        app:drawableTopCompat=\"@drawable/ic_star\"\n        app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsCommentsButton\"\n        app:layout_constraintEnd_toStartOf=\"@+id/movieDetailsCommentsButton\"\n        app:layout_constraintStart_toEndOf=\"@id/movieDetailsTrailerButton\"\n        app:layout_constraintTop_toTopOf=\"@id/movieDetailsCommentsButton\"\n        />\n\n      <ProgressBar\n        android:id=\"@+id/movieDetailsRateProgress\"\n        style=\"@style/ProgressBar.Dark\"\n        android:layout_width=\"30dp\"\n        android:layout_height=\"30dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsRateButton\"\n        app:layout_constraintEnd_toEndOf=\"@id/movieDetailsRateButton\"\n        app:layout_constraintStart_toStartOf=\"@id/movieDetailsRateButton\"\n        app:layout_constraintTop_toTopOf=\"@id/movieDetailsRateButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsCommentsButton\"\n        style=\"@style/MovieDetails.ExtraButton\"\n        android:text=\"@string/textMovieComments2\"\n        app:drawableTopCompat=\"@drawable/ic_comment\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator5\"\n        app:layout_constraintEnd_toStartOf=\"@+id/movieDetailsLinksButton\"\n        app:layout_constraintStart_toEndOf=\"@+id/movieDetailsRateButton\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator2\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/movieDetailsCollectionsFragment\"\n        android:name=\"com.michaldrabik.ui_movie.sections.collections.list.MovieDetailsCollectionsFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:visibility=\"gone\"\n        app:layout_constrainedHeight=\"true\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator5\"\n        tools:layout=\"@layout/fragment_movie_details_collection\"\n        tools:layout_height=\"40dp\"\n        tools:visibility=\"visible\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/movieDetailsRelatedFragment\"\n        android:name=\"com.michaldrabik.ui_movie.sections.related.MovieDetailsRelatedFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsCollectionsFragment\"\n        app:layout_goneMarginTop=\"@dimen/spaceNormal\"\n        tools:layout=\"@layout/fragment_movie_details_related\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsCustomImagesButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:drawablePadding=\"@dimen/spaceTiny\"\n        android:fontFamily=\"sans-serif-medium\"\n        android:gravity=\"center_vertical\"\n        android:text=\"@string/textSetCustomImages\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"13sp\"\n        android:visibility=\"gone\"\n        app:drawableStartCompat=\"@drawable/ic_custom_image\"\n        app:drawableTint=\"?android:attr/textColorPrimary\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsPremiumAd\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsRelatedFragment\"\n        app:layout_goneMarginTop=\"0dp\"\n        tools:visibility=\"visible\"\n        />\n\n      <View\n        android:id=\"@+id/separator5\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:background=\"?attr/colorSeparator\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsCollectionsFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.0\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsCommentsButton\"\n        app:layout_goneMarginBottom=\"@dimen/spaceBig\"\n        app:layout_goneMarginTop=\"54dp\"\n        tools:visibility=\"visible\"\n        />\n\n      <com.michaldrabik.ui_base.common.views.PremiumAdView\n        android:id=\"@+id/movieDetailsPremiumAd\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"100dp\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginTop=\"18dp\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsCustomImagesButton\"\n        />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n  </androidx.core.widget.NestedScrollView>\n\n  <ProgressBar\n    android:id=\"@+id/movieDetailsMainProgress\"\n    style=\"@style/ProgressBar.Accent\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:visibility=\"gone\"\n    />\n\n  <ImageView\n    android:id=\"@+id/movieDetailsBackArrow\"\n    android:layout_width=\"@dimen/backArrowSize\"\n    android:layout_height=\"@dimen/backArrowSize\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:padding=\"@dimen/backArrowPadding\"\n    app:srcCompat=\"@drawable/ic_arrow_back\"\n    />\n\n</FrameLayout>\n"
  },
  {
    "path": "ui-movie/src/main/res/layout/fragment_movie_details_collection.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <TextView\n    android:id=\"@+id/movieDetailsCollectionLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:text=\"@string/textMovieCollections\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"16sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"0\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/movieDetailsCollectionRecycler\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"0dp\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginBottom=\"@dimen/spaceBig\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    app:layout_constrainedHeight=\"true\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHeight_min=\"@dimen/movieCollectionHeight\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/movieDetailsCollectionLabel\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/movieDetailsCollectionProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsCollectionRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/movieDetailsCollectionRecycler\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-movie/src/main/res/layout/fragment_movie_details_people.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/movieDetailsActorsRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:minHeight=\"@dimen/actorMovieTileImageHeight\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:visibility=\"invisible\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/movieDetailsActorsProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsActorsRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <TextView\n    android:id=\"@+id/movieDetailsActorsEmptyView\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    android:gravity=\"center\"\n    android:text=\"@string/textMovieActorsEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsActorsRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/movieDetailsDirectingLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"13dp\"\n    android:gravity=\"center_vertical\"\n    android:text=\"@string/textDirecting\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"11sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/movieDetailsDirectingValue\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/movieDetailsActorsRecycler\"\n    app:layout_goneMarginTop=\"@dimen/spaceHuge\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/movieDetailsDirectingValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:minWidth=\"50dp\"\n    android:text=\"-\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"11sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintStart_toStartOf=\"@id/movieDetailsDirectingLabel\"\n    app:layout_constraintTop_toBottomOf=\"@id/movieDetailsDirectingLabel\"\n    tools:ignore=\"HardcodedText\"\n    tools:text=\"Steven\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/movieDetailsWritingLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:text=\"@string/textWriting\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"11sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsDirectingLabel\"\n    app:layout_constraintStart_toEndOf=\"@id/movieDetailsDirectingValue\"\n    app:layout_constraintTop_toTopOf=\"@id/movieDetailsDirectingLabel\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/movieDetailsWritingValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:minWidth=\"50dp\"\n    android:text=\"-\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"11sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintStart_toStartOf=\"@id/movieDetailsWritingLabel\"\n    app:layout_constraintTop_toBottomOf=\"@id/movieDetailsWritingLabel\"\n    tools:ignore=\"HardcodedText\"\n    tools:text=\"Steven Spilberg\\nTest\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/movieDetailsMusicLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"0dp\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:text=\"@string/textMusic\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"11sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsDirectingLabel\"\n    app:layout_constraintStart_toEndOf=\"@id/movieDetailsWritingValue\"\n    app:layout_constraintTop_toTopOf=\"@id/movieDetailsDirectingLabel\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/movieDetailsMusicValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:text=\"-\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"11sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"@id/movieDetailsMusicLabel\"\n    app:layout_constraintTop_toBottomOf=\"@id/movieDetailsMusicLabel\"\n    tools:ignore=\"HardcodedText\"\n    tools:text=\"Steven Spilberg\"\n    tools:visibility=\"visible\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-movie/src/main/res/layout/fragment_movie_details_ratings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipToPadding=\"false\"\n  >\n\n  <com.michaldrabik.ui_base.common.views.RatingsStripView\n    android:id=\"@+id/movieDetailsRatings\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    />\n\n</FrameLayout>\n"
  },
  {
    "path": "ui-movie/src/main/res/layout/fragment_movie_details_related.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  tools:layout_marginTop=\"-0dp\"\n  >\n\n  <TextView\n    android:id=\"@+id/movieDetailsRelatedLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:text=\"@string/textMovieYouMayAlsoLike\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"16sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintBottom_toTopOf=\"@id/movieDetailsRelatedRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"0\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/movieDetailsRelatedRecycler\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"0dp\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginBottom=\"@dimen/spaceNormal\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:visibility=\"invisible\"\n    app:layout_constrainedHeight=\"true\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHeight_min=\"@dimen/relatedMovieHeight\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/movieDetailsRelatedLabel\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/movieDetailsRelatedProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsRelatedRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/movieDetailsRelatedRecycler\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-movie/src/main/res/layout/fragment_movie_details_streamings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  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=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/movieDetailsStreamingsRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/streamingTileHeight\"\n    android:layout_marginTop=\"@dimen/spaceMicro\"\n    android:layout_marginBottom=\"@dimen/spaceNormal\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    app:layout_constraintBottom_toTopOf=\"@id/movieDetailsDescription\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@+id/movieDetailsRatings\"\n    />\n\n</FrameLayout>\n"
  },
  {
    "path": "ui-movie/src/main/res/layout/view_actor_movie.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"wrap_content\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/actorMovieRoot\"\n    android:layout_width=\"@dimen/actorMovieTileImageWidth\"\n    android:layout_height=\"@dimen/actorMovieTileImageHeight\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/actorMovieImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      />\n\n    <ImageView\n      android:id=\"@+id/actorMoviePlaceholder\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"@dimen/actorMovieTilePlaceholder\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_person_outline\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/actorMovieName\"\n      style=\"@style/ImageTitle\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"bottom|start\"\n      android:layout_marginStart=\"5dp\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:layout_marginBottom=\"3dp\"\n      android:gravity=\"start\"\n      android:maxLines=\"2\"\n      android:textAlignment=\"viewStart\"\n      android:textSize=\"@dimen/actorMovieTileTextSize\"\n      android:translationZ=\"10dp\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Erin Moriarty\"\n      />\n\n  </FrameLayout>\n\n</merge>"
  },
  {
    "path": "ui-movie/src/main/res/layout/view_add_to_movies_button.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:gravity=\"center\"\n    android:orientation=\"horizontal\"\n    >\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/addToMyMoviesButton\"\n      style=\"@style/RoundOutlinedButton\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:layout_weight=\"2\"\n      android:gravity=\"center\"\n      android:includeFontPadding=\"false\"\n      android:text=\"@string/textAddToMyMovies\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"@dimen/addToButtonTextSize\"\n      app:rippleColor=\"?android:attr/textColorPrimary\"\n      app:strokeColor=\"?android:attr/textColorPrimary\"\n      tools:visibility=\"gone\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/watchlistButton\"\n      style=\"@style/RoundOutlinedButton\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:layout_weight=\"1\"\n      android:gravity=\"center\"\n      android:maxLines=\"2\"\n      android:padding=\"0dp\"\n      android:text=\"@string/textWatchlist\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"@dimen/addToButtonTextSize\"\n      app:rippleColor=\"?android:attr/textColorSecondary\"\n      app:strokeWidth=\"0dp\"\n      tools:visibility=\"gone\"\n      />\n\n  </LinearLayout>\n\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:orientation=\"horizontal\"\n    >\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/addedToButton\"\n      style=\"@style/RoundOutlinedButton\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_weight=\"1\"\n      android:gravity=\"center\"\n      android:text=\"@string/textInMyMovies\"\n      android:textColor=\"?attr/colorAccent\"\n      android:textSize=\"@dimen/addToButtonTextSize\"\n      android:visibility=\"gone\"\n      app:icon=\"@drawable/ic_bookmark_full\"\n      app:iconGravity=\"textStart\"\n      app:iconPadding=\"@dimen/spaceTiny\"\n      app:iconTint=\"?attr/colorAccent\"\n      app:rippleColor=\"?attr/colorAccent\"\n      app:strokeColor=\"?attr/colorAccent\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageButton\n      android:id=\"@+id/checkButton\"\n      android:layout_width=\"48dp\"\n      android:layout_height=\"48dp\"\n      android:layout_marginStart=\"8dp\"\n      android:background=\"@drawable/bg_check_ripple\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_check\"\n      app:tint=\"?android:textColorPrimary\"\n      tools:visibility=\"visible\"\n      />\n\n  </LinearLayout>\n\n</merge>\n"
  },
  {
    "path": "ui-movie/src/main/res/layout/view_movie_collection.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"wrap_content\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/rootLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/movieCollectionHeight\"\n    android:background=\"@drawable/bg_collection_ripple\"\n    android:elevation=\"@dimen/elevationTiny\"\n    android:paddingStart=\"@dimen/spaceMedium\"\n    android:paddingTop=\"@dimen/spaceSmall\"\n    android:paddingEnd=\"@dimen/spaceMedium\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <TextView\n      android:id=\"@+id/title\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:ellipsize=\"end\"\n      android:fontFamily=\"sans-serif-medium\"\n      android:gravity=\"start|center_vertical\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toTopOf=\"@id/description\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Google Play Movies\"\n      />\n\n    <TextView\n      android:id=\"@+id/description\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceMicro\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start|center_vertical\"\n      android:maxLines=\"2\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"11sp\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/title\"\n      app:layout_constraintWidth_max=\"232dp\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-movie/src/main/res/layout/view_movie_collection_details.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  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/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/itemsRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@android:color/transparent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:minHeight=\"1000dp\"\n    android:overScrollMode=\"never\"\n    android:paddingTop=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"56dp\"\n    />\n\n  <com.google.android.material.floatingactionbutton.FloatingActionButton\n    android:id=\"@+id/backToTopButton\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"bottom|end\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    android:rotation=\"270\"\n    android:visibility=\"gone\"\n    app:backgroundTint=\"?attr/colorAccent\"\n    app:fabSize=\"mini\"\n    app:layout_behavior=\"com.google.android.material.floatingactionbutton.FloatingActionButton$Behavior\"\n    app:maxImageSize=\"30dp\"\n    app:srcCompat=\"@drawable/ic_arrow_right\"\n    app:tint=\"?attr/textColorOnSurface\"\n    tools:ignore=\"ContentDescription\"\n    tools:visibility=\"visible\"\n    />\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/snackbarLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"top\"\n    android:layout_marginTop=\"@dimen/personImageTipMargin\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "ui-movie/src/main/res/layout/view_movie_collection_items_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/itemsRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@android:color/transparent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingTop=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceNormal\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "ui-movie/src/main/res/layout/view_movie_collection_list_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/rootLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:clipToPadding=\"false\"\n    android:paddingBottom=\"@dimen/spaceMedium\"\n    >\n\n    <TextView\n      android:id=\"@+id/titleText\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start\"\n      android:maxLines=\"2\"\n      android:textAlignment=\"viewStart\"\n      android:textAllCaps=\"true\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"20sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintBottom_toTopOf=\"@id/subtitleText\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:text=\"Marvel Movies Collection\"\n      />\n\n    <com.michaldrabik.ui_base.common.views.FoldableTextView\n      android:id=\"@+id/subtitleText\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginTop=\"@dimen/spaceMicro\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"13sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/titleText\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-movie/src/main/res/layout/view_movie_collection_list_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/rootLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:clipToPadding=\"false\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingTop=\"@dimen/spaceSmall\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <androidx.constraintlayout.widget.Guideline\n      android:id=\"@+id/guideline1\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"vertical\"\n      app:layout_constraintGuide_begin=\"@dimen/movieCollectionImageGuide\"\n      />\n\n    <ImageView\n      android:id=\"@+id/movieImage\"\n      android:layout_width=\"@dimen/movieCollectionImageWidth\"\n      android:layout_height=\"@dimen/movieCollectionImageHeight\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/placeholderImage\"\n      android:layout_width=\"@dimen/movieCollectionImageWidth\"\n      android:layout_height=\"@dimen/movieCollectionImageHeight\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"18dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_film\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/rankText\"\n      android:layout_width=\"17dp\"\n      android:layout_height=\"17dp\"\n      android:layout_margin=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_rank\"\n      android:gravity=\"center\"\n      android:includeFontPadding=\"false\"\n      android:textColor=\"?attr/textColorOnSurface\"\n      android:textSize=\"10sp\"\n      android:textStyle=\"bold\"\n      android:translationZ=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"@id/movieImage\"\n      app:layout_constraintEnd_toEndOf=\"@id/movieImage\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"10\"\n      />\n\n    <ImageView\n      android:id=\"@+id/badgeImage\"\n      style=\"@style/Badge\"\n      android:layout_width=\"18dp\"\n      android:layout_height=\"18dp\"\n      android:layout_marginEnd=\"1dp\"\n      android:translationY=\"-3dp\"\n      app:layout_constraintEnd_toEndOf=\"@id/movieImage\"\n      app:layout_constraintTop_toTopOf=\"@id/movieImage\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/headerText\"\n      style=\"@style/CollectionItem.Header\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:textSize=\"@dimen/movieCollectionHeaderSize\"\n      app:layout_constraintBottom_toTopOf=\"@id/titleText\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/guideline1\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"2023\"\n      />\n\n    <TextView\n      android:id=\"@+id/titleText\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:textSize=\"@dimen/movieCollectionTitleSize\"\n      app:layout_constraintBottom_toTopOf=\"@id/descriptionText\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/guideline1\"\n      app:layout_constraintTop_toBottomOf=\"@id/headerText\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Kill Bill\"\n      />\n\n    <TextView\n      android:id=\"@+id/descriptionText\"\n      style=\"@style/CollectionItem.Description\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:textSize=\"@dimen/movieCollectionDescriptionSize\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/guideline1\"\n      app:layout_constraintTop_toBottomOf=\"@id/titleText\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-movie/src/main/res/layout/view_movie_collection_list_loading.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <ProgressBar\n    android:id=\"@+id/progressBar\"\n    style=\"@style/ProgressBar.Accent\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewPersonDetailsImageGuide2\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPersonDetailsImageGuide1\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPersonDetailsSubtitle\"\n    />\n\n</merge>\n\n"
  },
  {
    "path": "ui-movie/src/main/res/layout/view_related_movie.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"wrap_content\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/relatedMovieRoot\"\n    android:layout_width=\"@dimen/relatedMovieWidth\"\n    android:layout_height=\"@dimen/relatedMovieHeight\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/relatedMovieImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      />\n\n    <ImageView\n      android:id=\"@+id/relatedMoviePlaceholder\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"@dimen/relatedTileMoviePlaceholder\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_film\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/relatedMovieTitle\"\n      style=\"@style/ImageTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"bottom|start\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:layout_marginBottom=\"@dimen/spaceMicro\"\n      android:maxLines=\"1\"\n      android:textSize=\"11sp\"\n      android:translationZ=\"10dp\"\n      android:visibility=\"gone\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Erin Moriarty\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/relatedMovieBadge\"\n      style=\"@style/Badge\"\n      android:layout_width=\"22dp\"\n      android:layout_height=\"22dp\"\n      android:layout_marginEnd=\"@dimen/spaceMicro\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:translationY=\"-3dp\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n  </FrameLayout>\n\n</merge>"
  },
  {
    "path": "ui-movie/src/main/res/layout-sw600dp/fragment_movie_details.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  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/movieDetailsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:layout_marginTop=\"-0dp\"\n  >\n\n  <androidx.core.widget.NestedScrollView\n    android:id=\"@+id/movieDetailsMainLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:scrollbars=\"none\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    >\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:id=\"@+id/movieDetailsMainContent\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:clipChildren=\"false\"\n      android:clipToPadding=\"false\"\n      android:descendantFocusability=\"blocksDescendants\"\n      android:paddingBottom=\"@dimen/spaceBig\"\n      >\n\n      <androidx.constraintlayout.widget.Guideline\n        android:id=\"@+id/movieDetailsImageGuideline\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:orientation=\"horizontal\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator1\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:layout_constraintGuide_begin=\"230dp\"\n        />\n\n      <androidx.constraintlayout.widget.Guideline\n        android:id=\"@+id/movieDetailsImageGuidelineVertical\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:orientation=\"vertical\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintGuide_percent=\"0.7\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        />\n\n      <ImageView\n        android:id=\"@+id/movieDetailsImage\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsImageGuideline\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n      <ImageView\n        android:id=\"@+id/movieDetailsPlaceholder\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"?attr/colorPlaceholderBackground\"\n        android:padding=\"74dp\"\n        android:src=\"@drawable/ic_film\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsImageGuideline\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:tint=\"?attr/colorPlaceholderIcon\"\n        tools:visibility=\"visible\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsTitle\"\n        style=\"@style/ImageTitle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"@dimen/spaceNormal\"\n        android:gravity=\"start\"\n        android:maxLines=\"2\"\n        android:paddingStart=\"@dimen/spaceBig\"\n        android:paddingEnd=\"@dimen/spaceBig\"\n        android:textAlignment=\"viewStart\"\n        android:textSize=\"28sp\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsImageGuideline\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        tools:text=\"Lord Of The Rings\"\n        />\n\n      <ImageView\n        android:id=\"@+id/movieDetailsShareButton\"\n        android:layout_width=\"@dimen/backArrowSize\"\n        android:layout_height=\"@dimen/backArrowSize\"\n        android:layout_marginEnd=\"@dimen/spaceSmall\"\n        android:background=\"?android:attr/selectableItemBackground\"\n        android:padding=\"@dimen/backArrowPadding\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:srcCompat=\"@drawable/ic_share\"\n        />\n\n      <View\n        android:id=\"@+id/separator1\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:background=\"?attr/colorSeparator\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsExtraInfo\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsImageGuideline\"\n        />\n\n      <ProgressBar\n        android:id=\"@+id/movieDetailsImageProgress\"\n        style=\"@style/ProgressBar.Dark\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/movieDetailsImage\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/movieDetailsImage\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsExtraInfo\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginTop=\"20dp\"\n        android:ellipsize=\"end\"\n        android:gravity=\"start\"\n        android:maxLines=\"1\"\n        android:textAlignment=\"viewStart\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"15sp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsStatus\"\n        app:layout_constraintEnd_toEndOf=\"@id/movieDetailsImageGuidelineVertical\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator1\"\n        tools:text=\"2019 (PL) | 126 min | Drama, SF, Horror\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsStatus\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginBottom=\"@dimen/spaceSmall\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"14sp\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsAddButton\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/movieDetailsExtraInfo\"\n        tools:text=\"Post Production\"\n        />\n\n      <com.michaldrabik.ui_movie.views.AddToMoviesButton\n        android:id=\"@+id/movieDetailsAddButton\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsManageListsLabel\"\n        app:layout_constraintEnd_toEndOf=\"@id/movieDetailsImageGuidelineVertical\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsStatus\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsManageListsLabel\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginTop=\"@dimen/spaceMedium\"\n        android:drawablePadding=\"@dimen/spaceSmall\"\n        android:fontFamily=\"sans-serif-medium\"\n        android:gravity=\"start|center_vertical\"\n        android:text=\"@string/textMovieManageLists\"\n        android:textAlignment=\"viewStart\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"14sp\"\n        app:drawableStartCompat=\"@drawable/ic_lists\"\n        app:drawableTint=\"?android:attr/textColorPrimary\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator4\"\n        app:layout_constraintEnd_toStartOf=\"@id/movieDetailsHideLabel\"\n        app:layout_constraintHorizontal_bias=\"0\"\n        app:layout_constraintHorizontal_chainStyle=\"packed\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsAddButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsHideLabel\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"20dp\"\n        android:drawablePadding=\"6dp\"\n        android:fontFamily=\"sans-serif-medium\"\n        android:gravity=\"start|center_vertical\"\n        android:text=\"@string/textHide\"\n        android:textAlignment=\"viewStart\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"14sp\"\n        app:drawableStartCompat=\"@drawable/ic_eye_no\"\n        app:drawableTint=\"?android:attr/textColorPrimary\"\n        app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsManageListsLabel\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/movieDetailsManageListsLabel\"\n        app:layout_constraintTop_toTopOf=\"@id/movieDetailsManageListsLabel\"\n        />\n\n      <View\n        android:id=\"@+id/separator4\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginTop=\"18dp\"\n        android:layout_marginEnd=\"@dimen/spaceBig\"\n        android:background=\"?attr/colorSeparator\"\n        app:layout_constraintBottom_toTopOf=\"@+id/movieDetailsStreamingsFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.0\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsManageListsLabel\"\n        />\n\n      <com.michaldrabik.ui_base.common.views.FoldableTextView\n        android:id=\"@+id/movieDetailsDescription\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginEnd=\"@dimen/spaceBig\"\n        android:layout_marginBottom=\"@dimen/spaceTiny\"\n        android:ellipsize=\"end\"\n        android:gravity=\"start\"\n        android:textAlignment=\"viewStart\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"15sp\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsActorsFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsStreamingsFragment\"\n        app:layout_goneMarginBottom=\"@dimen/spaceHuge\"\n        app:layout_goneMarginTop=\"@dimen/spaceNormal\"\n        tools:targetApi=\"o\"\n        tools:text=\"Description\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/movieDetailsRatingsFragment\"\n        android:name=\"com.michaldrabik.ui_movie.sections.ratings.MovieDetailsRatingsFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"@dimen/spaceNormal\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator4\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/movieDetailsImageGuidelineVertical\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator1\"\n        tools:layout=\"@layout/fragment_movie_details_ratings\"\n        />\n\n      <View\n        android:id=\"@+id/separator2\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginTop=\"14dp\"\n        android:layout_marginEnd=\"@dimen/spaceBig\"\n        android:layout_marginBottom=\"@dimen/spaceNormal\"\n        android:background=\"?attr/colorSeparator\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsCommentsButton\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.0\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsActorsFragment\"\n        app:layout_goneMarginBottom=\"@dimen/spaceBig\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/movieDetailsStreamingsFragment\"\n        android:name=\"com.michaldrabik.ui_movie.sections.streamings.MovieDetailsStreamingsFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsDescription\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/separator4\"\n        tools:layout_height=\"40dp\"\n        tools:visibility=\"visible\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/movieDetailsActorsFragment\"\n        android:name=\"com.michaldrabik.ui_movie.sections.people.MovieDetailsPeopleFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceMedium\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator2\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsDescription\"\n        tools:layout=\"@layout/fragment_movie_details_people\"\n        tools:layout_height=\"100dp\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsTrailerButton\"\n        style=\"@style/MovieDetails.ExtraButton\"\n        android:text=\"@string/textMovieTrailer\"\n        app:drawableTopCompat=\"@drawable/ic_play\"\n        app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsCommentsButton\"\n        app:layout_constraintEnd_toStartOf=\"@+id/movieDetailsRateButton\"\n        app:layout_constraintHorizontal_chainStyle=\"spread\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@id/movieDetailsCommentsButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsLinksButton\"\n        style=\"@style/MovieDetails.ExtraButton\"\n        android:text=\"@string/textLink\"\n        app:drawableTopCompat=\"@drawable/ic_link\"\n        app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsCommentsButton\"\n        app:layout_constraintEnd_toStartOf=\"@id/movieDetailsCustomImagesButton\"\n        app:layout_constraintStart_toEndOf=\"@+id/movieDetailsCommentsButton\"\n        app:layout_constraintTop_toTopOf=\"@id/movieDetailsCommentsButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsCustomImagesButton\"\n        style=\"@style/MovieDetails.ExtraButton\"\n        android:text=\"@string/textSetCustomImages\"\n        app:drawableTopCompat=\"@drawable/ic_custom_image\"\n        app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsCommentsButton\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@+id/movieDetailsLinksButton\"\n        app:layout_constraintTop_toTopOf=\"@id/movieDetailsCommentsButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsRateButton\"\n        style=\"@style/MovieDetails.ExtraButton\"\n        android:text=\"@string/textMovieRate\"\n        app:drawableTopCompat=\"@drawable/ic_star\"\n        app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsCommentsButton\"\n        app:layout_constraintEnd_toStartOf=\"@+id/movieDetailsCommentsButton\"\n        app:layout_constraintStart_toEndOf=\"@id/movieDetailsTrailerButton\"\n        app:layout_constraintTop_toTopOf=\"@id/movieDetailsCommentsButton\"\n        />\n\n      <ProgressBar\n        android:id=\"@+id/movieDetailsRateProgress\"\n        style=\"@style/ProgressBar.Dark\"\n        android:layout_width=\"30dp\"\n        android:layout_height=\"30dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsRateButton\"\n        app:layout_constraintEnd_toEndOf=\"@id/movieDetailsRateButton\"\n        app:layout_constraintStart_toStartOf=\"@id/movieDetailsRateButton\"\n        app:layout_constraintTop_toTopOf=\"@id/movieDetailsRateButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/movieDetailsCommentsButton\"\n        style=\"@style/MovieDetails.ExtraButton\"\n        android:text=\"@string/textMovieComments2\"\n        app:drawableTopCompat=\"@drawable/ic_comment\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator5\"\n        app:layout_constraintEnd_toStartOf=\"@+id/movieDetailsLinksButton\"\n        app:layout_constraintStart_toEndOf=\"@+id/movieDetailsRateButton\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator2\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/movieDetailsCollectionsFragment\"\n        android:name=\"com.michaldrabik.ui_movie.sections.collections.list.MovieDetailsCollectionsFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:visibility=\"gone\"\n        app:layout_constrainedHeight=\"true\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator5\"\n        tools:layout=\"@layout/fragment_movie_details_collection\"\n        tools:layout_height=\"40dp\"\n        tools:visibility=\"visible\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/movieDetailsRelatedFragment\"\n        android:name=\"com.michaldrabik.ui_movie.sections.related.MovieDetailsRelatedFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsCollectionsFragment\"\n        app:layout_goneMarginTop=\"@dimen/spaceNormal\"\n        tools:layout=\"@layout/fragment_movie_details_related\"\n        />\n\n      <View\n        android:id=\"@+id/separator5\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceBig\"\n        android:background=\"?attr/colorSeparator\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toTopOf=\"@id/movieDetailsCollectionsFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.0\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsCommentsButton\"\n        app:layout_goneMarginBottom=\"@dimen/spaceBig\"\n        app:layout_goneMarginTop=\"54dp\"\n        tools:visibility=\"visible\"\n        />\n\n      <com.michaldrabik.ui_base.common.views.PremiumAdView\n        android:id=\"@+id/movieDetailsPremiumAd\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"100dp\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceBig\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/movieDetailsRelatedFragment\"\n        />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n  </androidx.core.widget.NestedScrollView>\n\n  <ProgressBar\n    android:id=\"@+id/movieDetailsMainProgress\"\n    style=\"@style/ProgressBar.Accent\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:visibility=\"gone\"\n    />\n\n  <ImageView\n    android:id=\"@+id/movieDetailsBackArrow\"\n    android:layout_width=\"@dimen/backArrowSize\"\n    android:layout_height=\"@dimen/backArrowSize\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:padding=\"@dimen/backArrowPadding\"\n    app:srcCompat=\"@drawable/ic_arrow_back\"\n    />\n\n</FrameLayout>\n"
  },
  {
    "path": "ui-movie/src/main/res/layout-sw600dp/fragment_movie_details_collection.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <TextView\n    android:id=\"@+id/movieDetailsCollectionLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:text=\"@string/textMovieCollections\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"18sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"0\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/movieDetailsCollectionRecycler\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"0dp\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginBottom=\"@dimen/spaceBig\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/spaceBig\"\n    android:paddingEnd=\"@dimen/spaceBig\"\n    app:layout_constrainedHeight=\"true\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHeight_min=\"@dimen/movieCollectionHeight\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/movieDetailsCollectionLabel\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/movieDetailsCollectionProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsCollectionRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/movieDetailsCollectionRecycler\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-movie/src/main/res/layout-sw600dp/fragment_movie_details_people.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/movieDetailsActorsRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:minHeight=\"@dimen/actorMovieTileImageHeight\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/spaceBig\"\n    android:paddingEnd=\"@dimen/spaceBig\"\n    android:visibility=\"invisible\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/movieDetailsActorsProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsActorsRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <TextView\n    android:id=\"@+id/movieDetailsActorsEmptyView\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"@dimen/spaceBig\"\n    android:gravity=\"center\"\n    android:text=\"@string/textMovieActorsEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"15sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsActorsRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/movieDetailsDirectingLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginTop=\"13dp\"\n    android:gravity=\"center_vertical\"\n    android:text=\"@string/textDirecting\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"13sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/movieDetailsDirectingValue\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/movieDetailsActorsRecycler\"\n    app:layout_goneMarginTop=\"@dimen/spaceHuge\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/movieDetailsDirectingValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:minWidth=\"80dp\"\n    android:text=\"-\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"13sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintStart_toStartOf=\"@id/movieDetailsDirectingLabel\"\n    app:layout_constraintTop_toBottomOf=\"@id/movieDetailsDirectingLabel\"\n    tools:ignore=\"HardcodedText\"\n    tools:text=\"Steven\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/movieDetailsWritingLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:text=\"@string/textWriting\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"13sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsDirectingLabel\"\n    app:layout_constraintStart_toEndOf=\"@id/movieDetailsDirectingValue\"\n    app:layout_constraintTop_toTopOf=\"@id/movieDetailsDirectingLabel\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/movieDetailsWritingValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:minWidth=\"80dp\"\n    android:text=\"-\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"13sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintStart_toStartOf=\"@id/movieDetailsWritingLabel\"\n    app:layout_constraintTop_toBottomOf=\"@id/movieDetailsWritingLabel\"\n    tools:ignore=\"HardcodedText\"\n    tools:text=\"Steven Spilberg\\nTest\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/movieDetailsMusicLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"0dp\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:text=\"@string/textMusic\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"13sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsDirectingLabel\"\n    app:layout_constraintStart_toEndOf=\"@id/movieDetailsWritingValue\"\n    app:layout_constraintTop_toTopOf=\"@id/movieDetailsDirectingLabel\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/movieDetailsMusicValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:text=\"-\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"13sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"@id/movieDetailsMusicLabel\"\n    app:layout_constraintTop_toBottomOf=\"@id/movieDetailsMusicLabel\"\n    tools:ignore=\"HardcodedText\"\n    tools:text=\"Steven Spilberg\"\n    tools:visibility=\"visible\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-movie/src/main/res/layout-sw600dp/fragment_movie_details_related.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  tools:layout_marginTop=\"-0dp\"\n  >\n\n  <TextView\n    android:id=\"@+id/movieDetailsRelatedLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:text=\"@string/textMovieYouMayAlsoLike\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"18sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintBottom_toTopOf=\"@id/movieDetailsRelatedRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"0\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/movieDetailsRelatedRecycler\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"0dp\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginBottom=\"@dimen/spaceNormal\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/spaceBig\"\n    android:paddingEnd=\"@dimen/spaceBig\"\n    android:visibility=\"invisible\"\n    app:layout_constrainedHeight=\"true\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHeight_min=\"@dimen/relatedMovieHeight\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/movieDetailsRelatedLabel\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/movieDetailsRelatedProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    app:layout_constraintBottom_toBottomOf=\"@id/movieDetailsRelatedRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/movieDetailsRelatedRecycler\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-movie/src/main/res/layout-sw600dp/fragment_movie_details_streamings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  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=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/movieDetailsStreamingsRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/streamingTileHeight\"\n    android:layout_marginTop=\"@dimen/spaceMicro\"\n    android:layout_marginBottom=\"@dimen/spaceNormal\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/spaceBig\"\n    android:paddingEnd=\"@dimen/spaceBig\"\n    app:layout_constraintBottom_toTopOf=\"@id/movieDetailsDescription\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@+id/movieDetailsRatings\"\n    />\n\n</FrameLayout>\n"
  },
  {
    "path": "ui-movie/src/main/res/layout-sw600dp/view_movie_collection.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"wrap_content\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/rootLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/movieCollectionHeight\"\n    android:background=\"@drawable/bg_collection_ripple\"\n    android:elevation=\"@dimen/elevationTiny\"\n    android:paddingStart=\"@dimen/spaceMedium\"\n    android:paddingTop=\"@dimen/spaceSmall\"\n    android:paddingEnd=\"@dimen/spaceMedium\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <TextView\n      android:id=\"@+id/title\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:ellipsize=\"end\"\n      android:fontFamily=\"sans-serif-medium\"\n      android:gravity=\"start|center_vertical\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"16sp\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toTopOf=\"@id/description\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Google Play Movies\"\n      />\n\n    <TextView\n      android:id=\"@+id/description\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceMicro\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start|center_vertical\"\n      android:maxLines=\"2\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"13sp\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/title\"\n      app:layout_constraintWidth_max=\"271dp\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-movie/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"addToButtonTextSize\">14sp</dimen>\n  <dimen name=\"dividerHorizontalList\">8dp</dimen>\n  <dimen name=\"movieDetailsExtraButtonTextSize\">12sp</dimen>\n\n  <dimen name=\"actorMovieTileCorner\">4dp</dimen>\n  <dimen name=\"actorMovieTilePlaceholder\">20dp</dimen>\n  <dimen name=\"actorMovieTileImageHeight\">120dp</dimen>\n  <dimen name=\"actorMovieTileImageWidth\">80dp</dimen>\n  <dimen name=\"actorMovieTileTextSize\">11sp</dimen>\n\n  <dimen name=\"relatedMovieHeight\">120dp</dimen>\n  <dimen name=\"relatedMovieWidth\">80dp</dimen>\n\n  <dimen name=\"movieCollectionHeight\">66dp</dimen>\n\n  <dimen name=\"movieCollectionImageWidth\">53dp</dimen>\n  <dimen name=\"movieCollectionImageHeight\">80dp</dimen>\n  <dimen name=\"movieCollectionImageGuide\">65dp</dimen>\n\n  <dimen name=\"movieCollectionHeaderSize\">11sp</dimen>\n  <dimen name=\"movieCollectionTitleSize\">16sp</dimen>\n  <dimen name=\"movieCollectionDescriptionSize\">12sp</dimen>\n</resources>"
  },
  {
    "path": "ui-movie/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textInMyMovies\">Added to my movies</string>\n  <string name=\"textInMoviesWatchlist\">Added to watchlist</string>\n  <string name=\"textInHidden\">Hidden</string>\n\n  <string name=\"textMovieActorsEmpty\">Actors are not available</string>\n  <string name=\"textMovieTrailer\">Trailer</string>\n  <string name=\"textMovieRate\">Rate</string>\n  <string name=\"textMovieComments2\">Comments</string>\n  <string name=\"textMovieYouMayAlsoLike\">You may also like</string>\n  <string name=\"textMovieCollections\">Collections</string>\n\n  <string name=\"textMovieExtraInfo\">%s %s | %s %s | %s</string>\n\n  <string name=\"textMovieManageLists\">Manage Lists</string>\n  <string name=\"textMovieManageListsCount\">Manage Lists (%d)</string>\n  <string name=\"textTraktSyncMovieRemovedFromTrakt\">Trakt.tv synced successfully.</string>\n</resources>"
  },
  {
    "path": "ui-movie/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <style name=\"MovieDetails\" />\n\n  <style name=\"MovieDetails.ExtraButton\" parent=\"MovieDetails\">\n    <item name=\"android:layout_width\">wrap_content</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:drawablePadding\">@dimen/spaceTiny</item>\n    <item name=\"android:gravity\">center</item>\n    <item name=\"android:textAllCaps\">true</item>\n    <item name=\"android:maxLines\">1</item>\n    <item name=\"android:textColor\">?android:attr/textColorPrimary</item>\n    <item name=\"drawableTint\">?android:attr/textColorPrimary</item>\n    <item name=\"android:textSize\">@dimen/movieDetailsExtraButtonTextSize</item>\n    <item name=\"android:background\">?android:attr/selectableItemBackgroundBorderless</item>\n    <item name=\"layout_constraintHorizontal_chainStyle\">spread_inside</item>\n  </style>\n\n  <style name=\"MovieLinksMenu\" />\n\n  <style name=\"MovieLinksMenu.Item\" parent=\"MovieLinksMenu\">\n    <item name=\"android:layout_width\">match_parent</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:orientation\">horizontal</item>\n    <item name=\"android:paddingStart\">@dimen/spaceMedium</item>\n    <item name=\"android:paddingEnd\">@dimen/spaceMedium</item>\n    <item name=\"android:paddingTop\">10dp</item>\n    <item name=\"android:paddingBottom\">10dp</item>\n    <item name=\"android:background\">?android:attr/selectableItemBackground</item>\n  </style>\n\n  <style name=\"MovieLinksMenu.Icon\" parent=\"MovieLinksMenu\">\n    <item name=\"android:layout_width\">42dp</item>\n    <item name=\"android:layout_height\">20dp</item>\n    <item name=\"android:layout_marginEnd\">@dimen/spaceMedium</item>\n  </style>\n\n  <style name=\"MovieLinksMenu.Title\" parent=\"MovieLinksMenu\">\n    <item name=\"android:layout_width\">wrap_content</item>\n    <item name=\"android:layout_height\">match_parent</item>\n    <item name=\"android:maxLines\">1</item>\n    <item name=\"android:textAlignment\">viewStart</item>\n    <item name=\"android:gravity\">start</item>\n    <item name=\"android:textColor\">?android:attr/textColorPrimary</item>\n    <item name=\"android:textSize\">14sp</item>\n  </style>\n\n</resources>"
  },
  {
    "path": "ui-movie/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textInMyMovies\">تمت الإضافة إلى أفلامي</string>\n  <string name=\"textInMoviesWatchlist\">تمت الإضافة إلى قائمة المشاهدة</string>\n  <string name=\"textInHidden\">المخفية</string>\n  <string name=\"textMovieActorsEmpty\">قائمة الممثلين غير متاحة</string>\n  <string name=\"textMovieTrailer\">العرض الدعائي</string>\n  <string name=\"textMovieRate\">قيم</string>\n  <string name=\"textMovieComments2\">التعليقات</string>\n  <string name=\"textMovieYouMayAlsoLike\">قد تعجبك أيضاً</string>\n  <string name=\"textMovieCollections\">مجموعات</string>\n  <string name=\"textMovieExtraInfo\">%s %s | %s %s | %s</string>\n  <string name=\"textMovieManageLists\">إدارة القوائم</string>\n  <string name=\"textMovieManageListsCount\">إدارة القوائم (%d)</string>\n  <string name=\"textTraktSyncMovieRemovedFromTrakt\">إكتملت مزامنة Trakt.tv بنجاح.</string>\n</resources>\n"
  },
  {
    "path": "ui-movie/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textInMyMovies\">In Meinen Filmen</string>\n  <string name=\"textInMoviesWatchlist\">Auf der Watchlist</string>\n  <string name=\"textInHidden\">Versteckt</string>\n  <string name=\"textMovieActorsEmpty\">Schauspieler sind nicht verfügbar.</string>\n  <string name=\"textMovieTrailer\">Trailer</string>\n  <string name=\"textMovieRate\">Bewerten</string>\n  <string name=\"textMovieComments2\">Kommentare</string>\n  <string name=\"textMovieYouMayAlsoLike\">Vielleicht mögen Sie auch</string>\n  <string name=\"textMovieCollections\">Sammlungen</string>\n  <string name=\"textMovieExtraInfo\">%s %s | %s %s | %s</string>\n  <string name=\"textMovieManageLists\">Listen verwalten</string>\n  <string name=\"textMovieManageListsCount\">Listen verwalten (%d)</string>\n  <string name=\"textTraktSyncMovieRemovedFromTrakt\">Trakt.tv Synchronisation erfolgreich.</string>\n</resources>\n"
  },
  {
    "path": "ui-movie/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textInMyMovies\">Añadido a mis películas</string>\n  <string name=\"textInMoviesWatchlist\">Añadido a pendientes</string>\n  <string name=\"textInHidden\">Ocultos</string>\n  <string name=\"textMovieActorsEmpty\">El elenco no está disponible</string>\n  <string name=\"textMovieTrailer\">Tráiler</string>\n  <string name=\"textMovieRate\">Calificar</string>\n  <string name=\"textMovieComments2\">Comentarios</string>\n  <string name=\"textMovieYouMayAlsoLike\">También podría gustarte</string>\n  <string name=\"textMovieCollections\">Colecciones</string>\n  <string name=\"textMovieExtraInfo\">%s %s | %s %s | %s</string>\n  <string name=\"textMovieManageLists\">Gestionar Listas</string>\n  <string name=\"textMovieManageListsCount\">Gestionar Listas (%d)</string>\n  <string name=\"textTraktSyncMovieRemovedFromTrakt\">Trakt.tv se sincronizó correctamente.</string>\n</resources>\n"
  },
  {
    "path": "ui-movie/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textInMyMovies\">Lisätty omiin elokuviin</string>\n  <string name=\"textInMoviesWatchlist\">Lisätty katselulistalle</string>\n  <string name=\"textInHidden\">Piilotetut</string>\n  <string name=\"textMovieActorsEmpty\">Näyttelijät eivät ole saatavilla</string>\n  <string name=\"textMovieTrailer\">Traileri</string>\n  <string name=\"textMovieRate\">Arvioi</string>\n  <string name=\"textMovieComments2\">Kommentit</string>\n  <string name=\"textMovieYouMayAlsoLike\">Saattaisit pitää myös näistä</string>\n  <string name=\"textMovieCollections\">Kokoelmat</string>\n  <string name=\"textMovieExtraInfo\">%s %s | %s %s | %s</string>\n  <string name=\"textMovieManageLists\">Hallitse listoja</string>\n  <string name=\"textMovieManageListsCount\">Hallitse listoja (%d)</string>\n  <string name=\"textTraktSyncMovieRemovedFromTrakt\">Trakt.tv synkronoitiin.</string>\n</resources>\n"
  },
  {
    "path": "ui-movie/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textInMyMovies\">Ajouté à mes films</string>\n  <string name=\"textInMoviesWatchlist\">Ajouté à la Watchlist</string>\n  <string name=\"textInHidden\">Masqué</string>\n  <string name=\"textMovieActorsEmpty\">Acteurs non disponibles</string>\n  <string name=\"textMovieTrailer\">Bande\\nAnnonce</string>\n  <string name=\"textMovieRate\">Noter</string>\n  <string name=\"textMovieComments2\">Commentaires</string>\n  <string name=\"textMovieYouMayAlsoLike\">Vous pourriez aussi aimer</string>\n  <string name=\"textMovieCollections\">Collections</string>\n  <string name=\"textMovieExtraInfo\">%s %s | %s %s | %s</string>\n  <string name=\"textMovieManageLists\">Gérer les listes</string>\n  <string name=\"textMovieManageListsCount\">Gérer les listes (%d)</string>\n  <string name=\"textTraktSyncMovieRemovedFromTrakt\">Succès de la synchronisation avec Track.tv.</string>\n</resources>\n"
  },
  {
    "path": "ui-movie/src/main/res/values-fr/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <style name=\"MovieDetails.ExtraButton\" parent=\"MovieDetails\">\n    <item name=\"android:layout_width\">wrap_content</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:drawablePadding\">@dimen/spaceTiny</item>\n    <item name=\"android:gravity\">center</item>\n    <item name=\"android:textAllCaps\">true</item>\n    <item name=\"android:maxLines\">2</item>\n    <item name=\"android:textColor\">@color/colorWhite</item>\n    <item name=\"android:textSize\">12sp</item>\n    <item name=\"android:background\">?android:attr/selectableItemBackgroundBorderless</item>\n    <item name=\"layout_constraintHorizontal_chainStyle\">spread_inside</item>\n  </style>\n\n</resources>"
  },
  {
    "path": "ui-movie/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textInMyMovies\">Aggiunto ai miei film</string>\n  <string name=\"textInMoviesWatchlist\">Aggiunto alla lista da vedere</string>\n  <string name=\"textInHidden\">Nascosti</string>\n  <string name=\"textMovieActorsEmpty\">Attori non disponibili</string>\n  <string name=\"textMovieTrailer\">Trailer</string>\n  <string name=\"textMovieRate\">Valuta</string>\n  <string name=\"textMovieComments2\">Commenti</string>\n  <string name=\"textMovieYouMayAlsoLike\">Potrebbe piacerti anche</string>\n  <string name=\"textMovieCollections\">Raccolte</string>\n  <string name=\"textMovieExtraInfo\">%s %s | %s %s | %s</string>\n  <string name=\"textMovieManageLists\">Gestisci liste</string>\n  <string name=\"textMovieManageListsCount\">Gestisci liste (%d)</string>\n  <string name=\"textTraktSyncMovieRemovedFromTrakt\">Trak.tv sincronizzato correttamente.</string>\n</resources>\n"
  },
  {
    "path": "ui-movie/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textInMyMovies\">W moich filmach</string>\n  <string name=\"textInMoviesWatchlist\">Na później</string>\n  <string name=\"textInHidden\">Ukryte</string>\n  <string name=\"textMovieActorsEmpty\">Obsada niedostępna</string>\n  <string name=\"textMovieTrailer\">Trailer</string>\n  <string name=\"textMovieRate\">Oceń</string>\n  <string name=\"textMovieComments2\">Opinie</string>\n  <string name=\"textMovieYouMayAlsoLike\">Zobacz również</string>\n  <string name=\"textMovieCollections\">Kolekcje</string>\n  <string name=\"textMovieExtraInfo\">%s %s | %s %s | %s</string>\n  <string name=\"textMovieManageLists\">Zarządzaj listami</string>\n  <string name=\"textMovieManageListsCount\">Zarządzaj listami (%d)</string>\n  <string name=\"textTraktSyncMovieRemovedFromTrakt\">Trakt.tv zostało zaktualizowane.</string>\n</resources>\n"
  },
  {
    "path": "ui-movie/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textInMyMovies\">Adicionado aos meus filmes</string>\n  <string name=\"textInMoviesWatchlist\">Adicionar à Interesses</string>\n  <string name=\"textInHidden\">Oculto</string>\n  <string name=\"textMovieActorsEmpty\">Atores não estão disponíveis</string>\n  <string name=\"textMovieTrailer\">Trailer</string>\n  <string name=\"textMovieRate\">Avaliar</string>\n  <string name=\"textMovieComments2\">Comentários</string>\n  <string name=\"textMovieYouMayAlsoLike\">Você também pode gostar</string>\n  <string name=\"textMovieCollections\">Coleções</string>\n  <string name=\"textMovieExtraInfo\">%s %s | %s %s | %s</string>\n  <string name=\"textMovieManageLists\">Gerenciar Listas</string>\n  <string name=\"textMovieManageListsCount\">Gerenciar Listas (%d)</string>\n  <string name=\"textTraktSyncMovieRemovedFromTrakt\">Trakt.tv sincronizado com sucesso.</string>\n</resources>\n"
  },
  {
    "path": "ui-movie/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textInMyMovies\">Добавлено в мои фильмы</string>\n  <string name=\"textInMoviesWatchlist\">Добавлено в Буду смотреть</string>\n  <string name=\"textInHidden\">Скрытое</string>\n  <string name=\"textMovieActorsEmpty\">Актеры недоступны</string>\n  <string name=\"textMovieTrailer\">Трейлер</string>\n  <string name=\"textMovieRate\">Оценить</string>\n  <string name=\"textMovieComments2\">Мнения</string>\n  <string name=\"textMovieYouMayAlsoLike\">Вам также может понравиться</string>\n  <string name=\"textMovieCollections\">Коллекция</string>\n  <string name=\"textMovieExtraInfo\">%s %s | %s %s | %s</string>\n  <string name=\"textMovieManageLists\">Управление списками</string>\n  <string name=\"textMovieManageListsCount\">Управление списками (%d)</string>\n  <string name=\"textTraktSyncMovieRemovedFromTrakt\">Trakt.tv успешно синхронизирован.</string>\n</resources>\n"
  },
  {
    "path": "ui-movie/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"addToButtonTextSize\">15sp</dimen>\n  <dimen name=\"movieDetailsExtraButtonTextSize\">13sp</dimen>\n  <dimen name=\"dividerHorizontalList\">12dp</dimen>\n\n  <dimen name=\"actorMovieTileCorner\">6dp</dimen>\n  <dimen name=\"actorMovieTileImageHeight\">140dp</dimen>\n  <dimen name=\"actorMovieTileImageWidth\">93dp</dimen>\n  <dimen name=\"actorMovieTileTextSize\">12sp</dimen>\n\n  <dimen name=\"relatedMovieHeight\">140dp</dimen>\n  <dimen name=\"relatedMovieWidth\">93dp</dimen>\n\n  <dimen name=\"movieCollectionHeight\">78dp</dimen>\n\n  <dimen name=\"movieCollectionImageWidth\">67dp</dimen>\n  <dimen name=\"movieCollectionImageHeight\">100dp</dimen>\n  <dimen name=\"movieCollectionImageGuide\">83dp</dimen>\n\n  <dimen name=\"movieCollectionHeaderSize\">13sp</dimen>\n  <dimen name=\"movieCollectionTitleSize\">18sp</dimen>\n  <dimen name=\"movieCollectionDescriptionSize\">14sp</dimen>\n</resources>"
  },
  {
    "path": "ui-movie/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textInMyMovies\">Filmlerime eklendi</string>\n  <string name=\"textInMoviesWatchlist\">İstek listesine eklendi</string>\n  <string name=\"textInHidden\">Gizlenmiş</string>\n  <string name=\"textMovieActorsEmpty\">Oyuncular mevcut değil</string>\n  <string name=\"textMovieTrailer\">Fragman</string>\n  <string name=\"textMovieRate\">Oy Ver</string>\n  <string name=\"textMovieComments2\">Yorumlar</string>\n  <string name=\"textMovieYouMayAlsoLike\">Bunlar da hoşunuza gidebilir</string>\n  <string name=\"textMovieCollections\">Koleksiyonlar</string>\n  <string name=\"textMovieExtraInfo\">%s %s | %s %s | %s</string>\n  <string name=\"textMovieManageLists\">Listeleri Yönet</string>\n  <string name=\"textMovieManageListsCount\">Listeleri Yönet (%d)</string>\n  <string name=\"textTraktSyncMovieRemovedFromTrakt\">Trakt.tv eşitlemesi başarılı.</string>\n</resources>\n"
  },
  {
    "path": "ui-movie/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textInMyMovies\">Додано до моїх фільмів</string>\n  <string name=\"textInMoviesWatchlist\">Додано в \\'На потім\\'</string>\n  <string name=\"textInHidden\">Приховане</string>\n  <string name=\"textMovieActorsEmpty\">Актори недоступні</string>\n  <string name=\"textMovieTrailer\">Трейлер</string>\n  <string name=\"textMovieRate\">Оцінити</string>\n  <string name=\"textMovieComments2\">Коментарі</string>\n  <string name=\"textMovieYouMayAlsoLike\">Вам також може сподобатись</string>\n  <string name=\"textMovieCollections\">Колекції</string>\n  <string name=\"textMovieExtraInfo\">%s %s | %s %s | %s</string>\n  <string name=\"textMovieManageLists\">Керування списками</string>\n  <string name=\"textMovieManageListsCount\">Керування списками (%d)</string>\n  <string name=\"textTraktSyncMovieRemovedFromTrakt\">Trakt.tv успішно синхронізовано.</string>\n</resources>\n"
  },
  {
    "path": "ui-movie/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textInMyMovies\">Đã thêm vào phim của tôi</string>\n  <string name=\"textInMoviesWatchlist\">Đã thêm vào danh sách theo dõi</string>\n  <string name=\"textInHidden\">Ẩn</string>\n\n  <string name=\"textMovieActorsEmpty\">Diễn viên không có sẵn</string>\n  <string name=\"textMovieTrailer\">Đoạn giới thiệu</string>\n  <string name=\"textMovieRate\">Xếp hạng</string>\n  <string name=\"textMovieComments2\">Bình luận</string>\n  <string name=\"textMovieYouMayAlsoLike\">Bạn cũng có thể thích</string>\n  <string name=\"textMovieCollections\">Bộ sưu tập</string>\n\n  <string name=\"textMovieExtraInfo\">%s %s</string>\n\n  <string name=\"textMovieManageLists\">Quản lý các danh sách</string>\n  <string name=\"textMovieManageListsCount\">Quản lý các danh sách (%d)</string>\n  <string name=\"textTraktSyncMovieRemovedFromTrakt\">Trakt.tv đã được đồng bộ hóa thành công.</string>\n</resources>\n"
  },
  {
    "path": "ui-movie/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textInMyMovies\">已加入我的电影</string>\n  <string name=\"textInMoviesWatchlist\">已加入待看列表</string>\n  <string name=\"textInHidden\">已隐藏</string>\n  <string name=\"textMovieActorsEmpty\">暂无演职人员信息</string>\n  <string name=\"textMovieTrailer\">预告片</string>\n  <string name=\"textMovieRate\">评分</string>\n  <string name=\"textMovieComments2\">评论</string>\n  <string name=\"textMovieYouMayAlsoLike\">您还可能喜欢</string>\n  <string name=\"textMovieCollections\">系列</string>\n  <string name=\"textMovieExtraInfo\">%s %s | %s %s | %s</string>\n  <string name=\"textMovieManageLists\">管理列表</string>\n  <string name=\"textMovieManageListsCount\">管理列表 (%d)</string>\n  <string name=\"textTraktSyncMovieRemovedFromTrakt\">Trakt.tv 同步成功。</string>\n</resources>\n"
  },
  {
    "path": "ui-my-movies/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-my-movies/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_my_movies'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':ui-base')\n  implementation project(':repository')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  implementation libs.android.gridlayout\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest />\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/common/helpers/CollectionItemFilter.kt",
    "content": "package com.michaldrabik.ui_my_movies.common.helpers\n\nimport com.michaldrabik.common.extensions.nowUtcDay\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CollectionItemFilter @Inject constructor() {\n\n  fun filterUpcoming(\n    item: CollectionListItem,\n    isUpcoming: Boolean,\n  ): Boolean {\n    if (isUpcoming) {\n      val nowUtcDay = nowUtcDay()\n      val releasedAt = item.movie.released\n      val isUpcomingDate = releasedAt != null && releasedAt.toEpochDay() > nowUtcDay.toEpochDay()\n      val isUpcomingYear = releasedAt == null && item.movie.year > nowUtcDay.year\n      return isUpcomingDate || isUpcomingYear\n    }\n    return true\n  }\n\n  fun filterByQuery(\n    item: CollectionListItem.MovieItem,\n    query: String,\n  ): Boolean {\n    return item.movie.title.contains(query, true) ||\n      item.translation?.title?.contains(query, true) == true\n  }\n\n  fun filterGenres(\n    item: CollectionListItem,\n    genres: List<String>,\n  ): Boolean {\n    if (genres.isEmpty()) {\n      return true\n    }\n    return item.movie.genres.any { genre -> genre.lowercase() in genres }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/common/helpers/CollectionItemSorter.kt",
    "content": "package com.michaldrabik.ui_my_movies.common.helpers\n\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_ADDED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SortType.ASCENDING\nimport com.michaldrabik.ui_model.SortType.DESCENDING\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem\nimport javax.inject.Inject\n\nclass CollectionItemSorter @Inject constructor() {\n\n  fun sort(sortOrder: SortOrder, sortType: SortType) = when (sortType) {\n    ASCENDING -> sortAscending(sortOrder)\n    DESCENDING -> sortDescending(sortOrder)\n  }\n\n  private fun sortAscending(sortOrder: SortOrder) = when (sortOrder) {\n    NAME -> compareBy { getTitle(it) }\n    RATING -> compareBy { it.movie.rating }\n    USER_RATING ->\n      compareByDescending<CollectionListItem.MovieItem> { it.userRating != null }\n        .thenBy { it.userRating }\n        .thenBy { getTitle(it) }\n    DATE_ADDED -> compareBy { it.movie.createdAt }\n    NEWEST -> compareBy<CollectionListItem.MovieItem> { it.movie.released }.thenBy { it.movie.year }\n    else -> throw IllegalStateException(\"Invalid sort order\")\n  }\n\n  private fun sortDescending(sortOrder: SortOrder) = when (sortOrder) {\n    NAME -> compareByDescending { getTitle(it) }\n    RATING -> compareByDescending { it.movie.rating }\n    USER_RATING ->\n      compareByDescending<CollectionListItem.MovieItem> { it.userRating != null }\n        .thenByDescending { it.userRating }\n        .thenBy { getTitle(it) }\n    DATE_ADDED -> compareByDescending { it.movie.createdAt }\n    NEWEST -> compareByDescending<CollectionListItem.MovieItem> { it.movie.released }.thenByDescending { it.movie.year }\n    else -> throw IllegalStateException(\"Invalid sort order\")\n  }\n\n  private fun getTitle(item: CollectionListItem.MovieItem): String {\n    val translatedTitle =\n      if (item.translation?.hasTitle == true) item.translation.title\n      else item.movie.titleNoThe\n    return translatedTitle.uppercase()\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/common/layout/CollectionMovieGridItemDecoration.kt",
    "content": "package com.michaldrabik.ui_my_movies.common.layout\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.annotation.DimenRes\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ItemDecoration\nimport com.michaldrabik.ui_my_movies.common.views.CollectionMovieGridTitleView\nimport com.michaldrabik.ui_my_movies.common.views.CollectionMovieGridView\n\nclass CollectionMovieGridItemDecoration : ItemDecoration {\n\n  private var spacing: Int\n  private var halfSpacing: Int\n\n  constructor(\n    context: Context,\n    @DimenRes spacingDimen: Int,\n  ) {\n    this.spacing = context.resources.getDimensionPixelSize(spacingDimen)\n    this.halfSpacing = spacing / 2\n  }\n\n  override fun getItemOffsets(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n    state: RecyclerView.State,\n  ) {\n    if (parent.layoutManager !is GridLayoutManager) return\n\n    val totalSpan = (parent.layoutManager as GridLayoutManager).spanCount\n\n    if (view is CollectionMovieGridView || view is CollectionMovieGridTitleView) {\n      outRect.top = halfSpacing\n      outRect.bottom = halfSpacing\n\n      val position = parent.getChildAdapterPosition(view) - 1\n      val column = position % totalSpan\n\n      outRect.left = spacing * column / totalSpan\n      outRect.right = spacing * ((totalSpan - 1) - column) / totalSpan\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/common/layout/CollectionMovieLayoutManagerProvider.kt",
    "content": "package com.michaldrabik.ui_my_movies.common.layout\n\nimport android.content.Context\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN_TABLET\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\n\ninternal object CollectionMovieLayoutManagerProvider {\n\n  fun provideLayoutManger(\n    context: Context,\n    viewMode: ListViewMode,\n    gridSpanSize: Int,\n  ): RecyclerView.LayoutManager {\n    return if (context.isTablet()) {\n      provideTabletLayout(context, viewMode, gridSpanSize)\n    } else {\n      providePhoneLayout(context, viewMode)\n    }\n  }\n\n  private fun providePhoneLayout(\n    context: Context,\n    viewMode: ListViewMode,\n  ): RecyclerView.LayoutManager {\n    return when (viewMode) {\n      LIST_NORMAL, LIST_COMPACT -> LinearLayoutManager(context, VERTICAL, false)\n      GRID, GRID_TITLE -> GridLayoutManager(context, LISTS_GRID_SPAN)\n    }\n  }\n\n  private fun provideTabletLayout(\n    context: Context,\n    viewMode: ListViewMode,\n    gridSpanSize: Int,\n  ): RecyclerView.LayoutManager {\n    return when (viewMode) {\n      LIST_NORMAL, LIST_COMPACT -> GridLayoutManager(context, gridSpanSize)\n      GRID, GRID_TITLE -> GridLayoutManager(context, LISTS_GRID_SPAN_TABLET)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/common/layout/CollectionMovieListItemDecoration.kt",
    "content": "package com.michaldrabik.ui_my_movies.common.layout\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.annotation.DimenRes\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ItemDecoration\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_my_movies.common.views.CollectionMovieCompactView\nimport com.michaldrabik.ui_my_movies.common.views.CollectionMovieView\n\nclass CollectionMovieListItemDecoration : ItemDecoration {\n\n  private var spacing: Int\n  private var halfSpacing: Int\n  private val isTablet: Boolean\n\n  constructor(\n    context: Context,\n    @DimenRes spacingDimen: Int,\n  ) {\n    this.spacing = context.resources.getDimensionPixelSize(spacingDimen)\n    this.halfSpacing = spacing / 2\n    this.isTablet = context.isTablet()\n  }\n\n  override fun getItemOffsets(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n    state: RecyclerView.State,\n  ) {\n    if (view !is CollectionMovieView && view !is CollectionMovieCompactView) {\n      return\n    }\n    if (!isTablet && (parent.layoutManager is LinearLayoutManager)) {\n      getItemOffsetsPhone(outRect, view)\n      return\n    }\n    if (isTablet && (parent.layoutManager is GridLayoutManager)) {\n      getItemOffsetsTablet(outRect, view, parent)\n      return\n    }\n  }\n\n  private fun getItemOffsetsTablet(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n  ) {\n    if (view is CollectionMovieView) {\n      outRect.top = spacing\n      outRect.bottom = spacing\n    } else if (view is CollectionMovieCompactView) {\n      outRect.top = halfSpacing\n      outRect.bottom = halfSpacing\n    }\n\n    val totalSpan = (parent.layoutManager as GridLayoutManager).spanCount\n    val column = getPosition(parent, view) % totalSpan\n\n    outRect.left = (spacing * 2) * column / totalSpan\n    outRect.right = (spacing * 2) * ((totalSpan - 1) - column) / totalSpan\n  }\n\n  private fun getItemOffsetsPhone(outRect: Rect, view: View) {\n    if (view is CollectionMovieView) {\n      outRect.top = spacing\n      outRect.bottom = spacing\n    } else if (view is CollectionMovieCompactView) {\n      outRect.top = halfSpacing\n      outRect.bottom = halfSpacing\n    }\n    outRect.left = 0\n    outRect.right = 0\n  }\n\n  private fun getPosition(\n    parent: RecyclerView,\n    view: View\n  ): Int {\n    // Position omitting without filters view\n    return parent.getChildAdapterPosition(view) - 1\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/common/recycler/CollectionAdapter.kt",
    "content": "package com.michaldrabik.ui_my_movies.common.recycler\n\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseMovieAdapter\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem.FiltersItem\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem.MovieItem\nimport com.michaldrabik.ui_my_movies.common.views.CollectionMovieCompactView\nimport com.michaldrabik.ui_my_movies.common.views.CollectionMovieFiltersView\nimport com.michaldrabik.ui_my_movies.common.views.CollectionMovieGridTitleView\nimport com.michaldrabik.ui_my_movies.common.views.CollectionMovieGridView\nimport com.michaldrabik.ui_my_movies.common.views.CollectionMovieView\n\nclass CollectionAdapter(\n  listChangeListener: () -> Unit,\n  private val itemClickListener: (CollectionListItem) -> Unit,\n  private val itemLongClickListener: (CollectionListItem) -> Unit,\n  private val sortChipClickListener: (SortOrder, SortType) -> Unit,\n  private val upcomingChipClickListener: (Boolean) -> Unit,\n  private val genreChipClickListener: () -> Unit,\n  private val listViewChipClickListener: () -> Unit,\n  private val missingImageListener: (CollectionListItem, Boolean) -> Unit,\n  private val missingTranslationListener: (CollectionListItem) -> Unit,\n  private val upcomingChipVisible: Boolean = true,\n) : BaseMovieAdapter<CollectionListItem>(\n  listChangeListener = listChangeListener\n) {\n\n  companion object {\n    private const val VIEW_TYPE_SHOW = 1\n    private const val VIEW_TYPE_FILTERS = 2\n  }\n\n  override val asyncDiffer = AsyncListDiffer(this, CollectionItemDiffCallback())\n\n  var listViewMode: ListViewMode = LIST_NORMAL\n    set(value) {\n      field = value\n      notifyItemRangeChanged(0, asyncDiffer.currentList.size)\n    }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    when (viewType) {\n      VIEW_TYPE_SHOW -> BaseViewHolder(\n        when (listViewMode) {\n          LIST_NORMAL -> CollectionMovieView(parent.context)\n          LIST_COMPACT -> CollectionMovieCompactView(parent.context)\n          GRID -> CollectionMovieGridView(parent.context)\n          GRID_TITLE -> CollectionMovieGridTitleView(parent.context)\n        }.apply {\n          itemClickListener = this@CollectionAdapter.itemClickListener\n          itemLongClickListener = this@CollectionAdapter.itemLongClickListener\n          missingImageListener = this@CollectionAdapter.missingImageListener\n          missingTranslationListener = this@CollectionAdapter.missingTranslationListener\n        }\n      )\n      VIEW_TYPE_FILTERS -> BaseViewHolder(\n        CollectionMovieFiltersView(parent.context).apply {\n          onSortChipClicked = this@CollectionAdapter.sortChipClickListener\n          onFilterUpcomingClicked = this@CollectionAdapter.upcomingChipClickListener\n          onGenreChipClicked = this@CollectionAdapter.genreChipClickListener\n          onListViewModeClicked = this@CollectionAdapter.listViewChipClickListener\n          isUpcomingChipVisible = upcomingChipVisible\n        }\n      )\n      else -> throw IllegalStateException()\n    }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    when (val item = asyncDiffer.currentList[position]) {\n      is FiltersItem ->\n        (holder.itemView as CollectionMovieFiltersView).bind(item, listViewMode)\n      is MovieItem ->\n        when (listViewMode) {\n          LIST_NORMAL -> (holder.itemView as CollectionMovieView).bind(item)\n          LIST_COMPACT -> (holder.itemView as CollectionMovieCompactView).bind(item)\n          GRID -> (holder.itemView as CollectionMovieGridView).bind(item)\n          GRID_TITLE -> (holder.itemView as CollectionMovieGridTitleView).bind(item)\n        }\n    }\n  }\n\n  override fun getItemViewType(position: Int) =\n    when (asyncDiffer.currentList[position]) {\n      is MovieItem -> VIEW_TYPE_SHOW\n      is FiltersItem -> VIEW_TYPE_FILTERS\n      else -> throw IllegalStateException()\n    }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/common/recycler/CollectionItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_my_movies.common.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass CollectionItemDiffCallback : DiffUtil.ItemCallback<CollectionListItem>() {\n\n  override fun areItemsTheSame(oldItem: CollectionListItem, newItem: CollectionListItem): Boolean {\n    val areMovies = oldItem is CollectionListItem.MovieItem && newItem is CollectionListItem.MovieItem\n    val areFilters = oldItem is CollectionListItem.FiltersItem && newItem is CollectionListItem.FiltersItem\n\n    return when {\n      areMovies -> areItemsTheSame(\n        (oldItem as CollectionListItem.MovieItem),\n        (newItem as CollectionListItem.MovieItem)\n      )\n      areFilters -> true\n      else -> false\n    }\n  }\n\n  override fun areContentsTheSame(oldItem: CollectionListItem, newItem: CollectionListItem): Boolean {\n    return when (oldItem) {\n      is CollectionListItem.MovieItem -> areContentsTheSame(oldItem, (newItem as CollectionListItem.MovieItem))\n      is CollectionListItem.FiltersItem -> areContentsTheSame(oldItem, (newItem as CollectionListItem.FiltersItem))\n    }\n  }\n\n  private fun areItemsTheSame(\n    oldItem: CollectionListItem.MovieItem,\n    newItem: CollectionListItem.MovieItem,\n  ): Boolean {\n    return oldItem.movie.traktId == newItem.movie.traktId\n  }\n\n  private fun areContentsTheSame(\n    oldItem: CollectionListItem.MovieItem,\n    newItem: CollectionListItem.MovieItem,\n  ): Boolean {\n    return oldItem.image == newItem.image &&\n      oldItem.isLoading == newItem.isLoading &&\n      oldItem.translation == newItem.translation &&\n      oldItem.sortOrder == newItem.sortOrder &&\n      oldItem.spoilers == newItem.spoilers &&\n      oldItem.userRating == newItem.userRating\n  }\n\n  private fun areContentsTheSame(\n    oldItem: CollectionListItem.FiltersItem,\n    newItem: CollectionListItem.FiltersItem,\n  ): Boolean {\n    return oldItem.isUpcoming == newItem.isUpcoming &&\n      oldItem.sortOrder == newItem.sortOrder &&\n      oldItem.genres == newItem.genres &&\n      oldItem.sortType == newItem.sortType\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/common/recycler/CollectionListItem.kt",
    "content": "package com.michaldrabik.ui_my_movies.common.recycler\n\nimport com.michaldrabik.ui_base.common.MovieListItem\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.Translation\nimport java.time.format.DateTimeFormatter\n\nsealed class CollectionListItem(\n  override val movie: Movie,\n  override val image: Image,\n  override val isLoading: Boolean = false,\n) : MovieListItem {\n\n  data class MovieItem(\n    override val movie: Movie,\n    override val image: Image,\n    override val isLoading: Boolean = false,\n    val dateFormat: DateTimeFormatter,\n    val fullDateFormat: DateTimeFormatter,\n    val translation: Translation? = null,\n    val userRating: Int? = null,\n    val sortOrder: SortOrder? = null,\n    val spoilers: Spoilers,\n  ) : CollectionListItem(\n    movie = movie,\n    image = image,\n    isLoading = isLoading\n  ) {\n\n    data class Spoilers(\n      val isSpoilerHidden: Boolean,\n      val isSpoilerRatingsHidden: Boolean,\n      val isSpoilerTapToReveal: Boolean,\n    )\n  }\n\n  data class FiltersItem(\n    val sortOrder: SortOrder,\n    val sortType: SortType,\n    val isUpcoming: Boolean,\n    val genres: List<Genre>\n  ) : CollectionListItem(\n    movie = Movie.EMPTY,\n    image = Image.createUnknown(ImageType.POSTER),\n    isLoading = false\n  ) {\n\n    fun hasActiveFilters() = isUpcoming || genres.isNotEmpty()\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/common/views/CollectionMovieCompactView.kt",
    "content": "package com.michaldrabik.ui_my_movies.common.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.nowUtcDay\nimport com.michaldrabik.ui_base.common.views.MovieView\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.setOutboundRipple\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_my_movies.R\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_movies.databinding.ViewCollectionMovieCompactBinding\nimport java.util.Locale.ENGLISH\n\n@SuppressLint(\"SetTextI18n\")\nclass CollectionMovieCompactView : MovieView<CollectionListItem.MovieItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionMovieCompactBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      collectionMovieRoot.onClick { itemClickListener?.invoke(item) }\n      collectionMovieRoot.onLongClick { itemLongClickListener?.invoke(item) }\n      collectionMovieRoot.setOutboundRipple(\n        size = (context.dimenToPx(R.dimen.collectionItemRippleSpaceSmall)).toFloat(),\n        corner = context.dimenToPx(R.dimen.mediaTileCorner).toFloat()\n      )\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionMovieImage\n  override val placeholderView: ImageView = binding.collectionMoviePlaceholder\n\n  private val nowUtc = nowUtcDay()\n  private lateinit var item: CollectionListItem.MovieItem\n\n  override fun bind(item: CollectionListItem.MovieItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      collectionMovieProgress.visibleIf(item.isLoading)\n      collectionMovieTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.movie.title\n        else item.translation?.title\n\n      val releaseDate = item.movie.released\n      val isUpcoming = releaseDate?.let { it.toEpochDay() > nowUtc.toEpochDay() } ?: false\n\n      with(collectionMovieYear) {\n        when {\n          isUpcoming -> gone()\n          releaseDate != null -> {\n            visible()\n            text = item.dateFormat.format(releaseDate)?.capitalizeWords()\n          }\n          item.movie.year > 0 -> {\n            visible()\n            text = String.format(ENGLISH, \"%d\", item.movie.year)\n          }\n        }\n      }\n\n      with(collectionMovieReleaseDate) {\n        visibleIf(isUpcoming)\n        releaseDate?.let {\n          text = item.fullDateFormat.format(it)?.capitalizeWords()\n        }\n      }\n\n      bindRating(item)\n      item.userRating?.let {\n        collectionMovieUserStarIcon.visible()\n        collectionMovieUserRating.visible()\n        collectionMovieUserRating.text = String.format(ENGLISH, \"%d\", it)\n      }\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindRating(item: CollectionListItem.MovieItem) {\n    with(binding) {\n      var rating = String.format(ENGLISH, \"%.1f\", item.movie.rating)\n\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionMovieRating.tag = rating\n        rating = Config.SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          with(collectionMovieRating) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      }\n\n      collectionMovieRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionMovieTitle.text = \"\"\n      collectionMovieYear.text = \"\"\n      collectionMovieRating.text = \"\"\n      collectionMoviePlaceholder.gone()\n      collectionMovieUserRating.gone()\n      collectionMovieUserStarIcon.gone()\n      Glide.with(this@CollectionMovieCompactView).clear(collectionMovieImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/common/views/CollectionMovieFiltersView.kt",
    "content": "package com.michaldrabik.ui_my_movies.common.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport androidx.core.content.ContextCompat\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SortType.ASCENDING\nimport com.michaldrabik.ui_model.SortType.DESCENDING\nimport com.michaldrabik.ui_my_movies.R\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_movies.databinding.ViewMoviesFiltersBinding\n\nclass CollectionMovieFiltersView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewMoviesFiltersBinding.inflate(LayoutInflater.from(context), this)\n\n  var onSortChipClicked: ((SortOrder, SortType) -> Unit)? = null\n  var onGenreChipClicked: (() -> Unit)? = null\n  var onFilterUpcomingClicked: ((Boolean) -> Unit)? = null\n  var onListViewModeClicked: (() -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    clipChildren = false\n    clipToPadding = false\n  }\n\n  var isUpcomingChipVisible: Boolean\n    get() = binding.followedMoviesUpcomingChip.visibility == VISIBLE\n    set(value) {\n      binding.followedMoviesUpcomingChip.visibleIf(value)\n    }\n\n  fun bind(\n    item: CollectionListItem.FiltersItem,\n    viewMode: ListViewMode,\n  ) {\n    with(binding) {\n      val sortIcon = when (item.sortType) {\n        ASCENDING -> R.drawable.ic_arrow_alt_up\n        DESCENDING -> R.drawable.ic_arrow_alt_down\n      }\n      followedMoviesSortingChip.closeIcon = ContextCompat.getDrawable(context, sortIcon)\n      followedMoviesSortingChip.text = context.getText(item.sortOrder.displayString)\n      followedMoviesUpcomingChip.isChecked = item.isUpcoming\n      followedMoviesListViewChip.setChipIconResource(\n        when (viewMode) {\n          LIST_NORMAL, LIST_COMPACT -> R.drawable.ic_view_list\n          GRID, GRID_TITLE -> R.drawable.ic_view_grid\n        }\n      )\n\n      followedMoviesGenresChip.isSelected = item.genres.isNotEmpty()\n      followedMoviesGenresChip.text = when {\n        item.genres.isEmpty() -> context.getString(R.string.textGenres).filter { it.isLetter() }\n        item.genres.size == 1 -> context.getString(item.genres.first().displayName)\n        item.genres.size == 2 -> \"${context.getString(item.genres[0].displayName)}, ${context.getString(item.genres[1].displayName)}\"\n        else -> \"${context.getString(item.genres[0].displayName)}, ${context.getString(item.genres[1].displayName)} + ${item.genres.size - 2}\"\n      }\n\n      followedMoviesGenresChip.onClick { onGenreChipClicked?.invoke() }\n      followedMoviesSortingChip.onClick { onSortChipClicked?.invoke(item.sortOrder, item.sortType) }\n      followedMoviesUpcomingChip.onClick { onFilterUpcomingClicked?.invoke(followedMoviesUpcomingChip.isChecked) }\n      followedMoviesListViewChip.onClick { onListViewModeClicked?.invoke() }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/common/views/CollectionMovieGridTitleView.kt",
    "content": "package com.michaldrabik.ui_my_movies.common.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.views.MovieView\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_movies.databinding.ViewCollectionMovieGridTitleBinding\nimport java.util.Locale\n\n@SuppressLint(\"SetTextI18n\")\nclass CollectionMovieGridTitleView : MovieView<CollectionListItem.MovieItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionMovieGridTitleBinding.inflate(LayoutInflater.from(context), this)\n\n  private val width by lazy {\n    val span = if (context.isTablet()) Config.LISTS_GRID_SPAN_TABLET else Config.LISTS_GRID_SPAN\n    val itemSpacing = context.dimenToPx(R.dimen.spaceSmall)\n    val screenMargin = context.dimenToPx(R.dimen.screenMarginHorizontal)\n    val screenWidth = screenWidth().toFloat()\n    ((screenWidth - (screenMargin * 2.0)) - ((span - 1) * itemSpacing)) / span\n  }\n  private val height by lazy { width * 1.7305 }\n\n  init {\n    layoutParams = LayoutParams(width.toInt(), height.toInt())\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      collectionMovieRoot.onClick { itemClickListener?.invoke(item) }\n      collectionMovieRoot.onLongClick { itemLongClickListener?.invoke(item) }\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionMovieImage\n  override val placeholderView: ImageView = binding.collectionMoviePlaceholder\n\n  private lateinit var item: CollectionListItem.MovieItem\n\n  override fun bind(item: CollectionListItem.MovieItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      collectionMovieProgress.visibleIf(item.isLoading)\n\n      collectionMovieTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.movie.title\n        else item.translation?.title\n\n      if (item.sortOrder == SortOrder.RATING) {\n        bindRating(item)\n      } else if (item.sortOrder == SortOrder.USER_RATING && item.userRating != null) {\n        collectionMovieRating.visible()\n        collectionMovieRating.text = String.format(Locale.ENGLISH, \"%d\", item.userRating)\n      } else {\n        collectionMovieRating.gone()\n      }\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindRating(item: CollectionListItem.MovieItem) {\n    with(binding) {\n      var rating = String.format(Locale.ENGLISH, \"%.1f\", item.movie.rating)\n\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionMovieRating.tag = rating\n        rating = Config.SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          with(collectionMovieRating) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      }\n\n      collectionMovieRating.visible()\n      collectionMovieRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionMovieTitle.text = \"\"\n      collectionMoviePlaceholder.gone()\n      Glide.with(this@CollectionMovieGridTitleView).clear(collectionMovieImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/common/views/CollectionMovieGridView.kt",
    "content": "package com.michaldrabik.ui_my_movies.common.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.views.MovieView\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_movies.databinding.ViewCollectionMovieGridBinding\nimport java.util.Locale.ENGLISH\n\n@SuppressLint(\"SetTextI18n\")\nclass CollectionMovieGridView : MovieView<CollectionListItem.MovieItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionMovieGridBinding.inflate(LayoutInflater.from(context), this)\n\n  private val width by lazy {\n    val span = if (context.isTablet()) Config.LISTS_GRID_SPAN_TABLET else Config.LISTS_GRID_SPAN\n    val itemSpacing = context.dimenToPx(R.dimen.spaceSmall)\n    val screenMargin = context.dimenToPx(R.dimen.screenMarginHorizontal)\n    val screenWidth = screenWidth().toFloat()\n    ((screenWidth - (screenMargin * 2.0)) - ((span - 1) * itemSpacing)) / span\n  }\n  private val height by lazy { width * ASPECT_RATIO }\n\n  init {\n    layoutParams = LayoutParams(width.toInt(), height.toInt())\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      collectionMovieRoot.onClick { itemClickListener?.invoke(item) }\n      collectionMovieRoot.onLongClick { itemLongClickListener?.invoke(item) }\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionMovieImage\n  override val placeholderView: ImageView = binding.collectionMoviePlaceholder\n\n  private lateinit var item: CollectionListItem.MovieItem\n\n  override fun bind(item: CollectionListItem.MovieItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      collectionMovieProgress.visibleIf(item.isLoading)\n\n      if (item.sortOrder == RATING) {\n        bindRating(item)\n      } else if (item.sortOrder == USER_RATING && item.userRating != null) {\n        collectionMovieRating.visible()\n        collectionMovieRating.text = String.format(ENGLISH, \"%d\", item.userRating)\n      } else {\n        collectionMovieRating.gone()\n      }\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindRating(item: CollectionListItem.MovieItem) {\n    with(binding) {\n      var rating = String.format(ENGLISH, \"%.1f\", item.movie.rating)\n\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionMovieRating.tag = rating\n        rating = Config.SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          with(collectionMovieRating) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      }\n\n      collectionMovieRating.visible()\n      collectionMovieRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionMoviePlaceholder.gone()\n      Glide.with(this@CollectionMovieGridView).clear(collectionMovieImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/common/views/CollectionMovieView.kt",
    "content": "package com.michaldrabik.ui_my_movies.common.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_RATINGS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.common.extensions.nowUtcDay\nimport com.michaldrabik.ui_base.common.views.MovieView\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.setOutboundRipple\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_my_movies.R\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_movies.databinding.ViewCollectionMovieBinding\nimport java.util.Locale.ENGLISH\n\n@SuppressLint(\"SetTextI18n\")\nclass CollectionMovieView : MovieView<CollectionListItem.MovieItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionMovieBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      collectionMovieRoot.onClick { itemClickListener?.invoke(item) }\n      collectionMovieRoot.onLongClick { itemLongClickListener?.invoke(item) }\n      collectionMovieRoot.setOutboundRipple(\n        size = (context.dimenToPx(R.dimen.collectionItemRippleSpace)).toFloat(),\n        corner = context.dimenToPx(R.dimen.mediaTileCorner).toFloat()\n      )\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionMovieImage\n  override val placeholderView: ImageView = binding.collectionMoviePlaceholder\n\n  private var nowUtc = nowUtcDay()\n  private lateinit var item: CollectionListItem.MovieItem\n\n  override fun bind(item: CollectionListItem.MovieItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      collectionMovieProgress.visibleIf(item.isLoading)\n      collectionMovieTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.movie.title\n        else item.translation?.title\n\n      bindDescription(item)\n      bindRating(item)\n\n      val releaseDate = item.movie.released\n      val isUpcoming = releaseDate?.let { it.toEpochDay() > nowUtc.toEpochDay() } ?: false\n\n      with(collectionMovieYear) {\n        when {\n          isUpcoming -> gone()\n          releaseDate != null -> {\n            visible()\n            text = item.dateFormat.format(releaseDate)?.capitalizeWords()\n          }\n          item.movie.year > 0 -> {\n            visible()\n            text = String.format(ENGLISH, \"%d\", item.movie.year)\n          }\n        }\n      }\n\n      with(collectionMovieReleaseDate) {\n        visibleIf(isUpcoming)\n        releaseDate?.let {\n          text = item.fullDateFormat.format(it)?.capitalizeWords()\n        }\n      }\n\n      item.userRating?.let {\n        collectionMovieUserStarIcon.visible()\n        collectionMovieUserRating.visible()\n        collectionMovieUserRating.text = String.format(ENGLISH, \"%d\", it)\n      }\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindDescription(item: CollectionListItem.MovieItem) {\n    with(binding) {\n      collectionMovieDescription.text =\n        if (item.translation?.overview.isNullOrBlank()) item.movie.overview\n        else item.translation?.overview\n\n      if (item.spoilers.isSpoilerHidden) {\n        collectionMovieDescription.tag = collectionMovieDescription.text\n        collectionMovieDescription.text =\n          SPOILERS_REGEX.replace(collectionMovieDescription.text, SPOILERS_HIDE_SYMBOL)\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          collectionMovieDescription.onClick { view ->\n            view.tag?.let {\n              collectionMovieDescription.text = it.toString()\n            }\n            view.isClickable = false\n          }\n        }\n      }\n\n      collectionMovieDescription.visibleIf(item.movie.overview.isNotBlank())\n    }\n  }\n\n  private fun bindRating(item: CollectionListItem.MovieItem) {\n    with(binding) {\n      var rating = String.format(ENGLISH, \"%.1f\", item.movie.rating)\n\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionMovieRating.tag = rating\n        rating = SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          collectionMovieRating.onClick { view ->\n            view.tag?.let {\n              collectionMovieRating.text = it.toString()\n            }\n            view.isClickable = false\n          }\n        }\n      }\n\n      collectionMovieRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionMovieTitle.text = \"\"\n      collectionMovieDescription.text = \"\"\n      collectionMovieYear.text = \"\"\n      collectionMovieRating.text = \"\"\n      collectionMovieYear.gone()\n      collectionMoviePlaceholder.gone()\n      collectionMovieUserStarIcon.gone()\n      collectionMovieUserRating.gone()\n      collectionMovieReleaseDate.gone()\n      Glide.with(this@CollectionMovieView).clear(collectionMovieImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/filters/CollectionFiltersOrigin.kt",
    "content": "package com.michaldrabik.ui_my_movies.filters\n\ninternal enum class CollectionFiltersOrigin {\n  MY_MOVIES,\n  WATCHLIST_MOVIES,\n  HIDDEN_MOVIES\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/filters/CollectionFiltersUiEvent.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_my_movies.filters\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\ninternal sealed class CollectionFiltersUiEvent<T>(action: T) : Event<T>(action) {\n  object ApplyFilters : CollectionFiltersUiEvent<Unit>(Unit)\n  object CloseFilters : CollectionFiltersUiEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/filters/genre/CollectionFiltersGenreBottomSheet.kt",
    "content": "package com.michaldrabik.ui_my_movies.filters.genre\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.content.ContextCompat\nimport androidx.core.os.bundleOf\nimport androidx.core.view.forEach\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetDialog\nimport com.google.android.material.chip.Chip\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.requireSerializable\nimport com.michaldrabik.ui_base.utilities.extensions.screenHeight\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_my_movies.R\nimport com.michaldrabik.ui_my_movies.databinding.ViewFiltersGenresBinding\nimport com.michaldrabik.ui_my_movies.filters.CollectionFiltersOrigin\nimport com.michaldrabik.ui_my_movies.filters.CollectionFiltersUiEvent.ApplyFilters\nimport com.michaldrabik.ui_my_movies.filters.CollectionFiltersUiEvent.CloseFilters\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\ninternal class CollectionFiltersGenreBottomSheet : BaseBottomSheetFragment(R.layout.view_filters_genres) {\n\n  companion object {\n    private const val ARG_ORIGIN = \"ARG_ORIGIN\"\n    const val REQUEST_COLLECTION_FILTERS_GENRE = \"REQUEST_COLLECTION_FILTERS_GENRE\"\n\n    fun createBundle(origin: CollectionFiltersOrigin): Bundle {\n      return bundleOf(ARG_ORIGIN to origin)\n    }\n  }\n\n  private val viewModel by viewModels<CollectionFiltersGenreViewModel>()\n  private val binding by viewBinding(ViewFiltersGenresBinding::bind)\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = {\n        val origin = requireSerializable<CollectionFiltersOrigin>(ARG_ORIGIN)\n        viewModel.loadData(origin)\n      }\n    )\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun setupView() {\n    val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior\n    behavior.skipCollapsed = true\n    behavior.maxHeight = (screenHeight() * 0.9).toInt()\n\n    with(binding) {\n      applyButton.onClick { saveGenres() }\n      clearButton.onClick { renderGenres(emptyList()) }\n    }\n  }\n\n  private fun saveGenres() {\n    with(binding) {\n      val genres = mutableListOf<Genre>().apply {\n        genresChipGroup.forEach { chip ->\n          if ((chip as Chip).isChecked) {\n            add(Genre.valueOf(chip.tag.toString()))\n          }\n        }\n      }\n      viewModel.saveGenres(genres)\n    }\n  }\n\n  private fun render(uiState: CollectionFiltersGenreUiState) {\n    with(uiState) {\n      genres?.let { renderGenres(it) }\n    }\n  }\n\n  private fun renderGenres(genres: List<Genre>) {\n    binding.genresChipGroup.removeAllViews()\n    binding.clearButton.visibleIf(genres.isNotEmpty())\n\n    val genresNames = genres.map { it.name }\n    Genre.values()\n      .sortedBy { requireContext().getString(it.displayName) }\n      .forEach { genre ->\n        val chip = Chip(requireContext()).apply {\n          tag = genre.name\n          text = requireContext().getString(genre.displayName)\n          isCheckable = true\n          isCheckedIconVisible = false\n          setEnsureMinTouchTargetSize(false)\n          shapeAppearanceModel = shapeAppearanceModel.toBuilder()\n            .setAllCornerSizes(100f)\n            .build()\n          chipBackgroundColor = ContextCompat.getColorStateList(context, R.color.selector_discover_chip_background)\n          setChipStrokeColorResource(R.color.selector_discover_chip_text)\n          setChipStrokeWidthResource(R.dimen.discoverFilterChipStroke)\n          setTextColor(ContextCompat.getColorStateList(context, R.color.selector_discover_chip_text))\n          isChecked = genre.name in genresNames\n        }\n        binding.genresChipGroup.addView(chip)\n      }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is ApplyFilters -> {\n        setFragmentResult(REQUEST_COLLECTION_FILTERS_GENRE, Bundle.EMPTY)\n        closeSheet()\n      }\n      is CloseFilters -> closeSheet()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/filters/genre/CollectionFiltersGenreUiState.kt",
    "content": "package com.michaldrabik.ui_my_movies.filters.genre\n\nimport com.michaldrabik.ui_model.Genre\n\ninternal data class CollectionFiltersGenreUiState(\n  val genres: List<Genre>? = null,\n  val isLoading: Boolean? = null,\n)\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/filters/genre/CollectionFiltersGenreViewModel.kt",
    "content": "package com.michaldrabik.ui_my_movies.filters.genre\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_my_movies.filters.CollectionFiltersOrigin\nimport com.michaldrabik.ui_my_movies.filters.CollectionFiltersOrigin.HIDDEN_MOVIES\nimport com.michaldrabik.ui_my_movies.filters.CollectionFiltersOrigin.MY_MOVIES\nimport com.michaldrabik.ui_my_movies.filters.CollectionFiltersOrigin.WATCHLIST_MOVIES\nimport com.michaldrabik.ui_my_movies.filters.CollectionFiltersUiEvent.ApplyFilters\nimport com.michaldrabik.ui_my_movies.filters.CollectionFiltersUiEvent.CloseFilters\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\ninternal class CollectionFiltersGenreViewModel @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val genresState = MutableStateFlow<List<Genre>?>(null)\n  private val loadingState = MutableStateFlow(false)\n\n  private lateinit var origin: CollectionFiltersOrigin\n\n  fun loadData(origin: CollectionFiltersOrigin) {\n    this.origin = origin\n    genresState.value = when (origin) {\n      MY_MOVIES -> settingsRepository.filters.myMoviesGenres\n      WATCHLIST_MOVIES -> settingsRepository.filters.watchlistMoviesGenres\n      HIDDEN_MOVIES -> settingsRepository.filters.hiddenMoviesGenres\n    }.toList()\n  }\n\n  fun saveGenres(genres: List<Genre>) {\n    viewModelScope.launch {\n      if (genres == genresState.value) {\n        eventChannel.send(CloseFilters)\n        return@launch\n      }\n      when (origin) {\n        MY_MOVIES -> settingsRepository.filters.myMoviesGenres = genres\n        WATCHLIST_MOVIES -> settingsRepository.filters.watchlistMoviesGenres = genres\n        HIDDEN_MOVIES -> settingsRepository.filters.hiddenMoviesGenres = genres\n      }\n      eventChannel.send(ApplyFilters)\n    }\n  }\n\n  val uiState = combine(\n    genresState,\n    loadingState,\n  ) { s1, s2 ->\n    CollectionFiltersGenreUiState(\n      genres = s1,\n      isLoading = s2,\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = CollectionFiltersGenreUiState()\n  )\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/hidden/HiddenFragment.kt",
    "content": "package com.michaldrabik.ui_my_movies.hidden\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.postDelayed\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.common.OnScrollResetListener\nimport com.michaldrabik.ui_base.common.OnSearchClickListener\nimport com.michaldrabik.ui_base.common.sheets.sort_order.SortOrderBottomSheet\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.withSpanSizeLookup\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_ADDED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_movies.R\nimport com.michaldrabik.ui_my_movies.common.layout.CollectionMovieGridItemDecoration\nimport com.michaldrabik.ui_my_movies.common.layout.CollectionMovieLayoutManagerProvider\nimport com.michaldrabik.ui_my_movies.common.layout.CollectionMovieListItemDecoration\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionAdapter\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem.FiltersItem\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem.MovieItem\nimport com.michaldrabik.ui_my_movies.databinding.FragmentHiddenMoviesBinding\nimport com.michaldrabik.ui_my_movies.filters.CollectionFiltersOrigin.HIDDEN_MOVIES\nimport com.michaldrabik.ui_my_movies.filters.genre.CollectionFiltersGenreBottomSheet\nimport com.michaldrabik.ui_my_movies.filters.genre.CollectionFiltersGenreBottomSheet.Companion.REQUEST_COLLECTION_FILTERS_GENRE\nimport com.michaldrabik.ui_my_movies.main.FollowedMoviesFragment\nimport com.michaldrabik.ui_my_movies.main.FollowedMoviesUiEvent.OpenPremium\nimport com.michaldrabik.ui_my_movies.main.FollowedMoviesViewModel\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass HiddenFragment :\n  BaseFragment<HiddenViewModel>(R.layout.fragment_hidden_movies),\n  OnScrollResetListener,\n  OnSearchClickListener {\n\n  @Inject lateinit var settings: SettingsViewModeRepository\n\n  override val navigationId = R.id.followedMoviesFragment\n  private val binding by viewBinding(FragmentHiddenMoviesBinding::bind)\n\n  private val parentViewModel by viewModels<FollowedMoviesViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<HiddenViewModel>()\n\n  private var adapter: CollectionAdapter? = null\n  private var layoutManager: LayoutManager? = null\n  private var statusBarHeight = 0\n  private var isSearching = false\n  private val tabletGridSpanSize by lazy { settings.tabletGridSpanSize }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupStatusBar()\n    setupRecycler()\n\n    launchAndRepeatStarted(\n      { parentViewModel.uiState.collect { viewModel.onParentState(it) } },\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = { viewModel.loadMovies() }\n    )\n  }\n\n  private fun setupRecycler() {\n    layoutManager = CollectionMovieLayoutManagerProvider.provideLayoutManger(requireContext(), LIST_NORMAL, tabletGridSpanSize)\n    adapter = CollectionAdapter(\n      itemClickListener = { openMovieDetails(it.movie) },\n      itemLongClickListener = { openMovieMenu(it.movie) },\n      sortChipClickListener = ::openSortOrderDialog,\n      genreChipClickListener = ::openGenresDialog,\n      missingImageListener = viewModel::loadMissingImage,\n      missingTranslationListener = viewModel::loadMissingTranslation,\n      listViewChipClickListener = viewModel::setNextViewMode,\n      upcomingChipVisible = false,\n      upcomingChipClickListener = {},\n      listChangeListener = {\n        binding.hiddenMoviesRecycler.scrollToPosition(0)\n        (requireParentFragment() as FollowedMoviesFragment).resetTranslations()\n      },\n    )\n    binding.hiddenMoviesRecycler.apply {\n      setHasFixedSize(true)\n      adapter = this@HiddenFragment.adapter\n      layoutManager = this@HiddenFragment.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      addItemDecoration(CollectionMovieListItemDecoration(requireContext(), R.dimen.spaceSmall))\n      addItemDecoration(CollectionMovieGridItemDecoration(requireContext(), R.dimen.spaceSmall))\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      if (statusBarHeight != 0) {\n        hiddenMoviesContent.updatePadding(top = hiddenMoviesContent.paddingTop + statusBarHeight)\n        hiddenMoviesRecycler.updatePadding(top = dimenToPx(R.dimen.collectionTabsViewPadding))\n        return\n      }\n      hiddenMoviesContent.doOnApplyWindowInsets { view, insets, padding, _ ->\n        val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n        statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n        view.updatePadding(top = padding.top + statusBarHeight)\n        hiddenMoviesRecycler.updatePadding(top = dimenToPx(R.dimen.collectionTabsViewPadding))\n      }\n    }\n  }\n\n  private fun render(uiState: HiddenUiState) {\n    uiState.run {\n      viewMode.let {\n        if (adapter?.listViewMode != it) {\n          layoutManager = CollectionMovieLayoutManagerProvider.provideLayoutManger(\n            context = requireContext(),\n            viewMode = it,\n            gridSpanSize = tabletGridSpanSize\n          )\n          adapter?.listViewMode = it\n          binding.hiddenMoviesRecycler?.let { recycler ->\n            recycler.layoutManager = layoutManager\n            recycler.adapter = adapter\n          }\n        }\n      }\n      items.let {\n        val notifyChange = resetScroll?.consume() == true\n        adapter?.setItems(it, notifyChange = notifyChange)\n        (layoutManager as? GridLayoutManager)?.withSpanSizeLookup { pos ->\n          when (adapter?.getItems()?.get(pos)) {\n            is FiltersItem -> {\n              when (viewMode) {\n                LIST_NORMAL, LIST_COMPACT -> if (isTablet) tabletGridSpanSize else Config.LISTS_GRID_SPAN\n                GRID, GRID_TITLE -> if (isTablet) Config.LISTS_GRID_SPAN_TABLET else Config.LISTS_GRID_SPAN\n              }\n            }\n            is MovieItem -> 1\n            else -> throw Error(\"Unsupported span size!\")\n          }\n        }\n\n        binding.hiddenMoviesEmptyView.root.fadeIf(it.isEmpty() && !isSearching)\n      }\n      sortOrder?.let { event ->\n        event.consume()?.let { openSortOrderDialog(it.first, it.second) }\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is OpenPremium -> {\n        (requireParentFragment() as? FollowedMoviesFragment)?.openPremium()\n      }\n    }\n  }\n\n  private fun openSortOrderDialog(order: SortOrder, type: SortType) {\n    val options = listOf(NAME, RATING, USER_RATING, NEWEST, DATE_ADDED)\n    val args = SortOrderBottomSheet.createBundle(options, order, type)\n\n    requireParentFragment().setFragmentResultListener(NavigationArgs.REQUEST_SORT_ORDER) { _, bundle ->\n      val sortOrder = bundle.getSerializable(NavigationArgs.ARG_SELECTED_SORT_ORDER) as SortOrder\n      val sortType = bundle.getSerializable(NavigationArgs.ARG_SELECTED_SORT_TYPE) as SortType\n      viewModel.setSortOrder(sortOrder, sortType)\n    }\n\n    navigateTo(R.id.actionFollowedMoviesFragmentToSortOrder, args)\n  }\n\n  private fun openGenresDialog() {\n    requireParentFragment().setFragmentResultListener(REQUEST_COLLECTION_FILTERS_GENRE) { _, _ ->\n      viewModel.loadMovies(resetScroll = true)\n    }\n\n    val bundle = CollectionFiltersGenreBottomSheet.createBundle(HIDDEN_MOVIES)\n    navigateToSafe(R.id.actionFollowedMoviesFragmentToGenres, bundle)\n  }\n\n  private fun openMovieDetails(movie: Movie) {\n    (requireParentFragment() as? FollowedMoviesFragment)?.openMovieDetails(movie)\n  }\n\n  private fun openMovieMenu(movie: Movie) {\n    (requireParentFragment() as? FollowedMoviesFragment)?.openMovieMenu(movie)\n  }\n\n  override fun onEnterSearch() {\n    isSearching = true\n    with(binding.hiddenMoviesRecycler) {\n      translationY = dimenToPx(R.dimen.myMoviesSearchLocalOffset).toFloat()\n      smoothScrollToPosition(0)\n    }\n  }\n\n  override fun onExitSearch() {\n    isSearching = false\n    with(binding.hiddenMoviesRecycler) {\n      translationY = 0F\n      postDelayed(200) { layoutManager?.scrollToPosition(0) }\n    }\n  }\n\n  override fun onScrollReset() = binding.hiddenMoviesRecycler.scrollToPosition(0)\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/hidden/HiddenUiState.kt",
    "content": "package com.michaldrabik.ui_my_movies.hidden\n\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem\n\ndata class HiddenUiState(\n  val items: List<CollectionListItem> = emptyList(),\n  val viewMode: ListViewMode = ListViewMode.LIST_NORMAL,\n  val resetScroll: Event<Boolean>? = null,\n  val sortOrder: Event<Pair<SortOrder, SortType>>? = null,\n)\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/hidden/HiddenViewModel.kt",
    "content": "package com.michaldrabik.ui_my_movies.hidden\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.ReloadData\nimport com.michaldrabik.ui_base.events.TraktSyncAuthError\nimport com.michaldrabik.ui_base.events.TraktSyncError\nimport com.michaldrabik.ui_base.events.TraktSyncSuccess\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_movies.hidden.cases.HiddenLoadMoviesCase\nimport com.michaldrabik.ui_my_movies.hidden.cases.HiddenSortOrderCase\nimport com.michaldrabik.ui_my_movies.hidden.cases.HiddenViewModeCase\nimport com.michaldrabik.ui_my_movies.main.FollowedMoviesUiEvent.OpenPremium\nimport com.michaldrabik.ui_my_movies.main.FollowedMoviesUiState\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\nimport com.michaldrabik.ui_base.events.Event as EventSync\n\n@HiltViewModel\nclass HiddenViewModel @Inject constructor(\n  private val sortOrderCase: HiddenSortOrderCase,\n  private val loadMoviesCase: HiddenLoadMoviesCase,\n  private val viewModeCase: HiddenViewModeCase,\n  private val settingsRepository: SettingsRepository,\n  private val imagesProvider: MovieImagesProvider,\n  private val eventsManager: EventsManager,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private var loadItemsJob: Job? = null\n\n  private val itemsState = MutableStateFlow<List<CollectionListItem>>(emptyList())\n  private val viewModeState = MutableStateFlow(ListViewMode.LIST_NORMAL)\n  private val sortOrderState = MutableStateFlow<Event<Pair<SortOrder, SortType>>?>(null)\n  private val scrollState = MutableStateFlow<Event<Boolean>?>(null)\n\n  private var searchQuery: String? = null\n\n  init {\n    viewModelScope.launch {\n      eventsManager.events.collect { onEvent(it) }\n    }\n  }\n\n  fun onParentState(state: FollowedMoviesUiState) {\n    when {\n      this.searchQuery != state.searchQuery -> {\n        this.searchQuery = state.searchQuery\n        loadMovies(resetScroll = state.searchQuery.isNullOrBlank())\n      }\n    }\n  }\n\n  fun loadMovies(resetScroll: Boolean = false) {\n    loadItemsJob?.cancel()\n    loadItemsJob = viewModelScope.launch {\n      viewModeState.value = viewModeCase.getListViewMode()\n      itemsState.value = loadMoviesCase.loadMovies(searchQuery ?: \"\")\n      scrollState.value = Event(resetScroll)\n    }\n  }\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType) {\n    viewModelScope.launch {\n      sortOrderCase.setSortOrder(sortOrder, sortType)\n      loadMovies(resetScroll = true)\n    }\n  }\n\n  fun setNextViewMode() {\n    if (settingsRepository.isPremium) {\n      viewModeState.value = viewModeCase.setNextViewMode()\n      return\n    }\n    viewModelScope.launch {\n      eventChannel.send(OpenPremium)\n    }\n  }\n\n  fun loadMissingImage(item: CollectionListItem, force: Boolean) {\n    check(item is CollectionListItem.MovieItem)\n    viewModelScope.launch {\n      updateItem(item.copy(isLoading = true))\n      try {\n        val image = imagesProvider.loadRemoteImage(item.movie, item.image.type, force)\n        updateItem(item.copy(isLoading = false, image = image))\n      } catch (t: Throwable) {\n        updateItem(item.copy(isLoading = false, image = Image.createUnavailable(item.image.type)))\n      }\n    }\n  }\n\n  fun loadMissingTranslation(item: CollectionListItem) {\n    check(item is CollectionListItem.MovieItem)\n    if (item.translation != null || settingsRepository.language == Config.DEFAULT_LANGUAGE) return\n    viewModelScope.launch {\n      try {\n        val translation = loadMoviesCase.loadTranslation(item.movie, false)\n        updateItem(item.copy(translation = translation))\n      } catch (error: Throwable) {\n        Timber.e(error)\n      }\n    }\n  }\n\n  private fun updateItem(new: CollectionListItem) {\n    val currentItems = uiState.value.items.toMutableList()\n    currentItems.findReplace(new) { it isSameAs (new) }\n    itemsState.value = currentItems\n  }\n\n  private fun onEvent(event: EventSync) =\n    when (event) {\n      is TraktSyncSuccess -> loadMovies()\n      is TraktSyncError -> loadMovies()\n      is TraktSyncAuthError -> loadMovies()\n      is ReloadData -> loadMovies()\n      else -> Unit\n    }\n\n  val uiState = combine(\n    itemsState,\n    sortOrderState,\n    scrollState,\n    viewModeState\n  ) { s1, s2, s3, s4 ->\n    HiddenUiState(\n      items = s1,\n      sortOrder = s2,\n      resetScroll = s3,\n      viewMode = s4\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = HiddenUiState()\n  )\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/hidden/cases/HiddenLoadMoviesCase.kt",
    "content": "package com.michaldrabik.ui_my_movies.hidden.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.TraktRating\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_my_movies.common.helpers.CollectionItemSorter\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport java.time.format.DateTimeFormatter\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass HiddenLoadMoviesCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val ratingsCase: HiddenRatingsCase,\n  private val sorter: CollectionItemSorter,\n  private val moviesRepository: MoviesRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val dateFormatProvider: DateFormatProvider,\n  private val imagesProvider: MovieImagesProvider,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun loadMovies(searchQuery: String): List<CollectionListItem> =\n    withContext(dispatchers.IO) {\n      val language = translationsRepository.getLanguage()\n      val ratings = ratingsCase.loadRatings()\n      val dateFormat = dateFormatProvider.loadShortDayFormat()\n      val fullDateFormat = dateFormatProvider.loadFullDayFormat()\n      val translations =\n        if (language == Config.DEFAULT_LANGUAGE) emptyMap()\n        else translationsRepository.loadAllMoviesLocal(language)\n      val spoilersSettings = settingsRepository.spoilers.getAll()\n\n      val sortOrder = settingsRepository.sorting.hiddenMoviesSortOrder\n      val sortType = settingsRepository.sorting.hiddenMoviesSortType\n      val genres = settingsRepository.filters.hiddenMoviesGenres\n\n      val filtersItem = loadFiltersItem(sortOrder, sortType, genres)\n      val moviesItems = moviesRepository.hiddenMovies.loadAll()\n        .map {\n          toListItemAsync(\n            movie = it,\n            translation = translations[it.traktId],\n            userRating = ratings[it.ids.trakt],\n            dateFormat = dateFormat,\n            fullDateFormat = fullDateFormat,\n            sortOrder = sortOrder,\n            spoilers = spoilersSettings\n          )\n        }\n        .awaitAll()\n        .filterByQuery(searchQuery)\n        .filterByGenre(genres.map { it.slug.lowercase() })\n        .sortedWith(sorter.sort(sortOrder, sortType))\n\n      if (moviesItems.isNotEmpty() || filtersItem.hasActiveFilters()) {\n        listOf(filtersItem) + moviesItems\n      } else {\n        moviesItems\n      }\n    }\n\n  private fun loadFiltersItem(\n    sortOrder: SortOrder,\n    sortType: SortType,\n    genres: List<Genre>,\n  ): CollectionListItem.FiltersItem {\n    return CollectionListItem.FiltersItem(\n      sortOrder = sortOrder,\n      sortType = sortType,\n      genres = genres,\n      isUpcoming = false\n    )\n  }\n\n  private fun List<CollectionListItem.MovieItem>.filterByQuery(query: String) =\n    this.filter {\n      it.movie.title.contains(query, true) ||\n        it.translation?.title?.contains(query, true) == true\n    }\n\n  private fun List<CollectionListItem.MovieItem>.filterByGenre(genres: List<String>) =\n    filter { genres.isEmpty() || it.movie.genres.any { genre -> genre.lowercase() in genres } }\n\n  suspend fun loadTranslation(movie: Movie, onlyLocal: Boolean): Translation? =\n    withContext(dispatchers.IO) {\n      val language = translationsRepository.getLanguage()\n      if (language == Config.DEFAULT_LANGUAGE) {\n        return@withContext Translation.EMPTY\n      }\n      translationsRepository.loadTranslation(movie, language, onlyLocal)\n    }\n\n  private fun CoroutineScope.toListItemAsync(\n    movie: Movie,\n    translation: Translation?,\n    userRating: TraktRating?,\n    dateFormat: DateTimeFormatter,\n    fullDateFormat: DateTimeFormatter,\n    sortOrder: SortOrder,\n    spoilers: SpoilersSettings,\n  ) = async {\n    val image = imagesProvider.findCachedImage(movie, ImageType.POSTER)\n    CollectionListItem.MovieItem(\n      isLoading = false,\n      movie = movie,\n      image = image,\n      dateFormat = dateFormat,\n      fullDateFormat = fullDateFormat,\n      translation = translation,\n      sortOrder = sortOrder,\n      userRating = userRating?.rating,\n      spoilers = CollectionListItem.MovieItem.Spoilers(\n        isSpoilerHidden = spoilers.isHiddenMoviesHidden,\n        isSpoilerRatingsHidden = spoilers.isHiddenMoviesRatingsHidden,\n        isSpoilerTapToReveal = spoilers.isTapToReveal\n      )\n    )\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/hidden/cases/HiddenRatingsCase.kt",
    "content": "package com.michaldrabik.ui_my_movies.hidden.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.TraktRating\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass HiddenRatingsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val ratingsRepository: RatingsRepository,\n  private val userTraktManager: UserTraktManager,\n) {\n\n  suspend fun loadRatings(): Map<IdTrakt, TraktRating?> = withContext(dispatchers.IO) {\n    if (!userTraktManager.isAuthorized()) {\n      return@withContext emptyMap()\n    }\n    ratingsRepository.movies.loadMoviesRatings().associateBy { it.idTrakt }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/hidden/cases/HiddenSortOrderCase.kt",
    "content": "package com.michaldrabik.ui_my_movies.hidden.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass HiddenSortOrderCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType) {\n    settingsRepository.sorting.hiddenMoviesSortOrder = sortOrder\n    settingsRepository.sorting.hiddenMoviesSortType = sortType\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/hidden/cases/HiddenViewModeCase.kt",
    "content": "package com.michaldrabik.ui_my_movies.hidden.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass HiddenViewModeCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setNextViewMode(): ListViewMode {\n    val viewModes = ListViewMode.values()\n    val index = viewModes.indexOf(getListViewMode()) + 1\n    val nextIndex = if (index >= viewModes.size) 0 else index\n    settingsRepository.viewMode.hiddenMoviesViewMode = viewModes[nextIndex].name\n    return viewModes[nextIndex]\n  }\n\n  fun getListViewMode(): ListViewMode {\n    if (!settingsRepository.isPremium) {\n      return ListViewMode.valueOf(Config.DEFAULT_LIST_VIEW_MODE)\n    }\n    val viewMode = settingsRepository.viewMode.hiddenMoviesViewMode\n    return ListViewMode.valueOf(viewMode)\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/main/FollowedMoviesFragment.kt",
    "content": "package com.michaldrabik.ui_my_movies.main\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.activity.addCallback\nimport androidx.core.os.bundleOf\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.widget.doAfterTextChanged\nimport androidx.fragment.app.clearFragmentResultListener\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.viewpager.widget.ViewPager\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.OnScrollResetListener\nimport com.michaldrabik.ui_base.common.OnSearchClickListener\nimport com.michaldrabik.ui_base.common.OnTabReselectedListener\nimport com.michaldrabik.ui_base.common.sheets.context_menu.ContextMenuBottomSheet\nimport com.michaldrabik.ui_base.utilities.extensions.add\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.disableUi\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.enableUi\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.hideKeyboard\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.nextPage\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.showKeyboard\nimport com.michaldrabik.ui_base.utilities.extensions.updateTopMargin\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.PremiumFeature\nimport com.michaldrabik.ui_my_movies.R\nimport com.michaldrabik.ui_my_movies.databinding.FragmentFollowedMoviesBinding\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ITEM\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_MOVIE_ID\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass FollowedMoviesFragment :\n  BaseFragment<FollowedMoviesViewModel>(R.layout.fragment_followed_movies),\n  OnTabReselectedListener {\n\n  companion object {\n    private const val TRANSLATION_DURATION = 225L\n  }\n\n  override val navigationId = R.id.followedMoviesFragment\n\n  override val viewModel by viewModels<FollowedMoviesViewModel>()\n  private val binding by viewBinding(FragmentFollowedMoviesBinding::bind)\n\n  private var searchViewTranslation = 0F\n  private var tabsViewTranslation = 0F\n  private var currentPage = 0\n  private var isSearching = false\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n\n    savedInstanceState?.let {\n      searchViewTranslation = it.getFloat(\"ARG_SEARCH_POSITION\")\n      tabsViewTranslation = it.getFloat(\"ARG_TABS_POSITION\")\n      currentPage = it.getInt(\"ARG_PAGE\")\n    }\n  }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupPager()\n    setupStatusBar()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } }\n    )\n  }\n\n  override fun onSaveInstanceState(outState: Bundle) {\n    super.onSaveInstanceState(outState)\n    outState.putFloat(\"ARG_SEARCH_POSITION\", searchViewTranslation)\n    outState.putFloat(\"ARG_TABS_POSITION\", tabsViewTranslation)\n    outState.putInt(\"ARG_PAGE\", currentPage)\n  }\n\n  override fun onResume() {\n    super.onResume()\n    showNavigation()\n  }\n\n  override fun onPause() {\n    enableUi()\n    tabsViewTranslation = binding.followedMoviesTabs.translationY\n    searchViewTranslation = binding.followedMoviesSearchView.translationY\n    super.onPause()\n  }\n\n  override fun onDestroyView() {\n    binding.followedMoviesPager.removeOnPageChangeListener(pageChangeListener)\n    super.onDestroyView()\n  }\n\n  private fun setupView() {\n    with(binding) {\n      followedMoviesSearchView.run {\n        hint = getString(R.string.textSearchFor)\n        statsIconVisible = true\n        onClick { openMainSearch() }\n        onSettingsClickListener = { openSettings() }\n        onStatsClickListener = { openStatistics() }\n      }\n      with(followedMoviesSearchLocalView) {\n        onCloseClickListener = { exitSearch() }\n      }\n      followedMoviesModeTabs.run {\n        onModeSelected = { mode = it }\n        onListsSelected = { navigateTo(R.id.actionNavigateListsFragment) }\n        showLists(true)\n        selectMovies()\n      }\n      followedMoviesSearchIcon.run {\n        onClick { if (!isSearching) enterSearch() else exitSearch() }\n      }\n\n      followedMoviesSearchView.translationY = searchViewTranslation\n      followedMoviesTabs.translationY = tabsViewTranslation\n      followedMoviesModeTabs.translationY = tabsViewTranslation\n      followedMoviesIcons.translationY = tabsViewTranslation\n    }\n  }\n\n  private fun setupPager() {\n    with(binding) {\n      followedMoviesPager.run {\n        offscreenPageLimit = FollowedPagesAdapter.PAGES_COUNT\n        adapter = FollowedPagesAdapter(childFragmentManager, requireContext())\n        addOnPageChangeListener(pageChangeListener)\n      }\n      followedMoviesTabs.setupWithViewPager(followedMoviesPager)\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      followedMoviesRoot.doOnApplyWindowInsets { _, insets, _, _ ->\n        val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n        val statusBarSize = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n        followedMoviesSearchView.applyWindowInsetBehaviour(dimenToPx(R.dimen.spaceNormal) + statusBarSize)\n        followedMoviesSearchView.updateTopMargin(dimenToPx(R.dimen.spaceMedium) + statusBarSize)\n        followedMoviesTabs.updateTopMargin(dimenToPx(R.dimen.myMoviesSearchViewPadding) + statusBarSize)\n        followedMoviesModeTabs.updateTopMargin(dimenToPx(R.dimen.collectionTabsMargin) + statusBarSize)\n        followedMoviesIcons.updateTopMargin(dimenToPx(R.dimen.myMoviesSearchViewPadding) + statusBarSize)\n        followedMoviesSearchLocalView.updateTopMargin(dimenToPx(R.dimen.myMoviesSearchLocalViewPadding) + statusBarSize)\n      }\n    }\n  }\n\n  override fun setupBackPressed() {\n    val dispatcher = requireActivity().onBackPressedDispatcher\n    dispatcher.addCallback(viewLifecycleOwner) {\n      if (isSearching) {\n        exitSearch()\n      } else {\n        isEnabled = false\n        activity?.onBackPressed()\n      }\n    }\n  }\n\n  private fun enterSearch() {\n    resetTranslations()\n    binding.followedMoviesSearchLocalView.fadeIn(150)\n    with(binding.followedMoviesSearchLocalView.binding.searchViewLocalInput) {\n      setText(\"\")\n      doAfterTextChanged { viewModel.onSearchQuery(it?.toString()) }\n      visible()\n      showKeyboard()\n      requestFocus()\n    }\n    isSearching = true\n    childFragmentManager.fragments.forEach { (it as? OnSearchClickListener)?.onEnterSearch() }\n  }\n\n  private fun exitSearch() {\n    isSearching = false\n    childFragmentManager.fragments.forEach { (it as? OnSearchClickListener)?.onExitSearch() }\n    resetTranslations()\n    binding.followedMoviesSearchLocalView.gone()\n    with(binding.followedMoviesSearchLocalView.binding.searchViewLocalInput) {\n      setText(\"\")\n      gone()\n      hideKeyboard()\n      clearFocus()\n    }\n  }\n\n  private fun openMainSearch() {\n    disableUi()\n    hideNavigation()\n    with(binding) {\n      followedMoviesModeTabs.fadeOut(duration = 200).add(animations)\n      followedMoviesTabs.fadeOut(duration = 200).add(animations)\n      followedMoviesIcons.fadeOut(duration = 200).add(animations)\n      followedMoviesPager.fadeOut(duration = 200) {\n        super.navigateTo(R.id.actionFollowedMoviesFragmentToSearch, null)\n      }.add(animations)\n    }\n  }\n\n  fun openMovieDetails(movie: Movie) {\n    disableUi()\n    hideNavigation()\n    binding.followedMoviesRoot.fadeOut(150) {\n      val bundle = Bundle().apply { putLong(ARG_MOVIE_ID, movie.traktId) }\n      navigateToSafe(R.id.actionFollowedMoviesFragmentToMovieDetailsFragment, bundle)\n      exitSearch()\n    }.add(animations)\n  }\n\n  fun openMovieMenu(movie: Movie) {\n    setFragmentResultListener(NavigationArgs.REQUEST_ITEM_MENU) { requestKey, _ ->\n      if (requestKey == NavigationArgs.REQUEST_ITEM_MENU) {\n        viewModel.refreshData()\n      }\n      clearFragmentResultListener(NavigationArgs.REQUEST_ITEM_MENU)\n    }\n    val bundle = ContextMenuBottomSheet.createBundle(movie.ids.trakt)\n    navigateToSafe(R.id.actionFollowedMoviesFragmentToItemMenu, bundle)\n  }\n\n  fun openPremium() {\n    hideNavigation()\n    exitSearch()\n    val args = bundleOf(ARG_ITEM to PremiumFeature.VIEW_TYPES)\n    navigateToSafe(R.id.actionFollowedMoviesFragmentToPremium, args)\n  }\n\n  private fun openSettings() {\n    hideNavigation()\n    exitSearch()\n    navigateToSafe(R.id.actionFollowedMoviesFragmentToSettingsFragment)\n  }\n\n  private fun openStatistics() {\n    hideNavigation()\n    exitSearch()\n    navigateToSafe(R.id.actionFollowedMoviesFragmentToStatisticsFragment)\n  }\n\n  override fun onTabReselected() {\n    if (view == null) return\n    resetTranslations(duration = 0)\n    binding.followedMoviesPager.nextPage()\n    childFragmentManager.fragments.forEach {\n      (it as? OnScrollResetListener)?.onScrollReset()\n    }\n  }\n\n  fun resetTranslations(duration: Long = TRANSLATION_DURATION) {\n    if (view == null) return\n    with(binding) {\n      arrayOf(\n        followedMoviesSearchView,\n        followedMoviesTabs,\n        followedMoviesModeTabs,\n        followedMoviesIcons,\n        followedMoviesSearchLocalView\n      ).forEach {\n        it.animate().translationY(0F).setDuration(duration).add(animations)?.start()\n      }\n    }\n  }\n\n  private fun render(uiState: FollowedMoviesUiState) {\n    uiState.isSyncing?.let {\n      binding.followedMoviesSearchView.setTraktProgress(it)\n      binding.followedMoviesSearchView.isEnabled = !it\n    }\n  }\n\n  private val pageChangeListener = object : ViewPager.OnPageChangeListener {\n    override fun onPageSelected(position: Int) {\n      if (currentPage == position) return\n\n      if (binding.followedMoviesTabs.translationY != 0F) {\n        resetTranslations()\n        requireView().postDelayed(\n          {\n            childFragmentManager.fragments.forEach { (it as? OnScrollResetListener)?.onScrollReset() }\n          },\n          225L\n        )\n      }\n\n      currentPage = position\n    }\n\n    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) = Unit\n    override fun onPageScrollStateChanged(state: Int) = Unit\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/main/FollowedMoviesUiEvent.kt",
    "content": "package com.michaldrabik.ui_my_movies.main\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\nsealed class FollowedMoviesUiEvent<T>(action: T) : Event<T>(action) {\n  object OpenPremium : FollowedMoviesUiEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/main/FollowedMoviesUiState.kt",
    "content": "package com.michaldrabik.ui_my_movies.main\n\ndata class FollowedMoviesUiState(\n  val searchQuery: String? = null,\n  val isSyncing: Boolean? = null\n)\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/main/FollowedMoviesViewModel.kt",
    "content": "package com.michaldrabik.ui_my_movies.main\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkInfo\nimport androidx.work.WorkManager\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.ReloadData\nimport com.michaldrabik.ui_base.trakt.TraktSyncWorker\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass FollowedMoviesViewModel @Inject constructor(\n  private val eventsManager: EventsManager,\n  workManager: WorkManager\n) : ViewModel() {\n\n  private val searchQueryState = MutableStateFlow<String?>(null)\n  private val syncingState = MutableStateFlow(false)\n\n  init {\n    workManager.getWorkInfosByTagLiveData(TraktSyncWorker.TAG_ID).observeForever { work ->\n      syncingState.value = work.any { it.state == WorkInfo.State.RUNNING }\n    }\n  }\n\n  fun onSearchQuery(searchQuery: String?) {\n    searchQueryState.value = searchQuery\n  }\n\n  fun refreshData() {\n    viewModelScope.launch {\n      eventsManager.sendEvent(ReloadData)\n    }\n  }\n\n  val uiState = combine(\n    searchQueryState,\n    syncingState\n  ) { s1, s2 ->\n    FollowedMoviesUiState(\n      searchQuery = s1,\n      isSyncing = s2\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = FollowedMoviesUiState()\n  )\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/main/FollowedPagesAdapter.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.ui_my_movies.main\n\nimport android.content.Context\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentManager\nimport androidx.fragment.app.FragmentStatePagerAdapter\nimport com.michaldrabik.ui_my_movies.R\nimport com.michaldrabik.ui_my_movies.hidden.HiddenFragment\nimport com.michaldrabik.ui_my_movies.mymovies.MyMoviesFragment\nimport com.michaldrabik.ui_my_movies.watchlist.WatchlistFragment\n\nclass FollowedPagesAdapter(\n  fragManager: FragmentManager,\n  private val context: Context,\n) : FragmentStatePagerAdapter(fragManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {\n\n  companion object {\n    const val PAGES_COUNT = 3\n  }\n\n  override fun getCount() = PAGES_COUNT\n\n  override fun getItem(position: Int): Fragment = when (position) {\n    0 -> MyMoviesFragment()\n    1 -> WatchlistFragment()\n    2 -> HiddenFragment()\n    else -> throw IllegalStateException(\"Unknown position\")\n  }\n\n  override fun getPageTitle(position: Int) =\n    when (position) {\n      0 -> context.getString(R.string.menuMyMovies)\n      1 -> context.getString(R.string.menuWatchlistMovies)\n      2 -> context.getString(R.string.menuHidden)\n      else -> throw IllegalStateException()\n    }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/MyMoviesFragment.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.postDelayed\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN_TABLET\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.common.OnScrollResetListener\nimport com.michaldrabik.ui_base.common.OnSearchClickListener\nimport com.michaldrabik.ui_base.common.sheets.sort_order.SortOrderBottomSheet\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.withSpanSizeLookup\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_ADDED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_movies.R\nimport com.michaldrabik.ui_my_movies.databinding.FragmentMyMoviesBinding\nimport com.michaldrabik.ui_my_movies.filters.CollectionFiltersOrigin.MY_MOVIES\nimport com.michaldrabik.ui_my_movies.filters.genre.CollectionFiltersGenreBottomSheet\nimport com.michaldrabik.ui_my_movies.filters.genre.CollectionFiltersGenreBottomSheet.Companion.REQUEST_COLLECTION_FILTERS_GENRE\nimport com.michaldrabik.ui_my_movies.main.FollowedMoviesFragment\nimport com.michaldrabik.ui_my_movies.main.FollowedMoviesUiEvent.OpenPremium\nimport com.michaldrabik.ui_my_movies.main.FollowedMoviesViewModel\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesAdapter\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem.Type.ALL_MOVIES_ITEM\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem.Type.HEADER\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem.Type.RECENT_MOVIES\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesLayoutManagerProvider\nimport com.michaldrabik.ui_my_movies.mymovies.utilities.MyMoviesGridItemDecoration\nimport com.michaldrabik.ui_my_movies.mymovies.utilities.MyMoviesListItemDecoration\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass MyMoviesFragment :\n  BaseFragment<MyMoviesViewModel>(R.layout.fragment_my_movies),\n  OnScrollResetListener,\n  OnSearchClickListener {\n\n  @Inject lateinit var settings: SettingsViewModeRepository\n\n  override val navigationId = R.id.followedMoviesFragment\n  private val binding by viewBinding(FragmentMyMoviesBinding::bind)\n\n  private val parentViewModel by viewModels<FollowedMoviesViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<MyMoviesViewModel>()\n\n  private var adapter: MyMoviesAdapter? = null\n  private var layoutManager: LayoutManager? = null\n  private var statusBarHeight = 0\n  private var isSearching = false\n  private val gridSpanSize by lazy { settings.tabletGridSpanSize }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupStatusBar()\n    setupRecycler()\n\n    launchAndRepeatStarted(\n      { parentViewModel.uiState.collect { viewModel.onParentState(it) } },\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = { viewModel.loadMovies() }\n    )\n  }\n\n  private fun setupRecycler() {\n    layoutManager = MyMoviesLayoutManagerProvider.provideLayoutManger(\n      context = requireContext(),\n      viewMode = LIST_NORMAL,\n      gridSpanSize = gridSpanSize\n    )\n    adapter = MyMoviesAdapter(\n      itemClickListener = { openMovieDetails(it.movie) },\n      itemLongClickListener = { openMovieMenu(it.movie) },\n      onSortOrderClickListener = ::openSortOrderDialog,\n      onGenresClickListener = ::openGenresDialog,\n      onListViewModeClickListener = viewModel::setNextViewMode,\n      missingImageListener = { item, force -> viewModel.loadMissingImage(item, force) },\n      missingTranslationListener = { viewModel.loadMissingTranslation(it) },\n      listChangeListener = {\n        layoutManager?.scrollToPosition(0)\n        (requireParentFragment() as FollowedMoviesFragment).resetTranslations()\n      }\n    )\n    binding.myMoviesRecycler.apply {\n      adapter = this@MyMoviesFragment.adapter\n      layoutManager = this@MyMoviesFragment.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      setHasFixedSize(true)\n      addItemDecoration(MyMoviesGridItemDecoration(requireContext(), R.dimen.spaceSmall))\n      addItemDecoration(MyMoviesListItemDecoration(requireContext(), R.dimen.spaceSmall))\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      if (statusBarHeight != 0) {\n        myMoviesRoot.updatePadding(top = statusBarHeight)\n        myMoviesRecycler.updatePadding(top = dimenToPx(R.dimen.myMoviesTabsViewPadding))\n        return\n      }\n      myMoviesRoot.doOnApplyWindowInsets { view, insets, _, _ ->\n        val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n        statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n        view.updatePadding(top = statusBarHeight)\n        myMoviesRecycler.updatePadding(top = dimenToPx(R.dimen.myMoviesTabsViewPadding))\n      }\n    }\n  }\n\n  private fun render(uiState: MyMoviesUiState) {\n    uiState.run {\n      with(binding) {\n        viewMode.let {\n          if (adapter?.listViewMode != it) {\n            val state = myMoviesRecycler.layoutManager?.onSaveInstanceState()\n            layoutManager = MyMoviesLayoutManagerProvider.provideLayoutManger(requireContext(), it, gridSpanSize)\n            adapter?.listViewMode = it\n            myMoviesRecycler.let { recycler ->\n              recycler.layoutManager = layoutManager\n              recycler.adapter = adapter\n              recycler.layoutManager?.onRestoreInstanceState(state)\n            }\n          }\n        }\n        items?.let {\n          val notifyChange = resetScroll?.consume() == true\n          adapter?.setItems(it, notifyChange)\n          (layoutManager as? GridLayoutManager)?.withSpanSizeLookup { pos ->\n            val item = adapter?.getItems()?.get(pos)\n            when (item?.type) {\n              RECENT_MOVIES, HEADER -> {\n                when (viewMode) {\n                  LIST_NORMAL, LIST_COMPACT -> if (isTablet) gridSpanSize else LISTS_GRID_SPAN\n                  GRID, GRID_TITLE -> if (isTablet) LISTS_GRID_SPAN_TABLET else LISTS_GRID_SPAN\n                }\n              }\n              ALL_MOVIES_ITEM -> 1\n              null -> throw Error(\"Unsupported span size!\")\n            }\n          }\n          myMoviesEmptyView.root.fadeIf(showEmptyView && !isSearching)\n        }\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is OpenPremium -> {\n        (requireParentFragment() as? FollowedMoviesFragment)?.openPremium()\n      }\n    }\n  }\n\n  private fun openMovieDetails(movie: Movie) {\n    (requireParentFragment() as? FollowedMoviesFragment)?.openMovieDetails(movie)\n  }\n\n  private fun openMovieMenu(movie: Movie) {\n    (requireParentFragment() as? FollowedMoviesFragment)?.openMovieMenu(movie)\n  }\n\n  private fun openSortOrderDialog(order: SortOrder, type: SortType) {\n    val options = listOf(NAME, RATING, USER_RATING, NEWEST, DATE_ADDED)\n    val args = SortOrderBottomSheet.createBundle(options, order, type)\n\n    requireParentFragment().setFragmentResultListener(NavigationArgs.REQUEST_SORT_ORDER) { _, bundle ->\n      val sortOrder = bundle.getSerializable(NavigationArgs.ARG_SELECTED_SORT_ORDER) as SortOrder\n      val sortType = bundle.getSerializable(NavigationArgs.ARG_SELECTED_SORT_TYPE) as SortType\n      viewModel.setSortOrder(sortOrder, sortType)\n    }\n\n    navigateTo(R.id.actionFollowedMoviesFragmentToSortOrder, args)\n  }\n\n  private fun openGenresDialog() {\n    requireParentFragment().setFragmentResultListener(REQUEST_COLLECTION_FILTERS_GENRE) { _, _ ->\n      viewModel.loadMovies()\n    }\n    val bundle = CollectionFiltersGenreBottomSheet.createBundle(MY_MOVIES)\n    navigateToSafe(R.id.actionFollowedMoviesFragmentToGenres, bundle)\n  }\n\n  override fun onEnterSearch() {\n    isSearching = true\n    with(binding.myMoviesRecycler) {\n      translationY = dimenToPx(R.dimen.myMoviesSearchLocalOffset).toFloat()\n      smoothScrollToPosition(0)\n    }\n  }\n\n  override fun onExitSearch() {\n    isSearching = false\n    with(binding.myMoviesRecycler) {\n      translationY = 0F\n      postDelayed(200) { layoutManager?.scrollToPosition(0) }\n    }\n  }\n\n  override fun onScrollReset() = binding.myMoviesRecycler.scrollToPosition(0)\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/MyMoviesUiState.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies\n\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem\n\ndata class MyMoviesUiState(\n  val items: List<MyMoviesItem>? = null,\n  val showEmptyView: Boolean = false,\n  val viewMode: ListViewMode = ListViewMode.LIST_NORMAL,\n  val resetScroll: Event<Boolean>? = null,\n)\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/MyMoviesViewModel.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Config.DEFAULT_LANGUAGE\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.ReloadData\nimport com.michaldrabik.ui_base.events.TraktSyncAuthError\nimport com.michaldrabik.ui_base.events.TraktSyncError\nimport com.michaldrabik.ui_base.events.TraktSyncSuccess\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.MyMoviesSection.ALL\nimport com.michaldrabik.ui_model.MyMoviesSection.RECENTS\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.TraktRating\nimport com.michaldrabik.ui_my_movies.main.FollowedMoviesUiEvent\nimport com.michaldrabik.ui_my_movies.main.FollowedMoviesUiState\nimport com.michaldrabik.ui_my_movies.mymovies.cases.MyMoviesLoadCase\nimport com.michaldrabik.ui_my_movies.mymovies.cases.MyMoviesRatingsCase\nimport com.michaldrabik.ui_my_movies.mymovies.cases.MyMoviesSortingCase\nimport com.michaldrabik.ui_my_movies.mymovies.cases.MyMoviesViewModeCase\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem.Type\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem.Type.ALL_MOVIES_ITEM\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem.Type.RECENT_MOVIES\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport java.time.format.DateTimeFormatter\nimport javax.inject.Inject\nimport com.michaldrabik.ui_base.events.Event as EventSync\n\n@HiltViewModel\nclass MyMoviesViewModel @Inject constructor(\n  private val loadMoviesCase: MyMoviesLoadCase,\n  private val ratingsCase: MyMoviesRatingsCase,\n  private val sortingCase: MyMoviesSortingCase,\n  private val viewModeCase: MyMoviesViewModeCase,\n  private val settingsRepository: SettingsRepository,\n  private val eventsManager: EventsManager,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private var loadItemsJob: Job? = null\n\n  private val itemsState = MutableStateFlow<List<MyMoviesItem>?>(null)\n  private val itemsUpdateState = MutableStateFlow<Event<Boolean>?>(null)\n  private val viewModeState = MutableStateFlow(ListViewMode.LIST_NORMAL)\n  private val showEmptyViewState = MutableStateFlow(false)\n\n  private var searchQuery: String? = null\n\n  init {\n    viewModelScope.launch { eventsManager.events.collect { onEvent(it) } }\n  }\n\n  fun onParentState(state: FollowedMoviesUiState) {\n    when {\n      this.searchQuery != state.searchQuery -> {\n        this.searchQuery = state.searchQuery\n        loadMovies(notifyListsUpdate = state.searchQuery.isNullOrBlank())\n      }\n    }\n  }\n\n  fun loadMovies(notifyListsUpdate: Boolean = false) {\n    loadItemsJob?.cancel()\n    loadItemsJob = viewModelScope.launch {\n      val settings = loadMoviesCase.loadSettings()\n      val dateFormat = loadMoviesCase.loadDateFormat()\n      val ratings = ratingsCase.loadRatings()\n      val sortOrder = sortingCase.loadSortOrder()\n      val genresFilter = settingsRepository.filters.myMoviesGenres\n      val spoilers = settingsRepository.spoilers.getAll()\n\n      val movies = loadMoviesCase.loadAll().map {\n        toListItemAsync(\n          itemType = ALL_MOVIES_ITEM,\n          movie = it,\n          dateFormat = dateFormat,\n          type = POSTER,\n          userRating = ratings[it.ids.trakt],\n          sortOrder = sortOrder.first,\n          spoilers = spoilers\n        )\n      }.awaitAll()\n\n      val allMovies = loadMoviesCase.filterSectionMovies(\n        allMovies = movies,\n        sortOrder = sortOrder,\n        genres = genresFilter.map { it.slug },\n        searchQuery = searchQuery\n      )\n      val recentMovies = if (settings.myRecentsAmount > 0) {\n        loadMoviesCase.loadRecentMovies().map {\n          toListItemAsync(\n            itemType = RECENT_MOVIES,\n            movie = it,\n            dateFormat = dateFormat,\n            type = ImageType.FANART,\n            userRating = ratings[it.ids.trakt],\n            sortOrder = sortOrder.first,\n            spoilers = spoilers\n          )\n        }.awaitAll()\n      } else {\n        emptyList()\n      }\n\n      val isNotSearching = searchQuery.isNullOrBlank()\n      val hasAnyFilters = genresFilter.isNotEmpty()\n      val listItems = mutableListOf<MyMoviesItem>()\n      listItems.run {\n        if (isNotSearching && recentMovies.isNotEmpty()) {\n          add(MyMoviesItem.createHeader(RECENTS, recentMovies.count(), null, null))\n          add(MyMoviesItem.createRecentsSection(recentMovies))\n        }\n        if (allMovies.isNotEmpty() || hasAnyFilters) {\n          add(\n            MyMoviesItem.createHeader(\n              section = ALL,\n              itemCount = allMovies.count(),\n              sortOrder = sortOrder,\n              genres = genresFilter\n            )\n          )\n          addAll(allMovies)\n        }\n      }\n\n      itemsState.value = listItems\n      itemsUpdateState.value = Event(notifyListsUpdate)\n      showEmptyViewState.value = movies.isEmpty()\n      viewModeState.value = viewModeCase.getListViewMode()\n    }\n  }\n\n  fun setSortOrder(order: SortOrder, type: SortType) {\n    viewModelScope.launch {\n      sortingCase.setSortOrder(order, type)\n      loadMovies(notifyListsUpdate = true)\n    }\n  }\n\n  fun loadMissingImage(item: MyMoviesItem, force: Boolean) {\n    viewModelScope.launch {\n      updateItem(item.copy(isLoading = true))\n      try {\n        val image = loadMoviesCase.loadMissingImage(item.movie, item.image.type, force)\n        updateItem(item.copy(isLoading = false, image = image))\n      } catch (t: Throwable) {\n        updateItem(item.copy(isLoading = false, image = Image.createUnavailable(item.image.type)))\n      }\n    }\n  }\n\n  fun loadMissingTranslation(item: MyMoviesItem) {\n    if (item.translation != null || settingsRepository.language == DEFAULT_LANGUAGE) return\n    viewModelScope.launch {\n      try {\n        val translation = loadMoviesCase.loadTranslation(item.movie, false)\n        updateItem(item.copy(translation = translation))\n      } catch (error: Throwable) {\n        Timber.e(error)\n      }\n    }\n  }\n\n  fun setNextViewMode() {\n    if (settingsRepository.isPremium) {\n      viewModeState.value = viewModeCase.setNextViewMode()\n      return\n    }\n    viewModelScope.launch {\n      eventChannel.send(FollowedMoviesUiEvent.OpenPremium)\n    }\n  }\n\n  private fun updateItem(new: MyMoviesItem) {\n    val items = uiState.value.items?.toMutableList()\n    items?.findReplace(new) { it isSameAs new }\n    itemsState.value = items\n  }\n\n  private fun CoroutineScope.toListItemAsync(\n    itemType: Type,\n    movie: Movie,\n    dateFormat: DateTimeFormatter,\n    type: ImageType = POSTER,\n    userRating: TraktRating?,\n    sortOrder: SortOrder?,\n    spoilers: SpoilersSettings,\n  ) = async {\n    val image = loadMoviesCase.findCachedImage(movie, type)\n    val translation = loadMoviesCase.loadTranslation(movie, true)\n    MyMoviesItem(\n      type = itemType,\n      header = null,\n      recentsSection = null,\n      movie = movie,\n      image = image,\n      isLoading = false,\n      translation = translation,\n      userRating = userRating?.rating,\n      dateFormat = dateFormat,\n      sortOrder = sortOrder,\n      spoilers = MyMoviesItem.Spoilers(\n        isSpoilerHidden = spoilers.isMyMoviesHidden,\n        isSpoilerRatingsHidden = spoilers.isMyMoviesRatingsHidden,\n        isSpoilerTapToReveal = spoilers.isTapToReveal\n      )\n    )\n  }\n\n  private fun onEvent(event: EventSync) =\n    when (event) {\n      is TraktSyncSuccess -> loadMovies()\n      is TraktSyncError -> loadMovies()\n      is TraktSyncAuthError -> loadMovies()\n      is ReloadData -> loadMovies()\n      else -> Unit\n    }\n\n  val uiState = combine(\n    itemsState,\n    itemsUpdateState,\n    viewModeState,\n    showEmptyViewState\n  ) { s1, s2, s3, s4 ->\n    MyMoviesUiState(\n      items = s1,\n      resetScroll = s2,\n      viewMode = s3,\n      showEmptyView = s4\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = MyMoviesUiState()\n  )\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/cases/MyMoviesLoadCase.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_my_movies.mymovies.helpers.MyMoviesSorter\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MyMoviesLoadCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val sorter: MyMoviesSorter,\n  private val imagesProvider: MovieImagesProvider,\n  private val moviesRepository: MoviesRepository,\n  private val dateFormatProvider: DateFormatProvider,\n  private val translationsRepository: TranslationsRepository,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun loadSettings() = withContext(dispatchers.IO) {\n    settingsRepository.load()\n  }\n\n  suspend fun loadAll() = withContext(dispatchers.IO) {\n    moviesRepository.myMovies.loadAll()\n  }\n\n  fun filterSectionMovies(\n    allMovies: List<MyMoviesItem>,\n    sortOrder: Pair<SortOrder, SortType>,\n    genres: List<String>,\n    searchQuery: String? = null,\n  ) = allMovies\n    .filterByQuery(searchQuery)\n    .filterByGenre(genres)\n    .sortedWith(sorter.sort(sortOrder.first, sortOrder.second))\n\n  private fun List<MyMoviesItem>.filterByQuery(query: String?) = when {\n    query.isNullOrBlank() -> this\n    else -> this.filter {\n      it.movie.title.contains(query, true) ||\n        it.translation?.title?.contains(query, true) == true\n    }\n  }\n\n  private fun List<MyMoviesItem>.filterByGenre(genres: List<String>) =\n    filter { genres.isEmpty() || it.movie.genres.any { genre -> genre.lowercase() in genres } }\n\n  suspend fun loadRecentMovies(): List<Movie> = withContext(dispatchers.IO) {\n    val amount = loadSettings().myRecentsAmount\n    moviesRepository.myMovies.loadAllRecent(amount)\n  }\n\n  suspend fun loadTranslation(movie: Movie, onlyLocal: Boolean): Translation? =\n    withContext(dispatchers.IO) {\n      val language = translationsRepository.getLanguage()\n      if (language == Config.DEFAULT_LANGUAGE) {\n        return@withContext Translation.EMPTY\n      }\n      translationsRepository.loadTranslation(movie, language, onlyLocal)\n    }\n\n  fun loadDateFormat() = dateFormatProvider.loadShortDayFormat()\n\n  suspend fun findCachedImage(movie: Movie, type: ImageType) =\n    imagesProvider.findCachedImage(movie, type)\n\n  suspend fun loadMissingImage(movie: Movie, type: ImageType, force: Boolean) =\n    imagesProvider.loadRemoteImage(movie, type, force)\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/cases/MyMoviesRatingsCase.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.TraktRating\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MyMoviesRatingsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val ratingsRepository: RatingsRepository,\n  private val userTraktManager: UserTraktManager,\n) {\n\n  suspend fun loadRatings(): Map<IdTrakt, TraktRating?> =\n    withContext(dispatchers.IO) {\n      if (!userTraktManager.isAuthorized()) {\n        return@withContext emptyMap()\n      }\n      ratingsRepository.movies.loadMoviesRatings().associateBy { it.idTrakt }\n    }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/cases/MyMoviesSortingCase.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MyMoviesSortingCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun loadSortOrder() = Pair(\n    settingsRepository.sorting.myMoviesAllSortOrder,\n    settingsRepository.sorting.myMoviesAllSortType\n  )\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType) {\n    settingsRepository.sorting.myMoviesAllSortOrder = sortOrder\n    settingsRepository.sorting.myMoviesAllSortType = sortType\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/cases/MyMoviesViewModeCase.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MyMoviesViewModeCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setNextViewMode(): ListViewMode {\n    val viewModes = ListViewMode.values()\n    val index = viewModes.indexOf(getListViewMode()) + 1\n    val nextIndex = if (index >= viewModes.size) 0 else index\n    settingsRepository.viewMode.myMoviesViewMode = viewModes[nextIndex].name\n    return viewModes[nextIndex]\n  }\n\n  fun getListViewMode(): ListViewMode {\n    if (!settingsRepository.isPremium) {\n      return ListViewMode.valueOf(Config.DEFAULT_LIST_VIEW_MODE)\n    }\n    val viewMode = settingsRepository.viewMode.myMoviesViewMode\n    return ListViewMode.valueOf(viewMode)\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/helpers/MyMoviesSorter.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.helpers\n\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_ADDED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SortType.ASCENDING\nimport com.michaldrabik.ui_model.SortType.DESCENDING\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass MyMoviesSorter @Inject constructor() {\n\n  fun sort(sortOrder: SortOrder, sortType: SortType) = when (sortType) {\n    ASCENDING -> sortAscending(sortOrder)\n    DESCENDING -> sortDescending(sortOrder)\n  }\n\n  private fun sortAscending(sortOrder: SortOrder) = when (sortOrder) {\n    NAME -> compareBy { getTitle(it) }\n    RATING -> compareBy { it.movie.rating }\n    USER_RATING ->\n      compareByDescending<MyMoviesItem> { it.userRating != null }\n        .thenBy { it.userRating }\n        .thenBy { getTitle(it) }\n    DATE_ADDED -> compareBy { it.movie.updatedAt }\n    NEWEST -> compareBy<MyMoviesItem> { it.movie.year }.thenBy { it.movie.released }\n    else -> throw IllegalStateException(\"Invalid sort order\")\n  }\n\n  private fun sortDescending(sortOrder: SortOrder) = when (sortOrder) {\n    NAME -> compareByDescending { getTitle(it) }\n    RATING -> compareByDescending { it.movie.rating }\n    USER_RATING ->\n      compareByDescending<MyMoviesItem> { it.userRating != null }\n        .thenByDescending { it.userRating }\n        .thenBy { getTitle(it) }\n    DATE_ADDED -> compareByDescending { it.movie.updatedAt }\n    NEWEST -> compareByDescending<MyMoviesItem> { it.movie.year }.thenByDescending { it.movie.released }\n    else -> throw IllegalStateException(\"Invalid sort order\")\n  }\n\n  private fun getTitle(item: MyMoviesItem): String {\n    val translatedTitle =\n      if (item.translation?.hasTitle == true) item.translation.title\n      else item.movie.titleNoThe\n    return translatedTitle.uppercase()\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/recycler/MyMoviesAdapter.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.recycler\n\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseAdapter\nimport com.michaldrabik.ui_base.BaseMovieAdapter\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem.Type\nimport com.michaldrabik.ui_my_movies.mymovies.views.MyMovieAllCompactView\nimport com.michaldrabik.ui_my_movies.mymovies.views.MyMovieAllGridTitleView\nimport com.michaldrabik.ui_my_movies.mymovies.views.MyMovieAllGridView\nimport com.michaldrabik.ui_my_movies.mymovies.views.MyMovieAllView\nimport com.michaldrabik.ui_my_movies.mymovies.views.MyMovieHeaderView\nimport com.michaldrabik.ui_my_movies.mymovies.views.MyMoviesRecentsView\n\nclass MyMoviesAdapter(\n  private val itemClickListener: (MyMoviesItem) -> Unit,\n  private val itemLongClickListener: (MyMoviesItem) -> Unit,\n  private val missingImageListener: (MyMoviesItem, Boolean) -> Unit,\n  private val missingTranslationListener: (MyMoviesItem) -> Unit,\n  private val onSortOrderClickListener: (SortOrder, SortType) -> Unit,\n  private val onGenresClickListener: () -> Unit,\n  private val onListViewModeClickListener: () -> Unit,\n  listChangeListener: (() -> Unit),\n) : BaseMovieAdapter<MyMoviesItem>(\n  listChangeListener = listChangeListener\n) {\n\n  companion object {\n    private const val VIEW_TYPE_HEADER = 1\n    private const val VIEW_TYPE_MOVIE_ITEM = 2\n    private const val VIEW_TYPE_RECENTS_SECTION = 3\n  }\n\n  init {\n    stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY\n  }\n\n  override val asyncDiffer = AsyncListDiffer(this, MyMoviesItemDiffCallback())\n\n  var listViewMode: ListViewMode = LIST_NORMAL\n    set(value) {\n      field = value\n      notifyItemRangeChanged(0, asyncDiffer.currentList.size)\n    }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    when (viewType) {\n      VIEW_TYPE_HEADER -> BaseViewHolder(MyMovieHeaderView(parent.context))\n      VIEW_TYPE_RECENTS_SECTION -> BaseViewHolder(MyMoviesRecentsView(parent.context))\n      VIEW_TYPE_MOVIE_ITEM -> BaseAdapter.BaseViewHolder(\n        when (listViewMode) {\n          LIST_NORMAL -> MyMovieAllView(parent.context)\n          LIST_COMPACT -> MyMovieAllCompactView(parent.context)\n          GRID -> MyMovieAllGridView(parent.context)\n          GRID_TITLE -> MyMovieAllGridTitleView(parent.context)\n        }.apply {\n          itemClickListener = this@MyMoviesAdapter.itemClickListener\n          itemLongClickListener = this@MyMoviesAdapter.itemLongClickListener\n          missingImageListener = this@MyMoviesAdapter.missingImageListener\n          missingTranslationListener = this@MyMoviesAdapter.missingTranslationListener\n        }\n      )\n      else -> throw IllegalStateException()\n    }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    when (holder.itemViewType) {\n      VIEW_TYPE_HEADER -> (holder.itemView as MyMovieHeaderView).bind(\n        item.header!!,\n        listViewMode,\n        onSortOrderClickListener,\n        onGenresClickListener,\n        onListViewModeClickListener\n      )\n      VIEW_TYPE_RECENTS_SECTION -> (holder.itemView as MyMoviesRecentsView).bind(\n        item.recentsSection!!,\n        itemClickListener,\n        itemLongClickListener\n      )\n      VIEW_TYPE_MOVIE_ITEM -> when (listViewMode) {\n        LIST_NORMAL -> (holder.itemView as MyMovieAllView).bind(item)\n        LIST_COMPACT -> (holder.itemView as MyMovieAllCompactView).bind(item)\n        GRID -> (holder.itemView as MyMovieAllGridView).bind(item)\n        GRID_TITLE -> (holder.itemView as MyMovieAllGridTitleView).bind(item)\n      }\n    }\n  }\n\n  override fun getItemViewType(position: Int) =\n    when (asyncDiffer.currentList[position].type) {\n      Type.HEADER -> VIEW_TYPE_HEADER\n      Type.ALL_MOVIES_ITEM -> VIEW_TYPE_MOVIE_ITEM\n      Type.RECENT_MOVIES -> VIEW_TYPE_RECENTS_SECTION\n    }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/recycler/MyMoviesItem.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.recycler\n\nimport com.michaldrabik.ui_base.common.MovieListItem\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.MyMoviesSection\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.Translation\nimport java.time.format.DateTimeFormatter\n\ndata class MyMoviesItem(\n  val type: Type,\n  val header: Header?,\n  val recentsSection: RecentsSection?,\n  override val movie: Movie,\n  override val image: Image,\n  override val isLoading: Boolean,\n  val spoilers: Spoilers,\n  val translation: Translation? = null,\n  val userRating: Int? = null,\n  val dateFormat: DateTimeFormatter? = null,\n  val sortOrder: SortOrder? = null\n) : MovieListItem {\n\n  enum class Type {\n    HEADER,\n    RECENT_MOVIES,\n    ALL_MOVIES_ITEM\n  }\n\n  data class Header(\n    val section: MyMoviesSection,\n    val itemCount: Int,\n    val sortOrder: Pair<SortOrder, SortType>?,\n    val genres: List<Genre>?,\n  )\n\n  data class RecentsSection(\n    val items: List<MyMoviesItem>,\n  )\n\n  data class Spoilers(\n    val isSpoilerHidden: Boolean,\n    val isSpoilerRatingsHidden: Boolean,\n    val isSpoilerTapToReveal: Boolean,\n  )\n\n  companion object {\n\n    fun createHeader(\n      section: MyMoviesSection,\n      itemCount: Int,\n      sortOrder: Pair<SortOrder, SortType>?,\n      genres: List<Genre>?,\n    ) = MyMoviesItem(\n      type = Type.HEADER,\n      header = Header(section, itemCount, sortOrder, genres),\n      recentsSection = null,\n      movie = Movie.EMPTY,\n      image = Image.createUnavailable(POSTER),\n      isLoading = false,\n      spoilers = Spoilers(\n        isSpoilerHidden = false,\n        isSpoilerRatingsHidden = false,\n        isSpoilerTapToReveal = false\n      )\n    )\n\n    fun createRecentsSection(\n      movies: List<MyMoviesItem>,\n    ) = MyMoviesItem(\n      type = Type.RECENT_MOVIES,\n      header = null,\n      recentsSection = RecentsSection(movies),\n      movie = Movie.EMPTY,\n      image = Image.createUnavailable(POSTER),\n      isLoading = false,\n      spoilers = Spoilers(\n        isSpoilerHidden = false,\n        isSpoilerRatingsHidden = false,\n        isSpoilerTapToReveal = false\n      )\n    )\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/recycler/MyMoviesItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass MyMoviesItemDiffCallback : DiffUtil.ItemCallback<MyMoviesItem>() {\n\n  override fun areItemsTheSame(oldItem: MyMoviesItem, newItem: MyMoviesItem) =\n    when (oldItem.type) {\n      MyMoviesItem.Type.RECENT_MOVIES -> true\n      else -> oldItem.type == newItem.type && oldItem.movie.ids.trakt == newItem.movie.ids.trakt\n    }\n\n  override fun areContentsTheSame(oldItem: MyMoviesItem, newItem: MyMoviesItem) =\n    when (oldItem.type) {\n      MyMoviesItem.Type.HEADER -> oldItem.header == newItem.header\n      MyMoviesItem.Type.RECENT_MOVIES -> oldItem.recentsSection == newItem.recentsSection\n      else ->\n        oldItem.image == newItem.image &&\n          oldItem.isLoading == newItem.isLoading &&\n          oldItem.translation == newItem.translation &&\n          oldItem.spoilers == newItem.spoilers &&\n          oldItem.userRating == newItem.userRating\n    }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/recycler/MyMoviesLayoutManagerProvider.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.recycler\n\nimport android.content.Context\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN_TABLET\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\n\ninternal object MyMoviesLayoutManagerProvider {\n\n  fun provideLayoutManger(\n    context: Context,\n    viewMode: ListViewMode,\n    gridSpanSize: Int,\n  ): RecyclerView.LayoutManager {\n    return if (context.isTablet()) {\n      provideTabletLayout(context, viewMode, gridSpanSize)\n    } else {\n      providePhoneLayout(context, viewMode)\n    }\n  }\n\n  private fun providePhoneLayout(\n    context: Context,\n    viewMode: ListViewMode,\n  ): RecyclerView.LayoutManager {\n    return when (viewMode) {\n      LIST_NORMAL, LIST_COMPACT -> LinearLayoutManager(context, VERTICAL, false)\n      GRID, GRID_TITLE -> GridLayoutManager(context, LISTS_GRID_SPAN)\n    }\n  }\n\n  private fun provideTabletLayout(\n    context: Context,\n    viewMode: ListViewMode,\n    gridSpanSize: Int,\n  ): RecyclerView.LayoutManager {\n    return when (viewMode) {\n      LIST_NORMAL, LIST_COMPACT -> GridLayoutManager(context, gridSpanSize)\n      GRID, GRID_TITLE -> GridLayoutManager(context, LISTS_GRID_SPAN_TABLET)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/utilities/MyMoviesGridItemDecoration.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.utilities\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.annotation.DimenRes\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ItemDecoration\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesAdapter\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem.Type\nimport com.michaldrabik.ui_my_movies.mymovies.views.MyMovieAllGridTitleView\nimport com.michaldrabik.ui_my_movies.mymovies.views.MyMovieAllGridView\n\nclass MyMoviesGridItemDecoration : ItemDecoration {\n\n  private var spacing: Int\n  private var halfSpacing: Int\n\n  constructor(\n    context: Context,\n    @DimenRes spacingDimen: Int,\n  ) {\n    this.spacing = context.resources.getDimensionPixelSize(spacingDimen)\n    this.halfSpacing = spacing / 2\n  }\n\n  override fun getItemOffsets(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n    state: RecyclerView.State,\n  ) {\n    if (parent.layoutManager !is GridLayoutManager) return\n\n    val totalSpan = (parent.layoutManager as GridLayoutManager).spanCount\n\n    if (view is MyMovieAllGridView || view is MyMovieAllGridTitleView) {\n      outRect.top = halfSpacing\n      outRect.bottom = halfSpacing\n\n      val nonMyShowItemCount = (parent.adapter as MyMoviesAdapter)\n        .getItems()\n        .count { it.type != Type.ALL_MOVIES_ITEM }\n\n      val position = parent.getChildAdapterPosition(view) - nonMyShowItemCount\n      val column = position % totalSpan\n\n      outRect.left = spacing * column / totalSpan\n      outRect.right = spacing * ((totalSpan - 1) - column) / totalSpan\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/utilities/MyMoviesListItemDecoration.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.utilities\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.annotation.DimenRes\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ItemDecoration\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesAdapter\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem.Type\nimport com.michaldrabik.ui_my_movies.mymovies.views.MyMovieAllCompactView\nimport com.michaldrabik.ui_my_movies.mymovies.views.MyMovieAllView\n\nclass MyMoviesListItemDecoration : ItemDecoration {\n\n  private var spacing: Int\n  private var halfSpacing: Int\n  private val isTablet: Boolean\n\n  constructor(\n    context: Context,\n    @DimenRes spacingDimen: Int,\n  ) {\n    this.spacing = context.resources.getDimensionPixelSize(spacingDimen)\n    this.halfSpacing = spacing / 2\n    this.isTablet = context.isTablet()\n  }\n\n  override fun getItemOffsets(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n    state: RecyclerView.State,\n  ) {\n    if (view !is MyMovieAllView && view !is MyMovieAllCompactView) {\n      return\n    }\n    if (!isTablet && (parent.layoutManager is LinearLayoutManager)) {\n      getItemOffsetsPhone(outRect, view)\n      return\n    }\n    if (isTablet && (parent.layoutManager is GridLayoutManager)) {\n      getItemOffsetsTablet(outRect, view, parent)\n      return\n    }\n  }\n\n  private fun getItemOffsetsTablet(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n  ) {\n    if (view is MyMovieAllView) {\n      outRect.top = spacing\n      outRect.bottom = spacing\n    } else if (view is MyMovieAllCompactView) {\n      outRect.top = halfSpacing\n      outRect.bottom = halfSpacing\n    }\n\n    val totalSpan = (parent.layoutManager as GridLayoutManager).spanCount\n    val column = getPosition(parent, view) % totalSpan\n\n    outRect.left = (spacing * 2) * column / totalSpan\n    outRect.right = (spacing * 2) * ((totalSpan - 1) - column) / totalSpan\n  }\n\n  private fun getItemOffsetsPhone(outRect: Rect, view: View) {\n    if (view is MyMovieAllView) {\n      outRect.top = spacing\n      outRect.bottom = spacing\n    } else if (view is MyMovieAllCompactView) {\n      outRect.top = halfSpacing\n      outRect.bottom = halfSpacing\n    }\n    outRect.left = 0\n    outRect.right = 0\n  }\n\n  private fun getPosition(\n    parent: RecyclerView,\n    view: View\n  ): Int {\n    val nonMyShowItemCount = (parent.adapter as MyMoviesAdapter)\n      .getItems()\n      .count { it.type != Type.ALL_MOVIES_ITEM }\n\n    return parent.getChildAdapterPosition(view) - nonMyShowItemCount\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/views/MyMovieAllCompactView.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.common.views.MovieView\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.setOutboundRipple\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_my_movies.R\nimport com.michaldrabik.ui_my_movies.databinding.ViewCollectionMovieCompactBinding\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem\nimport java.util.Locale.ENGLISH\n\n@SuppressLint(\"SetTextI18n\")\nclass MyMovieAllCompactView : MovieView<MyMoviesItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionMovieCompactBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      collectionMovieRoot.onClick { itemClickListener?.invoke(item) }\n      collectionMovieRoot.onLongClick { itemLongClickListener?.invoke(item) }\n      collectionMovieRoot.setOutboundRipple(\n        size = (context.dimenToPx(R.dimen.collectionItemRippleSpaceSmall)).toFloat(),\n        corner = context.dimenToPx(R.dimen.mediaTileCorner).toFloat()\n      )\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionMovieImage\n  override val placeholderView: ImageView = binding.collectionMoviePlaceholder\n\n  private lateinit var item: MyMoviesItem\n\n  override fun bind(item: MyMoviesItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      collectionMovieProgress.visibleIf(item.isLoading)\n      collectionMovieTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.movie.title\n        else item.translation?.title\n\n      bindRating(item)\n\n      collectionMovieYear.visibleIf(item.movie.released != null || item.movie.year > 0)\n      collectionMovieYear.text = when {\n        item.movie.released != null -> item.dateFormat?.format(item.movie.released)?.capitalizeWords()\n        else -> String.format(ENGLISH, \"%d\", item.movie.year)\n      }\n\n      item.userRating?.let {\n        collectionMovieUserStarIcon.visible()\n        collectionMovieUserRating.visible()\n        collectionMovieUserRating.text = String.format(ENGLISH, \"%d\", it)\n      }\n\n      loadImage(item)\n    }\n  }\n\n  private fun bindRating(item: MyMoviesItem) {\n    with(binding) {\n      var rating = String.format(ENGLISH, \"%.1f\", item.movie.rating)\n\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionMovieRating.tag = rating\n        rating = Config.SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          with(collectionMovieRating) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      }\n\n      collectionMovieRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionMovieTitle.text = \"\"\n      collectionMovieYear.text = \"\"\n      collectionMovieRating.text = \"\"\n      collectionMovieUserRating.gone()\n      collectionMovieUserStarIcon.gone()\n      collectionMoviePlaceholder.gone()\n      Glide.with(this@MyMovieAllCompactView).clear(collectionMovieImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/views/MyMovieAllGridTitleView.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Config.SPOILERS_RATINGS_HIDE_SYMBOL\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.views.MovieView\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_my_movies.databinding.ViewCollectionMovieGridTitleBinding\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem\nimport java.util.Locale\n\n@SuppressLint(\"SetTextI18n\")\nclass MyMovieAllGridTitleView : MovieView<MyMoviesItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionMovieGridTitleBinding.inflate(LayoutInflater.from(context), this)\n\n  private val width by lazy {\n    val span = if (context.isTablet()) Config.LISTS_GRID_SPAN_TABLET else Config.LISTS_GRID_SPAN\n    val itemSpacing = context.dimenToPx(R.dimen.spaceSmall)\n    val screenMargin = context.dimenToPx(R.dimen.screenMarginHorizontal)\n    val screenWidth = screenWidth().toFloat()\n    ((screenWidth - (screenMargin * 2.0)) - ((span - 1) * itemSpacing)) / span\n  }\n  private val height by lazy { width * 1.7305 }\n\n  init {\n    layoutParams = LayoutParams(width.toInt(), height.toInt())\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      collectionMovieRoot.onClick { itemClickListener?.invoke(item) }\n      collectionMovieRoot.onLongClick { itemLongClickListener?.invoke(item) }\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionMovieImage\n  override val placeholderView: ImageView = binding.collectionMoviePlaceholder\n\n  private lateinit var item: MyMoviesItem\n\n  override fun bind(item: MyMoviesItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      collectionMovieProgress.visibleIf(item.isLoading)\n\n      collectionMovieTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.movie.title\n        else item.translation?.title\n\n      if (item.sortOrder == SortOrder.RATING) {\n        bindRating(item)\n      } else if (item.sortOrder == SortOrder.USER_RATING && item.userRating != null) {\n        collectionMovieRating.visible()\n        collectionMovieRating.text = String.format(Locale.ENGLISH, \"%d\", item.userRating)\n      } else {\n        collectionMovieRating.gone()\n      }\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindRating(item: MyMoviesItem) {\n    with(binding) {\n      var rating = String.format(Locale.ENGLISH, \"%.1f\", item.movie.rating)\n\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionMovieRating.tag = rating\n        rating = SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          with(collectionMovieRating) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      }\n\n      collectionMovieRating.visible()\n      collectionMovieRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionMovieTitle.text = \"\"\n      collectionMoviePlaceholder.gone()\n      Glide.with(this@MyMovieAllGridTitleView).clear(collectionMovieImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/views/MyMovieAllGridView.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Config.SPOILERS_RATINGS_HIDE_SYMBOL\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.views.MovieView\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_my_movies.databinding.ViewCollectionMovieGridBinding\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem\nimport java.util.Locale.ENGLISH\n\n@SuppressLint(\"SetTextI18n\")\nclass MyMovieAllGridView : MovieView<MyMoviesItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionMovieGridBinding.inflate(LayoutInflater.from(context), this)\n\n  private val width by lazy {\n    val span = if (context.isTablet()) Config.LISTS_GRID_SPAN_TABLET else Config.LISTS_GRID_SPAN\n    val itemSpacing = context.dimenToPx(R.dimen.spaceSmall)\n    val screenMargin = context.dimenToPx(R.dimen.screenMarginHorizontal)\n    val screenWidth = screenWidth().toFloat()\n    ((screenWidth - (screenMargin * 2.0)) - ((span - 1) * itemSpacing)) / span\n  }\n  private val height by lazy { width * ASPECT_RATIO }\n\n  init {\n    layoutParams = LayoutParams(width.toInt(), height.toInt())\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      collectionMovieRoot.onClick { itemClickListener?.invoke(item) }\n      collectionMovieRoot.onLongClick { itemLongClickListener?.invoke(item) }\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionMovieImage\n  override val placeholderView: ImageView = binding.collectionMoviePlaceholder\n\n  private lateinit var item: MyMoviesItem\n\n  override fun bind(item: MyMoviesItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      collectionMovieProgress.visibleIf(item.isLoading)\n\n      if (item.sortOrder == RATING) {\n        bindRating(item)\n      } else if (item.sortOrder == USER_RATING && item.userRating != null) {\n        collectionMovieRating.visible()\n        collectionMovieRating.text = String.format(ENGLISH, \"%d\", item.userRating)\n      } else {\n        collectionMovieRating.gone()\n      }\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindRating(item: MyMoviesItem) {\n    with(binding) {\n      var rating = String.format(ENGLISH, \"%.1f\", item.movie.rating)\n\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionMovieRating.tag = rating\n        rating = SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          with(collectionMovieRating) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      }\n\n      collectionMovieRating.visible()\n      collectionMovieRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionMoviePlaceholder.gone()\n      Glide.with(this@MyMovieAllGridView).clear(collectionMovieImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/views/MyMovieAllView.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_RATINGS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.ui_base.common.views.MovieView\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.setOutboundRipple\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_my_movies.R\nimport com.michaldrabik.ui_my_movies.databinding.ViewCollectionMovieBinding\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem\nimport java.util.Locale.ENGLISH\n\n@SuppressLint(\"SetTextI18n\")\nclass MyMovieAllView : MovieView<MyMoviesItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionMovieBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      collectionMovieRoot.onClick { itemClickListener?.invoke(item) }\n      collectionMovieRoot.onLongClick { itemLongClickListener?.invoke(item) }\n      collectionMovieRoot.setOutboundRipple(\n        size = (context.dimenToPx(R.dimen.collectionItemRippleSpace)).toFloat(),\n        corner = context.dimenToPx(R.dimen.mediaTileCorner).toFloat()\n      )\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionMovieImage\n  override val placeholderView: ImageView = binding.collectionMoviePlaceholder\n\n  private lateinit var item: MyMoviesItem\n\n  override fun bind(item: MyMoviesItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      collectionMovieProgress.visibleIf(item.isLoading)\n      collectionMovieTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.movie.title\n        else item.translation?.title\n\n      bindDescription(item)\n      bindRating(item)\n\n      collectionMovieYear.visibleIf(item.movie.released != null || item.movie.year > 0)\n      collectionMovieYear.text = when {\n        item.movie.released != null -> item.dateFormat?.format(item.movie.released)?.capitalizeWords()\n        else -> String.format(ENGLISH, \"%d\", item.movie.year)\n      }\n\n      item.userRating?.let {\n        collectionMovieUserStarIcon.visible()\n        collectionMovieUserRating.visible()\n        collectionMovieUserRating.text = String.format(ENGLISH, \"%d\", it)\n      }\n    }\n    loadImage(item)\n  }\n\n  private fun bindDescription(item: MyMoviesItem) {\n    var description =\n      if (item.translation?.overview.isNullOrBlank()) item.movie.overview\n      else item.translation?.overview\n\n    with(binding) {\n      if (item.spoilers.isSpoilerHidden) {\n        collectionMovieDescription.tag = description.toString()\n        description = SPOILERS_REGEX.replace(description.toString(), SPOILERS_HIDE_SYMBOL)\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          collectionMovieDescription.onClick { view ->\n            view.tag?.let {\n              collectionMovieDescription.text = it.toString()\n            }\n            view.isClickable = false\n          }\n        }\n      }\n\n      collectionMovieDescription.text = description\n      collectionMovieDescription.visibleIf(item.movie.overview.isNotBlank())\n    }\n  }\n\n  private fun bindRating(item: MyMoviesItem) {\n    var rating = String.format(ENGLISH, \"%.1f\", item.movie.rating)\n\n    with(binding) {\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionMovieRating.tag = rating\n        rating = SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          collectionMovieRating.onClick { view ->\n            view.tag?.let {\n              collectionMovieRating.text = it.toString()\n            }\n            view.isClickable = false\n          }\n        }\n      }\n      collectionMovieRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionMovieTitle.text = \"\"\n      collectionMovieDescription.text = \"\"\n      collectionMovieYear.text = \"\"\n      collectionMovieRating.text = \"\"\n      collectionMovieUserRating.gone()\n      collectionMovieUserStarIcon.gone()\n      collectionMoviePlaceholder.gone()\n      Glide.with(this@MyMovieAllView).clear(collectionMovieImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/views/MyMovieFanartView.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions\nimport com.michaldrabik.common.Config.IMAGE_FADE_DURATION_MS\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageStatus\nimport com.michaldrabik.ui_my_movies.R\nimport com.michaldrabik.ui_my_movies.databinding.ViewMyMoviesFanartBinding\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem\n\nclass MyMovieFanartView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewMyMoviesFanartBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    setBackgroundResource(R.drawable.bg_media_view_elevation)\n    elevation = context.dimenToPx(R.dimen.elevationSmall).toFloat()\n  }\n\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.myMoviesFanartCorner) }\n\n  fun bind(\n    item: MyMoviesItem,\n    clickListener: (MyMoviesItem) -> Unit,\n    longClickListener: (MyMoviesItem) -> Unit,\n  ) {\n    clear()\n    with(binding) {\n      myMovieFanartTitle.visible()\n      myMovieFanartTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.movie.title\n        else item.translation?.title\n    }\n    onClick { clickListener(item) }\n    onLongClick { longClickListener(item) }\n    loadImage(item.image)\n  }\n\n  private fun loadImage(image: Image) {\n    with(binding) {\n      if (image.status != ImageStatus.AVAILABLE) {\n        myMovieFanartPlaceholder.visible()\n        myMovieFanartRoot.setBackgroundResource(R.drawable.bg_media_view_placeholder)\n        return\n      }\n      Glide.with(this@MyMovieFanartView)\n        .load(image.fullFileUrl)\n        .transform(CenterCrop(), RoundedCorners(cornerRadius))\n        .transition(DrawableTransitionOptions.withCrossFade(IMAGE_FADE_DURATION_MS))\n        .withFailListener {\n          myMovieFanartPlaceholder.visible()\n          myMovieFanartImage.gone()\n        }\n        .into(myMovieFanartImage)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      myMovieFanartPlaceholder.gone()\n      myMovieFanartTitle.text = \"\"\n      myMovieFanartRoot.setBackgroundResource(0)\n      Glide.with(this@MyMovieFanartView).clear(myMovieFanartImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/views/MyMovieHeaderView.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport androidx.core.content.ContextCompat\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.MyMoviesSection.ALL\nimport com.michaldrabik.ui_model.MyMoviesSection.RECENTS\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_movies.R\nimport com.michaldrabik.ui_my_movies.databinding.ViewMyMoviesHeaderBinding\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem\nimport java.util.Locale.ENGLISH\n\nclass MyMovieHeaderView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewMyMoviesHeaderBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    clipChildren = false\n    clipToPadding = false\n  }\n\n  fun bind(\n    item: MyMoviesItem.Header,\n    viewMode: ListViewMode,\n    sortClickListener: (SortOrder, SortType) -> Unit,\n    genresClickListener: () -> Unit,\n    listModeClickListener: (() -> Unit)?,\n  ) {\n    bindLabel(item)\n    with(binding) {\n      myMoviesFilterChipsScroll.visibleIf(item.section == ALL)\n      myMoviesSortChip.visibleIf(item.sortOrder != null)\n      myMoviesGenresChip.visibleIf(item.genres != null)\n\n      with(myMoviesSortListViewChip) {\n        when (viewMode) {\n          LIST_NORMAL, LIST_COMPACT -> setChipIconResource(R.drawable.ic_view_list)\n          GRID, GRID_TITLE -> setChipIconResource(R.drawable.ic_view_grid)\n        }\n        onClick { listModeClickListener?.invoke() }\n      }\n\n      item.sortOrder?.let { sortOrder ->\n        myMoviesSortChip.text = context.getString(sortOrder.first.displayString)\n        myMoviesSortChip.onClick {\n          sortClickListener.invoke(sortOrder.first, sortOrder.second)\n        }\n        val sortIcon = when (sortOrder.second) {\n          SortType.ASCENDING -> R.drawable.ic_arrow_alt_up\n          SortType.DESCENDING -> R.drawable.ic_arrow_alt_down\n        }\n        myMoviesSortChip.closeIcon = ContextCompat.getDrawable(context, sortIcon)\n      }\n\n      item.genres?.let { genres ->\n        myMoviesGenresChip.isSelected = genres.isNotEmpty()\n        myMoviesGenresChip.onClick { genresClickListener.invoke() }\n        myMoviesGenresChip.text = when {\n          genres.isEmpty() -> context.getString(R.string.textGenres).filter { it.isLetter() }\n          genres.size == 1 -> context.getString(genres.first().displayName)\n          genres.size == 2 -> \"${context.getString(genres[0].displayName)}, ${context.getString(genres[1].displayName)}\"\n          else -> \"${context.getString(genres[0].displayName)}, ${context.getString(genres[1].displayName)} + ${genres.size - 2}\"\n        }\n      }\n    }\n  }\n\n  private fun bindLabel(item: MyMoviesItem.Header) {\n    val headerLabel = context.getString(item.section.displayString)\n    binding.myMoviesHeaderLabel.text = when (item.section) {\n      RECENTS -> headerLabel\n      else -> String.format(ENGLISH, \"%s (%d)\", headerLabel, item.itemCount)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/mymovies/views/MyMoviesRecentsView.kt",
    "content": "package com.michaldrabik.ui_my_movies.mymovies.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport android.widget.GridLayout\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_my_movies.R\nimport com.michaldrabik.ui_my_movies.databinding.ViewMyMoviesRecentsBinding\nimport com.michaldrabik.ui_my_movies.mymovies.recycler.MyMoviesItem\n\nclass MyMoviesRecentsView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewMyMoviesRecentsBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    clipChildren = false\n  }\n\n  private val itemMargin by lazy { context.dimenToPx(R.dimen.spaceTiny) }\n  private val itemHeight by lazy { context.dimenToPx(R.dimen.myMoviesFanartHeight) }\n  private val itemWidth by lazy {\n    val space = context.dimenToPx(R.dimen.screenMarginHorizontal) * 2\n    ((screenWidth() - space) / 2) - itemMargin\n  }\n\n  fun bind(\n    item: MyMoviesItem.RecentsSection,\n    itemClickListener: ((MyMoviesItem) -> Unit)?,\n    itemLongClickListener: ((MyMoviesItem) -> Unit)?,\n  ) {\n    binding.myMoviesRecentsContainer.removeAllViews()\n\n    val clickListener: (MyMoviesItem) -> Unit = { itemClickListener?.invoke(it) }\n    val longClickListener: (MyMoviesItem) -> Unit = { itemLongClickListener?.invoke(it) }\n\n    item.items.forEachIndexed { index, showItem ->\n      val view = MyMovieFanartView(context).apply {\n        layoutParams = LayoutParams(0, MATCH_PARENT)\n        bind(showItem, clickListener, longClickListener)\n      }\n      val layoutParams = GridLayout.LayoutParams().apply {\n        width = itemWidth\n        height = itemHeight\n        columnSpec = GridLayout.spec(index % 2, 1F)\n        if (index % 2 == 0) {\n          setMargins(0, itemMargin, itemMargin, itemMargin)\n        } else {\n          setMargins(itemMargin, itemMargin, 0, itemMargin)\n        }\n      }\n      binding.myMoviesRecentsContainer.addView(view, layoutParams)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/watchlist/WatchlistFragment.kt",
    "content": "package com.michaldrabik.ui_my_movies.watchlist\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.postDelayed\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN_TABLET\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.common.OnScrollResetListener\nimport com.michaldrabik.ui_base.common.OnSearchClickListener\nimport com.michaldrabik.ui_base.common.sheets.sort_order.SortOrderBottomSheet\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.withSpanSizeLookup\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_ADDED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_movies.R\nimport com.michaldrabik.ui_my_movies.common.layout.CollectionMovieGridItemDecoration\nimport com.michaldrabik.ui_my_movies.common.layout.CollectionMovieLayoutManagerProvider\nimport com.michaldrabik.ui_my_movies.common.layout.CollectionMovieListItemDecoration\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionAdapter\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem.FiltersItem\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem.MovieItem\nimport com.michaldrabik.ui_my_movies.databinding.FragmentWatchlistMoviesBinding\nimport com.michaldrabik.ui_my_movies.filters.CollectionFiltersOrigin.WATCHLIST_MOVIES\nimport com.michaldrabik.ui_my_movies.filters.genre.CollectionFiltersGenreBottomSheet\nimport com.michaldrabik.ui_my_movies.filters.genre.CollectionFiltersGenreBottomSheet.Companion.REQUEST_COLLECTION_FILTERS_GENRE\nimport com.michaldrabik.ui_my_movies.main.FollowedMoviesFragment\nimport com.michaldrabik.ui_my_movies.main.FollowedMoviesUiEvent.OpenPremium\nimport com.michaldrabik.ui_my_movies.main.FollowedMoviesViewModel\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass WatchlistFragment :\n  BaseFragment<WatchlistViewModel>(R.layout.fragment_watchlist_movies),\n  OnScrollResetListener,\n  OnSearchClickListener {\n\n  @Inject lateinit var settings: SettingsViewModeRepository\n\n  override val navigationId = R.id.followedMoviesFragment\n  private val binding by viewBinding(FragmentWatchlistMoviesBinding::bind)\n\n  private val parentViewModel by viewModels<FollowedMoviesViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<WatchlistViewModel>()\n\n  private var adapter: CollectionAdapter? = null\n  private var layoutManager: LayoutManager? = null\n  private var statusBarHeight = 0\n  private var isSearching = false\n  private val tabletGridSpanSize by lazy { settings.tabletGridSpanSize }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupStatusBar()\n    setupRecycler()\n\n    launchAndRepeatStarted(\n      { parentViewModel.uiState.collect { viewModel.onParentState(it) } },\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = { viewModel.loadMovies() }\n    )\n  }\n\n  private fun setupRecycler() {\n    layoutManager = CollectionMovieLayoutManagerProvider.provideLayoutManger(\n      context = requireContext(),\n      viewMode = LIST_NORMAL,\n      gridSpanSize = settings.tabletGridSpanSize\n    )\n    adapter = CollectionAdapter(\n      itemClickListener = { openMovieDetails(it.movie) },\n      itemLongClickListener = { openMovieMenu(it.movie) },\n      sortChipClickListener = ::openSortOrderDialog,\n      genreChipClickListener = ::openGenresDialog,\n      upcomingChipClickListener = viewModel::setFilters,\n      missingImageListener = viewModel::loadMissingImage,\n      missingTranslationListener = viewModel::loadMissingTranslation,\n      listViewChipClickListener = viewModel::setNextViewMode,\n      listChangeListener = {\n        binding.watchlistMoviesRecycler.scrollToPosition(0)\n        (requireParentFragment() as FollowedMoviesFragment).resetTranslations()\n      },\n    )\n    binding.watchlistMoviesRecycler.apply {\n      setHasFixedSize(true)\n      adapter = this@WatchlistFragment.adapter\n      layoutManager = this@WatchlistFragment.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      addItemDecoration(CollectionMovieListItemDecoration(requireContext(), R.dimen.spaceSmall))\n      addItemDecoration(CollectionMovieGridItemDecoration(requireContext(), R.dimen.spaceSmall))\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      if (statusBarHeight != 0) {\n        watchlistMoviesContent.updatePadding(top = watchlistMoviesContent.paddingTop + statusBarHeight)\n        watchlistMoviesRecycler.updatePadding(top = dimenToPx(R.dimen.collectionTabsViewPadding))\n        return\n      }\n      watchlistMoviesContent.doOnApplyWindowInsets { view, insets, padding, _ ->\n        val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n        statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n        view.updatePadding(top = padding.top + statusBarHeight)\n        watchlistMoviesRecycler.updatePadding(top = dimenToPx(R.dimen.collectionTabsViewPadding))\n      }\n    }\n  }\n\n  private fun render(uiState: WatchlistUiState) {\n    uiState.run {\n      viewMode.let {\n        if (adapter?.listViewMode != it) {\n          layoutManager = CollectionMovieLayoutManagerProvider.provideLayoutManger(\n            context = requireContext(),\n            viewMode = it,\n            gridSpanSize = tabletGridSpanSize\n          )\n          adapter?.listViewMode = it\n          binding.watchlistMoviesRecycler.let { recycler ->\n            recycler.layoutManager = layoutManager\n            recycler.adapter = adapter\n          }\n        }\n      }\n      items.let {\n        val notifyChange = resetScroll?.consume() == true\n        adapter?.setItems(it, notifyChange = notifyChange)\n        (layoutManager as? GridLayoutManager)?.withSpanSizeLookup { pos ->\n          when (adapter?.getItems()?.get(pos)) {\n            is FiltersItem -> {\n              when (viewMode) {\n                LIST_NORMAL, LIST_COMPACT -> if (isTablet) tabletGridSpanSize else LISTS_GRID_SPAN\n                GRID, GRID_TITLE -> if (isTablet) LISTS_GRID_SPAN_TABLET else LISTS_GRID_SPAN\n              }\n            }\n            is MovieItem -> 1\n            else -> throw Error(\"Unsupported span size!\")\n          }\n        }\n        binding.watchlistMoviesEmptyView.root.fadeIf(it.isEmpty() && !isSearching)\n      }\n      sortOrder?.let { event ->\n        event.consume()?.let { openSortOrderDialog(it.first, it.second) }\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is OpenPremium -> {\n        (requireParentFragment() as? FollowedMoviesFragment)?.openPremium()\n      }\n    }\n  }\n\n  private fun openSortOrderDialog(order: SortOrder, type: SortType) {\n    val options = listOf(NAME, RATING, USER_RATING, NEWEST, DATE_ADDED)\n    val args = SortOrderBottomSheet.createBundle(options, order, type)\n\n    requireParentFragment().setFragmentResultListener(NavigationArgs.REQUEST_SORT_ORDER) { _, bundle ->\n      val sortOrder = bundle.getSerializable(NavigationArgs.ARG_SELECTED_SORT_ORDER) as SortOrder\n      val sortType = bundle.getSerializable(NavigationArgs.ARG_SELECTED_SORT_TYPE) as SortType\n      viewModel.setSortOrder(sortOrder, sortType)\n    }\n\n    navigateTo(R.id.actionFollowedMoviesFragmentToSortOrder, args)\n  }\n\n  private fun openGenresDialog() {\n    requireParentFragment().setFragmentResultListener(REQUEST_COLLECTION_FILTERS_GENRE) { _, _ ->\n      viewModel.loadMovies(resetScroll = true)\n    }\n\n    val bundle = CollectionFiltersGenreBottomSheet.createBundle(WATCHLIST_MOVIES)\n    navigateToSafe(R.id.actionFollowedMoviesFragmentToGenres, bundle)\n  }\n\n  private fun openMovieDetails(movie: Movie) {\n    (requireParentFragment() as? FollowedMoviesFragment)?.openMovieDetails(movie)\n  }\n\n  private fun openMovieMenu(movie: Movie) {\n    (requireParentFragment() as? FollowedMoviesFragment)?.openMovieMenu(movie)\n  }\n\n  override fun onEnterSearch() {\n    isSearching = true\n    with(binding) {\n      watchlistMoviesRecycler.translationY = dimenToPx(R.dimen.myMoviesSearchLocalOffset).toFloat()\n      watchlistMoviesRecycler.smoothScrollToPosition(0)\n    }\n  }\n\n  override fun onExitSearch() {\n    isSearching = false\n    with(binding.watchlistMoviesRecycler) {\n      translationY = 0F\n      postDelayed(200) { layoutManager?.scrollToPosition(0) }\n    }\n  }\n\n  override fun onScrollReset() = binding.watchlistMoviesRecycler.scrollToPosition(0)\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/watchlist/WatchlistUiState.kt",
    "content": "package com.michaldrabik.ui_my_movies.watchlist\n\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem\n\ndata class WatchlistUiState(\n  val items: List<CollectionListItem> = emptyList(),\n  val viewMode: ListViewMode = ListViewMode.LIST_NORMAL,\n  val resetScroll: Event<Boolean>? = null,\n  val sortOrder: Event<Pair<SortOrder, SortType>>? = null,\n)\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/watchlist/WatchlistViewModel.kt",
    "content": "package com.michaldrabik.ui_my_movies.watchlist\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Config.DEFAULT_LANGUAGE\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.ReloadData\nimport com.michaldrabik.ui_base.events.TraktSyncAuthError\nimport com.michaldrabik.ui_base.events.TraktSyncError\nimport com.michaldrabik.ui_base.events.TraktSyncSuccess\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem.MovieItem\nimport com.michaldrabik.ui_my_movies.main.FollowedMoviesUiEvent.OpenPremium\nimport com.michaldrabik.ui_my_movies.main.FollowedMoviesUiState\nimport com.michaldrabik.ui_my_movies.watchlist.cases.WatchlistFiltersCase\nimport com.michaldrabik.ui_my_movies.watchlist.cases.WatchlistLoadMoviesCase\nimport com.michaldrabik.ui_my_movies.watchlist.cases.WatchlistSortOrderCase\nimport com.michaldrabik.ui_my_movies.watchlist.cases.WatchlistViewModeCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\nimport com.michaldrabik.ui_base.events.Event as EventSync\n\n@HiltViewModel\nclass WatchlistViewModel @Inject constructor(\n  private val sortOrderCase: WatchlistSortOrderCase,\n  private val filtersCase: WatchlistFiltersCase,\n  private val loadMoviesCase: WatchlistLoadMoviesCase,\n  private val viewModeCase: WatchlistViewModeCase,\n  private val settingsRepository: SettingsRepository,\n  private val imagesProvider: MovieImagesProvider,\n  private val eventsManager: EventsManager,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private var loadItemsJob: Job? = null\n\n  private val itemsState = MutableStateFlow<List<CollectionListItem>>(emptyList())\n  private val viewModeState = MutableStateFlow(ListViewMode.LIST_NORMAL)\n  private val sortOrderState = MutableStateFlow<Event<Pair<SortOrder, SortType>>?>(null)\n  private val scrollState = MutableStateFlow<Event<Boolean>?>(null)\n\n  private var searchQuery: String? = null\n\n  init {\n    viewModelScope.launch { eventsManager.events.collect { onEvent(it) } }\n  }\n\n  fun onParentState(state: FollowedMoviesUiState) {\n    when {\n      this.searchQuery != state.searchQuery -> {\n        this.searchQuery = state.searchQuery\n        loadMovies(resetScroll = state.searchQuery.isNullOrBlank())\n      }\n    }\n  }\n\n  fun loadMovies(resetScroll: Boolean = false) {\n    loadItemsJob?.cancel()\n    loadItemsJob = viewModelScope.launch {\n      viewModeState.value = viewModeCase.getListViewMode()\n      itemsState.value = loadMoviesCase.loadMovies(searchQuery ?: \"\")\n      scrollState.value = Event(resetScroll)\n    }\n  }\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType) {\n    viewModelScope.launch {\n      sortOrderCase.setSortOrder(sortOrder, sortType)\n      loadMovies(resetScroll = true)\n    }\n  }\n\n  fun setFilters(isUpcoming: Boolean) {\n    viewModelScope.launch {\n      filtersCase.setIsUpcoming(isUpcoming)\n      loadMovies(resetScroll = true)\n    }\n  }\n\n  fun setNextViewMode() {\n    if (settingsRepository.isPremium) {\n      viewModeState.value = viewModeCase.setNextViewMode()\n      return\n    }\n    viewModelScope.launch {\n      eventChannel.send(OpenPremium)\n    }\n  }\n\n  fun loadMissingImage(item: CollectionListItem, force: Boolean) {\n    check(item is MovieItem)\n    viewModelScope.launch {\n      updateItem(item.copy(isLoading = true))\n      try {\n        val image = imagesProvider.loadRemoteImage(item.movie, item.image.type, force)\n        updateItem(item.copy(isLoading = false, image = image))\n      } catch (t: Throwable) {\n        updateItem(item.copy(isLoading = false, image = Image.createUnavailable(item.image.type)))\n      }\n    }\n  }\n\n  fun loadMissingTranslation(item: CollectionListItem) {\n    check(item is MovieItem)\n    if (item.translation != null || settingsRepository.language == DEFAULT_LANGUAGE) return\n    viewModelScope.launch {\n      try {\n        val translation = loadMoviesCase.loadTranslation(item.movie, false)\n        updateItem(item.copy(translation = translation))\n      } catch (error: Throwable) {\n        Timber.e(error)\n      }\n    }\n  }\n\n  private fun updateItem(item: CollectionListItem) {\n    val currentItems = uiState.value.items.toMutableList()\n    currentItems.findReplace(item) { it isSameAs item }\n    itemsState.value = currentItems\n  }\n\n  private fun onEvent(event: EventSync) =\n    when (event) {\n      is TraktSyncSuccess -> loadMovies()\n      is TraktSyncError -> loadMovies()\n      is TraktSyncAuthError -> loadMovies()\n      is ReloadData -> loadMovies()\n      else -> Unit\n    }\n\n  val uiState = combine(\n    itemsState,\n    sortOrderState,\n    scrollState,\n    viewModeState\n  ) { s1, s2, s3, s4 ->\n    WatchlistUiState(\n      items = s1,\n      sortOrder = s2,\n      resetScroll = s3,\n      viewMode = s4\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = WatchlistUiState()\n  )\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/watchlist/cases/WatchlistFiltersCase.kt",
    "content": "package com.michaldrabik.ui_my_movies.watchlist.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass WatchlistFiltersCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setIsUpcoming(isUpcoming: Boolean) {\n    settingsRepository.filters.watchlistMoviesUpcoming = isUpcoming\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/watchlist/cases/WatchlistLoadMoviesCase.kt",
    "content": "package com.michaldrabik.ui_my_movies.watchlist.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.TraktRating\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_my_movies.common.helpers.CollectionItemFilter\nimport com.michaldrabik.ui_my_movies.common.helpers.CollectionItemSorter\nimport com.michaldrabik.ui_my_movies.common.recycler.CollectionListItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport java.time.format.DateTimeFormatter\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass WatchlistLoadMoviesCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val ratingsCase: WatchlistRatingsCase,\n  private val sorter: CollectionItemSorter,\n  private val filters: CollectionItemFilter,\n  private val moviesRepository: MoviesRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val dateFormatProvider: DateFormatProvider,\n  private val imagesProvider: MovieImagesProvider,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun loadMovies(searchQuery: String): List<CollectionListItem> =\n    withContext(dispatchers.IO) {\n      val ratings = ratingsCase.loadRatings()\n      val dateFormat = dateFormatProvider.loadShortDayFormat()\n      val fullDateFormat = dateFormatProvider.loadFullDayFormat()\n      val language = translationsRepository.getLanguage()\n      val spoilers = settingsRepository.spoilers.getAll()\n      val translations = if (language == Config.DEFAULT_LANGUAGE) {\n        emptyMap()\n      } else {\n        translationsRepository.loadAllMoviesLocal(language)\n      }\n\n      val filtersItem = loadFiltersItem()\n      val filtersGenres = filtersItem.genres.map { it.slug.lowercase() }\n\n      val moviesItems = moviesRepository.watchlistMovies.loadAll()\n        .map {\n          toListItemAsync(\n            movie = it,\n            translation = translations[it.traktId],\n            userRating = ratings[it.ids.trakt],\n            dateFormat = dateFormat,\n            fullDateFormat = fullDateFormat,\n            sortOrder = filtersItem.sortOrder,\n            spoilers = spoilers\n          )\n        }\n        .awaitAll()\n        .filter {\n          filters.filterByQuery(it, searchQuery) &&\n            filters.filterUpcoming(it, filtersItem.isUpcoming) &&\n            filters.filterGenres(it, filtersGenres)\n        }\n        .sortedWith(sorter.sort(filtersItem.sortOrder, filtersItem.sortType))\n\n      if (moviesItems.isNotEmpty() || filtersItem.hasActiveFilters()) {\n        listOf(filtersItem) + moviesItems\n      } else {\n        moviesItems\n      }\n    }\n\n  private fun loadFiltersItem(): CollectionListItem.FiltersItem {\n    return CollectionListItem.FiltersItem(\n      sortOrder = settingsRepository.sorting.watchlistMoviesSortOrder,\n      sortType = settingsRepository.sorting.watchlistMoviesSortType,\n      genres = settingsRepository.filters.watchlistMoviesGenres,\n      isUpcoming = settingsRepository.filters.watchlistMoviesUpcoming\n    )\n  }\n\n  suspend fun loadTranslation(movie: Movie, onlyLocal: Boolean): Translation? =\n    withContext(dispatchers.IO) {\n      val language = translationsRepository.getLanguage()\n      if (language == Config.DEFAULT_LANGUAGE) {\n        return@withContext Translation.EMPTY\n      }\n      translationsRepository.loadTranslation(movie, language, onlyLocal)\n    }\n\n  private fun CoroutineScope.toListItemAsync(\n    movie: Movie,\n    translation: Translation?,\n    userRating: TraktRating?,\n    dateFormat: DateTimeFormatter,\n    fullDateFormat: DateTimeFormatter,\n    sortOrder: SortOrder,\n    spoilers: SpoilersSettings\n  ) = async {\n    CollectionListItem.MovieItem(\n      isLoading = false,\n      movie = movie,\n      image = imagesProvider.findCachedImage(movie, ImageType.POSTER),\n      dateFormat = dateFormat,\n      fullDateFormat = fullDateFormat,\n      translation = translation,\n      userRating = userRating?.rating,\n      sortOrder = sortOrder,\n      spoilers = CollectionListItem.MovieItem.Spoilers(\n        isSpoilerHidden = spoilers.isWatchlistMoviesHidden,\n        isSpoilerRatingsHidden = spoilers.isWatchlistMoviesRatingsHidden,\n        isSpoilerTapToReveal = spoilers.isTapToReveal\n      )\n    )\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/watchlist/cases/WatchlistRatingsCase.kt",
    "content": "package com.michaldrabik.ui_my_movies.watchlist.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.TraktRating\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass WatchlistRatingsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val ratingsRepository: RatingsRepository,\n  private val userTraktManager: UserTraktManager,\n) {\n\n  suspend fun loadRatings(): Map<IdTrakt, TraktRating?> = withContext(dispatchers.IO) {\n    if (!userTraktManager.isAuthorized()) {\n      return@withContext emptyMap()\n    }\n    ratingsRepository.movies.loadMoviesRatings().associateBy { it.idTrakt }\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/watchlist/cases/WatchlistSortOrderCase.kt",
    "content": "package com.michaldrabik.ui_my_movies.watchlist.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass WatchlistSortOrderCase @Inject constructor(\n  private val settingsRepository: SettingsRepository\n) {\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType) {\n    settingsRepository.sorting.watchlistMoviesSortOrder = sortOrder\n    settingsRepository.sorting.watchlistMoviesSortType = sortType\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/watchlist/cases/WatchlistViewModeCase.kt",
    "content": "package com.michaldrabik.ui_my_movies.watchlist.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass WatchlistViewModeCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setNextViewMode(): ListViewMode {\n    val viewModes = ListViewMode.values()\n    val index = viewModes.indexOf(getListViewMode()) + 1\n    val nextIndex = if (index >= viewModes.size) 0 else index\n    settingsRepository.viewMode.watchlistMoviesViewMode = viewModes[nextIndex].name\n    return viewModes[nextIndex]\n  }\n\n  fun getListViewMode(): ListViewMode {\n    if (!settingsRepository.isPremium) {\n      return ListViewMode.valueOf(Config.DEFAULT_LIST_VIEW_MODE)\n    }\n    val viewMode = settingsRepository.viewMode.watchlistMoviesViewMode\n    return ListViewMode.valueOf(viewMode)\n  }\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/java/com/michaldrabik/ui_my_movies/watchlist/recycler/WatchlistListItem.kt",
    "content": "package com.michaldrabik.ui_my_movies.watchlist.recycler\n\nimport com.michaldrabik.ui_base.common.MovieListItem\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.Translation\nimport java.time.format.DateTimeFormatter\n\nsealed class WatchlistListItem(\n  override val movie: Movie,\n  override val image: Image,\n  override val isLoading: Boolean = false,\n) : MovieListItem {\n\n  data class FiltersItem(\n    val sortOrder: SortOrder,\n    val sortType: SortType,\n    val isUpcoming: Boolean,\n  ) : WatchlistListItem(\n    movie = Movie.EMPTY,\n    image = Image.createUnknown(POSTER),\n    isLoading = false\n  )\n\n  data class MovieItem(\n    override val movie: Movie,\n    override val image: Image,\n    override val isLoading: Boolean = false,\n    val translation: Translation? = null,\n    val userRating: Int? = null,\n    val dateFormat: DateTimeFormatter? = null,\n    val fullDateFormat: DateTimeFormatter? = null,\n  ) : WatchlistListItem(\n    movie = movie,\n    image = image,\n    isLoading = isLoading\n  )\n}\n"
  },
  {
    "path": "ui-my-movies/src/main/res/drawable/divider_my_movies_fanart.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <size\n    android:width=\"8dp\"\n    android:height=\"8dp\"\n    />\n</shape>"
  },
  {
    "path": "ui-my-movies/src/main/res/drawable/divider_my_movies_horizontal.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <size\n    android:width=\"6dp\"\n    android:height=\"6dp\"\n    />\n</shape>"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/fragment_followed_movies.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  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/followedMoviesRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.viewpager.widget.ViewPager\n    android:id=\"@+id/followedMoviesPager\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:overScrollMode=\"never\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.ModeTabsView\n    android:id=\"@+id/followedMoviesModeTabs\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/collectionTabsMargin\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.ScrollableTabLayout\n    android:id=\"@+id/followedMoviesTabs\"\n    style=\"@style/ScrollableTabsStyle\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"36dp\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/myMoviesSearchViewPadding\"\n    android:translationX=\"@dimen/myMoviesTabsTranslation\"\n    app:tabTextAppearance=\"@style/ScrollableTabTextStyle\"\n    />\n\n  <FrameLayout\n    android:id=\"@+id/followedMoviesIcons\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"end\"\n    android:layout_marginTop=\"@dimen/myMoviesSearchViewPadding\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    >\n\n    <com.michaldrabik.ui_base.common.views.ScrollableImageView\n      android:id=\"@+id/followedMoviesSearchIcon\"\n      android:layout_width=\"36dp\"\n      android:layout_height=\"36dp\"\n      android:layout_gravity=\"end\"\n      android:paddingStart=\"14dp\"\n      app:srcCompat=\"@drawable/ic_search\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n  </FrameLayout>\n\n  <com.michaldrabik.ui_base.common.views.SearchLocalView\n    android:id=\"@+id/followedMoviesSearchLocalView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/searchLocalViewHeight\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/myMoviesSearchLocalViewPadding\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.SearchView\n    android:id=\"@+id/followedMoviesSearchView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/searchViewHeight\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/fragment_hidden_movies.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/hiddenMoviesContent\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/hiddenMoviesRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/transparent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/screenMarginHorizontal\"\n    android:paddingTop=\"@dimen/collectionTabsViewPadding\"\n    android:paddingEnd=\"@dimen/screenMarginHorizontal\"\n    android:paddingBottom=\"@dimen/myMoviesBottomPadding\"\n    />\n\n  <include\n    android:id=\"@+id/hiddenMoviesEmptyView\"\n    layout=\"@layout/layout_hidden_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/fragment_my_movies.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/myMoviesRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/myMoviesRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/screenMarginHorizontal\"\n    android:paddingTop=\"@dimen/myMoviesTabsViewPadding\"\n    android:paddingEnd=\"@dimen/screenMarginHorizontal\"\n    android:paddingBottom=\"@dimen/myMoviesBottomPadding\"\n    />\n\n  <include\n    android:id=\"@+id/myMoviesEmptyView\"\n    layout=\"@layout/layout_my_movies_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/fragment_watchlist_movies.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/watchlistMoviesContent\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/watchlistMoviesRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/transparent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/screenMarginHorizontal\"\n    android:paddingTop=\"@dimen/collectionTabsViewPadding\"\n    android:paddingEnd=\"@dimen/screenMarginHorizontal\"\n    android:paddingBottom=\"@dimen/myMoviesBottomPadding\"\n    />\n\n  <include\n    android:id=\"@+id/watchlistMoviesEmptyView\"\n    layout=\"@layout/layout_watchlist_movies_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/layout_hidden_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/menuHidden\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textHiddenEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/layout_my_movies_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/menuMyMovies\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textMyMoviesEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/layout_watchlist_movies_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/menuWatchlistMovies\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textWatchlistMoviesEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/view_collection_movie.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/collectionMovieRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    >\n\n    <ImageView\n      android:id=\"@+id/collectionMovieImage\"\n      android:layout_width=\"@dimen/collectionImageWidth\"\n      android:layout_height=\"@dimen/collectionImageHeight\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionMoviePlaceholder\"\n      android:layout_width=\"@dimen/collectionImageWidth\"\n      android:layout_height=\"@dimen/collectionImageHeight\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"22dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_film\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/collectionMovieProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"@id/collectionMovieImage\"\n      app:layout_constraintStart_toStartOf=\"@id/collectionMovieImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionMovieTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionMovieDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/collectionMovieImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/collectionMovieYear\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Game of Thrones\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionMovieYear\"\n      style=\"@style/CollectionItem.Header\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionMovieTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/collectionMovieImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"24 Mar 2022\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionMovieStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionMovieTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/collectionMovieRating\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionMovieYear\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionMovieRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionMovieTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionMovieYear\"\n      tools:text=\"7.6\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionMovieUserStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionMovieTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/collectionMovieUserRating\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionMovieYear\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionMovieUserRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionMovieTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/collectionMovieStarIcon\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionMovieYear\"\n      tools:text=\"10\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionMovieDescription\"\n      style=\"@style/CollectionItem.Description\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionMovieReleaseDate\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/collectionMovieImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/collectionMovieTitle\"\n      tools:text=\"Lorem Ipsum\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionMovieReleaseDate\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_badge\"\n      android:drawablePadding=\"@dimen/spaceTiny\"\n      android:elevation=\"@dimen/elevationTiny\"\n      android:gravity=\"start|center_vertical\"\n      android:includeFontPadding=\"false\"\n      android:paddingStart=\"4dp\"\n      android:paddingTop=\"4dp\"\n      android:paddingEnd=\"6dp\"\n      android:paddingBottom=\"4dp\"\n      android:textAlignment=\"gravity\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"13sp\"\n      android:visibility=\"gone\"\n      app:drawableStartCompat=\"@drawable/ic_clock_small\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/collectionMovieImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/collectionMovieDescription\"\n      tools:text=\"Wednesday, 27 June 2019\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/view_collection_movie_compact.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/collectionMovieRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    >\n\n    <ImageView\n      android:id=\"@+id/collectionMovieImage\"\n      android:layout_width=\"@dimen/collectionImageCompactWidth\"\n      android:layout_height=\"@dimen/collectionImageCompactHeight\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionMoviePlaceholder\"\n      android:layout_width=\"@dimen/collectionImageCompactWidth\"\n      android:layout_height=\"@dimen/collectionImageCompactHeight\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"@dimen/spaceMedium\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/collectionMovieProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"20dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"@id/collectionMovieImage\"\n      app:layout_constraintStart_toStartOf=\"@id/collectionMovieImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionMovieTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:textSize=\"16sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionMovieReleaseDate\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/collectionMovieImage\"\n      app:layout_constraintTop_toBottomOf=\"@+id/collectionMovieYear\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      app:layout_goneMarginTop=\"@dimen/spaceNormal\"\n      tools:text=\"Game of Thrones\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionMovieYear\"\n      style=\"@style/CollectionItem.Header\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:textSize=\"11sp\"\n      app:layout_constraintBottom_toTopOf=\"@+id/collectionMovieTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/collectionMovieImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Netflix\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionMovieStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      app:layout_constraintBottom_toTopOf=\"@+id/collectionMovieTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/collectionMovieRating\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionMovieYear\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionMovieRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"11sp\"\n      app:layout_constraintBottom_toTopOf=\"@+id/collectionMovieTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionMovieYear\"\n      tools:text=\"7.6\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionMovieUserStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionMovieTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/collectionMovieUserRating\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionMovieYear\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionMovieUserRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"11sp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionMovieTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/collectionMovieStarIcon\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionMovieYear\"\n      tools:text=\"10\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionMovieReleaseDate\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:layout_marginTop=\"@dimen/spaceTiny\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_badge\"\n      android:drawablePadding=\"@dimen/spaceTiny\"\n      android:elevation=\"@dimen/elevationTiny\"\n      android:gravity=\"start|center_vertical\"\n      android:includeFontPadding=\"false\"\n      android:paddingStart=\"4dp\"\n      android:paddingTop=\"4dp\"\n      android:paddingEnd=\"6dp\"\n      android:paddingBottom=\"4dp\"\n      android:textAlignment=\"gravity\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"11sp\"\n      android:visibility=\"gone\"\n      app:drawableStartCompat=\"@drawable/ic_clock_compact\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/collectionMovieImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/collectionMovieTitle\"\n      tools:text=\"Wednesday, 27 June 2019\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/view_collection_movie_grid.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/collectionMovieRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@drawable/bg_media_view_elevation\"\n    android:elevation=\"@dimen/elevationTiny\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/collectionMovieImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionMoviePlaceholder\"\n      android:layout_width=\"@dimen/showTilePlaceholder\"\n      android:layout_height=\"@dimen/showTilePlaceholder\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/collectionMovieProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionMovieRating\"\n      style=\"@style/ImageTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"top|end\"\n      android:layout_margin=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_text_on_surface\"\n      android:drawablePadding=\"2dp\"\n      android:gravity=\"end\"\n      android:paddingStart=\"4dp\"\n      android:paddingTop=\"2dp\"\n      android:paddingEnd=\"4dp\"\n      android:paddingBottom=\"2dp\"\n      android:shadowColor=\"@color/colorTransparent\"\n      android:textColor=\"@color/colorWhite\"\n      android:textSize=\"11sp\"\n      android:visibility=\"gone\"\n      app:drawableStartCompat=\"@drawable/ic_star_small\"\n      app:drawableTint=\"@color/colorWhite\"\n      tools:text=\"9.9\"\n      tools:visibility=\"visible\"\n      />\n\n  </FrameLayout>\n\n</merge>"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/view_collection_movie_grid_title.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/collectionMovieRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginBottom=\"@dimen/collectionImageMargin\"\n    android:background=\"@drawable/bg_media_view_elevation\"\n    android:elevation=\"@dimen/elevationTiny\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/collectionMovieImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionMoviePlaceholder\"\n      android:layout_width=\"@dimen/showTilePlaceholder\"\n      android:layout_height=\"@dimen/showTilePlaceholder\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/collectionMovieProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionMovieRating\"\n      style=\"@style/ImageTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"top|end\"\n      android:layout_margin=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_text_on_surface\"\n      android:drawablePadding=\"2dp\"\n      android:gravity=\"end\"\n      android:paddingStart=\"4dp\"\n      android:paddingTop=\"2dp\"\n      android:paddingEnd=\"4dp\"\n      android:paddingBottom=\"2dp\"\n      android:shadowColor=\"@color/colorTransparent\"\n      android:textColor=\"@color/colorWhite\"\n      android:textSize=\"11sp\"\n      android:visibility=\"gone\"\n      app:drawableStartCompat=\"@drawable/ic_star_small\"\n      app:drawableTint=\"@color/colorWhite\"\n      tools:text=\"9.9\"\n      tools:visibility=\"visible\"\n      />\n\n  </FrameLayout>\n\n  <TextView\n    android:id=\"@+id/collectionMovieTitle\"\n    style=\"@style/ImageTitleGrid\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"bottom|start\"\n    android:layout_marginBottom=\"@dimen/collectionImageTitleMargin\"\n    android:gravity=\"start\"\n    android:maxLines=\"1\"\n    android:textAlignment=\"viewStart\"\n    android:textSize=\"@dimen/collectionImageTitleSize\"\n    android:visibility=\"visible\"\n    tools:text=\"Game Of Thrones\"\n    tools:visibility=\"visible\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/view_filters_genres.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  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/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_filters_sheet\"\n  android:orientation=\"vertical\"\n  android:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.core.widget.NestedScrollView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\"\n    >\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:clipToPadding=\"false\"\n      android:paddingStart=\"@dimen/spaceNormal\"\n      android:paddingTop=\"@dimen/spaceNormal\"\n      android:paddingEnd=\"@dimen/spaceNormal\"\n      tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n      >\n\n      <TextView\n        android:id=\"@+id/genresTitle\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/textGenres\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"18sp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintBottom_toTopOf=\"@id/genresChipGroup\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n      <com.google.android.material.chip.ChipGroup\n        android:id=\"@+id/genresChipGroup\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceTiny\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/genresTitle\"\n        app:lineSpacing=\"10dp\"\n        />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n  </androidx.core.widget.NestedScrollView>\n\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingTop=\"@dimen/spaceMedium\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceNormal\"\n    >\n\n    <ImageView\n      android:id=\"@+id/clearButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center_vertical\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_delete\"\n      app:tint=\"?android:textColorSecondary\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/applyButton\"\n      style=\"@style/RoundMaterialButton\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:backgroundTint=\"?attr/colorAccent\"\n      android:gravity=\"center\"\n      android:text=\"@string/textApply\"\n      android:textColor=\"?attr/textColorOnSurface\"\n      app:rippleColor=\"?android:attr/textColorPrimary\"\n      />\n\n  </LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/view_movies_filters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <HorizontalScrollView\n    android:id=\"@+id/followedMoviesScroll\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:scrollbars=\"none\"\n    >\n\n    <com.google.android.material.chip.ChipGroup\n      android:id=\"@+id/followedMoviesChips\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:paddingTop=\"@dimen/spaceSmall\"\n      android:paddingBottom=\"@dimen/spaceSmall\"\n      app:singleLine=\"true\"\n      >\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/followedMoviesSortingChip\"\n        style=\"@style/ShowlyChip.Sort\"\n        android:text=\"@string/textSortName\"\n        />\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/followedMoviesGenresChip\"\n        style=\"@style/ShowlyChip.Filter\"\n        android:checkable=\"false\"\n        android:text=\"@string/textGenres\"\n        />\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/followedMoviesUpcomingChip\"\n        style=\"@style/ShowlyChip.Filter\"\n        android:text=\"@string/textWatchlistIncoming\"\n        android:visibility=\"gone\"\n        tools:visibility=\"visible\"\n        />\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/followedMoviesListViewChip\"\n        style=\"@style/ShowlyChip.Filter\"\n        android:checkable=\"false\"\n        app:chipIcon=\"@drawable/ic_view_grid\"\n        app:chipIconEnabled=\"true\"\n        app:chipIconTint=\"?android:textColorPrimary\"\n        app:iconEndPadding=\"-14dp\"\n        app:iconStartPadding=\"2dp\"\n        />\n\n    </com.google.android.material.chip.ChipGroup>\n\n  </HorizontalScrollView>\n\n</merge>"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/view_my_movies_all.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/myMovieAllRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:clipToPadding=\"false\"\n    android:paddingLeft=\"@dimen/myMovieAllMarginHorizontal\"\n    android:paddingTop=\"@dimen/spaceSmall\"\n    android:paddingRight=\"@dimen/myMovieAllMarginHorizontal\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <ImageView\n      android:id=\"@+id/myMovieAllImage\"\n      android:layout_width=\"@dimen/myMovieAllImageWidth\"\n      android:layout_height=\"@dimen/myMovieAllImageHeight\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/myMovieAllPlaceholder\"\n      android:layout_width=\"@dimen/myMovieAllImageWidth\"\n      android:layout_height=\"@dimen/myMovieAllImageHeight\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"@dimen/spaceBig\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_film\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/myMovieAllProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"@id/myMovieAllImage\"\n      app:layout_constraintStart_toStartOf=\"@id/myMovieAllImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/myMovieAllTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/myMovieAllDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/myMovieAllImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/myMovieAllYear\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Game of Thrones\"\n      />\n\n    <TextView\n      android:id=\"@+id/myMovieAllYear\"\n      style=\"@style/CollectionItem.Header\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/myMovieAllTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/myMovieAllImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Netflix\"\n      />\n\n    <ImageView\n      android:id=\"@+id/myMovieAllStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      app:layout_constraintBottom_toTopOf=\"@id/myMovieAllTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/myMovieAllRating\"\n      app:layout_constraintTop_toTopOf=\"@id/myMovieAllYear\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <TextView\n      android:id=\"@+id/myMovieAllRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/myMovieAllTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/myMovieAllYear\"\n      tools:text=\"7.6\"\n      />\n\n    <ImageView\n      android:id=\"@+id/myMovieAllUserStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/myMovieAllTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/myMovieUserRating\"\n      app:layout_constraintTop_toTopOf=\"@id/myMovieAllYear\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/myMovieUserRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/myMovieAllTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/myMovieAllStarIcon\"\n      app:layout_constraintTop_toTopOf=\"@id/myMovieAllYear\"\n      tools:text=\"10\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/myMovieAllDescription\"\n      style=\"@style/CollectionItem.Description\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/myMovieAllImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/myMovieAllTitle\"\n      tools:text=\"Lorem Ipsum\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/view_my_movies_fanart.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/myMovieFanartRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/myMovieFanartImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/myMovieFanartPlaceholder\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:padding=\"42dp\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_film\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/myMovieFanartTitle\"\n      style=\"@style/ImageTitle\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"bottom|start\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:gravity=\"start\"\n      android:maxLines=\"2\"\n      android:textAlignment=\"viewStart\"\n      android:textSize=\"14sp\"\n      android:visibility=\"gone\"\n      tools:text=\"@tools:sample/lorem/random\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/myMovieFanartProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n  </FrameLayout>\n</merge>"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/view_my_movies_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/myMoviesHeaderRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    >\n\n    <androidx.appcompat.widget.AppCompatTextView\n      android:id=\"@+id/myMoviesHeaderLabel\"\n      style=\"@style/MyMovies.Label\"\n      android:layout_marginTop=\"@dimen/spaceNormal\"\n      app:layout_constraintBottom_toTopOf=\"@id/myMoviesFilterChipsScroll\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_goneMarginBottom=\"@dimen/spaceSmall\"\n      tools:text=\"Section Label\"\n      />\n\n    <HorizontalScrollView\n      android:id=\"@+id/myMoviesFilterChipsScroll\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"14dp\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:clipToPadding=\"false\"\n      android:overScrollMode=\"never\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/myMoviesHeaderLabel\"\n      >\n\n      <com.google.android.material.chip.ChipGroup\n        android:id=\"@+id/myMoviesFilterChips\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        app:singleLine=\"true\"\n        >\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/myMoviesSortChip\"\n          style=\"@style/ShowlyChip.Sort\"\n          android:text=\"@string/textSortName\"\n          android:visibility=\"gone\"\n          tools:visibility=\"visible\"\n          />\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/myMoviesGenresChip\"\n          style=\"@style/ShowlyChip.Filter\"\n          android:checkable=\"false\"\n          android:text=\"@string/textGenres\"\n          />\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/myMoviesSortListViewChip\"\n          style=\"@style/ShowlyChip.Filter\"\n          android:checkable=\"false\"\n          app:chipIcon=\"@drawable/ic_view_grid\"\n          app:chipIconEnabled=\"true\"\n          app:chipIconTint=\"?android:textColorPrimary\"\n          app:iconEndPadding=\"-14dp\"\n          app:iconStartPadding=\"2dp\"\n          />\n\n      </com.google.android.material.chip.ChipGroup>\n\n    </HorizontalScrollView>\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-my-movies/src/main/res/layout/view_my_movies_recents.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.gridlayout.widget.GridLayout\n    android:id=\"@+id/myMoviesRecentsContainer\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    app:columnCount=\"2\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-my-movies/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"myMoviesTabsTranslation\">-7dp</dimen>\n\n  <dimen name=\"myMoviesSearchViewPadding\">104dp</dimen>\n  <dimen name=\"myMoviesTabsViewPadding\">136dp</dimen>\n  <dimen name=\"myMoviesHeaderGridPadding\">3dp</dimen>\n  <dimen name=\"myMoviesRecentsGridPadding\">-1dp</dimen>\n\n  <dimen name=\"myMoviesBottomPadding\">76dp</dimen>\n  <dimen name=\"myMoviesFanartCorner\">4dp</dimen>\n  <dimen name=\"myMoviesFanartHeight\">126dp</dimen>\n\n  <dimen name=\"myMoviesSearchLocalViewPadding\">150dp</dimen>\n  <dimen name=\"myMoviesSearchLocalOffset\">40dp</dimen>\n\n  <dimen name=\"myMovieAllImageHeight\">120dp</dimen>\n  <dimen name=\"myMovieAllImageWidth\">80dp</dimen>\n  <dimen name=\"myMovieAllMarginHorizontal\">12dp</dimen>\n\n  <dimen name=\"collectionTabsViewPadding\">148dp</dimen>\n  <dimen name=\"collectionMarginHorizontal\">12dp</dimen>\n  <dimen name=\"collectionImageHeight\">120dp</dimen>\n  <dimen name=\"collectionImageWidth\">80dp</dimen>\n  <dimen name=\"collectionImageCompactHeight\">64dp</dimen>\n  <dimen name=\"collectionImageCompactWidth\">43dp</dimen>\n  <dimen name=\"collectionImageMargin\">23dp</dimen>\n  <dimen name=\"collectionImageTitleMargin\">4dp</dimen>\n  <dimen name=\"collectionImageTitleSize\">11sp</dimen>\n  <dimen name=\"collectionFiltersPaddingHorizontal\">3dp</dimen>\n  <dimen name=\"collectionFiltersPaddingBottom\">5dp</dimen>\n</resources>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyMovies\">My Movies</string>\n  <string name=\"menuWatchlistMovies\">Watchlist</string>\n  <string name=\"menuHidden\">Hidden</string>\n\n  <string name=\"textMyMoviesEmpty\">Your movies collection is currently empty.\\n\\nVisit <b>Discover</b> tab to add your first movie.</string>\n  <string name=\"textWatchlistMoviesEmpty\">Your <b>Watchlist</b> is currently empty.\\n\\nVisit <b>Discover</b> tab to add movies you want to watch in the future.</string>\n  <string name=\"textHiddenEmpty\">Your <b>Hidden</b> list is currently empty.\\n\\n<b>Hidden</b> is a list of items that you are not interested in.\\n\\nHidden items will no longer appear in <b>Progress</b>, <b>Discover</b> and other sections.</string>\n</resources>"
  },
  {
    "path": "ui-my-movies/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <style name=\"MyMovies\" />\n\n  <style name=\"MyMovies.Label\" parent=\"MyMovies\">\n    <item name=\"android:layout_width\">0dp</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:textColor\">?android:attr/textColorPrimary</item>\n    <item name=\"android:textStyle\">bold</item>\n    <item name=\"android:textAlignment\">viewStart</item>\n    <item name=\"android:textSize\">22sp</item>\n    <item name=\"android:gravity\">start|center_vertical</item>\n    <item name=\"android:maxLines\">1</item>\n  </style>\n\n</resources>"
  },
  {
    "path": "ui-my-movies/src/main/res/values-ar/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"myMoviesTabsTranslation\">16dp</dimen>\n</resources>"
  },
  {
    "path": "ui-my-movies/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyMovies\">أفلامي</string>\n  <string name=\"menuWatchlistMovies\">قائمة المشاهدة</string>\n  <string name=\"menuHidden\">المخفية</string>\n  <string name=\"textMyMoviesEmpty\">مجموعة أفلامك فارغة حالياً.\\n\\nإذهب إلى صفحة <b>إكتشف</b> وأضف فيلمك الأول.</string>\n  <string name=\"textWatchlistMoviesEmpty\">مجموعة <b>قائمة المشاهدة</b> فارغة حالياً.\\n\\nإذهب إلى صفحة <b>إكتشف</b> وأضف أفلام قد ترغب بمشاهدتها في المستقبل.</string>\n  <string name=\"textHiddenEmpty\">قائمة <b>المخفية</b> فارغة حاليًا.\\n\\n<b>المخفية</b> هي قائمة تحتوى على محتويات أنت لا تهتم بها.\\n\\nالمحتويات المخفية لن تظهر في قوائم <b>مستوى التقدم</b> و <b>اكتشف</b> وفي القوائم الأخرى.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/values-de/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"myMoviesTabsTranslation\">-8dp</dimen>\n</resources>"
  },
  {
    "path": "ui-my-movies/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyMovies\">Meine Filme</string>\n  <string name=\"menuWatchlistMovies\">Watchlist</string>\n  <string name=\"menuHidden\">Versteckt</string>\n  <string name=\"textMyMoviesEmpty\">Deine Filmsammlung ist derzeit leer.\\n\\nGehe auf den <b>Entdecken</b>Tab um deinen ersten Film hinzuzufügen.</string>\n  <string name=\"textWatchlistMoviesEmpty\">Deine <b>Watchlist</b> ist zurzeit leer.\\n\\nGehe auf den <b>Entdecken</b> Tab um Filme hinzuzufügen, die du später anschauen möchtest.</string>\n  <string name=\"textHiddenEmpty\">Deine <b>Versteckt</b> Liste ist derzeit leer.\\n\\n<b>Versteckt</b> ist eine Liste von Elementen, die Sie nicht interessieren.\\n\\nVersteckte Elemente werden nicht mehr in <b>Fortschritt</b>, <b>Entdecke</b> und andere Abschnitte.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyMovies\">Mis Películas</string>\n  <string name=\"menuWatchlistMovies\">Pendientes</string>\n  <string name=\"menuHidden\">Ocultos</string>\n  <string name=\"textMyMoviesEmpty\">Tu colección de películas actualmente está vacía.\\n\\nVisita la pestaña <b>Descubrir</b> para añadir tu primera película.</string>\n  <string name=\"textWatchlistMoviesEmpty\">Tu lista <b>Pendientes</b> actualmente está vacía.\\n\\nVisita la pestaña <b>Descubrir</b> para añadir  películas que quieras ver en el futuro.</string>\n  <string name=\"textHiddenEmpty\">Tu lista de <b>Ocultos</b> está actualmente vacía.\\n\\n<b>Ocultos</b> es una lista de elementos que no te interesan.\\n\\nLos elementos ocultos ya no aparecerán en <b>Progreso</b>, <b>Descubrir</b> y otras secciones.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyMovies\">Omat elokuvat</string>\n  <string name=\"menuWatchlistMovies\">Katselulista</string>\n  <string name=\"menuHidden\">Piilotetut</string>\n  <string name=\"textMyMoviesEmpty\">Elokuvakoelmasi on tyhjä.\\n\\nLisää ensimmäinen elokuvasi <b>Etsi</b>-osiosta.</string>\n  <string name=\"textWatchlistMoviesEmpty\"><b>Katselulista</b> on tyhjä.\\n\\nLisää <b>Etsi</b>-osiosta elokuvia, joita haluat katsella tulevaisuudessa.</string>\n  <string name=\"textHiddenEmpty\"><b>Piilotetut</b>-lista on tyhjä.\\n\\n<b>Piilotetut</b> on lista kohteista, joista et ole kiinnostunut.\\n\\nPiilotettuja kohteita ei enää näytetä mm. <b>Katselutila</b>- ja <b>Etsi</b>-osioissa.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyMovies\">Mes films</string>\n  <string name=\"menuWatchlistMovies\">Watchlist</string>\n  <string name=\"menuHidden\">Masqué</string>\n  <string name=\"textMyMoviesEmpty\">Votre collection de films est présentement vide.\\n\\nVisitez l\\'onglet <b>Découvrir</b> pour ajouter votre premier film.</string>\n  <string name=\"textWatchlistMoviesEmpty\">Votre liste <b>Watchlist</b> est présentement vide.\\n\\nVisitez l\\'onglet <b>Découvrir</b> pour ajouter des films que vous souhaitez visionner plus tard.</string>\n  <string name=\"textHiddenEmpty\">Votre liste <b>masqué</b> est actuellement vide.\\n\\n<b>Masqué</b> est une liste d\\'éléments qui ne vous intéressent pas.\\n\\nLes éléments masqués n\\'apparaîtront plus dans <b>Progression</b>, <b>Découvrir</b> et d\\'autres sections.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyMovies\">I miei film</string>\n  <string name=\"menuWatchlistMovies\">Da vedere</string>\n  <string name=\"menuHidden\">Nascosti</string>\n  <string name=\"textMyMoviesEmpty\">La tua raccolta di film è vuota.\\n\\nVisita la pagina <b>Scopri</b> per aggiungere il tuo primo film.</string>\n  <string name=\"textWatchlistMoviesEmpty\">La tua lista <b>Da vedere</b> è vuota.\\n\\nVisita la pagina <b>Scopri</b> per aggiungere i film che hai intenzione di vedere più avanti.</string>\n  <string name=\"textHiddenEmpty\">La tua lista <b>Nascosti</b> è attualmente vuota.\\n\\n<b>Nascosti</b> è un elenco di elementi a cui non sei interessato.\\n\\nGli elementi nascosti non appariranno più in <b>Progressi</b>, <b>Scopri</b> e altre sezioni.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/values-pl/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"myMoviesTabsTranslation\">-8dp</dimen>\n</resources>"
  },
  {
    "path": "ui-my-movies/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyMovies\">Moje Filmy</string>\n  <string name=\"menuWatchlistMovies\">Na Później</string>\n  <string name=\"menuHidden\">Ukryte</string>\n  <string name=\"textMyMoviesEmpty\">Kolekcja jest obecnie pusta.\\n\\nWybierz zakładkę <b>Odkrywaj</b> aby dodać swój pierwszy film.</string>\n  <string name=\"textWatchlistMoviesEmpty\">Kolekcja <b>Na Później</b> jest obecnie pusta.\\n\\nWybierz zakładkę <b>Odkrywaj</b> aby dodać filmy które chcesz obejrzeć w przyszłości.</string>\n  <string name=\"textHiddenEmpty\">Twoja lista <b>Ukrytych</b> jest obecnie pusta.\\n\\n<b>Ukryte</b> to lista elementów, którymi nie jesteś zainteresowany.\\n\\nUkryte przedmioty nie pojawią się już w sekcjach <b>Postępu</b>, <b>Odkrywania</b> i innych.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyMovies\">Meus Filmes</string>\n  <string name=\"menuWatchlistMovies\">Interesses</string>\n  <string name=\"menuHidden\">Oculto</string>\n  <string name=\"textMyMoviesEmpty\">Sua coleção de filmes está vazia no momento.\\n\\nVisite a guia <b>Explorar</b> para adicionar seu primeiro filme.</string>\n  <string name=\"textWatchlistMoviesEmpty\">Seus <b>Interesses</b> está vazia no momento.\\n\\nAcesse a guia <b>Explorar</b> para adicionar filmes que deseja assistir no futuro.</string>\n  <string name=\"textHiddenEmpty\">Sua lista <b>Oculta</b> está vazia no momento.\\n\\n<b>Oculto</b> é uma lista de itens nos quais você não tem interesse.\\n\\nItens escondidos não aparecerão mais em <b>Progresso</b>, <b>Explorar</b> e outras seções.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyMovies\">Мои фильмы</string>\n  <string name=\"menuWatchlistMovies\">Буду смотреть</string>\n  <string name=\"menuHidden\">Скрытое</string>\n  <string name=\"textMyMoviesEmpty\">В настоящее время коллекция фильмов пуста.\\n\\nПосетите вкладку <b>Открытия</b> и добавьте свой первый фильм.</string>\n  <string name=\"textWatchlistMoviesEmpty\">Ваш список <b>Буду смотреть</b> в настоящее время пуст.\\n\\nПосетите вкладку <b>Открытия</b>, чтобы добавить фильмы, которые вы хотите посмотреть в будущем.</string>\n  <string name=\"textHiddenEmpty\">Ваш список <b>Скрытое</b> в настоящее время пуст.\\n\\n<b>Скрытое</b> это список элементов, которые вас больше не интересуют.\\n\\nСкрытые элементы не появятся в <b>Прогресс</b>, <b>Открытия</b> и других разделах.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"collectionImageMargin\">29dp</dimen>\n  <dimen name=\"collectionImageTitleMargin\">8dp</dimen>\n  <dimen name=\"collectionImageTitleSize\">12sp</dimen>\n\n  <dimen name=\"myMoviesFanartHeight\">176dp</dimen>\n  <dimen name=\"myMoviesFanartCorner\">6dp</dimen>\n  <dimen name=\"myMoviesBottomPadding\">86dp</dimen>\n</resources>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyMovies\">Filmlerim</string>\n  <string name=\"menuWatchlistMovies\">İstek Listesi</string>\n  <string name=\"menuHidden\">Gizlenmiş</string>\n  <string name=\"textMyMoviesEmpty\">Film koleksiyonunuz şu anda boştur.\\n\\nİlk filminizi eklemek için <b>Keşfet</b> sekmesini ziyaret edin.</string>\n  <string name=\"textWatchlistMoviesEmpty\"><b>İstek Listeniz</b> şu anda boştur.\\n\\nGelecekte izlemek istediğiniz filmleri eklemek için <b>Keşfet</b> sekmesini ziyaret edin.</string>\n  <string name=\"textHiddenEmpty\">&lt;b&gt;Gizli&lt;/b&gt; listeniz şu anda boş.\\n\\n&lt;b&gt;Gizli&lt;/b&gt;, ilgilenmediğiniz öğelerin listesidir.\\n\\nGizli öğeler artık &lt;b içinde görünmeyecek &gt;İlerleme&lt;/b&gt;, &lt;b&gt;Keşfet&lt;/b&gt; ve diğer bölümler.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyMovies\">Мої Фільми</string>\n  <string name=\"menuWatchlistMovies\">На потім</string>\n  <string name=\"menuHidden\">Приховане</string>\n  <string name=\"textMyMoviesEmpty\">Ваша колекція фільмів наразі порожня.\\n\\nВідвідайте <b>Огляд</b>, щоб додати свій перший фільм.</string>\n  <string name=\"textWatchlistMoviesEmpty\">Ваш список <b>На потім</b> зараз порожній.\\n\\nВідвідайте вкладку <b>Огляд</b>, щоб додати фільми, які ви хочете переглянути у майбутньому.</string>\n  <string name=\"textHiddenEmpty\">Наразі Ваш список <b>Приховане</b> порожній.\\n\\n<b>Приховане</b> це список елементів, які вас не цікавлять.\\n\\nПриховані елементи більше не з\\'являтимуться в <b>Прогрес</b>, <b>Огляд</b> та інших розділах.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyMovies\">Phim của tôi</string>\n  <string name=\"menuWatchlistMovies\">Danh sách theo dõi</string>\n  <string name=\"menuHidden\">Ẩn</string>\n\n  <string name=\"textMyMoviesEmpty\">Bộ sưu tập phim của bạn hiện trống.\\n\\nTruy cập tab <b>Khám phá</b> để thêm phim đầu tiên của bạn.</string>\n  <string name=\"textWatchlistMoviesEmpty\"><b>Danh sách theo dõi</b> của bạn hiện trống.\\n\\nTruy cập tab <b>Khám phá</b> để thêm phim bạn muốn xem trong tương lai.</string>\n  <string name=\"textHiddenEmpty\">Danh sách <b>Ẩn</b> của bạn hiện trống.\\n\\n<b>Ẩn</b> là danh sách các mục mà bạn không quan tâm.\\n\\nCác mục ẩn sẽ không còn xuất hiện trong <b >Tiến độ</b>, <b>Khám phá</b> và các mục khác.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-movies/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyMovies\">我的电影</string>\n  <string name=\"menuWatchlistMovies\">待看列表</string>\n  <string name=\"menuHidden\">已隐藏</string>\n  <string name=\"textMyMoviesEmpty\">当前您的电影合集为空。\\n\\n访问 <b>发现</b> 选项卡来添加您的第一部电影。</string>\n  <string name=\"textWatchlistMoviesEmpty\">您的 <b>待看列表</b> 目前为空。\\n\\n访问 <b>发现</b> 选项卡添加您想要看的电影。</string>\n  <string name=\"textHiddenEmpty\">您的 <b>隐藏</b> 列表目前是空的。\\n\\n<b>隐藏</b> 列表包含您不感兴趣的项目。\\n\\n隐藏的项目将不再出现在 <b>进度</b>, <b>发现</b> 和其它模块中。</string>\n</resources>\n"
  },
  {
    "path": "ui-my-shows/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-my-shows/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_my_shows'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-local')\n  implementation project(':ui-base')\n  implementation project(':repository')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  implementation libs.android.gridlayout\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/filters/CollectionFiltersOrigin.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.filters\n\ninternal enum class CollectionFiltersOrigin {\n  MY_SHOWS,\n  WATCHLIST_SHOWS,\n  HIDDEN_SHOWS\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/filters/CollectionFiltersUiEvent.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_my_shows.common.filters\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\ninternal sealed class CollectionFiltersUiEvent<T>(action: T) : Event<T>(action) {\n  object ApplyFilters : CollectionFiltersUiEvent<Unit>(Unit)\n  object CloseFilters : CollectionFiltersUiEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/filters/genre/CollectionFiltersGenreBottomSheet.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.filters.genre\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.content.ContextCompat\nimport androidx.core.os.bundleOf\nimport androidx.core.view.forEach\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetDialog\nimport com.google.android.material.chip.Chip\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.requireSerializable\nimport com.michaldrabik.ui_base.utilities.extensions.screenHeight\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersOrigin\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersUiEvent.ApplyFilters\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersUiEvent.CloseFilters\nimport com.michaldrabik.ui_my_shows.databinding.ViewFiltersGenresBinding\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\ninternal class CollectionFiltersGenreBottomSheet : BaseBottomSheetFragment(R.layout.view_filters_genres) {\n\n  companion object {\n    private const val ARG_ORIGIN = \"ARG_ORIGIN\"\n    const val REQUEST_COLLECTION_FILTERS_GENRE = \"REQUEST_COLLECTION_FILTERS_GENRE\"\n\n    fun createBundle(origin: CollectionFiltersOrigin): Bundle {\n      return bundleOf(ARG_ORIGIN to origin)\n    }\n  }\n\n  private val viewModel by viewModels<CollectionFiltersGenreViewModel>()\n  private val binding by viewBinding(ViewFiltersGenresBinding::bind)\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = {\n        val origin = requireSerializable<CollectionFiltersOrigin>(ARG_ORIGIN)\n        viewModel.loadData(origin)\n      }\n    )\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun setupView() {\n    val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior\n    behavior.skipCollapsed = true\n    behavior.maxHeight = (screenHeight() * 0.9).toInt()\n\n    with(binding) {\n      applyButton.onClick { saveGenres() }\n      clearButton.onClick { renderGenres(emptyList()) }\n    }\n  }\n\n  private fun saveGenres() {\n    with(binding) {\n      val genres = mutableListOf<Genre>().apply {\n        genresChipGroup.forEach { chip ->\n          if ((chip as Chip).isChecked) {\n            add(Genre.valueOf(chip.tag.toString()))\n          }\n        }\n      }\n      viewModel.saveGenres(genres)\n    }\n  }\n\n  private fun render(uiState: CollectionFiltersGenreUiState) {\n    with(uiState) {\n      genres?.let { renderGenres(it) }\n    }\n  }\n\n  private fun renderGenres(genres: List<Genre>) {\n    binding.genresChipGroup.removeAllViews()\n    binding.clearButton.visibleIf(genres.isNotEmpty())\n\n    val genresNames = genres.map { it.name }\n    Genre.values()\n      .sortedBy { requireContext().getString(it.displayName) }\n      .forEach { genre ->\n        val chip = Chip(requireContext()).apply {\n          tag = genre.name\n          text = requireContext().getString(genre.displayName)\n          isCheckable = true\n          isCheckedIconVisible = false\n          setEnsureMinTouchTargetSize(false)\n          shapeAppearanceModel = shapeAppearanceModel.toBuilder()\n            .setAllCornerSizes(100f)\n            .build()\n          chipBackgroundColor = ContextCompat.getColorStateList(context, R.color.selector_discover_chip_background)\n          setChipStrokeColorResource(R.color.selector_discover_chip_text)\n          setChipStrokeWidthResource(R.dimen.discoverFilterChipStroke)\n          setTextColor(ContextCompat.getColorStateList(context, R.color.selector_discover_chip_text))\n          isChecked = genre.name in genresNames\n        }\n        binding.genresChipGroup.addView(chip)\n      }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is ApplyFilters -> {\n        setFragmentResult(REQUEST_COLLECTION_FILTERS_GENRE, Bundle.EMPTY)\n        closeSheet()\n      }\n      is CloseFilters -> closeSheet()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/filters/genre/CollectionFiltersGenreUiState.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.filters.genre\n\nimport com.michaldrabik.ui_model.Genre\n\ninternal data class CollectionFiltersGenreUiState(\n  val genres: List<Genre>? = null,\n  val isLoading: Boolean? = null,\n)\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/filters/genre/CollectionFiltersGenreViewModel.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.filters.genre\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersOrigin\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersOrigin.HIDDEN_SHOWS\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersOrigin.MY_SHOWS\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersOrigin.WATCHLIST_SHOWS\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersUiEvent.ApplyFilters\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersUiEvent.CloseFilters\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\ninternal class CollectionFiltersGenreViewModel @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val genresState = MutableStateFlow<List<Genre>?>(null)\n  private val loadingState = MutableStateFlow(false)\n\n  private lateinit var origin: CollectionFiltersOrigin\n\n  fun loadData(origin: CollectionFiltersOrigin) {\n    this.origin = origin\n    genresState.value = when (origin) {\n      MY_SHOWS -> settingsRepository.filters.myShowsGenres\n      WATCHLIST_SHOWS -> settingsRepository.filters.watchlistShowsGenres\n      HIDDEN_SHOWS -> settingsRepository.filters.hiddenShowsGenres\n    }.toList()\n  }\n\n  fun saveGenres(genres: List<Genre>) {\n    viewModelScope.launch {\n      if (genres == genresState.value) {\n        eventChannel.send(CloseFilters)\n        return@launch\n      }\n      when (origin) {\n        MY_SHOWS -> settingsRepository.filters.myShowsGenres = genres\n        WATCHLIST_SHOWS -> settingsRepository.filters.watchlistShowsGenres = genres\n        HIDDEN_SHOWS -> settingsRepository.filters.hiddenShowsGenres = genres\n      }\n      eventChannel.send(ApplyFilters)\n    }\n  }\n\n  val uiState = combine(\n    genresState,\n    loadingState,\n  ) { s1, s2 ->\n    CollectionFiltersGenreUiState(\n      genres = s1,\n      isLoading = s2,\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = CollectionFiltersGenreUiState()\n  )\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/filters/network/CollectionFiltersNetworkBottomSheet.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.filters.network\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.content.ContextCompat\nimport androidx.core.os.bundleOf\nimport androidx.core.view.forEach\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetDialog\nimport com.google.android.material.chip.Chip\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.NetworkIconProvider\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.requireSerializable\nimport com.michaldrabik.ui_base.utilities.extensions.screenHeight\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.Network\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersOrigin\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersUiEvent.ApplyFilters\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersUiEvent.CloseFilters\nimport com.michaldrabik.ui_my_shows.databinding.ViewFiltersNetworksBinding\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\ninternal class CollectionFiltersNetworkBottomSheet : BaseBottomSheetFragment(R.layout.view_filters_networks) {\n\n  companion object {\n    private const val ARG_ORIGIN = \"ARG_ORIGIN\"\n    const val REQUEST_COLLECTION_FILTERS_NETWORK = \"REQUEST_COLLECTION_FILTERS_NETWORK\"\n\n    fun createBundle(origin: CollectionFiltersOrigin): Bundle {\n      return bundleOf(ARG_ORIGIN to origin)\n    }\n  }\n\n  private val viewModel by viewModels<CollectionFiltersNetworkViewModel>()\n  private val binding by viewBinding(ViewFiltersNetworksBinding::bind)\n\n  @Inject\n  lateinit var networkIconProvider: NetworkIconProvider\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = {\n        val origin = requireSerializable<CollectionFiltersOrigin>(ARG_ORIGIN)\n        viewModel.loadData(origin)\n      }\n    )\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun setupView() {\n    val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior\n    behavior.skipCollapsed = true\n    behavior.maxHeight = (screenHeight() * 0.9).toInt()\n\n    with(binding) {\n      applyButton.onClick { saveNetworks() }\n      clearButton.onClick { renderNetworks(emptyList()) }\n    }\n  }\n\n  private fun saveNetworks() {\n    with(binding) {\n      val networks = mutableListOf<Network>().apply {\n        networksChipGroup.forEach { chip ->\n          if ((chip as Chip).isChecked) {\n            add(Network.valueOf(chip.tag.toString()))\n          }\n        }\n      }\n      viewModel.saveNetworks(networks)\n    }\n  }\n\n  private fun render(uiState: CollectionFiltersNetworkUiState) {\n    with(uiState) {\n      networks?.let { renderNetworks(it) }\n    }\n  }\n\n  private fun renderNetworks(networks: List<Network>) {\n    binding.networksChipGroup.removeAllViews()\n    binding.clearButton.visibleIf(networks.isNotEmpty())\n\n    val networksNames = networks.map { it.name }\n    Network.values()\n      .sortedBy { it.name }\n      .forEach { network ->\n        val icon = networkIconProvider.getIcon(network)\n        val chip = Chip(requireContext()).apply {\n          tag = network.name\n          text = network.channels.first()\n          isCheckable = true\n          isCheckedIconVisible = false\n          shapeAppearanceModel = shapeAppearanceModel.toBuilder()\n            .setAllCornerSizes(100f)\n            .build()\n          setEnsureMinTouchTargetSize(false)\n          setChipIconResource(icon)\n          chipBackgroundColor = ContextCompat.getColorStateList(requireContext(), R.color.selector_discover_chip_background)\n          setChipStrokeColorResource(R.color.selector_discover_chip_text)\n          setChipStrokeWidthResource(R.dimen.discoverFilterChipStroke)\n          setTextColor(ContextCompat.getColorStateList(requireContext(), R.color.selector_discover_chip_text))\n          isChecked = network.name in networksNames\n        }\n        binding.networksChipGroup.addView(chip)\n      }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is ApplyFilters -> {\n        setFragmentResult(REQUEST_COLLECTION_FILTERS_NETWORK, Bundle.EMPTY)\n        closeSheet()\n      }\n      is CloseFilters -> closeSheet()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/filters/network/CollectionFiltersNetworkUiState.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.filters.network\n\nimport com.michaldrabik.ui_model.Network\n\ninternal data class CollectionFiltersNetworkUiState(\n  val networks: List<Network>? = null,\n  val isLoading: Boolean? = null,\n)\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/filters/network/CollectionFiltersNetworkViewModel.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.filters.network\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Network\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersOrigin\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersOrigin.HIDDEN_SHOWS\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersOrigin.MY_SHOWS\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersOrigin.WATCHLIST_SHOWS\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersUiEvent.ApplyFilters\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersUiEvent.CloseFilters\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\ninternal class CollectionFiltersNetworkViewModel @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val networksState = MutableStateFlow<List<Network>?>(null)\n  private val loadingState = MutableStateFlow(false)\n\n  private lateinit var origin: CollectionFiltersOrigin\n\n  fun loadData(origin: CollectionFiltersOrigin) {\n    this.origin = origin\n    networksState.value = when (origin) {\n      MY_SHOWS -> settingsRepository.filters.myShowsNetworks\n      WATCHLIST_SHOWS -> settingsRepository.filters.watchlistShowsNetworks\n      HIDDEN_SHOWS -> settingsRepository.filters.hiddenShowsNetworks\n    }.toList()\n  }\n\n  fun saveNetworks(networks: List<Network>) {\n    viewModelScope.launch {\n      if (networks == networksState.value) {\n        eventChannel.send(CloseFilters)\n        return@launch\n      }\n      when (origin) {\n        MY_SHOWS -> settingsRepository.filters.myShowsNetworks = networks\n        WATCHLIST_SHOWS -> settingsRepository.filters.watchlistShowsNetworks = networks\n        HIDDEN_SHOWS -> settingsRepository.filters.hiddenShowsNetworks = networks\n      }\n      eventChannel.send(ApplyFilters)\n    }\n  }\n\n  val uiState = combine(\n    networksState,\n    loadingState,\n  ) { s1, s2 ->\n    CollectionFiltersNetworkUiState(\n      networks = s1,\n      isLoading = s2,\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = CollectionFiltersNetworkUiState()\n  )\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/layout/CollectionShowGridItemDecoration.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.layout\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.annotation.DimenRes\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ItemDecoration\nimport com.michaldrabik.ui_my_shows.common.views.CollectionShowGridTitleView\nimport com.michaldrabik.ui_my_shows.common.views.CollectionShowGridView\n\nclass CollectionShowGridItemDecoration : ItemDecoration {\n\n  private var spacing: Int\n  private var halfSpacing: Int\n\n  constructor(\n    context: Context,\n    @DimenRes spacingDimen: Int,\n  ) {\n    this.spacing = context.resources.getDimensionPixelSize(spacingDimen)\n    this.halfSpacing = spacing / 2\n  }\n\n  override fun getItemOffsets(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n    state: RecyclerView.State,\n  ) {\n    if (parent.layoutManager !is GridLayoutManager) return\n\n    val totalSpan = (parent.layoutManager as GridLayoutManager).spanCount\n\n    if (view is CollectionShowGridView || view is CollectionShowGridTitleView) {\n      outRect.top = halfSpacing\n      outRect.bottom = halfSpacing\n\n      val position = parent.getChildAdapterPosition(view) - 1\n      val column = position % totalSpan\n\n      outRect.left = spacing * column / totalSpan\n      outRect.right = spacing * ((totalSpan - 1) - column) / totalSpan\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/layout/CollectionShowLayoutManagerProvider.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.layout\n\nimport android.content.Context\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN_TABLET\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\n\ninternal object CollectionShowLayoutManagerProvider {\n\n  fun provideLayoutManger(\n    context: Context,\n    viewMode: ListViewMode,\n    gridSpanSize: Int,\n  ): RecyclerView.LayoutManager {\n    return if (context.isTablet()) {\n      provideTabletLayout(context, viewMode, gridSpanSize)\n    } else {\n      providePhoneLayout(context, viewMode)\n    }\n  }\n\n  private fun providePhoneLayout(\n    context: Context,\n    viewMode: ListViewMode,\n  ): RecyclerView.LayoutManager {\n    return when (viewMode) {\n      LIST_NORMAL, LIST_COMPACT -> LinearLayoutManager(context, VERTICAL, false)\n      GRID, GRID_TITLE -> GridLayoutManager(context, LISTS_GRID_SPAN)\n    }\n  }\n\n  private fun provideTabletLayout(\n    context: Context,\n    viewMode: ListViewMode,\n    gridSpanSize: Int,\n  ): RecyclerView.LayoutManager {\n    return when (viewMode) {\n      LIST_NORMAL, LIST_COMPACT -> GridLayoutManager(context, gridSpanSize)\n      GRID, GRID_TITLE -> GridLayoutManager(context, LISTS_GRID_SPAN_TABLET)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/layout/CollectionShowListItemDecoration.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.layout\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.annotation.DimenRes\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ItemDecoration\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_my_shows.common.views.CollectionShowCompactView\nimport com.michaldrabik.ui_my_shows.common.views.CollectionShowView\n\nclass CollectionShowListItemDecoration : ItemDecoration {\n\n  private var spacing: Int\n  private var halfSpacing: Int\n  private val isTablet: Boolean\n\n  constructor(\n    context: Context,\n    @DimenRes spacingDimen: Int,\n  ) {\n    this.spacing = context.resources.getDimensionPixelSize(spacingDimen)\n    this.halfSpacing = spacing / 2\n    this.isTablet = context.isTablet()\n  }\n\n  override fun getItemOffsets(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n    state: RecyclerView.State,\n  ) {\n    if (view !is CollectionShowView && view !is CollectionShowCompactView) {\n      return\n    }\n    if (!isTablet && (parent.layoutManager is LinearLayoutManager)) {\n      getItemOffsetsPhone(outRect, view)\n      return\n    }\n    if (isTablet && (parent.layoutManager is GridLayoutManager)) {\n      getItemOffsetsTablet(outRect, view, parent)\n      return\n    }\n  }\n\n  private fun getItemOffsetsTablet(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n  ) {\n    if (view is CollectionShowView) {\n      outRect.top = spacing\n      outRect.bottom = spacing\n    } else if (view is CollectionShowCompactView) {\n      outRect.top = halfSpacing\n      outRect.bottom = halfSpacing\n    }\n\n    val totalSpan = (parent.layoutManager as GridLayoutManager).spanCount\n    val column = getPosition(parent, view) % totalSpan\n\n    outRect.left = (spacing * 2) * column / totalSpan\n    outRect.right = (spacing * 2) * ((totalSpan - 1) - column) / totalSpan\n  }\n\n  private fun getItemOffsetsPhone(outRect: Rect, view: View) {\n    if (view is CollectionShowView) {\n      outRect.top = spacing\n      outRect.bottom = spacing\n    } else if (view is CollectionShowCompactView) {\n      outRect.top = halfSpacing\n      outRect.bottom = halfSpacing\n    }\n    outRect.left = 0\n    outRect.right = 0\n  }\n\n  private fun getPosition(\n    parent: RecyclerView,\n    view: View\n  ): Int {\n    // Position omitting without filters view\n    return parent.getChildAdapterPosition(view) - 1\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/recycler/CollectionAdapter.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.recycler\n\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseAdapter\nimport com.michaldrabik.ui_base.BaseMovieAdapter\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem.FiltersItem\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem.ShowItem\nimport com.michaldrabik.ui_my_shows.common.views.CollectionShowCompactView\nimport com.michaldrabik.ui_my_shows.common.views.CollectionShowFiltersView\nimport com.michaldrabik.ui_my_shows.common.views.CollectionShowGridTitleView\nimport com.michaldrabik.ui_my_shows.common.views.CollectionShowGridView\nimport com.michaldrabik.ui_my_shows.common.views.CollectionShowView\n\nclass CollectionAdapter(\n  listChangeListener: () -> Unit,\n  private val itemClickListener: (CollectionListItem) -> Unit,\n  private val itemLongClickListener: (CollectionListItem) -> Unit,\n  private val sortChipClickListener: (SortOrder, SortType) -> Unit,\n  private val upcomingChipClickListener: (Boolean) -> Unit,\n  private val listViewChipClickListener: () -> Unit,\n  private val networksChipClickListener: () -> Unit,\n  private val genresChipClickListener: () -> Unit,\n  private val missingImageListener: (CollectionListItem, Boolean) -> Unit,\n  private val missingTranslationListener: (CollectionListItem) -> Unit,\n  private val upcomingChipVisible: Boolean = true,\n) : BaseAdapter<CollectionListItem>(\n  listChangeListener = listChangeListener\n) {\n\n  companion object {\n    private const val VIEW_TYPE_SHOW = 1\n    private const val VIEW_TYPE_FILTERS = 2\n  }\n\n  override val asyncDiffer = AsyncListDiffer(this, CollectionItemDiffCallback())\n\n  var listViewMode: ListViewMode = LIST_NORMAL\n    set(value) {\n      field = value\n      notifyItemRangeChanged(0, asyncDiffer.currentList.size)\n    }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    when (viewType) {\n      VIEW_TYPE_SHOW -> BaseMovieAdapter.BaseViewHolder(\n        when (listViewMode) {\n          LIST_NORMAL -> CollectionShowView(parent.context)\n          LIST_COMPACT -> CollectionShowCompactView(parent.context)\n          GRID -> CollectionShowGridView(parent.context)\n          GRID_TITLE -> CollectionShowGridTitleView(parent.context)\n        }.apply {\n          itemClickListener = this@CollectionAdapter.itemClickListener\n          itemLongClickListener = this@CollectionAdapter.itemLongClickListener\n          missingImageListener = this@CollectionAdapter.missingImageListener\n          missingTranslationListener = this@CollectionAdapter.missingTranslationListener\n        }\n      )\n      VIEW_TYPE_FILTERS -> BaseMovieAdapter.BaseViewHolder(\n        CollectionShowFiltersView(parent.context).apply {\n          onSortChipClicked = this@CollectionAdapter.sortChipClickListener\n          onFilterUpcomingClicked = this@CollectionAdapter.upcomingChipClickListener\n          onListViewModeClicked = this@CollectionAdapter.listViewChipClickListener\n          onNetworksChipClick = this@CollectionAdapter.networksChipClickListener\n          onGenresChipClick = this@CollectionAdapter.genresChipClickListener\n          isUpcomingChipVisible = upcomingChipVisible\n        }\n      )\n      else -> throw IllegalStateException()\n    }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    when (val item = asyncDiffer.currentList[position]) {\n      is FiltersItem ->\n        (holder.itemView as CollectionShowFiltersView).bind(item, listViewMode)\n      is ShowItem ->\n        when (listViewMode) {\n          LIST_NORMAL -> (holder.itemView as CollectionShowView).bind(item)\n          LIST_COMPACT -> (holder.itemView as CollectionShowCompactView).bind(item)\n          GRID -> (holder.itemView as CollectionShowGridView).bind(item)\n          GRID_TITLE -> (holder.itemView as CollectionShowGridTitleView).bind(item)\n        }\n    }\n  }\n\n  override fun getItemViewType(position: Int) =\n    when (asyncDiffer.currentList[position]) {\n      is ShowItem -> VIEW_TYPE_SHOW\n      is FiltersItem -> VIEW_TYPE_FILTERS\n      else -> throw IllegalStateException()\n    }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/recycler/CollectionItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass CollectionItemDiffCallback : DiffUtil.ItemCallback<CollectionListItem>() {\n\n  override fun areItemsTheSame(oldItem: CollectionListItem, newItem: CollectionListItem): Boolean {\n    val areMovies = oldItem is CollectionListItem.ShowItem && newItem is CollectionListItem.ShowItem\n    val areFilters = oldItem is CollectionListItem.FiltersItem && newItem is CollectionListItem.FiltersItem\n\n    return when {\n      areMovies -> areItemsTheSame(\n        (oldItem as CollectionListItem.ShowItem),\n        (newItem as CollectionListItem.ShowItem)\n      )\n      areFilters -> true\n      else -> false\n    }\n  }\n\n  override fun areContentsTheSame(oldItem: CollectionListItem, newItem: CollectionListItem): Boolean {\n    return when (oldItem) {\n      is CollectionListItem.ShowItem -> areContentsTheSame(oldItem, (newItem as CollectionListItem.ShowItem))\n      is CollectionListItem.FiltersItem -> areContentsTheSame(oldItem, (newItem as CollectionListItem.FiltersItem))\n    }\n  }\n\n  private fun areItemsTheSame(\n    oldItem: CollectionListItem.ShowItem,\n    newItem: CollectionListItem.ShowItem,\n  ): Boolean {\n    return oldItem.show.traktId == newItem.show.traktId\n  }\n\n  private fun areContentsTheSame(\n    oldItem: CollectionListItem.ShowItem,\n    newItem: CollectionListItem.ShowItem,\n  ): Boolean {\n    return oldItem.show.firstAired == newItem.show.firstAired &&\n      oldItem.image == newItem.image &&\n      oldItem.isLoading == newItem.isLoading &&\n      oldItem.translation == newItem.translation &&\n      oldItem.sortOrder == newItem.sortOrder &&\n      oldItem.spoilers == newItem.spoilers &&\n      oldItem.userRating == newItem.userRating\n  }\n\n  private fun areContentsTheSame(\n    oldItem: CollectionListItem.FiltersItem,\n    newItem: CollectionListItem.FiltersItem,\n  ): Boolean {\n    return oldItem.isUpcoming == newItem.isUpcoming &&\n      oldItem.networks == newItem.networks &&\n      oldItem.genres == newItem.genres &&\n      oldItem.sortOrder == newItem.sortOrder &&\n      oldItem.sortType == newItem.sortType\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/recycler/CollectionListItem.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.recycler\n\nimport com.michaldrabik.common.extensions.toZonedDateTime\nimport com.michaldrabik.ui_base.common.ListItem\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Network\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.Translation\nimport java.time.ZonedDateTime\nimport java.time.format.DateTimeFormatter\n\nsealed class CollectionListItem(\n  override val show: Show,\n  override val image: Image,\n  override val isLoading: Boolean = false,\n) : ListItem {\n\n  fun getReleaseDate(): ZonedDateTime? = show.firstAired.toZonedDateTime()\n\n  data class ShowItem(\n    override val show: Show,\n    override val image: Image,\n    override val isLoading: Boolean = false,\n    val dateFormat: DateTimeFormatter,\n    val translation: Translation? = null,\n    val userRating: Int? = null,\n    val sortOrder: SortOrder? = null,\n    val spoilers: Spoilers\n  ) : CollectionListItem(\n    show = show,\n    image = image,\n    isLoading = isLoading\n  ) {\n\n    data class Spoilers(\n      val isSpoilerHidden: Boolean,\n      val isSpoilerRatingsHidden: Boolean,\n      val isSpoilerTapToReveal: Boolean,\n    )\n  }\n\n  data class FiltersItem(\n    val sortOrder: SortOrder,\n    val sortType: SortType,\n    val networks: List<Network>,\n    val genres: List<Genre>,\n    val isUpcoming: Boolean,\n  ) : CollectionListItem(\n    show = Show.EMPTY,\n    image = Image.createUnknown(ImageType.POSTER),\n    isLoading = false\n  ) {\n\n    fun hasActiveFilters() = isUpcoming || networks.isNotEmpty() || genres.isNotEmpty()\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/views/CollectionShowCompactView.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.setOutboundRipple\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_shows.databinding.ViewCollectionShowCompactBinding\nimport java.util.Locale.ENGLISH\n\n@SuppressLint(\"SetTextI18n\")\nclass CollectionShowCompactView : ShowView<CollectionListItem.ShowItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionShowCompactBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      collectionShowRoot.onClick { itemClickListener?.invoke(item) }\n      collectionShowRoot.onLongClick { itemLongClickListener?.invoke(item) }\n      collectionShowRoot.setOutboundRipple(\n        size = (context.dimenToPx(R.dimen.collectionItemRippleSpaceSmall)).toFloat(),\n        corner = context.dimenToPx(R.dimen.mediaTileCorner).toFloat()\n      )\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionShowImage\n  override val placeholderView: ImageView = binding.collectionShowPlaceholder\n\n  private val nowUtc = nowUtc()\n  private lateinit var item: CollectionListItem.ShowItem\n\n  override fun bind(item: CollectionListItem.ShowItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      collectionShowProgress.visibleIf(item.isLoading)\n      collectionShowTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.show.title\n        else item.translation?.title\n\n      collectionShowNetwork.text =\n        if (item.show.year > 0) context.getString(R.string.textNetwork, item.show.network, item.show.year.toString())\n        else String.format(\"%s\", item.show.network)\n\n      bindRating(item)\n      collectionShowNetwork.visibleIf(item.show.network.isNotBlank())\n\n      with(collectionShowReleaseDate) {\n        val releaseDate = item.getReleaseDate()\n        if (releaseDate != null) {\n          visibleIf(releaseDate.isAfter(nowUtc))\n          text = item.dateFormat.format(releaseDate).capitalizeWords()\n        } else {\n          gone()\n        }\n      }\n\n      item.userRating?.let {\n        collectionShowUserStarIcon.visible()\n        collectionShowUserRating.visible()\n        collectionShowUserRating.text = String.format(ENGLISH, \"%d\", it)\n      }\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindRating(item: CollectionListItem.ShowItem) {\n    with(binding) {\n      var rating = String.format(ENGLISH, \"%.1f\", item.show.rating)\n\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionShowRating.tag = rating\n        rating = Config.SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          with(collectionShowRating) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      }\n\n      collectionShowRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionShowTitle.text = \"\"\n      collectionShowNetwork.text = \"\"\n      collectionShowRating.text = \"\"\n      collectionShowPlaceholder.gone()\n      collectionShowUserRating.gone()\n      collectionShowUserStarIcon.gone()\n      Glide.with(this@CollectionShowCompactView).clear(collectionShowImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/views/CollectionShowFiltersView.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport androidx.core.content.ContextCompat\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SortType.ASCENDING\nimport com.michaldrabik.ui_model.SortType.DESCENDING\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_shows.databinding.ViewShowsFiltersBinding\n\nclass CollectionShowFiltersView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewShowsFiltersBinding.inflate(LayoutInflater.from(context), this)\n\n  var onSortChipClicked: ((SortOrder, SortType) -> Unit)? = null\n  var onFilterUpcomingClicked: ((Boolean) -> Unit)? = null\n  var onListViewModeClicked: (() -> Unit)? = null\n  var onNetworksChipClick: (() -> Unit)? = null\n  var onGenresChipClick: (() -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    clipChildren = false\n    clipToPadding = false\n  }\n\n  var isUpcomingChipVisible: Boolean\n    get() = binding.followedShowsUpcomingChip.visibility == VISIBLE\n    set(value) {\n      binding.followedShowsUpcomingChip.visibleIf(value)\n    }\n\n  fun bind(\n    item: CollectionListItem.FiltersItem,\n    viewMode: ListViewMode,\n  ) {\n    with(binding) {\n      val sortIcon = when (item.sortType) {\n        ASCENDING -> R.drawable.ic_arrow_alt_up\n        DESCENDING -> R.drawable.ic_arrow_alt_down\n      }\n      followedShowsSortingChip.closeIcon = ContextCompat.getDrawable(context, sortIcon)\n      followedShowsSortingChip.text = context.getText(item.sortOrder.displayString)\n\n      followedShowsNetworksChip.isSelected = item.networks.isNotEmpty()\n      followedShowsNetworksChip.text = when {\n        item.networks.isEmpty() -> context.getString(R.string.textNetworks).filter { it.isLetter() }\n        item.networks.size == 1 -> item.networks[0].channels.first()\n        else -> throw IllegalStateException()\n      }\n\n      followedShowsGenresChip.isSelected = item.genres.isNotEmpty()\n      followedShowsGenresChip.text = when {\n        item.genres.isEmpty() -> context.getString(R.string.textGenres).filter { it.isLetter() }\n        item.genres.size == 1 -> context.getString(item.genres.first().displayName)\n        item.genres.size == 2 -> \"${context.getString(item.genres[0].displayName)}, ${context.getString(item.genres[1].displayName)}\"\n        else -> \"${context.getString(item.genres[0].displayName)}, ${context.getString(item.genres[1].displayName)} + ${item.genres.size - 2}\"\n      }\n\n      followedShowsUpcomingChip.isChecked = item.isUpcoming\n      followedShowsListViewChip.setChipIconResource(\n        when (viewMode) {\n          LIST_NORMAL, LIST_COMPACT -> R.drawable.ic_view_list\n          GRID, GRID_TITLE -> R.drawable.ic_view_grid\n        }\n      )\n\n      followedShowsSortingChip.onClick { onSortChipClicked?.invoke(item.sortOrder, item.sortType) }\n      followedShowsUpcomingChip.onClick { onFilterUpcomingClicked?.invoke(followedShowsUpcomingChip.isChecked) }\n      followedShowsListViewChip.onClick { onListViewModeClicked?.invoke() }\n      followedShowsNetworksChip.onClick { onNetworksChipClick?.invoke() }\n      followedShowsGenresChip.onClick { onGenresChipClick?.invoke() }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/views/CollectionShowGridTitleView.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_shows.databinding.ViewCollectionShowGridTitleBinding\nimport java.util.Locale\n\n@SuppressLint(\"SetTextI18n\")\nclass CollectionShowGridTitleView : ShowView<CollectionListItem.ShowItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionShowGridTitleBinding.inflate(LayoutInflater.from(context), this)\n\n  private val width by lazy {\n    val span = if (context.isTablet()) Config.LISTS_GRID_SPAN_TABLET else Config.LISTS_GRID_SPAN\n    val itemSpacing = context.dimenToPx(R.dimen.spaceSmall)\n    val screenMargin = context.dimenToPx(R.dimen.screenMarginHorizontal)\n    val screenWidth = screenWidth().toFloat()\n    ((screenWidth - (screenMargin * 2.0)) - ((span - 1) * itemSpacing)) / span\n  }\n  private val height by lazy { width * 1.7305 }\n\n  init {\n    layoutParams = LayoutParams(width.toInt(), height.toInt())\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      collectionShowRoot.onClick { itemClickListener?.invoke(item) }\n      collectionShowRoot.onLongClick { itemLongClickListener?.invoke(item) }\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionShowImage\n  override val placeholderView: ImageView = binding.collectionShowPlaceholder\n\n  private lateinit var item: CollectionListItem.ShowItem\n\n  override fun bind(item: CollectionListItem.ShowItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      collectionShowProgress.visibleIf(item.isLoading)\n\n      collectionShowTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.show.title\n        else item.translation?.title\n\n      if (item.sortOrder == SortOrder.RATING) {\n        bindRating(item)\n      } else if (item.sortOrder == SortOrder.USER_RATING && item.userRating != null) {\n        collectionShowRating.visible()\n        collectionShowRating.text = String.format(Locale.ENGLISH, \"%d\", item.userRating)\n      } else {\n        collectionShowRating.gone()\n      }\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindRating(item: CollectionListItem.ShowItem) {\n    with(binding) {\n      var rating = String.format(Locale.ENGLISH, \"%.1f\", item.show.rating)\n\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionShowRating.tag = rating\n        rating = Config.SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          with(collectionShowRating) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      }\n\n      collectionShowRating.visible()\n      collectionShowRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionShowTitle.text = \"\"\n      collectionShowPlaceholder.gone()\n      Glide.with(this@CollectionShowGridTitleView).clear(collectionShowImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/views/CollectionShowGridView.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_shows.databinding.ViewCollectionShowGridBinding\nimport java.util.Locale.ENGLISH\n\n@SuppressLint(\"SetTextI18n\")\nclass CollectionShowGridView : ShowView<CollectionListItem.ShowItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionShowGridBinding.inflate(LayoutInflater.from(context), this)\n\n  private val width by lazy {\n    val span = if (context.isTablet()) Config.LISTS_GRID_SPAN_TABLET else Config.LISTS_GRID_SPAN\n    val itemSpacing = context.dimenToPx(R.dimen.spaceSmall)\n    val screenMargin = context.dimenToPx(R.dimen.screenMarginHorizontal)\n    val screenWidth = screenWidth().toFloat()\n    ((screenWidth - (screenMargin * 2.0)) - ((span - 1) * itemSpacing)) / span\n  }\n  private val height by lazy { width * ASPECT_RATIO }\n\n  init {\n    layoutParams = LayoutParams(width.toInt(), height.toInt())\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      collectionShowRoot.onClick { itemClickListener?.invoke(item) }\n      collectionShowRoot.onLongClick { itemLongClickListener?.invoke(item) }\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionShowImage\n  override val placeholderView: ImageView = binding.collectionShowPlaceholder\n\n  private lateinit var item: CollectionListItem.ShowItem\n\n  override fun bind(item: CollectionListItem.ShowItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      collectionShowProgress.visibleIf(item.isLoading)\n\n      if (item.sortOrder == RATING) {\n        bindRating(item)\n      } else if (item.sortOrder == USER_RATING && item.userRating != null) {\n        collectionShowRating.visible()\n        collectionShowRating.text = String.format(ENGLISH, \"%d\", item.userRating)\n      } else {\n        collectionShowRating.gone()\n      }\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindRating(item: CollectionListItem.ShowItem) {\n    with(binding) {\n      var rating = String.format(ENGLISH, \"%.1f\", item.show.rating)\n\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionShowRating.tag = rating\n        rating = Config.SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          with(collectionShowRating) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      }\n\n      collectionShowRating.visible()\n      collectionShowRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionShowPlaceholder.gone()\n      Glide.with(this@CollectionShowGridView).clear(collectionShowImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/common/views/CollectionShowView.kt",
    "content": "package com.michaldrabik.ui_my_shows.common.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.setOutboundRipple\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_shows.databinding.ViewCollectionShowBinding\nimport java.util.Locale.ENGLISH\n\n@SuppressLint(\"SetTextI18n\")\nclass CollectionShowView : ShowView<CollectionListItem.ShowItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionShowBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding.collectionShowRoot) {\n      onClick { itemClickListener?.invoke(item) }\n      onLongClick { itemLongClickListener?.invoke(item) }\n      setOutboundRipple(\n        size = (context.dimenToPx(R.dimen.collectionItemRippleSpace)).toFloat(),\n        corner = context.dimenToPx(R.dimen.mediaTileCorner).toFloat()\n      )\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionShowImage\n  override val placeholderView: ImageView = binding.collectionShowPlaceholder\n\n  private var nowUtc = nowUtc()\n\n  private lateinit var item: CollectionListItem.ShowItem\n\n  override fun bind(item: CollectionListItem.ShowItem) {\n    clear()\n    this.item = item\n    with(binding) {\n      collectionShowProgress.visibleIf(item.isLoading)\n      collectionShowTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.show.title\n        else item.translation?.title\n\n      bindDescription(item)\n      bindRating(item)\n\n      collectionShowNetwork.text =\n        if (item.show.year > 0) context.getString(R.string.textNetwork, item.show.network, item.show.year.toString())\n        else String.format(\"%s\", item.show.network)\n\n      collectionShowNetwork.visibleIf(item.show.network.isNotBlank())\n\n      with(collectionShowReleaseDate) {\n        val releaseDate = item.getReleaseDate()\n        if (releaseDate != null) {\n          visibleIf(releaseDate.isAfter(nowUtc))\n          text = item.dateFormat.format(releaseDate).capitalizeWords()\n        } else {\n          gone()\n        }\n      }\n\n      item.userRating?.let {\n        collectionShowUserStarIcon.visible()\n        collectionShowUserRating.visible()\n        collectionShowUserRating.text = String.format(ENGLISH, \"%d\", it)\n      }\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindDescription(item: CollectionListItem.ShowItem) {\n    with(binding) {\n      collectionShowDescription.text =\n        if (item.translation?.overview.isNullOrBlank()) item.show.overview\n        else item.translation?.overview\n\n      if (item.spoilers.isSpoilerHidden) {\n        collectionShowDescription.tag = collectionShowDescription.text\n        collectionShowDescription.text =\n          SPOILERS_REGEX.replace(collectionShowDescription.text, SPOILERS_HIDE_SYMBOL)\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          collectionShowDescription.onClick { view ->\n            view.tag?.let { collectionShowDescription.text = it.toString() }\n            view.isClickable = false\n          }\n        }\n      }\n\n      collectionShowDescription.visibleIf(item.show.overview.isNotBlank())\n    }\n  }\n\n  private fun bindRating(item: CollectionListItem.ShowItem) {\n    var rating = String.format(ENGLISH, \"%.1f\", item.show.rating)\n\n    with(binding) {\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionShowRating.tag = rating\n        rating = Config.SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          collectionShowRating.onClick { view ->\n            view.tag?.let { collectionShowRating.text = it.toString() }\n            view.isClickable = false\n          }\n        }\n      }\n\n      collectionShowRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionShowTitle.text = \"\"\n      collectionShowDescription.text = \"\"\n      collectionShowNetwork.text = \"\"\n      collectionShowRating.text = \"\"\n      collectionShowPlaceholder.gone()\n      collectionShowUserRating.gone()\n      collectionShowUserStarIcon.gone()\n      Glide.with(this@CollectionShowView).clear(collectionShowImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/hidden/HiddenFragment.kt",
    "content": "package com.michaldrabik.ui_my_shows.hidden\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.postDelayed\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN_TABLET\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.common.OnScrollResetListener\nimport com.michaldrabik.ui_base.common.OnSearchClickListener\nimport com.michaldrabik.ui_base.common.sheets.sort_order.SortOrderBottomSheet\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.withSpanSizeLookup\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_ADDED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersOrigin.HIDDEN_SHOWS\nimport com.michaldrabik.ui_my_shows.common.filters.genre.CollectionFiltersGenreBottomSheet\nimport com.michaldrabik.ui_my_shows.common.filters.genre.CollectionFiltersGenreBottomSheet.Companion.REQUEST_COLLECTION_FILTERS_GENRE\nimport com.michaldrabik.ui_my_shows.common.filters.network.CollectionFiltersNetworkBottomSheet\nimport com.michaldrabik.ui_my_shows.common.filters.network.CollectionFiltersNetworkBottomSheet.Companion.REQUEST_COLLECTION_FILTERS_NETWORK\nimport com.michaldrabik.ui_my_shows.common.layout.CollectionShowGridItemDecoration\nimport com.michaldrabik.ui_my_shows.common.layout.CollectionShowLayoutManagerProvider\nimport com.michaldrabik.ui_my_shows.common.layout.CollectionShowListItemDecoration\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionAdapter\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem.FiltersItem\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem.ShowItem\nimport com.michaldrabik.ui_my_shows.databinding.FragmentHiddenBinding\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsFragment\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsUiEvent.OpenPremium\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsViewModel\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_SORT_ORDER\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_SORT_TYPE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_SORT_ORDER\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass HiddenFragment :\n  BaseFragment<HiddenViewModel>(R.layout.fragment_hidden),\n  OnScrollResetListener,\n  OnSearchClickListener {\n\n  @Inject lateinit var settings: SettingsViewModeRepository\n\n  override val navigationId = R.id.followedShowsFragment\n  private val binding by viewBinding(FragmentHiddenBinding::bind)\n\n  private val parentViewModel by viewModels<FollowedShowsViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<HiddenViewModel>()\n\n  private var adapter: CollectionAdapter? = null\n  private var layoutManager: LayoutManager? = null\n  private var statusBarHeight = 0\n  private var isSearching = false\n  private val tabletGridSpanSize by lazy { settings.tabletGridSpanSize }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupStatusBar()\n    setupRecycler()\n\n    launchAndRepeatStarted(\n      { parentViewModel.uiState.collect { viewModel.onParentState(it) } },\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = { viewModel.loadShows() }\n    )\n  }\n\n  private fun setupRecycler() {\n    layoutManager = CollectionShowLayoutManagerProvider.provideLayoutManger(requireContext(), LIST_NORMAL, tabletGridSpanSize)\n    adapter = CollectionAdapter(\n      itemClickListener = { openShowDetails(it.show) },\n      itemLongClickListener = { item -> openShowMenu(item.show) },\n      sortChipClickListener = ::openSortOrderDialog,\n      missingImageListener = viewModel::loadMissingImage,\n      missingTranslationListener = viewModel::loadMissingTranslation,\n      listViewChipClickListener = viewModel::setNextViewMode,\n      networksChipClickListener = ::openNetworksDialog,\n      genresChipClickListener = ::openGenresDialog,\n      upcomingChipClickListener = {},\n      listChangeListener = {\n        binding.hiddenRecycler.scrollToPosition(0)\n        (requireParentFragment() as FollowedShowsFragment).resetTranslations()\n      },\n      upcomingChipVisible = false\n    ).apply {\n      stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY\n    }\n    binding.hiddenRecycler.apply {\n      setHasFixedSize(true)\n      adapter = this@HiddenFragment.adapter\n      layoutManager = this@HiddenFragment.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      addItemDecoration(CollectionShowListItemDecoration(requireContext(), R.dimen.spaceSmall))\n      addItemDecoration(CollectionShowGridItemDecoration(requireContext(), R.dimen.spaceSmall))\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      if (statusBarHeight != 0) {\n        hiddenContent.updatePadding(top = hiddenContent.paddingTop + statusBarHeight)\n        hiddenRecycler.updatePadding(top = dimenToPx(R.dimen.archiveTabsViewPadding))\n        return\n      }\n      hiddenContent.doOnApplyWindowInsets { view, insets, padding, _ ->\n        val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n        statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n        view.updatePadding(top = padding.top + statusBarHeight)\n        hiddenRecycler.updatePadding(top = dimenToPx(R.dimen.archiveTabsViewPadding))\n      }\n    }\n  }\n\n  private fun render(uiState: HiddenUiState) {\n    uiState.run {\n      with(binding) {\n        viewMode.let {\n          if (adapter?.listViewMode != it) {\n            layoutManager = CollectionShowLayoutManagerProvider.provideLayoutManger(requireContext(), it, tabletGridSpanSize)\n            adapter?.listViewMode = it\n            hiddenRecycler.let { recycler ->\n              recycler.layoutManager = layoutManager\n              recycler.adapter = adapter\n            }\n          }\n        }\n        items.let {\n          val notifyChange = resetScroll?.consume() == true\n          adapter?.setItems(it, notifyChange = notifyChange)\n          (layoutManager as? GridLayoutManager)?.withSpanSizeLookup { pos ->\n            when (adapter?.getItems()?.get(pos)) {\n              is FiltersItem -> {\n                when (viewMode) {\n                  LIST_NORMAL, LIST_COMPACT -> if (isTablet) tabletGridSpanSize else LISTS_GRID_SPAN\n                  GRID, GRID_TITLE -> if (isTablet) LISTS_GRID_SPAN_TABLET else LISTS_GRID_SPAN\n                }\n              }\n              is ShowItem -> 1\n              else -> throw Error(\"Unsupported span size!\")\n            }\n          }\n          hiddenEmptyView.root.fadeIf(it.isEmpty() && !isSearching)\n        }\n      }\n      sortOrder?.let { event ->\n        event.consume()?.let { openSortOrderDialog(it.first, it.second) }\n      }\n    }\n  }\n\n  private fun openShowDetails(show: Show) {\n    (requireParentFragment() as? FollowedShowsFragment)?.openShowDetails(show)\n  }\n\n  private fun openShowMenu(show: Show) {\n    (requireParentFragment() as? FollowedShowsFragment)?.openShowMenu(show)\n  }\n\n  private fun openSortOrderDialog(order: SortOrder, type: SortType) {\n    val options = listOf(NAME, RATING, USER_RATING, NEWEST, DATE_ADDED)\n    val args = SortOrderBottomSheet.createBundle(options, order, type)\n\n    requireParentFragment().setFragmentResultListener(REQUEST_SORT_ORDER) { _, bundle ->\n      val sortOrder = bundle.getSerializable(ARG_SELECTED_SORT_ORDER) as SortOrder\n      val sortType = bundle.getSerializable(ARG_SELECTED_SORT_TYPE) as SortType\n      viewModel.setSortOrder(sortOrder, sortType)\n    }\n\n    navigateTo(R.id.actionFollowedShowsFragmentToSortOrder, args)\n  }\n\n  private fun openNetworksDialog() {\n    requireParentFragment().setFragmentResultListener(REQUEST_COLLECTION_FILTERS_NETWORK) { _, _ ->\n      viewModel.loadShows(resetScroll = true)\n    }\n\n    val bundle = CollectionFiltersNetworkBottomSheet.createBundle(HIDDEN_SHOWS)\n    navigateToSafe(R.id.actionFollowedShowsFragmentToNetworks, bundle)\n  }\n\n  private fun openGenresDialog() {\n    requireParentFragment().setFragmentResultListener(REQUEST_COLLECTION_FILTERS_GENRE) { _, _ ->\n      viewModel.loadShows(resetScroll = true)\n    }\n\n    val bundle = CollectionFiltersGenreBottomSheet.createBundle(HIDDEN_SHOWS)\n    navigateToSafe(R.id.actionFollowedShowsFragmentToGenres, bundle)\n  }\n\n  override fun onEnterSearch() {\n    isSearching = true\n    with(binding) {\n      hiddenRecycler.translationY = dimenToPx(R.dimen.myShowsSearchLocalOffset).toFloat()\n      hiddenRecycler.smoothScrollToPosition(0)\n    }\n  }\n\n  override fun onExitSearch() {\n    isSearching = false\n    with(binding.hiddenRecycler) {\n      translationY = 0F\n      postDelayed(200) { layoutManager?.scrollToPosition(0) }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is OpenPremium -> {\n        (requireParentFragment() as? FollowedShowsFragment)?.openPremium()\n      }\n    }\n  }\n\n  override fun onScrollReset() = binding.hiddenRecycler.scrollToPosition(0)\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/hidden/HiddenUiState.kt",
    "content": "package com.michaldrabik.ui_my_shows.hidden\n\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem\n\ndata class HiddenUiState(\n  val items: List<CollectionListItem> = emptyList(),\n  val viewMode: ListViewMode = ListViewMode.LIST_NORMAL,\n  val resetScroll: Event<Boolean>? = null,\n  val sortOrder: Event<Pair<SortOrder, SortType>>? = null,\n)\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/hidden/HiddenViewModel.kt",
    "content": "package com.michaldrabik.ui_my_shows.hidden\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.ReloadData\nimport com.michaldrabik.ui_base.events.TraktSyncAuthError\nimport com.michaldrabik.ui_base.events.TraktSyncError\nimport com.michaldrabik.ui_base.events.TraktSyncSuccess\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem.ShowItem\nimport com.michaldrabik.ui_my_shows.hidden.cases.HiddenLoadShowsCase\nimport com.michaldrabik.ui_my_shows.hidden.cases.HiddenSortOrderCase\nimport com.michaldrabik.ui_my_shows.hidden.cases.HiddenTranslationsCase\nimport com.michaldrabik.ui_my_shows.hidden.cases.HiddenViewModeCase\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsUiEvent.OpenPremium\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsUiState\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\nimport com.michaldrabik.ui_base.events.Event as EventSync\n\n@HiltViewModel\nclass HiddenViewModel @Inject constructor(\n  private val sortOrderCase: HiddenSortOrderCase,\n  private val loadShowsCase: HiddenLoadShowsCase,\n  private val translationsCase: HiddenTranslationsCase,\n  private val viewModeCase: HiddenViewModeCase,\n  private val imagesProvider: ShowImagesProvider,\n  private val eventsManager: EventsManager,\n  private val settingsRepository: SettingsRepository\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private var loadItemsJob: Job? = null\n\n  private val itemsState = MutableStateFlow<List<CollectionListItem>>(emptyList())\n  private val sortOrderState = MutableStateFlow<Event<Pair<SortOrder, SortType>>?>(null)\n  private val scrollState = MutableStateFlow<Event<Boolean>?>(null)\n  private val viewModeState = MutableStateFlow(ListViewMode.LIST_NORMAL)\n\n  private var searchQuery: String? = null\n\n  init {\n    viewModelScope.launch { eventsManager.events.collect { onEvent(it) } }\n  }\n\n  fun onParentState(state: FollowedShowsUiState) {\n    when {\n      this.searchQuery != state.searchQuery -> {\n        this.searchQuery = state.searchQuery\n        loadShows(resetScroll = state.searchQuery.isNullOrBlank())\n      }\n    }\n  }\n\n  fun loadShows(resetScroll: Boolean = false) {\n    loadItemsJob?.cancel()\n    loadItemsJob = viewModelScope.launch {\n      viewModeState.value = viewModeCase.getListViewMode()\n      itemsState.value = loadShowsCase.loadShows(searchQuery ?: \"\")\n      scrollState.value = Event(resetScroll)\n    }\n  }\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType) {\n    viewModelScope.launch {\n      sortOrderCase.setSortOrder(sortOrder, sortType)\n      loadShows(resetScroll = true)\n    }\n  }\n\n  fun loadMissingImage(item: CollectionListItem, force: Boolean) {\n    check(item is ShowItem)\n    viewModelScope.launch {\n      updateItem(item.copy(isLoading = true))\n      try {\n        val image = imagesProvider.loadRemoteImage(item.show, item.image.type, force)\n        updateItem(item.copy(isLoading = false, image = image))\n      } catch (t: Throwable) {\n        updateItem(item.copy(isLoading = false, image = Image.createUnavailable(item.image.type)))\n      }\n    }\n  }\n\n  fun loadMissingTranslation(item: CollectionListItem) {\n    check(item is ShowItem)\n    if (item.translation != null || translationsCase.getLanguage() == Config.DEFAULT_LANGUAGE) return\n    viewModelScope.launch {\n      try {\n        val translation = translationsCase.loadTranslation(item.show, false)\n        updateItem(item.copy(translation = translation))\n      } catch (error: Throwable) {\n        Timber.e(error)\n      }\n    }\n  }\n\n  fun setNextViewMode() {\n    if (settingsRepository.isPremium) {\n      viewModeState.value = viewModeCase.setNextViewMode()\n      return\n    }\n    viewModelScope.launch {\n      eventChannel.send(OpenPremium)\n    }\n  }\n\n  private fun updateItem(new: CollectionListItem) {\n    val currentItems = uiState.value.items.toMutableList()\n    currentItems.findReplace(new) { it.isSameAs(new) }\n    itemsState.value = currentItems\n  }\n\n  private fun onEvent(event: EventSync) =\n    when (event) {\n      is TraktSyncSuccess -> loadShows()\n      is TraktSyncError -> loadShows()\n      is TraktSyncAuthError -> loadShows()\n      is ReloadData -> loadShows()\n      else -> Unit\n    }\n\n  val uiState = combine(\n    itemsState,\n    sortOrderState,\n    scrollState,\n    viewModeState\n  ) { s1, s2, s3, s4 ->\n    HiddenUiState(\n      items = s1,\n      sortOrder = s2,\n      resetScroll = s3,\n      viewMode = s4\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = HiddenUiState()\n  )\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/hidden/cases/HiddenLoadShowsCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.hidden.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.TraktRating\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_shows.hidden.helpers.HiddenItemSorter\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport java.time.format.DateTimeFormatter\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass HiddenLoadShowsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val ratingsCase: HiddenRatingsCase,\n  private val sorter: HiddenItemSorter,\n  private val showsRepository: ShowsRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val settingsRepository: SettingsRepository,\n  private val imagesProvider: ShowImagesProvider,\n  private val dateFormatProvider: DateFormatProvider,\n) {\n\n  suspend fun loadShows(searchQuery: String): List<CollectionListItem> =\n    withContext(dispatchers.IO) {\n      val language = translationsRepository.getLanguage()\n      val ratings = ratingsCase.loadRatings()\n      val dateFormat = dateFormatProvider.loadFullDayFormat()\n      val translations =\n        if (language == Config.DEFAULT_LANGUAGE) emptyMap()\n        else translationsRepository.loadAllShowsLocal(language)\n      val spoilers = settingsRepository.spoilers.getAll()\n\n      val sortOrder = settingsRepository.sorting.hiddenShowsSortOrder\n      val sortType = settingsRepository.sorting.hiddenShowsSortType\n\n      val filtersItem = loadFiltersItem(sortOrder, sortType)\n      val filtersNetworks = filtersItem.networks\n        .flatMap { network -> network.channels.map { it } }\n      val filtersGenres = filtersItem.genres.map { it.slug.lowercase() }\n\n      val hiddenItems = showsRepository.hiddenShows.loadAll()\n        .map {\n          toListItemAsync(\n            show = it,\n            translation = translations[it.traktId],\n            userRating = ratings[it.ids.trakt],\n            dateFormat = dateFormat,\n            sortOrder = sortOrder,\n            spoilers = spoilers\n          )\n        }\n        .awaitAll()\n        .filterByQuery(searchQuery)\n        .filterByNetwork(filtersNetworks)\n        .filterByGenre(filtersGenres)\n        .sortedWith(sorter.sort(sortOrder, sortType))\n\n      if (hiddenItems.isNotEmpty() || filtersItem.hasActiveFilters()) {\n        listOf(filtersItem) + hiddenItems\n      } else {\n        hiddenItems\n      }\n    }\n\n  private fun List<CollectionListItem.ShowItem>.filterByQuery(query: String) =\n    filter {\n      it.show.title.contains(query, true) ||\n        it.translation?.title?.contains(query, true) == true\n    }\n\n  private fun List<CollectionListItem.ShowItem>.filterByNetwork(networks: List<String>) =\n    filter { networks.isEmpty() || it.show.network in networks }\n\n  private fun List<CollectionListItem.ShowItem>.filterByGenre(genres: List<String>) =\n    filter { genres.isEmpty() || it.show.genres.any { genre -> genre.lowercase() in genres } }\n\n  private fun loadFiltersItem(\n    sortOrder: SortOrder,\n    sortType: SortType,\n  ): CollectionListItem.FiltersItem {\n    return CollectionListItem.FiltersItem(\n      sortOrder = sortOrder,\n      sortType = sortType,\n      networks = settingsRepository.filters.hiddenShowsNetworks,\n      genres = settingsRepository.filters.hiddenShowsGenres,\n      isUpcoming = false,\n    )\n  }\n\n  private fun CoroutineScope.toListItemAsync(\n    show: Show,\n    translation: Translation?,\n    userRating: TraktRating?,\n    dateFormat: DateTimeFormatter,\n    sortOrder: SortOrder,\n    spoilers: SpoilersSettings,\n  ) = async {\n    val image = imagesProvider.findCachedImage(show, ImageType.POSTER)\n    CollectionListItem.ShowItem(\n      isLoading = false,\n      show = show,\n      image = image,\n      translation = translation,\n      userRating = userRating?.rating,\n      dateFormat = dateFormat,\n      sortOrder = sortOrder,\n      spoilers = CollectionListItem.ShowItem.Spoilers(\n        isSpoilerHidden = spoilers.isHiddenShowsHidden,\n        isSpoilerRatingsHidden = spoilers.isHiddenShowsRatingsHidden,\n        isSpoilerTapToReveal = spoilers.isTapToReveal\n      )\n    )\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/hidden/cases/HiddenRatingsCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.hidden.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.TraktRating\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass HiddenRatingsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val ratingsRepository: RatingsRepository,\n  private val userTraktManager: UserTraktManager\n) {\n\n  suspend fun loadRatings(): Map<IdTrakt, TraktRating?> =\n    withContext(dispatchers.IO) {\n      if (!userTraktManager.isAuthorized()) {\n        return@withContext emptyMap()\n      }\n      ratingsRepository.shows.loadShowsRatings().associateBy { it.idTrakt }\n    }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/hidden/cases/HiddenSortOrderCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.hidden.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass HiddenSortOrderCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType) {\n    settingsRepository.sorting.hiddenShowsSortOrder = sortOrder\n    settingsRepository.sorting.hiddenShowsSortType = sortType\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/hidden/cases/HiddenTranslationsCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.hidden.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.Translation\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass HiddenTranslationsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val translationsRepository: TranslationsRepository,\n) {\n\n  fun getLanguage() = translationsRepository.getLanguage()\n\n  suspend fun loadTranslation(show: Show, onlyLocal: Boolean): Translation? =\n    withContext(dispatchers.IO) {\n      val language = getLanguage()\n      if (language == Config.DEFAULT_LANGUAGE) {\n        return@withContext Translation.EMPTY\n      }\n      translationsRepository.loadTranslation(show, language, onlyLocal)\n    }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/hidden/cases/HiddenViewModeCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.hidden.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass HiddenViewModeCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setNextViewMode(): ListViewMode {\n    val viewModes = ListViewMode.values()\n    val index = viewModes.indexOf(getListViewMode()) + 1\n    val nextIndex = if (index >= viewModes.size) 0 else index\n    settingsRepository.viewMode.hiddenShowsViewMode = viewModes[nextIndex].name\n    return viewModes[nextIndex]\n  }\n\n  fun getListViewMode(): ListViewMode {\n    if (!settingsRepository.isPremium) {\n      return ListViewMode.valueOf(Config.DEFAULT_LIST_VIEW_MODE)\n    }\n    return ListViewMode.valueOf(settingsRepository.viewMode.hiddenShowsViewMode)\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/hidden/helpers/HiddenItemSorter.kt",
    "content": "package com.michaldrabik.ui_my_shows.hidden.helpers\n\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_ADDED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.RECENTLY_WATCHED\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SortType.ASCENDING\nimport com.michaldrabik.ui_model.SortType.DESCENDING\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass HiddenItemSorter @Inject constructor() {\n\n  fun sort(sortOrder: SortOrder, sortType: SortType) = when (sortType) {\n    ASCENDING -> sortAscending(sortOrder)\n    DESCENDING -> sortDescending(sortOrder)\n  }\n\n  private fun sortAscending(sortOrder: SortOrder): Comparator<CollectionListItem.ShowItem> =\n    when (sortOrder) {\n      NAME -> compareBy { getTitle(it) }\n      RATING -> compareBy { it.show.rating }\n      USER_RATING ->\n        compareByDescending<CollectionListItem.ShowItem> { it.userRating != null }\n          .thenBy { it.userRating }\n          .thenBy { getTitle(it) }\n      DATE_ADDED -> compareBy { it.show.createdAt }\n      RECENTLY_WATCHED -> compareBy { it.show.updatedAt }\n      NEWEST -> compareBy<CollectionListItem.ShowItem> { it.show.year }.thenBy { it.show.firstAired }\n      else -> throw IllegalStateException(\"Invalid sort order\")\n    }\n\n  private fun sortDescending(sortOrder: SortOrder): Comparator<CollectionListItem.ShowItem> =\n    when (sortOrder) {\n      NAME -> compareByDescending { getTitle(it) }\n      RATING -> compareByDescending { it.show.rating }\n      USER_RATING ->\n        compareByDescending<CollectionListItem.ShowItem> { it.userRating != null }\n          .thenByDescending { it.userRating }\n          .thenBy { getTitle(it) }\n      DATE_ADDED -> compareByDescending { it.show.createdAt }\n      RECENTLY_WATCHED -> compareByDescending { it.show.updatedAt }\n      NEWEST -> compareByDescending<CollectionListItem.ShowItem> { it.show.year }.thenByDescending { it.show.firstAired }\n      else -> throw IllegalStateException(\"Invalid sort order\")\n    }\n\n  private fun getTitle(item: CollectionListItem.ShowItem): String {\n    val translatedTitle =\n      if (item.translation?.hasTitle == true) item.translation.title\n      else item.show.titleNoThe\n    return translatedTitle.uppercase()\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/main/FollowedPagesAdapter.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.ui_my_shows.main\n\nimport android.content.Context\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentManager\nimport androidx.fragment.app.FragmentStatePagerAdapter\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.hidden.HiddenFragment\nimport com.michaldrabik.ui_my_shows.myshows.MyShowsFragment\nimport com.michaldrabik.ui_my_shows.watchlist.WatchlistFragment\n\nclass FollowedPagesAdapter(\n  fragManager: FragmentManager,\n  private val context: Context,\n) : FragmentStatePagerAdapter(fragManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {\n\n  companion object {\n    const val PAGES_COUNT = 3\n  }\n\n  override fun getCount() = PAGES_COUNT\n\n  override fun getItem(position: Int): Fragment = when (position) {\n    0 -> MyShowsFragment()\n    1 -> WatchlistFragment()\n    2 -> HiddenFragment()\n    else -> throw IllegalStateException(\"Unknown position\")\n  }\n\n  override fun getPageTitle(position: Int) =\n    when (position) {\n      0 -> context.getString(R.string.menuMyShows)\n      1 -> context.getString(R.string.menuWatchlist)\n      2 -> context.getString(R.string.menuHidden)\n      else -> throw IllegalStateException()\n    }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/main/FollowedShowsFragment.kt",
    "content": "package com.michaldrabik.ui_my_shows.main\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.activity.addCallback\nimport androidx.core.os.bundleOf\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.widget.doAfterTextChanged\nimport androidx.fragment.app.clearFragmentResultListener\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.viewpager.widget.ViewPager\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.OnScrollResetListener\nimport com.michaldrabik.ui_base.common.OnSearchClickListener\nimport com.michaldrabik.ui_base.common.OnTabReselectedListener\nimport com.michaldrabik.ui_base.common.sheets.context_menu.ContextMenuBottomSheet\nimport com.michaldrabik.ui_base.utilities.extensions.add\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.disableUi\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.enableUi\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.hideKeyboard\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.nextPage\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.showKeyboard\nimport com.michaldrabik.ui_base.utilities.extensions.updateTopMargin\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.PremiumFeature\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.databinding.FragmentFollowedShowsBinding\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ITEM\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SHOW_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_ITEM_MENU\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass FollowedShowsFragment :\n  BaseFragment<FollowedShowsViewModel>(R.layout.fragment_followed_shows),\n  OnTabReselectedListener {\n\n  companion object {\n    const val REQUEST_MY_SHOWS_FILTERS = \"REQUEST_MY_SHOWS_FILTERS\"\n    private const val TRANSLATION_DURATION = 225L\n  }\n\n  override val navigationId = R.id.followedShowsFragment\n\n  override val viewModel by viewModels<FollowedShowsViewModel>()\n  private val binding by viewBinding(FragmentFollowedShowsBinding::bind)\n\n  private var searchViewTranslation = 0F\n  private var tabsViewTranslation = 0F\n  private var currentPage = 0\n  private var isSearching = false\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n\n    savedInstanceState?.let {\n      searchViewTranslation = it.getFloat(\"ARG_SEARCH_POSITION\")\n      tabsViewTranslation = it.getFloat(\"ARG_TABS_POSITION\")\n      currentPage = it.getInt(\"ARG_PAGE\")\n    }\n  }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupPager()\n    setupStatusBar()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } }\n    )\n\n    setFragmentResultListener(REQUEST_MY_SHOWS_FILTERS) { _, _ ->\n      viewModel.refreshData()\n    }\n  }\n\n  override fun onSaveInstanceState(outState: Bundle) {\n    super.onSaveInstanceState(outState)\n    outState.putFloat(\"ARG_SEARCH_POSITION\", searchViewTranslation)\n    outState.putFloat(\"ARG_TABS_POSITION\", tabsViewTranslation)\n    outState.putInt(\"ARG_PAGE\", currentPage)\n  }\n\n  override fun onResume() {\n    super.onResume()\n    showNavigation()\n  }\n\n  override fun onPause() {\n    enableUi()\n    tabsViewTranslation = binding.followedShowsTabs.translationY\n    searchViewTranslation = binding.followedShowsSearchView.translationY\n    super.onPause()\n  }\n\n  override fun onDestroyView() {\n    binding.followedShowsPager.removeOnPageChangeListener(pageChangeListener)\n    super.onDestroyView()\n  }\n\n  private fun setupView() {\n    with(binding) {\n      followedShowsSearchView.run {\n        hint = getString(R.string.textSearchFor)\n        statsIconVisible = true\n        onClick { openMainSearch() }\n        onSettingsClickListener = { openSettings() }\n        onStatsClickListener = { openStatistics() }\n      }\n      with(followedShowsSearchLocalView) {\n        onCloseClickListener = { exitSearch() }\n      }\n      followedShowsModeTabs.run {\n        onModeSelected = { mode = it }\n        onListsSelected = { navigateTo(R.id.actionNavigateListsFragment) }\n        showMovies(moviesEnabled)\n        showLists(true, anchorEnd = moviesEnabled)\n        selectShows()\n      }\n      followedShowsSearchIcon.onClick {\n        if (!isSearching) enterSearch() else exitSearch()\n      }\n      followedShowsSearchView.translationY = searchViewTranslation\n      followedShowsTabs.translationY = tabsViewTranslation\n      followedShowsModeTabs.translationY = tabsViewTranslation\n      followedShowsIcons.translationY = tabsViewTranslation\n    }\n  }\n\n  private fun setupPager() {\n    with(binding) {\n      followedShowsPager.run {\n        offscreenPageLimit = FollowedPagesAdapter.PAGES_COUNT\n        adapter = FollowedPagesAdapter(childFragmentManager, requireContext())\n        addOnPageChangeListener(pageChangeListener)\n      }\n      followedShowsTabs.setupWithViewPager(followedShowsPager)\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      followedShowsRoot.doOnApplyWindowInsets { _, insets, _, _ ->\n        val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n        val statusBarSize = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n        followedShowsSearchView.applyWindowInsetBehaviour(dimenToPx(R.dimen.spaceNormal) + statusBarSize)\n        followedShowsSearchView.updateTopMargin(dimenToPx(R.dimen.spaceMedium) + statusBarSize)\n        followedShowsModeTabs.updateTopMargin(dimenToPx(R.dimen.collectionTabsMargin) + statusBarSize)\n        followedShowsTabs.updateTopMargin(dimenToPx(R.dimen.myShowsSearchViewPadding) + statusBarSize)\n        followedShowsIcons.updateTopMargin(dimenToPx(R.dimen.myShowsSearchViewPadding) + statusBarSize)\n        followedShowsSearchLocalView.updateTopMargin(dimenToPx(R.dimen.myShowsSearchLocalViewPadding) + statusBarSize)\n      }\n    }\n  }\n\n  override fun setupBackPressed() {\n    val dispatcher = requireActivity().onBackPressedDispatcher\n    dispatcher.addCallback(viewLifecycleOwner) {\n      if (isSearching) {\n        exitSearch()\n      } else {\n        isEnabled = false\n        activity?.onBackPressed()\n      }\n    }\n  }\n\n  private fun enterSearch() {\n    with(binding) {\n      resetTranslations()\n      followedShowsSearchLocalView.fadeIn(150)\n      with(followedShowsSearchLocalView.binding.searchViewLocalInput) {\n        setText(\"\")\n        doAfterTextChanged { viewModel.onSearchQuery(it?.toString()) }\n        visible()\n        showKeyboard()\n        requestFocus()\n      }\n      isSearching = true\n      childFragmentManager.fragments.forEach { (it as? OnSearchClickListener)?.onEnterSearch() }\n    }\n  }\n\n  private fun exitSearch() {\n    with(binding) {\n      isSearching = false\n      childFragmentManager.fragments.forEach { (it as? OnSearchClickListener)?.onExitSearch() }\n      resetTranslations()\n      followedShowsSearchLocalView.gone()\n      with(followedShowsSearchLocalView.binding.searchViewLocalInput) {\n        setText(\"\")\n        gone()\n        hideKeyboard()\n        clearFocus()\n      }\n    }\n  }\n\n  private fun openMainSearch() {\n    with(binding) {\n      disableUi()\n      hideNavigation()\n      followedShowsModeTabs.fadeOut(duration = 200).add(animations)\n      followedShowsTabs.fadeOut(duration = 200).add(animations)\n      followedShowsIcons.fadeOut(duration = 200).add(animations)\n      followedShowsPager.fadeOut(duration = 200) {\n        super.navigateTo(R.id.actionFollowedShowsFragmentToSearch, null)\n      }.add(animations)\n    }\n  }\n\n  fun openShowDetails(show: Show) {\n    disableUi()\n    hideNavigation()\n    binding.followedShowsRoot.fadeOut(150) {\n      val bundle = Bundle().apply { putLong(ARG_SHOW_ID, show.traktId) }\n      navigateToSafe(R.id.actionFollowedShowsFragmentToShowDetailsFragment, bundle)\n      exitSearch()\n    }.add(animations)\n  }\n\n  fun openShowMenu(show: Show) {\n    setFragmentResultListener(REQUEST_ITEM_MENU) { requestKey, _ ->\n      if (requestKey == REQUEST_ITEM_MENU) {\n        viewModel.refreshData()\n      }\n      clearFragmentResultListener(REQUEST_ITEM_MENU)\n    }\n    val bundle = ContextMenuBottomSheet.createBundle(show.ids.trakt)\n    navigateToSafe(R.id.actionFollowedShowsFragmentToItemMenu, bundle)\n  }\n\n  fun openPremium() {\n    hideNavigation()\n    exitSearch()\n    val args = bundleOf(ARG_ITEM to PremiumFeature.VIEW_TYPES)\n    navigateToSafe(R.id.actionFollowedShowsFragmentToPremium, args)\n  }\n\n  private fun openSettings() {\n    hideNavigation()\n    exitSearch()\n    navigateToSafe(R.id.actionFollowedShowsFragmentToSettingsFragment)\n  }\n\n  private fun openStatistics() {\n    hideNavigation()\n    exitSearch()\n    navigateToSafe(R.id.actionFollowedShowsFragmentToStatisticsFragment)\n  }\n\n  override fun onTabReselected() {\n    if (view == null) return\n    resetTranslations(duration = 0)\n    binding.followedShowsPager.nextPage()\n    childFragmentManager.fragments.forEach {\n      (it as? OnScrollResetListener)?.onScrollReset()\n    }\n  }\n\n  fun resetTranslations(duration: Long = TRANSLATION_DURATION) {\n    if (view == null) return\n    with(binding) {\n      arrayOf(\n        followedShowsSearchView,\n        followedShowsTabs,\n        followedShowsModeTabs,\n        followedShowsIcons,\n        followedShowsSearchLocalView\n      ).forEach {\n        it.animate().translationY(0F).setDuration(duration).add(animations)?.start()\n      }\n    }\n  }\n\n  private fun render(uiState: FollowedShowsUiState) {\n    uiState.isSyncing?.let {\n      binding.followedShowsSearchView.setTraktProgress(it)\n      binding.followedShowsSearchView.isEnabled = !it\n    }\n  }\n\n  private val pageChangeListener = object : ViewPager.OnPageChangeListener {\n\n    override fun onPageSelected(position: Int) {\n      if (currentPage == position) return\n\n      if (binding.followedShowsTabs.translationY != 0F) {\n        resetTranslations()\n        requireView().postDelayed(\n          {\n            childFragmentManager.fragments.forEach { (it as? OnScrollResetListener)?.onScrollReset() }\n          },\n          225L\n        )\n      }\n\n      currentPage = position\n    }\n\n    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) = Unit\n    override fun onPageScrollStateChanged(state: Int) = Unit\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/main/FollowedShowsUiEvent.kt",
    "content": "package com.michaldrabik.ui_my_shows.main\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\nsealed class FollowedShowsUiEvent<T>(action: T) : Event<T>(action) {\n  object OpenPremium : FollowedShowsUiEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/main/FollowedShowsUiState.kt",
    "content": "package com.michaldrabik.ui_my_shows.main\n\ndata class FollowedShowsUiState(\n  val searchQuery: String? = null,\n  val isSyncing: Boolean? = null\n)\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/main/FollowedShowsViewModel.kt",
    "content": "package com.michaldrabik.ui_my_shows.main\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkInfo\nimport androidx.work.WorkManager\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.ReloadData\nimport com.michaldrabik.ui_base.trakt.TraktSyncWorker\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass FollowedShowsViewModel @Inject constructor(\n  private val eventsManager: EventsManager,\n  workManager: WorkManager\n) : ViewModel() {\n\n  private val searchQueryState = MutableStateFlow<String?>(null)\n  private val syncingState = MutableStateFlow(false)\n\n  init {\n    workManager.getWorkInfosByTagLiveData(TraktSyncWorker.TAG_ID).observeForever { work ->\n      syncingState.value = work.any { it.state == WorkInfo.State.RUNNING }\n    }\n  }\n\n  fun onSearchQuery(searchQuery: String?) {\n    searchQueryState.value = searchQuery\n  }\n\n  fun refreshData() {\n    viewModelScope.launch {\n      eventsManager.sendEvent(ReloadData)\n    }\n  }\n\n  val uiState = combine(\n    searchQueryState,\n    syncingState\n  ) { s1, s2 ->\n    FollowedShowsUiState(\n      searchQuery = s1,\n      isSyncing = s2\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = FollowedShowsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/MyShowsFragment.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.postDelayed\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN_TABLET\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.common.OnScrollResetListener\nimport com.michaldrabik.ui_base.common.OnSearchClickListener\nimport com.michaldrabik.ui_base.common.sheets.sort_order.SortOrderBottomSheet\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.withSpanSizeLookup\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.MyShowsSection\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_ADDED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.RECENTLY_WATCHED\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersOrigin.MY_SHOWS\nimport com.michaldrabik.ui_my_shows.common.filters.genre.CollectionFiltersGenreBottomSheet\nimport com.michaldrabik.ui_my_shows.common.filters.genre.CollectionFiltersGenreBottomSheet.Companion.REQUEST_COLLECTION_FILTERS_GENRE\nimport com.michaldrabik.ui_my_shows.common.filters.network.CollectionFiltersNetworkBottomSheet\nimport com.michaldrabik.ui_my_shows.common.filters.network.CollectionFiltersNetworkBottomSheet.Companion.REQUEST_COLLECTION_FILTERS_NETWORK\nimport com.michaldrabik.ui_my_shows.databinding.FragmentMyShowsBinding\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsFragment\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsUiEvent.OpenPremium\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsViewModel\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsAdapter\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem.Type.ALL_SHOWS_HEADER\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem.Type.ALL_SHOWS_ITEM\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem.Type.RECENT_SHOWS\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsLayoutManagerProvider\nimport com.michaldrabik.ui_my_shows.utilities.MyShowsGridItemDecoration\nimport com.michaldrabik.ui_my_shows.utilities.MyShowsListItemDecoration\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass MyShowsFragment :\n  BaseFragment<MyShowsViewModel>(R.layout.fragment_my_shows),\n  OnScrollResetListener,\n  OnSearchClickListener {\n\n  @Inject lateinit var settings: SettingsViewModeRepository\n\n  override val navigationId = R.id.followedShowsFragment\n  private val binding by viewBinding(FragmentMyShowsBinding::bind)\n\n  private val parentViewModel by viewModels<FollowedShowsViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<MyShowsViewModel>()\n\n  private var adapter: MyShowsAdapter? = null\n  private var layoutManager: LayoutManager? = null\n  private var statusBarHeight = 0\n  private var isSearching = false\n  private val tabletGridSpanSize by lazy { settings.tabletGridSpanSize }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupStatusBar()\n    setupRecycler()\n\n    launchAndRepeatStarted(\n      { parentViewModel.uiState.collect { viewModel.onParentState(it) } },\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = { viewModel.loadShows() }\n    )\n  }\n\n  private fun setupRecycler() {\n    layoutManager = MyShowsLayoutManagerProvider.provideLayoutManger(requireContext(), LIST_NORMAL, tabletGridSpanSize)\n    adapter = MyShowsAdapter(\n      itemClickListener = { openShowDetails(it.show) },\n      itemLongClickListener = { item -> openShowMenu(item.show) },\n      onSortOrderClickListener = { section, order, type -> openSortOrderDialog(section, order, type) },\n      onTypeClickListener = { navigateToSafe(R.id.actionFollowedShowsFragmentToMyShowsFilters) },\n      onListViewModeClickListener = viewModel::toggleViewMode,\n      onNetworksClickListener = ::openNetworksDialog,\n      onGenresClickListener = ::openGenresDialog,\n      missingImageListener = { item, force -> viewModel.loadMissingImage(item as MyShowsItem, force) },\n      missingTranslationListener = { viewModel.loadMissingTranslation(it as MyShowsItem) },\n      listChangeListener = {\n        layoutManager?.scrollToPosition(0)\n        (requireParentFragment() as FollowedShowsFragment).resetTranslations()\n      }\n    ).apply {\n      stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY\n    }\n    binding.myShowsRecycler.apply {\n      adapter = this@MyShowsFragment.adapter\n      layoutManager = this@MyShowsFragment.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      setHasFixedSize(true)\n      addItemDecoration(MyShowsGridItemDecoration(requireContext(), R.dimen.spaceSmall))\n      addItemDecoration(MyShowsListItemDecoration(requireContext(), R.dimen.spaceSmall))\n    }\n  }\n\n  private fun setupStatusBar() {\n    if (statusBarHeight != 0) {\n      binding.myShowsRoot.updatePadding(top = statusBarHeight)\n      binding.myShowsRecycler.updatePadding(top = dimenToPx(R.dimen.myShowsTabsViewPadding))\n      return\n    }\n    binding.myShowsRoot.doOnApplyWindowInsets { view, insets, _, _ ->\n      val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n      statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n      view.updatePadding(top = statusBarHeight)\n      binding.myShowsRecycler.updatePadding(top = dimenToPx(R.dimen.myShowsTabsViewPadding))\n    }\n  }\n\n  private fun render(uiState: MyShowsUiState) {\n    uiState.run {\n      with(binding) {\n        viewMode.let {\n          if (adapter?.listViewMode != it) {\n            val state = myShowsRecycler.layoutManager?.onSaveInstanceState()\n            layoutManager = MyShowsLayoutManagerProvider.provideLayoutManger(requireContext(), it, tabletGridSpanSize)\n            adapter?.listViewMode = it\n            myShowsRecycler.let { recycler ->\n              recycler.layoutManager = layoutManager\n              recycler.adapter = adapter\n              recycler.layoutManager?.onRestoreInstanceState(state)\n            }\n          }\n        }\n        items?.let { items ->\n          val notifyChangeList = resetScrollMap?.consume()\n          adapter?.setItems(items, notifyChangeList)\n          (layoutManager as? GridLayoutManager)?.withSpanSizeLookup { pos ->\n            val item = adapter?.getItems()?.get(pos)\n            when (item?.type) {\n              RECENT_SHOWS, ALL_SHOWS_HEADER -> {\n                when (viewMode) {\n                  LIST_NORMAL, LIST_COMPACT -> if (isTablet) tabletGridSpanSize else LISTS_GRID_SPAN\n                  GRID, GRID_TITLE -> if (isTablet) LISTS_GRID_SPAN_TABLET else LISTS_GRID_SPAN\n                }\n              }\n              ALL_SHOWS_ITEM -> 1\n              null -> throw Error(\"Unsupported span size!\")\n            }\n          }\n          myShowsEmptyView.root.fadeIf(showEmptyView && !isSearching)\n        }\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is OpenPremium -> {\n        (requireParentFragment() as? FollowedShowsFragment)?.openPremium()\n      }\n    }\n  }\n\n  private fun openShowDetails(show: Show) {\n    (requireParentFragment() as? FollowedShowsFragment)?.openShowDetails(show)\n  }\n\n  private fun openShowMenu(show: Show) {\n    (requireParentFragment() as? FollowedShowsFragment)?.openShowMenu(show)\n  }\n\n  @Suppress(\"DEPRECATION\")\n  private fun openSortOrderDialog(\n    section: MyShowsSection,\n    order: SortOrder,\n    type: SortType,\n  ) {\n    val options = listOf(NAME, RATING, USER_RATING, NEWEST, DATE_ADDED, RECENTLY_WATCHED)\n    val key = NavigationArgs.requestSortOrderSection(section.name)\n    val args = SortOrderBottomSheet.createBundle(options, order, type, key)\n\n    requireParentFragment().setFragmentResultListener(key) { requestKey, bundle ->\n      val sortOrder = bundle.getSerializable(NavigationArgs.ARG_SELECTED_SORT_ORDER) as SortOrder\n      val sortType = bundle.getSerializable(NavigationArgs.ARG_SELECTED_SORT_TYPE) as SortType\n      MyShowsSection.values()\n        .find { NavigationArgs.requestSortOrderSection(it.name) == requestKey }\n        ?.let { viewModel.setSortOrder(sortOrder, sortType) }\n    }\n\n    navigateTo(R.id.actionFollowedShowsFragmentToSortOrder, args)\n  }\n\n  private fun openNetworksDialog() {\n    requireParentFragment().setFragmentResultListener(REQUEST_COLLECTION_FILTERS_NETWORK) { _, _ ->\n      viewModel.loadShows()\n    }\n\n    val bundle = CollectionFiltersNetworkBottomSheet.createBundle(MY_SHOWS)\n    navigateToSafe(R.id.actionFollowedShowsFragmentToNetworks, bundle)\n  }\n\n  private fun openGenresDialog() {\n    requireParentFragment().setFragmentResultListener(REQUEST_COLLECTION_FILTERS_GENRE) { _, _ ->\n      viewModel.loadShows()\n    }\n\n    val bundle = CollectionFiltersGenreBottomSheet.createBundle(MY_SHOWS)\n    navigateToSafe(R.id.actionFollowedShowsFragmentToGenres, bundle)\n  }\n\n  override fun onEnterSearch() {\n    isSearching = true\n    with(binding) {\n      myShowsRecycler.translationY = dimenToPx(R.dimen.myShowsSearchLocalOffset).toFloat()\n      myShowsRecycler.smoothScrollToPosition(0)\n    }\n  }\n\n  override fun onExitSearch() {\n    isSearching = false\n    with(binding.myShowsRecycler) {\n      translationY = 0F\n      postDelayed(200) { layoutManager?.scrollToPosition(0) }\n    }\n  }\n\n  override fun onScrollReset() = binding.myShowsRecycler.scrollToPosition(0)\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/MyShowsUiState.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows\n\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem\n\ndata class MyShowsUiState(\n  val items: List<MyShowsItem>? = null,\n  val showEmptyView: Boolean = false,\n  val viewMode: ListViewMode = ListViewMode.LIST_NORMAL,\n  val resetScrollMap: Event<List<MyShowsItem.Type>?>? = null,\n)\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/MyShowsViewModel.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.ReloadData\nimport com.michaldrabik.ui_base.events.TraktSyncAuthError\nimport com.michaldrabik.ui_base.events.TraktSyncError\nimport com.michaldrabik.ui_base.events.TraktSyncSuccess\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport com.michaldrabik.ui_model.MyShowsSection.ALL\nimport com.michaldrabik.ui_model.MyShowsSection.RECENTS\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.TraktRating\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsUiEvent.OpenPremium\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsUiState\nimport com.michaldrabik.ui_my_shows.myshows.cases.MyShowsLoadShowsCase\nimport com.michaldrabik.ui_my_shows.myshows.cases.MyShowsRatingsCase\nimport com.michaldrabik.ui_my_shows.myshows.cases.MyShowsSortingCase\nimport com.michaldrabik.ui_my_shows.myshows.cases.MyShowsTranslationsCase\nimport com.michaldrabik.ui_my_shows.myshows.cases.MyShowsViewModeCase\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem.Type\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\nimport com.michaldrabik.ui_base.events.Event as EventSync\n\n@HiltViewModel\nclass MyShowsViewModel @Inject constructor(\n  private val loadShowsCase: MyShowsLoadShowsCase,\n  private val sortingCase: MyShowsSortingCase,\n  private val ratingsCase: MyShowsRatingsCase,\n  private val viewModeCase: MyShowsViewModeCase,\n  private val translationsCase: MyShowsTranslationsCase,\n  private val settingsRepository: SettingsRepository,\n  private val imagesProvider: ShowImagesProvider,\n  private val eventsManager: EventsManager,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private var loadItemsJob: Job? = null\n\n  private val itemsState = MutableStateFlow<List<MyShowsItem>?>(null)\n  private val itemsUpdateState = MutableStateFlow<Event<List<Type>?>?>(null)\n  private val viewModeState = MutableStateFlow(ListViewMode.LIST_NORMAL)\n  private val showEmptyViewState = MutableStateFlow(false)\n\n  private var searchQuery: String? = null\n\n  init {\n    viewModelScope.launch { eventsManager.events.collect { onEvent(it) } }\n  }\n\n  fun onParentState(state: FollowedShowsUiState) {\n    when {\n      this.searchQuery != state.searchQuery -> {\n        this.searchQuery = state.searchQuery\n        val resetScrolls =\n          if (state.searchQuery.isNullOrBlank()) listOf(Type.ALL_SHOWS_ITEM)\n          else emptyList()\n        loadShows(resetScroll = resetScrolls)\n      }\n    }\n  }\n\n  fun loadShows(resetScroll: List<Type>? = null) {\n    loadItemsJob?.cancel()\n    loadItemsJob = viewModelScope.launch {\n      val settings = settingsRepository.load()\n      val ratings = ratingsCase.loadRatings()\n      val sortOrder = settingsRepository.sorting.myShowsAllSortOrder\n      val networks = settingsRepository.filters.myShowsNetworks\n      val genres = settingsRepository.filters.myShowsGenres\n      val spoilers = settingsRepository.spoilers.getAll()\n\n      val shows = loadShowsCase.loadAllShows()\n        .map {\n          toListItemAsync(\n            itemType = Type.ALL_SHOWS_ITEM,\n            show = it,\n            type = POSTER,\n            userRating = ratings[it.ids.trakt],\n            sortOrder = sortOrder,\n            spoilers = spoilers\n          )\n        }\n        .awaitAll()\n\n      val seasons = loadShowsCase.loadSeasonsForShows(shows.map { it.show.traktId })\n      val allShows = loadShowsCase.filterSectionShows(\n        allShows = shows,\n        allSeasons = seasons,\n        searchQuery = searchQuery,\n        networks = networks.flatMap { network -> network.channels.map { it } },\n        genres = genres.map { it.slug }\n      )\n\n      val recentShows = if (settings.myRecentsAmount > 0) {\n        loadShowsCase.loadRecentShows().map {\n          toListItemAsync(Type.RECENT_SHOWS, it, ImageType.FANART, ratings[it.ids.trakt], null, spoilers)\n        }.awaitAll()\n      } else {\n        emptyList()\n      }\n\n      val isNotSearching = searchQuery.isNullOrBlank()\n      val listItems = mutableListOf<MyShowsItem>()\n      listItems.run {\n        if (isNotSearching && recentShows.isNotEmpty()) {\n          add(MyShowsItem.createHeader(RECENTS, recentShows.count(), null, null, null))\n          add(MyShowsItem.createRecentsSection(recentShows))\n        }\n        if (shows.isNotEmpty()) {\n          add(\n            MyShowsItem.createHeader(\n              section = settingsRepository.filters.myShowsType,\n              itemCount = allShows.count(),\n              sortOrder = sortingCase.loadSectionSortOrder(ALL),\n              networks = settingsRepository.filters.myShowsNetworks,\n              genres = settingsRepository.filters.myShowsGenres\n            )\n          )\n          addAll(allShows)\n        }\n      }\n\n      itemsState.value = listItems\n      itemsUpdateState.value = Event(resetScroll)\n      showEmptyViewState.value = shows.isEmpty()\n      viewModeState.value = viewModeCase.getListViewMode()\n    }\n  }\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType) {\n    viewModelScope.launch {\n      sortingCase.setSectionSortOrder(ALL, sortOrder, sortType)\n      loadShows()\n    }\n  }\n\n  fun loadMissingImage(item: MyShowsItem, force: Boolean) {\n    viewModelScope.launch {\n      updateItem(item.copy(isLoading = true))\n      try {\n        val image = imagesProvider.loadRemoteImage(item.show, item.image.type, force)\n        updateItem(item.copy(isLoading = false, image = image))\n      } catch (t: Throwable) {\n        updateItem(item.copy(isLoading = false, image = Image.createUnavailable(item.image.type)))\n      }\n    }\n  }\n\n  fun loadMissingTranslation(item: MyShowsItem) {\n    if (item.translation != null || translationsCase.getLanguage() == Config.DEFAULT_LANGUAGE) return\n    viewModelScope.launch {\n      try {\n        val translation = translationsCase.loadTranslation(item.show, false)\n        updateItem(item.copy(translation = translation))\n      } catch (error: Throwable) {\n        Timber.e(error)\n      }\n    }\n  }\n\n  fun toggleViewMode() {\n    if (settingsRepository.isPremium) {\n      viewModeState.value = viewModeCase.setNextViewMode()\n      return\n    }\n    viewModelScope.launch {\n      eventChannel.send(OpenPremium)\n    }\n  }\n\n  private fun updateItem(new: MyShowsItem) {\n    val items = uiState.value.items?.toMutableList()\n    items?.findReplace(new) { it.isSameAs(new) }\n    itemsState.value = items\n  }\n\n  private fun CoroutineScope.toListItemAsync(\n    itemType: Type,\n    show: Show,\n    type: ImageType = POSTER,\n    userRating: TraktRating?,\n    sortOrder: SortOrder?,\n    spoilers: SpoilersSettings,\n  ) = async {\n    val image = imagesProvider.findCachedImage(show, type)\n    val translation = translationsCase.loadTranslation(show, true)\n    MyShowsItem(\n      type = itemType,\n      header = null,\n      recentsSection = null,\n      show = show,\n      image = image,\n      isLoading = false,\n      translation = translation,\n      userRating = userRating?.rating,\n      sortOrder = sortOrder,\n      spoilers = MyShowsItem.Spoilers(\n        isSpoilerHidden = spoilers.isMyShowsHidden,\n        isSpoilerRatingsHidden = spoilers.isMyShowsRatingsHidden,\n        isSpoilerTapToReveal = spoilers.isTapToReveal\n      )\n    )\n  }\n\n  private fun onEvent(event: EventSync) =\n    when (event) {\n      is TraktSyncSuccess -> loadShows()\n      is TraktSyncError -> loadShows()\n      is TraktSyncAuthError -> loadShows()\n      is ReloadData -> loadShows()\n      else -> Unit\n    }\n\n  val uiState = combine(\n    itemsState,\n    itemsUpdateState,\n    viewModeState,\n    showEmptyViewState\n  ) { s1, s2, s3, s4 ->\n    MyShowsUiState(\n      items = s1,\n      resetScrollMap = s2,\n      viewMode = s3,\n      showEmptyView = s4\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = MyShowsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/cases/MyShowsLoadShowsCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.Season\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_model.MyShowsSection.FINISHED\nimport com.michaldrabik.ui_model.MyShowsSection.UPCOMING\nimport com.michaldrabik.ui_model.MyShowsSection.WATCHING\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.ShowStatus.RETURNING\nimport com.michaldrabik.ui_my_shows.myshows.helpers.MyShowsItemSorter\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MyShowsLoadShowsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val sorter: MyShowsItemSorter,\n  private val showsRepository: ShowsRepository,\n  private val settingsRepository: SettingsRepository,\n  private val localSource: LocalDataSource,\n) {\n\n  suspend fun loadAllShows() = withContext(dispatchers.IO) {\n    showsRepository.myShows.loadAll()\n  }\n\n  suspend fun loadRecentShows(): List<Show> = withContext(dispatchers.IO) {\n    val amount = settingsRepository.load().myRecentsAmount\n    showsRepository.myShows.loadAllRecent(amount)\n  }\n\n  suspend fun loadSeasonsForShows(\n    traktIds: List<Long>,\n    buffer: MutableList<Season> = mutableListOf()\n  ): List<Season> = withContext(dispatchers.IO) {\n    val batch = traktIds.take(500)\n    if (batch.isEmpty()) {\n      return@withContext buffer\n    }\n\n    val seasons = localSource.seasons.getAllByShowsIds(batch)\n      .filter { it.seasonNumber != 0 }\n    buffer.addAll(seasons)\n\n    loadSeasonsForShows(traktIds.filter { it !in batch }, buffer)\n  }\n\n  fun filterSectionShows(\n    allShows: List<MyShowsItem>,\n    allSeasons: List<Season>,\n    searchQuery: String? = null,\n    networks: List<String>,\n    genres: List<String>\n  ): List<MyShowsItem> {\n    val shows = allShows\n      .filter { showItem ->\n        val seasons = allSeasons.filter { it.idShowTrakt == showItem.show.traktId }\n        val airedSeasons = seasons.filter { it.seasonFirstAired?.isBefore(nowUtc()) == true }\n\n        when (val type = settingsRepository.filters.myShowsType) {\n          WATCHING -> {\n            airedSeasons.any { !it.isWatched }\n          }\n          FINISHED -> {\n            type.allowedStatuses.contains(showItem.show.status) && seasons.all { it.isWatched }\n          }\n          UPCOMING -> {\n            type.allowedStatuses.contains(showItem.show.status) ||\n              (showItem.show.status == RETURNING && airedSeasons.all { it.isWatched })\n          }\n          else -> true\n        }\n      }\n\n    return shows\n      .filterByQuery(searchQuery)\n      .filterByNetwork(networks)\n      .filterByGenre(genres)\n      .sortedWith(\n        sorter.sort(\n          sortOrder = settingsRepository.sorting.myShowsAllSortOrder,\n          sortType = settingsRepository.sorting.myShowsAllSortType\n        )\n      )\n  }\n\n  private fun List<MyShowsItem>.filterByQuery(query: String?) = when {\n    query.isNullOrBlank() -> this\n    else -> this.filter {\n      it.show.title.contains(query, true) ||\n        it.translation?.title?.contains(query, true) == true\n    }\n  }\n\n  private fun List<MyShowsItem>.filterByNetwork(networks: List<String>) =\n    filter { networks.isEmpty() || it.show.network in networks }\n\n  private fun List<MyShowsItem>.filterByGenre(genres: List<String>) =\n    filter { genres.isEmpty() || it.show.genres.any { genre -> genre.lowercase() in genres } }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/cases/MyShowsRatingsCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.TraktRating\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MyShowsRatingsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val ratingsRepository: RatingsRepository,\n  private val userTraktManager: UserTraktManager,\n) {\n\n  suspend fun loadRatings(): Map<IdTrakt, TraktRating?> =\n    withContext(dispatchers.IO) {\n      if (!userTraktManager.isAuthorized()) {\n        return@withContext emptyMap()\n      }\n      ratingsRepository.shows.loadShowsRatings().associateBy { it.idTrakt }\n    }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/cases/MyShowsSortingCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.MyShowsSection\nimport com.michaldrabik.ui_model.MyShowsSection.ALL\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MyShowsSortingCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun loadSectionSortOrder(section: MyShowsSection) = when (section) {\n    ALL -> Pair(\n      settingsRepository.sorting.myShowsAllSortOrder,\n      settingsRepository.sorting.myShowsAllSortType\n    )\n    else -> error(\"Should not be used here.\")\n  }\n\n  fun setSectionSortOrder(\n    section: MyShowsSection,\n    sortOrder: SortOrder,\n    sortType: SortType,\n  ) = when (section) {\n    ALL -> {\n      settingsRepository.sorting.myShowsAllSortOrder = sortOrder\n      settingsRepository.sorting.myShowsAllSortType = sortType\n    }\n    else -> error(\"Should not be used here.\")\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/cases/MyShowsTranslationsCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.Translation\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MyShowsTranslationsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val translationsRepository: TranslationsRepository,\n) {\n\n  fun getLanguage() = translationsRepository.getLanguage()\n\n  suspend fun loadTranslation(show: Show, onlyLocal: Boolean): Translation? =\n    withContext(dispatchers.IO) {\n      val language = getLanguage()\n      if (language == Config.DEFAULT_LANGUAGE) {\n        return@withContext Translation.EMPTY\n      }\n      translationsRepository.loadTranslation(show, language, onlyLocal)\n    }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/cases/MyShowsViewModeCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass MyShowsViewModeCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setNextViewMode(): ListViewMode {\n    val viewModes = ListViewMode.values()\n    val index = viewModes.indexOf(getListViewMode()) + 1\n    val nextIndex = if (index >= viewModes.size) 0 else index\n    settingsRepository.viewMode.myShowsViewMode = viewModes[nextIndex].name\n    return viewModes[nextIndex]\n  }\n\n  fun getListViewMode(): ListViewMode {\n    if (!settingsRepository.isPremium) {\n      return ListViewMode.valueOf(Config.DEFAULT_LIST_VIEW_MODE)\n    }\n    val viewMode = settingsRepository.viewMode.myShowsViewMode\n    return ListViewMode.valueOf(viewMode)\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/filters/MyShowsFiltersBottomSheet.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.filters\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.view.forEach\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetDialog\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenHeight\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.MyShowsSection\nimport com.michaldrabik.ui_model.MyShowsSection.ALL\nimport com.michaldrabik.ui_model.MyShowsSection.FINISHED\nimport com.michaldrabik.ui_model.MyShowsSection.UPCOMING\nimport com.michaldrabik.ui_model.MyShowsSection.WATCHING\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.databinding.ViewMyShowsTypeFiltersBinding\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsFragment.Companion.REQUEST_MY_SHOWS_FILTERS\nimport com.michaldrabik.ui_my_shows.myshows.filters.MyShowsFiltersUiEvent.ApplyFilters\nimport com.michaldrabik.ui_my_shows.myshows.filters.MyShowsFiltersUiEvent.CloseFilters\nimport com.michaldrabik.ui_my_shows.myshows.filters.views.MyShowsFilterItemView\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\ninternal class MyShowsFiltersBottomSheet : BaseBottomSheetFragment(R.layout.view_my_shows_type_filters) {\n\n  private val viewModel by viewModels<MyShowsFiltersViewModel>()\n  private val binding by viewBinding(ViewMyShowsTypeFiltersBinding::bind)\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } }\n    )\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun setupView() {\n    val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior\n    behavior.skipCollapsed = true\n    behavior.maxHeight = (screenHeight() * 0.9).toInt()\n\n    with(binding) {\n      rootItemsLayout.removeAllViews()\n      listOf(ALL, WATCHING, UPCOMING, FINISHED)\n        .filter { it != MyShowsSection.RECENTS }\n        .forEach { section ->\n          val itemView = MyShowsFilterItemView(requireContext()).apply {\n            onItemClickListener = { toggleItem(it) }\n            bind(section, isChecked = false)\n          }\n          rootItemsLayout.addView(itemView)\n        }\n      applyButton.onClick { applyFilters() }\n    }\n  }\n\n  private fun toggleItem(section: MyShowsSection) {\n    with(binding) {\n      rootItemsLayout.forEach {\n        (it as MyShowsFilterItemView).bind(\n          sectionType = it.sectionType,\n          isChecked = it.sectionType == section\n        )\n      }\n    }\n  }\n\n  private fun applyFilters() {\n    with(binding) {\n      rootItemsLayout.forEach {\n        if ((it as MyShowsFilterItemView).isChecked) {\n          viewModel.applySectionType(it.sectionType)\n        }\n      }\n    }\n  }\n\n  private fun render(uiState: MyShowsFiltersUiState) {\n    with(uiState) {\n      sectionType?.let { sectionType ->\n        binding.rootItemsLayout.forEach {\n          (it as MyShowsFilterItemView).bind(\n            sectionType = it.sectionType,\n            isChecked = it.sectionType == sectionType\n          )\n        }\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is ApplyFilters -> {\n        setFragmentResult(REQUEST_MY_SHOWS_FILTERS, Bundle.EMPTY)\n        closeSheet()\n      }\n      is CloseFilters -> closeSheet()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/filters/MyShowsFiltersUiEvent.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_my_shows.myshows.filters\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\ninternal sealed class MyShowsFiltersUiEvent<T>(action: T) : Event<T>(action) {\n  object ApplyFilters : MyShowsFiltersUiEvent<Unit>(Unit)\n  object CloseFilters : MyShowsFiltersUiEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/filters/MyShowsFiltersUiState.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.filters\n\nimport com.michaldrabik.ui_model.MyShowsSection\n\ninternal data class MyShowsFiltersUiState(\n  val sectionType: MyShowsSection? = null,\n  val isLoading: Boolean? = null,\n)\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/filters/MyShowsFiltersViewModel.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.filters\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.MyShowsSection\nimport com.michaldrabik.ui_my_shows.myshows.filters.MyShowsFiltersUiEvent.ApplyFilters\nimport com.michaldrabik.ui_my_shows.myshows.filters.MyShowsFiltersUiEvent.CloseFilters\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\ninternal class MyShowsFiltersViewModel @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val sectionState = MutableStateFlow<MyShowsSection?>(null)\n  private val loadingState = MutableStateFlow(false)\n\n  init {\n    viewModelScope.launch {\n      sectionState.value = settingsRepository.filters.myShowsType\n    }\n  }\n\n  fun applySectionType(sectionType: MyShowsSection) {\n    viewModelScope.launch {\n      if (sectionType == sectionState.value) {\n        eventChannel.send(CloseFilters)\n        return@launch\n      }\n      settingsRepository.filters.myShowsType = sectionType\n      eventChannel.send(ApplyFilters)\n    }\n  }\n\n  val uiState = combine(\n    sectionState,\n    loadingState,\n  ) { s1, s2 ->\n    MyShowsFiltersUiState(\n      sectionType = s1,\n      isLoading = s2,\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = MyShowsFiltersUiState()\n  )\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/filters/views/MyShowsFilterItemView.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.filters.views\n\nimport android.content.Context\nimport android.graphics.Typeface\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport com.michaldrabik.ui_base.utilities.extensions.addRipple\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.MyShowsSection\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.databinding.ViewMyShowsTypeFilterItemBinding\n\nclass MyShowsFilterItemView : ConstraintLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewMyShowsTypeFilterItemBinding.inflate(LayoutInflater.from(context), this)\n\n  var onItemClickListener: ((MyShowsSection) -> Unit)? = null\n\n  lateinit var sectionType: MyShowsSection\n  var isChecked: Boolean = false\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    val paddingHorizontal = context.dimenToPx(R.dimen.spaceNormal)\n    setPadding(paddingHorizontal, 0, paddingHorizontal, 0)\n    addRipple()\n    onClick(safe = false) { onItemClickListener?.invoke(sectionType) }\n  }\n\n  fun bind(\n    sectionType: MyShowsSection,\n    isChecked: Boolean,\n  ) {\n    this.sectionType = sectionType\n    this.isChecked = isChecked\n\n    binding.viewMyShowsTypeItemBadge.visibleIf(isChecked)\n\n    with(binding.viewMyShowsTypeItemTitle) {\n      val color = if (isChecked) android.R.attr.textColorPrimary else android.R.attr.textColorSecondary\n      val typeface = if (isChecked) Typeface.DEFAULT_BOLD else Typeface.DEFAULT\n      setTextColor(context.colorFromAttr(color))\n      setTypeface(typeface)\n      text = context.getString(sectionType.displayString)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/helpers/MyShowsItemSorter.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.helpers\n\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_ADDED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.RECENTLY_WATCHED\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SortType.ASCENDING\nimport com.michaldrabik.ui_model.SortType.DESCENDING\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass MyShowsItemSorter @Inject constructor() {\n\n  fun sort(sortOrder: SortOrder, sortType: SortType) = when (sortType) {\n    ASCENDING -> sortAscending(sortOrder)\n    DESCENDING -> sortDescending(sortOrder)\n  }\n\n  private fun sortAscending(sortOrder: SortOrder): Comparator<MyShowsItem> =\n    when (sortOrder) {\n      NAME -> compareBy { getTitle(it) }\n      RATING -> compareBy { it.show.rating }\n      USER_RATING ->\n        compareByDescending<MyShowsItem> { it.userRating != null }\n          .thenBy { it.userRating }\n          .thenBy { getTitle(it) }\n      DATE_ADDED -> compareBy { it.show.createdAt }\n      RECENTLY_WATCHED -> compareBy { it.show.updatedAt }\n      NEWEST -> compareBy<MyShowsItem> { it.show.year }.thenBy { it.show.firstAired }\n      else -> throw IllegalStateException(\"Invalid sort order\")\n    }\n\n  private fun sortDescending(sortOrder: SortOrder): Comparator<MyShowsItem> =\n    when (sortOrder) {\n      NAME -> compareByDescending { getTitle(it) }\n      RATING -> compareByDescending { it.show.rating }\n      USER_RATING ->\n        compareByDescending<MyShowsItem> { it.userRating != null }\n          .thenByDescending { it.userRating }\n          .thenBy { getTitle(it) }\n      DATE_ADDED -> compareByDescending { it.show.createdAt }\n      RECENTLY_WATCHED -> compareByDescending { it.show.updatedAt }\n      NEWEST -> compareByDescending<MyShowsItem> { it.show.year }.thenByDescending { it.show.firstAired }\n      else -> throw IllegalStateException(\"Invalid sort order\")\n    }\n\n  private fun getTitle(item: MyShowsItem): String {\n    val translatedTitle =\n      if (item.translation?.hasTitle == true) item.translation.title\n      else item.show.titleNoThe\n    return translatedTitle.uppercase()\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/recycler/MyShowsAdapter.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.recycler\n\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseAdapter\nimport com.michaldrabik.ui_base.common.ListItem\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_model.MyShowsSection\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem.Type\nimport com.michaldrabik.ui_my_shows.myshows.views.MyShowAllCompactView\nimport com.michaldrabik.ui_my_shows.myshows.views.MyShowAllView\nimport com.michaldrabik.ui_my_shows.myshows.views.MyShowGridTitleView\nimport com.michaldrabik.ui_my_shows.myshows.views.MyShowGridView\nimport com.michaldrabik.ui_my_shows.myshows.views.MyShowHeaderView\nimport com.michaldrabik.ui_my_shows.myshows.views.MyShowsRecentsView\n\nclass MyShowsAdapter(\n  private val itemClickListener: (ListItem) -> Unit,\n  private val itemLongClickListener: (ListItem) -> Unit,\n  private val onSortOrderClickListener: (MyShowsSection, SortOrder, SortType) -> Unit,\n  private val onListViewModeClickListener: () -> Unit,\n  private val onNetworksClickListener: () -> Unit,\n  private val onGenresClickListener: () -> Unit,\n  private val onTypeClickListener: () -> Unit,\n  private val missingImageListener: (ListItem, Boolean) -> Unit,\n  private val missingTranslationListener: (ListItem) -> Unit,\n  listChangeListener: () -> Unit,\n) : BaseAdapter<MyShowsItem>(\n  listChangeListener = listChangeListener\n) {\n\n  companion object {\n    private const val VIEW_TYPE_HEADER = 1\n    private const val VIEW_TYPE_SHOW_ITEM = 2\n    private const val VIEW_TYPE_RECENTS_SECTION = 3\n  }\n\n  override val asyncDiffer = AsyncListDiffer(this, MyShowsItemDiffCallback())\n\n  var listViewMode: ListViewMode = LIST_NORMAL\n    set(value) {\n      field = value\n      notifyItemRangeChanged(0, asyncDiffer.currentList.size)\n    }\n\n  fun setItems(newItems: List<MyShowsItem>, notifyChangeList: List<Type>?) {\n    val notifyChange = notifyChangeList?.contains(Type.ALL_SHOWS_ITEM) == true\n    super.setItems(newItems, notifyChange)\n  }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    when (viewType) {\n      VIEW_TYPE_HEADER -> BaseViewHolder(MyShowHeaderView(parent.context))\n      VIEW_TYPE_RECENTS_SECTION -> BaseViewHolder(MyShowsRecentsView(parent.context))\n      VIEW_TYPE_SHOW_ITEM -> BaseViewHolder(\n        when (listViewMode) {\n          LIST_NORMAL -> MyShowAllView(parent.context)\n          LIST_COMPACT -> MyShowAllCompactView(parent.context)\n          GRID -> MyShowGridView(parent.context)\n          GRID_TITLE -> MyShowGridTitleView(parent.context)\n        }.apply {\n          itemClickListener = this@MyShowsAdapter.itemClickListener\n          itemLongClickListener = this@MyShowsAdapter.itemLongClickListener\n          missingImageListener = this@MyShowsAdapter.missingImageListener\n          missingTranslationListener = this@MyShowsAdapter.missingTranslationListener\n        }\n      )\n      else -> throw IllegalStateException()\n    }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    when (holder.itemViewType) {\n      VIEW_TYPE_HEADER -> (holder.itemView as MyShowHeaderView).bind(\n        item = item.header!!,\n        viewMode = listViewMode,\n        typeClickListener = onTypeClickListener,\n        sortClickListener = onSortOrderClickListener,\n        networksClickListener = onNetworksClickListener,\n        genresClickListener = onGenresClickListener,\n        listModeClickListener = onListViewModeClickListener\n      )\n      VIEW_TYPE_RECENTS_SECTION -> (holder.itemView as MyShowsRecentsView).bind(\n        item.recentsSection!!,\n        itemClickListener,\n        itemLongClickListener\n      )\n      VIEW_TYPE_SHOW_ITEM -> {\n        when (listViewMode) {\n          LIST_NORMAL -> (holder.itemView as MyShowAllView).bind(item)\n          LIST_COMPACT -> (holder.itemView as MyShowAllCompactView).bind(item)\n          GRID -> (holder.itemView as MyShowGridView).bind(item)\n          GRID_TITLE -> (holder.itemView as MyShowGridTitleView).bind(item)\n        }\n      }\n    }\n  }\n\n  override fun getItemViewType(position: Int) =\n    when (asyncDiffer.currentList[position].type) {\n      Type.ALL_SHOWS_HEADER -> VIEW_TYPE_HEADER\n      Type.ALL_SHOWS_ITEM -> VIEW_TYPE_SHOW_ITEM\n      Type.RECENT_SHOWS -> VIEW_TYPE_RECENTS_SECTION\n    }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/recycler/MyShowsItem.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.recycler\n\nimport com.michaldrabik.ui_base.common.ListItem\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport com.michaldrabik.ui_model.MyShowsSection\nimport com.michaldrabik.ui_model.Network\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.Translation\n\ndata class MyShowsItem(\n  val type: Type,\n  val header: Header?,\n  val recentsSection: RecentsSection?,\n  override val show: Show,\n  override val image: Image,\n  override val isLoading: Boolean,\n  val spoilers: Spoilers,\n  val translation: Translation? = null,\n  val userRating: Int? = null,\n  val sortOrder: SortOrder? = null,\n) : ListItem {\n\n  enum class Type {\n    RECENT_SHOWS,\n    ALL_SHOWS_HEADER,\n    ALL_SHOWS_ITEM,\n  }\n\n  data class Header(\n    val section: MyShowsSection,\n    val itemCount: Int,\n    val sortOrder: Pair<SortOrder, SortType>?,\n    val networks: List<Network>?,\n    val genres: List<Genre>?,\n  )\n\n  data class RecentsSection(\n    val items: List<MyShowsItem>,\n  )\n\n  data class Spoilers(\n    val isSpoilerHidden: Boolean,\n    val isSpoilerRatingsHidden: Boolean,\n    val isSpoilerTapToReveal: Boolean,\n  )\n\n  companion object {\n    fun createHeader(\n      section: MyShowsSection,\n      itemCount: Int,\n      sortOrder: Pair<SortOrder, SortType>?,\n      networks: List<Network>?,\n      genres: List<Genre>?\n    ) = MyShowsItem(\n      type = Type.ALL_SHOWS_HEADER,\n      header = Header(section, itemCount, sortOrder, networks, genres),\n      recentsSection = null,\n      show = Show.EMPTY,\n      image = Image.createUnavailable(POSTER),\n      isLoading = false,\n      spoilers = Spoilers(\n        isSpoilerHidden = false,\n        isSpoilerRatingsHidden = false,\n        isSpoilerTapToReveal = false\n      )\n    )\n\n    fun createRecentsSection(\n      shows: List<MyShowsItem>,\n    ) = MyShowsItem(\n      type = Type.RECENT_SHOWS,\n      header = null,\n      recentsSection = RecentsSection(shows),\n      show = Show.EMPTY,\n      image = Image.createUnavailable(POSTER),\n      isLoading = false,\n      spoilers = Spoilers(\n        isSpoilerHidden = false,\n        isSpoilerRatingsHidden = false,\n        isSpoilerTapToReveal = false\n      )\n    )\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/recycler/MyShowsItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem.Type.ALL_SHOWS_HEADER\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem.Type.RECENT_SHOWS\n\nclass MyShowsItemDiffCallback : DiffUtil.ItemCallback<MyShowsItem>() {\n\n  override fun areItemsTheSame(oldItem: MyShowsItem, newItem: MyShowsItem) =\n    when (oldItem.type) {\n      RECENT_SHOWS -> true\n      else -> oldItem.type == newItem.type && oldItem.show.ids.trakt == newItem.show.ids.trakt\n    }\n\n  override fun areContentsTheSame(oldItem: MyShowsItem, newItem: MyShowsItem) =\n    when (oldItem.type) {\n      ALL_SHOWS_HEADER -> oldItem.header == newItem.header\n      RECENT_SHOWS -> oldItem.recentsSection == newItem.recentsSection\n      else ->\n        oldItem.image == newItem.image &&\n          oldItem.isLoading == newItem.isLoading &&\n          oldItem.translation == newItem.translation &&\n          oldItem.userRating == newItem.userRating &&\n          oldItem.spoilers == newItem.spoilers &&\n          oldItem.sortOrder == newItem.sortOrder\n    }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/recycler/MyShowsLayoutManagerProvider.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.recycler\n\nimport android.content.Context\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN_TABLET\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\n\ninternal object MyShowsLayoutManagerProvider {\n\n  fun provideLayoutManger(\n    context: Context,\n    viewMode: ListViewMode,\n    gridSpanSize: Int,\n  ): RecyclerView.LayoutManager {\n    return if (context.isTablet()) {\n      provideTabletLayout(context, viewMode, gridSpanSize)\n    } else {\n      providePhoneLayout(context, viewMode)\n    }\n  }\n\n  private fun providePhoneLayout(\n    context: Context,\n    viewMode: ListViewMode,\n  ): RecyclerView.LayoutManager {\n    return when (viewMode) {\n      LIST_NORMAL, LIST_COMPACT -> LinearLayoutManager(context, VERTICAL, false)\n      GRID, GRID_TITLE -> GridLayoutManager(context, LISTS_GRID_SPAN)\n    }\n  }\n\n  private fun provideTabletLayout(\n    context: Context,\n    viewMode: ListViewMode,\n    gridSpanSize: Int,\n  ): RecyclerView.LayoutManager {\n    return when (viewMode) {\n      LIST_NORMAL, LIST_COMPACT -> GridLayoutManager(context, gridSpanSize)\n      GRID, GRID_TITLE -> GridLayoutManager(context, LISTS_GRID_SPAN_TABLET)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/views/MyShowAllCompactView.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.setOutboundRipple\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.databinding.ViewCollectionShowCompactBinding\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem\nimport java.util.Locale.ENGLISH\n\n@SuppressLint(\"SetTextI18n\")\nclass MyShowAllCompactView : ShowView<MyShowsItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionShowCompactBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      collectionShowRoot.onClick { itemClickListener?.invoke(item) }\n      collectionShowRoot.onLongClick { itemLongClickListener?.invoke(item) }\n      collectionShowRoot.setOutboundRipple(\n        size = (context.dimenToPx(R.dimen.collectionItemRippleSpaceSmall)).toFloat(),\n        corner = context.dimenToPx(R.dimen.mediaTileCorner).toFloat()\n      )\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionShowImage\n  override val placeholderView: ImageView = binding.collectionShowPlaceholder\n\n  private lateinit var item: MyShowsItem\n\n  override fun bind(item: MyShowsItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      collectionShowProgress.visibleIf(item.isLoading)\n      collectionShowTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.show.title\n        else item.translation?.title\n\n      collectionShowNetwork.text =\n        if (item.show.year > 0) context.getString(R.string.textNetwork, item.show.network, item.show.year.toString())\n        else String.format(\"%s\", item.show.network)\n\n      bindRating(item)\n      collectionShowNetwork.visibleIf(item.show.network.isNotBlank())\n\n      item.userRating?.let {\n        collectionShowUserStarIcon.visible()\n        collectionShowUserRating.visible()\n        collectionShowUserRating.text = String.format(ENGLISH, \"%d\", it)\n      }\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindRating(item: MyShowsItem) {\n    var rating = String.format(ENGLISH, \"%.1f\", item.show.rating)\n    with(binding) {\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionShowRating.tag = rating\n        rating = Config.SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          with(collectionShowRating) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      }\n\n      collectionShowRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionShowTitle.text = \"\"\n      collectionShowNetwork.text = \"\"\n      collectionShowRating.text = \"\"\n      collectionShowPlaceholder.gone()\n      collectionShowUserStarIcon.gone()\n      collectionShowUserRating.gone()\n      Glide.with(this@MyShowAllCompactView).clear(collectionShowImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/views/MyShowAllView.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.setOutboundRipple\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.databinding.ViewCollectionShowBinding\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem\nimport java.util.Locale.ENGLISH\n\n@SuppressLint(\"SetTextI18n\")\nclass MyShowAllView : ShowView<MyShowsItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionShowBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      collectionShowRoot.onClick { itemClickListener?.invoke(item) }\n      collectionShowRoot.onLongClick { itemLongClickListener?.invoke(item) }\n      collectionShowRoot.setOutboundRipple(\n        size = (context.dimenToPx(R.dimen.collectionItemRippleSpace)).toFloat(),\n        corner = context.dimenToPx(R.dimen.mediaTileCorner).toFloat()\n      )\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionShowImage\n  override val placeholderView: ImageView = binding.collectionShowPlaceholder\n\n  private lateinit var item: MyShowsItem\n\n  override fun bind(item: MyShowsItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      collectionShowProgress.visibleIf(item.isLoading)\n      collectionShowTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.show.title\n        else item.translation?.title\n\n      bindDescription(item)\n      bindRating(item)\n\n      collectionShowNetwork.text =\n        if (item.show.year > 0) context.getString(R.string.textNetwork, item.show.network, item.show.year.toString())\n        else String.format(\"%s\", item.show.network)\n\n      collectionShowNetwork.visibleIf(item.show.network.isNotBlank())\n\n      item.userRating?.let {\n        collectionShowUserStarIcon.visible()\n        collectionShowUserRating.visible()\n        collectionShowUserRating.text = String.format(ENGLISH, \"%d\", it)\n      }\n    }\n    loadImage(item)\n  }\n\n  private fun bindDescription(item: MyShowsItem) {\n    with(binding) {\n      var description =\n        if (item.translation?.overview.isNullOrBlank()) item.show.overview\n        else item.translation?.overview\n\n      if (item.spoilers.isSpoilerHidden) {\n        collectionShowDescription.tag = description.toString()\n        description = SPOILERS_REGEX.replace(description.toString(), SPOILERS_HIDE_SYMBOL)\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          collectionShowDescription.onClick { view ->\n            view.tag?.let { collectionShowDescription.text = it.toString() }\n            view.isClickable = false\n          }\n        }\n      }\n\n      collectionShowDescription.text = description\n      collectionShowDescription.visibleIf(item.show.overview.isNotBlank())\n    }\n  }\n\n  private fun bindRating(item: MyShowsItem) {\n    with(binding) {\n      var rating = String.format(ENGLISH, \"%.1f\", item.show.rating)\n\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionShowRating.tag = rating\n        rating = Config.SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          collectionShowRating.onClick { view ->\n            view.tag?.let { collectionShowRating.text = it.toString() }\n            view.isClickable = false\n          }\n        }\n      }\n\n      collectionShowRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionShowTitle.text = \"\"\n      collectionShowDescription.text = \"\"\n      collectionShowNetwork.text = \"\"\n      collectionShowRating.text = \"\"\n      collectionShowPlaceholder.gone()\n      collectionShowUserStarIcon.gone()\n      collectionShowUserRating.gone()\n      Glide.with(this@MyShowAllView).clear(collectionShowImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/views/MyShowFanartView.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions\nimport com.michaldrabik.common.Config.IMAGE_FADE_DURATION_MS\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageStatus\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.databinding.ViewMyShowsFanartBinding\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem\n\nclass MyShowFanartView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewMyShowsFanartBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    setBackgroundResource(R.drawable.bg_media_view_elevation)\n    elevation = context.dimenToPx(R.dimen.elevationSmall).toFloat()\n  }\n\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.myShowsFanartCorner) }\n\n  fun bind(\n    showItem: MyShowsItem,\n    clickListener: (MyShowsItem) -> Unit,\n    longClickListener: (MyShowsItem, View) -> Unit,\n  ) {\n    clear()\n    with(binding) {\n      myShowFanartTitle.visible()\n      myShowFanartTitle.text =\n        if (showItem.translation?.title.isNullOrBlank()) showItem.show.title\n        else showItem.translation?.title\n    }\n    onClick { clickListener(showItem) }\n    onLongClick { longClickListener(showItem, it) }\n    loadImage(showItem.image)\n  }\n\n  private fun loadImage(image: Image) {\n    with(binding) {\n      if (image.status != ImageStatus.AVAILABLE) {\n        myShowFanartPlaceholder.visible()\n        myShowFanartRoot.setBackgroundResource(R.drawable.bg_media_view_placeholder)\n        return\n      }\n      Glide.with(this@MyShowFanartView)\n        .load(image.fullFileUrl)\n        .transform(CenterCrop(), RoundedCorners(cornerRadius))\n        .transition(DrawableTransitionOptions.withCrossFade(IMAGE_FADE_DURATION_MS))\n        .withFailListener {\n          myShowFanartPlaceholder.visible()\n          myShowFanartImage.gone()\n        }\n        .into(myShowFanartImage)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      myShowFanartPlaceholder.gone()\n      myShowFanartTitle.text = \"\"\n      myShowFanartRoot.setBackgroundResource(0)\n      Glide.with(this@MyShowFanartView).clear(myShowFanartImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/views/MyShowGridTitleView.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.R\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_my_shows.databinding.ViewCollectionShowGridTitleBinding\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem\nimport java.util.Locale\n\n@SuppressLint(\"SetTextI18n\")\nclass MyShowGridTitleView : ShowView<MyShowsItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionShowGridTitleBinding.inflate(LayoutInflater.from(context), this)\n\n  private val width by lazy {\n    val span = if (context.isTablet()) Config.LISTS_GRID_SPAN_TABLET else Config.LISTS_GRID_SPAN\n    val itemSpacing = context.dimenToPx(R.dimen.spaceSmall)\n    val screenMargin = context.dimenToPx(R.dimen.screenMarginHorizontal)\n    val screenWidth = screenWidth().toFloat()\n    ((screenWidth - (screenMargin * 2.0)) - ((span - 1) * itemSpacing)) / span\n  }\n  private val height by lazy { width * 1.7305 }\n\n  init {\n    layoutParams = LayoutParams(width.toInt(), height.toInt())\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      collectionShowRoot.onClick { itemClickListener?.invoke(item) }\n      collectionShowRoot.onLongClick { itemLongClickListener?.invoke(item) }\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionShowImage\n  override val placeholderView: ImageView = binding.collectionShowPlaceholder\n\n  private lateinit var item: MyShowsItem\n\n  override fun bind(item: MyShowsItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      collectionShowProgress.visibleIf(item.isLoading)\n\n      collectionShowTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.show.title\n        else item.translation?.title\n\n      if (item.sortOrder == SortOrder.RATING) {\n        bindRating(item)\n      } else if (item.sortOrder == SortOrder.USER_RATING && item.userRating != null) {\n        collectionShowRating.visible()\n        collectionShowRating.text = String.format(Locale.ENGLISH, \"%d\", item.userRating)\n      } else {\n        collectionShowRating.gone()\n      }\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindRating(item: MyShowsItem) {\n    with(binding) {\n      var rating = String.format(Locale.ENGLISH, \"%.1f\", item.show.rating)\n\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionShowRating.tag = rating\n        rating = Config.SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          with(collectionShowRating) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      }\n\n      collectionShowRating.visible()\n      collectionShowRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionShowTitle.text = \"\"\n      collectionShowPlaceholder.gone()\n      Glide.with(this@MyShowGridTitleView).clear(collectionShowImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/views/MyShowGridView.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.databinding.ViewCollectionShowGridBinding\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem\nimport java.util.Locale.ENGLISH\n\n@SuppressLint(\"SetTextI18n\")\nclass MyShowGridView : ShowView<MyShowsItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCollectionShowGridBinding.inflate(LayoutInflater.from(context), this)\n\n  private val width by lazy {\n    val span = if (context.isTablet()) Config.LISTS_GRID_SPAN_TABLET else Config.LISTS_GRID_SPAN\n    val itemSpacing = context.dimenToPx(R.dimen.spaceSmall)\n    val screenMargin = context.dimenToPx(R.dimen.screenMarginHorizontal)\n    val screenWidth = screenWidth().toFloat()\n    ((screenWidth - (screenMargin * 2.0)) - ((span - 1) * itemSpacing)) / span\n  }\n  private val height by lazy { width * ASPECT_RATIO }\n\n  init {\n    layoutParams = LayoutParams(width.toInt(), height.toInt())\n\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      collectionShowRoot.onClick { itemClickListener?.invoke(item) }\n      collectionShowRoot.onLongClick { itemLongClickListener?.invoke(item) }\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = binding.collectionShowImage\n  override val placeholderView: ImageView = binding.collectionShowPlaceholder\n\n  private lateinit var item: MyShowsItem\n\n  override fun bind(item: MyShowsItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      collectionShowProgress.visibleIf(item.isLoading)\n\n      if (item.sortOrder == RATING) {\n        bindRating(item)\n      } else if (item.sortOrder == USER_RATING && item.userRating != null) {\n        collectionShowRating.visible()\n        collectionShowRating.text = String.format(ENGLISH, \"%d\", item.userRating)\n      } else {\n        collectionShowRating.gone()\n      }\n    }\n\n    loadImage(item)\n  }\n\n  private fun bindRating(item: MyShowsItem) {\n    with(binding) {\n      var rating = String.format(ENGLISH, \"%.1f\", item.show.rating)\n\n      if (item.spoilers.isSpoilerRatingsHidden) {\n        collectionShowRating.tag = rating\n        rating = Config.SPOILERS_RATINGS_HIDE_SYMBOL\n\n        if (item.spoilers.isSpoilerTapToReveal) {\n          with(collectionShowRating) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      }\n\n      collectionShowRating.visible()\n      collectionShowRating.text = rating\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      collectionShowPlaceholder.gone()\n      Glide.with(this@MyShowGridView).clear(collectionShowImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/views/MyShowHeaderView.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport androidx.core.content.ContextCompat\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.MyShowsSection\nimport com.michaldrabik.ui_model.MyShowsSection.ALL\nimport com.michaldrabik.ui_model.MyShowsSection.RECENTS\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.databinding.ViewMyShowsHeaderBinding\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem\nimport java.util.Locale.ENGLISH\n\nclass MyShowHeaderView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewMyShowsHeaderBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    clipChildren = false\n    clipToPadding = false\n  }\n\n  fun bind(\n    item: MyShowsItem.Header,\n    viewMode: ListViewMode,\n    typeClickListener: (() -> Unit)?,\n    sortClickListener: ((MyShowsSection, SortOrder, SortType) -> Unit)?,\n    networksClickListener: (() -> Unit)?,\n    genresClickListener: (() -> Unit)?,\n    listModeClickListener: (() -> Unit)?,\n  ) {\n    bindLabel(item)\n    with(binding) {\n      myShowsFilterChipsScroll.visibleIf(item.section != RECENTS)\n      myShowsSortChip.visibleIf(item.sortOrder != null)\n      myShowsNetworksChip.visibleIf(item.networks != null)\n      myShowsGenresChip.visibleIf(item.genres != null)\n\n      with(myShowsTypeChip) {\n        isSelected = item.section != ALL\n        text = context.getString(item.section.displayString)\n        visibleIf(item.section != RECENTS)\n        onClick { typeClickListener?.invoke() }\n      }\n\n      with(myShowsSortListViewChip) {\n        when (viewMode) {\n          LIST_NORMAL, LIST_COMPACT -> setChipIconResource(R.drawable.ic_view_list)\n          GRID, GRID_TITLE -> setChipIconResource(R.drawable.ic_view_grid)\n        }\n        onClick { listModeClickListener?.invoke() }\n      }\n\n      item.sortOrder?.let { sortOrder ->\n        myShowsSortChip.text = context.getString(sortOrder.first.displayString)\n        myShowsSortChip.onClick {\n          sortClickListener?.invoke(item.section, sortOrder.first, sortOrder.second)\n        }\n        val sortIcon = when (sortOrder.second) {\n          SortType.ASCENDING -> R.drawable.ic_arrow_alt_up\n          SortType.DESCENDING -> R.drawable.ic_arrow_alt_down\n        }\n        myShowsSortChip.closeIcon = ContextCompat.getDrawable(context, sortIcon)\n      }\n\n      item.networks?.let { networks ->\n        myShowsNetworksChip.isSelected = networks.isNotEmpty()\n        myShowsNetworksChip.onClick { networksClickListener?.invoke() }\n        myShowsNetworksChip.text = when {\n          networks.isEmpty() -> context.getString(R.string.textNetworks).filter { it.isLetter() }\n          networks.size == 1 -> networks[0].channels.first()\n          else -> throw IllegalStateException()\n        }\n      }\n\n      item.genres?.let { genres ->\n        myShowsGenresChip.isSelected = genres.isNotEmpty()\n        myShowsGenresChip.onClick { genresClickListener?.invoke() }\n        myShowsGenresChip.text = when {\n          genres.isEmpty() -> context.getString(R.string.textGenres).filter { it.isLetter() }\n          genres.size == 1 -> context.getString(genres.first().displayName)\n          genres.size == 2 -> \"${context.getString(genres[0].displayName)}, ${context.getString(genres[1].displayName)}\"\n          else -> \"${context.getString(genres[0].displayName)}, ${context.getString(genres[1].displayName)} + ${genres.size - 2}\"\n        }\n      }\n    }\n  }\n\n  private fun bindLabel(item: MyShowsItem.Header) {\n    val headerLabel = context.getString(item.section.displayString)\n    binding.myShowsHeaderLabel.text = when (item.section) {\n      RECENTS -> headerLabel\n      else -> String.format(ENGLISH, \"%s (%d)\", headerLabel, item.itemCount)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/myshows/views/MyShowsRecentsView.kt",
    "content": "package com.michaldrabik.ui_my_shows.myshows.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport android.widget.GridLayout\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.databinding.ViewMyShowsRecentsBinding\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem\n\nclass MyShowsRecentsView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewMyShowsRecentsBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    clipChildren = false\n  }\n\n  private val itemHeight by lazy { context.dimenToPx(R.dimen.myShowsFanartHeight) }\n  private val itemMargin by lazy { context.dimenToPx(R.dimen.spaceTiny) }\n  private val itemWidth by lazy {\n    val space = context.dimenToPx(R.dimen.screenMarginHorizontal) * 2\n    ((screenWidth() - space) / 2) - itemMargin\n  }\n\n  fun bind(\n    item: MyShowsItem.RecentsSection,\n    itemClickListener: ((MyShowsItem) -> Unit)?,\n    itemLongClickListener: ((MyShowsItem) -> Unit)?,\n  ) {\n    binding.myShowsRecentsContainer.removeAllViews()\n\n    val clickListener: (MyShowsItem) -> Unit = { itemClickListener?.invoke(it) }\n    val longClickListener: (MyShowsItem, View) -> Unit = { i, _ -> itemLongClickListener?.invoke(i) }\n\n    item.items.forEachIndexed { index, showItem ->\n      val view = MyShowFanartView(context).apply {\n        layoutParams = LayoutParams(0, MATCH_PARENT)\n        bind(showItem, clickListener, longClickListener)\n      }\n      val layoutParams = GridLayout.LayoutParams().apply {\n        width = itemWidth\n        height = itemHeight\n        columnSpec = GridLayout.spec(index % 2, 1F)\n        if (index % 2 == 0) {\n          setMargins(0, itemMargin, itemMargin, itemMargin)\n        } else {\n          setMargins(itemMargin, itemMargin, 0, itemMargin)\n        }\n      }\n      binding.myShowsRecentsContainer.addView(view, layoutParams)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/utilities/MyShowsGridItemDecoration.kt",
    "content": "package com.michaldrabik.ui_my_shows.utilities\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.annotation.DimenRes\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ItemDecoration\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsAdapter\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem.Type\nimport com.michaldrabik.ui_my_shows.myshows.views.MyShowGridTitleView\nimport com.michaldrabik.ui_my_shows.myshows.views.MyShowGridView\n\nclass MyShowsGridItemDecoration : ItemDecoration {\n\n  private var spacing: Int\n  private var halfSpacing: Int\n\n  constructor(\n    context: Context,\n    @DimenRes spacingDimen: Int,\n  ) {\n    this.spacing = context.resources.getDimensionPixelSize(spacingDimen)\n    this.halfSpacing = spacing / 2\n  }\n\n  override fun getItemOffsets(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n    state: RecyclerView.State,\n  ) {\n    if (parent.layoutManager !is GridLayoutManager) return\n\n    val totalSpan = (parent.layoutManager as GridLayoutManager).spanCount\n\n    if (view is MyShowGridView || view is MyShowGridTitleView) {\n      outRect.top = halfSpacing\n      outRect.bottom = halfSpacing\n\n      val nonMyShowItemCount = (parent.adapter as MyShowsAdapter)\n        .getItems()\n        .count { it.type != Type.ALL_SHOWS_ITEM }\n\n      val position = parent.getChildAdapterPosition(view) - nonMyShowItemCount\n      val column = position % totalSpan\n\n      outRect.left = spacing * column / totalSpan\n      outRect.right = spacing * ((totalSpan - 1) - column) / totalSpan\n    }\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/utilities/MyShowsListItemDecoration.kt",
    "content": "package com.michaldrabik.ui_my_shows.utilities\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.annotation.DimenRes\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ItemDecoration\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsAdapter\nimport com.michaldrabik.ui_my_shows.myshows.recycler.MyShowsItem\nimport com.michaldrabik.ui_my_shows.myshows.views.MyShowAllCompactView\nimport com.michaldrabik.ui_my_shows.myshows.views.MyShowAllView\n\nclass MyShowsListItemDecoration : ItemDecoration {\n\n  private var spacing: Int\n  private var halfSpacing: Int\n  private val isTablet: Boolean\n\n  constructor(\n    context: Context,\n    @DimenRes spacingDimen: Int,\n  ) {\n    this.spacing = context.resources.getDimensionPixelSize(spacingDimen)\n    this.halfSpacing = spacing / 2\n    this.isTablet = context.isTablet()\n  }\n\n  override fun getItemOffsets(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n    state: RecyclerView.State,\n  ) {\n    if (view !is MyShowAllView && view !is MyShowAllCompactView) {\n      return\n    }\n    if (!isTablet && (parent.layoutManager is LinearLayoutManager)) {\n      getItemOffsetsPhone(outRect, view)\n      return\n    }\n    if (isTablet && (parent.layoutManager is GridLayoutManager)) {\n      getItemOffsetsTablet(outRect, view, parent)\n      return\n    }\n  }\n\n  private fun getItemOffsetsTablet(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n  ) {\n    if (view is MyShowAllView) {\n      outRect.top = spacing\n      outRect.bottom = spacing\n    } else if (view is MyShowAllCompactView) {\n      outRect.top = halfSpacing\n      outRect.bottom = halfSpacing\n    }\n\n    val totalSpan = (parent.layoutManager as GridLayoutManager).spanCount\n    val column = getPosition(parent, view) % totalSpan\n\n    outRect.left = (spacing * 2) * column / totalSpan\n    outRect.right = (spacing * 2) * ((totalSpan - 1) - column) / totalSpan\n  }\n\n  private fun getItemOffsetsPhone(outRect: Rect, view: View) {\n    if (view is MyShowAllView) {\n      outRect.top = spacing\n      outRect.bottom = spacing\n    } else if (view is MyShowAllCompactView) {\n      outRect.top = halfSpacing\n      outRect.bottom = halfSpacing\n    }\n    outRect.left = 0\n    outRect.right = 0\n  }\n\n  private fun getPosition(\n    parent: RecyclerView,\n    view: View\n  ): Int {\n    val nonMyShowItemCount = (parent.adapter as MyShowsAdapter)\n      .getItems()\n      .count { it.type != MyShowsItem.Type.ALL_SHOWS_ITEM }\n\n    return parent.getChildAdapterPosition(view) - nonMyShowItemCount\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/watchlist/WatchlistFragment.kt",
    "content": "package com.michaldrabik.ui_my_shows.watchlist\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.postDelayed\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN\nimport com.michaldrabik.common.Config.LISTS_GRID_SPAN_TABLET\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID\nimport com.michaldrabik.ui_base.common.ListViewMode.GRID_TITLE\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_COMPACT\nimport com.michaldrabik.ui_base.common.ListViewMode.LIST_NORMAL\nimport com.michaldrabik.ui_base.common.OnScrollResetListener\nimport com.michaldrabik.ui_base.common.OnSearchClickListener\nimport com.michaldrabik.ui_base.common.sheets.sort_order.SortOrderBottomSheet\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.withSpanSizeLookup\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_ADDED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_shows.R\nimport com.michaldrabik.ui_my_shows.common.filters.CollectionFiltersOrigin.WATCHLIST_SHOWS\nimport com.michaldrabik.ui_my_shows.common.filters.genre.CollectionFiltersGenreBottomSheet\nimport com.michaldrabik.ui_my_shows.common.filters.genre.CollectionFiltersGenreBottomSheet.Companion.REQUEST_COLLECTION_FILTERS_GENRE\nimport com.michaldrabik.ui_my_shows.common.filters.network.CollectionFiltersNetworkBottomSheet\nimport com.michaldrabik.ui_my_shows.common.filters.network.CollectionFiltersNetworkBottomSheet.Companion.REQUEST_COLLECTION_FILTERS_NETWORK\nimport com.michaldrabik.ui_my_shows.common.layout.CollectionShowGridItemDecoration\nimport com.michaldrabik.ui_my_shows.common.layout.CollectionShowLayoutManagerProvider\nimport com.michaldrabik.ui_my_shows.common.layout.CollectionShowListItemDecoration\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionAdapter\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem.FiltersItem\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem.ShowItem\nimport com.michaldrabik.ui_my_shows.databinding.FragmentWatchlistBinding\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsFragment\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsUiEvent.OpenPremium\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsViewModel\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_SORT_ORDER\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_SORT_TYPE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_SORT_ORDER\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass WatchlistFragment :\n  BaseFragment<WatchlistViewModel>(R.layout.fragment_watchlist),\n  OnScrollResetListener,\n  OnSearchClickListener {\n\n  @Inject lateinit var settings: SettingsViewModeRepository\n\n  override val navigationId = R.id.followedShowsFragment\n  private val binding by viewBinding(FragmentWatchlistBinding::bind)\n\n  private val parentViewModel by viewModels<FollowedShowsViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<WatchlistViewModel>()\n\n  private var adapter: CollectionAdapter? = null\n  private var layoutManager: LayoutManager? = null\n  private var statusBarHeight = 0\n  private var isSearching = false\n  private val tabletGridSpanSize by lazy { settings.tabletGridSpanSize }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupStatusBar()\n    setupRecycler()\n\n    launchAndRepeatStarted(\n      { parentViewModel.uiState.collect { viewModel.onParentState(it) } },\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = { viewModel.loadShows() }\n    )\n  }\n\n  private fun setupRecycler() {\n    layoutManager = CollectionShowLayoutManagerProvider.provideLayoutManger(requireContext(), LIST_NORMAL, tabletGridSpanSize)\n    adapter = CollectionAdapter(\n      itemClickListener = { openShowDetails(it.show) },\n      itemLongClickListener = { item -> openShowMenu(item.show) },\n      sortChipClickListener = ::openSortOrderDialog,\n      upcomingChipClickListener = viewModel::setFilters,\n      listViewChipClickListener = viewModel::setNextViewMode,\n      networksChipClickListener = ::openNetworksDialog,\n      genresChipClickListener = ::openGenresDialog,\n      missingImageListener = viewModel::loadMissingImage,\n      missingTranslationListener = viewModel::loadMissingTranslation,\n      listChangeListener = {\n        binding.watchlistRecycler.scrollToPosition(0)\n        (requireParentFragment() as FollowedShowsFragment).resetTranslations()\n      }\n    ).apply {\n      stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY\n    }\n    binding.watchlistRecycler.apply {\n      setHasFixedSize(true)\n      adapter = this@WatchlistFragment.adapter\n      layoutManager = this@WatchlistFragment.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      addItemDecoration(CollectionShowListItemDecoration(requireContext(), R.dimen.spaceSmall))\n      addItemDecoration(CollectionShowGridItemDecoration(requireContext(), R.dimen.spaceSmall))\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      if (statusBarHeight != 0) {\n        watchlistContent.updatePadding(top = watchlistContent.paddingTop + statusBarHeight)\n        watchlistRecycler.updatePadding(top = dimenToPx(R.dimen.collectionTabsViewPadding))\n        return\n      }\n      watchlistContent.doOnApplyWindowInsets { view, insets, padding, _ ->\n        val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n        statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n        view.updatePadding(top = padding.top + statusBarHeight)\n        watchlistRecycler.updatePadding(top = dimenToPx(R.dimen.collectionTabsViewPadding))\n      }\n    }\n  }\n\n  private fun render(uiState: WatchlistUiState) {\n    uiState.run {\n      viewMode.let {\n        if (adapter?.listViewMode != it) {\n          layoutManager = CollectionShowLayoutManagerProvider.provideLayoutManger(requireContext(), it, tabletGridSpanSize)\n          adapter?.listViewMode = it\n          binding.watchlistRecycler.let { recycler ->\n            recycler.layoutManager = layoutManager\n            recycler.adapter = adapter\n          }\n        }\n      }\n      items.let {\n        val notifyChange = resetScroll?.consume() == true\n        adapter?.setItems(it, notifyChange = notifyChange)\n        (layoutManager as? GridLayoutManager)?.withSpanSizeLookup { pos ->\n          when (adapter?.getItems()?.get(pos)) {\n            is FiltersItem -> {\n              when (viewMode) {\n                LIST_NORMAL, LIST_COMPACT -> if (isTablet) tabletGridSpanSize else LISTS_GRID_SPAN\n                GRID, GRID_TITLE -> if (isTablet) LISTS_GRID_SPAN_TABLET else LISTS_GRID_SPAN\n              }\n            }\n            is ShowItem -> 1\n            else -> throw Error(\"Unsupported span size!\")\n          }\n        }\n        binding.watchlistEmptyView.root.fadeIf(it.isEmpty() && !isSearching)\n      }\n      sortOrder?.let { event ->\n        event.consume()?.let { openSortOrderDialog(it.first, it.second) }\n      }\n    }\n  }\n\n  private fun openShowDetails(show: Show) {\n    (requireParentFragment() as? FollowedShowsFragment)?.openShowDetails(show)\n  }\n\n  private fun openShowMenu(show: Show) {\n    (requireParentFragment() as? FollowedShowsFragment)?.openShowMenu(show)\n  }\n\n  private fun openSortOrderDialog(order: SortOrder, type: SortType) {\n    val options = listOf(NAME, RATING, USER_RATING, NEWEST, DATE_ADDED)\n    val args = SortOrderBottomSheet.createBundle(options, order, type)\n\n    requireParentFragment().setFragmentResultListener(REQUEST_SORT_ORDER) { _, bundle ->\n      val sortOrder = bundle.getSerializable(ARG_SELECTED_SORT_ORDER) as SortOrder\n      val sortType = bundle.getSerializable(ARG_SELECTED_SORT_TYPE) as SortType\n      viewModel.setSortOrder(sortOrder, sortType)\n    }\n\n    navigateTo(R.id.actionFollowedShowsFragmentToSortOrder, args)\n  }\n\n  private fun openNetworksDialog() {\n    requireParentFragment().setFragmentResultListener(REQUEST_COLLECTION_FILTERS_NETWORK) { _, _ ->\n      viewModel.loadShows(resetScroll = true)\n    }\n\n    val bundle = CollectionFiltersNetworkBottomSheet.createBundle(WATCHLIST_SHOWS)\n    navigateToSafe(R.id.actionFollowedShowsFragmentToNetworks, bundle)\n  }\n\n  private fun openGenresDialog() {\n    requireParentFragment().setFragmentResultListener(REQUEST_COLLECTION_FILTERS_GENRE) { _, _ ->\n      viewModel.loadShows(resetScroll = true)\n    }\n\n    val bundle = CollectionFiltersGenreBottomSheet.createBundle(WATCHLIST_SHOWS)\n    navigateToSafe(R.id.actionFollowedShowsFragmentToGenres, bundle)\n  }\n\n  override fun onEnterSearch() {\n    isSearching = true\n    with(binding) {\n      watchlistRecycler.translationY = dimenToPx(R.dimen.myShowsSearchLocalOffset).toFloat()\n      watchlistRecycler.smoothScrollToPosition(0)\n    }\n  }\n\n  override fun onExitSearch() {\n    isSearching = false\n    with(binding.watchlistRecycler) {\n      translationY = 0F\n      postDelayed(200) { layoutManager?.scrollToPosition(0) }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is OpenPremium -> {\n        (requireParentFragment() as? FollowedShowsFragment)?.openPremium()\n      }\n    }\n  }\n\n  override fun onScrollReset() = binding.watchlistRecycler.scrollToPosition(0)\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/watchlist/WatchlistUiState.kt",
    "content": "package com.michaldrabik.ui_my_shows.watchlist\n\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem\n\ndata class WatchlistUiState(\n  val items: List<CollectionListItem> = emptyList(),\n  val viewMode: ListViewMode = ListViewMode.LIST_NORMAL,\n  val resetScroll: Event<Boolean>? = null,\n  val sortOrder: Event<Pair<SortOrder, SortType>>? = null,\n)\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/watchlist/WatchlistViewModel.kt",
    "content": "package com.michaldrabik.ui_my_shows.watchlist\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.ReloadData\nimport com.michaldrabik.ui_base.events.TraktSyncAuthError\nimport com.michaldrabik.ui_base.events.TraktSyncError\nimport com.michaldrabik.ui_base.events.TraktSyncSuccess\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem.ShowItem\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsUiEvent.OpenPremium\nimport com.michaldrabik.ui_my_shows.main.FollowedShowsUiState\nimport com.michaldrabik.ui_my_shows.watchlist.cases.WatchlistFiltersCase\nimport com.michaldrabik.ui_my_shows.watchlist.cases.WatchlistLoadShowsCase\nimport com.michaldrabik.ui_my_shows.watchlist.cases.WatchlistSortOrderCase\nimport com.michaldrabik.ui_my_shows.watchlist.cases.WatchlistTranslationsCase\nimport com.michaldrabik.ui_my_shows.watchlist.cases.WatchlistViewModeCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\nimport com.michaldrabik.ui_base.events.Event as EventSync\n\n@HiltViewModel\nclass WatchlistViewModel @Inject constructor(\n  private val sortOrderCase: WatchlistSortOrderCase,\n  private val filtersCase: WatchlistFiltersCase,\n  private val loadShowsCase: WatchlistLoadShowsCase,\n  private val translationsCase: WatchlistTranslationsCase,\n  private val viewModeCase: WatchlistViewModeCase,\n  private val imagesProvider: ShowImagesProvider,\n  private val eventsManager: EventsManager,\n  private val settingsRepository: SettingsRepository\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private var loadItemsJob: Job? = null\n\n  private val itemsState = MutableStateFlow<List<CollectionListItem>>(emptyList())\n  private val viewModeState = MutableStateFlow(ListViewMode.LIST_NORMAL)\n  private val sortOrderState = MutableStateFlow<Event<Pair<SortOrder, SortType>>?>(null)\n  private val scrollState = MutableStateFlow<Event<Boolean>?>(null)\n\n  private var searchQuery: String? = null\n\n  init {\n    viewModelScope.launch { eventsManager.events.collect { onEvent(it) } }\n  }\n\n  fun onParentState(state: FollowedShowsUiState) {\n    when {\n      this.searchQuery != state.searchQuery -> {\n        this.searchQuery = state.searchQuery\n        loadShows(resetScroll = state.searchQuery.isNullOrBlank())\n      }\n    }\n  }\n\n  fun loadShows(resetScroll: Boolean = false) {\n    loadItemsJob?.cancel()\n    loadItemsJob = viewModelScope.launch {\n      viewModeState.value = viewModeCase.getListViewMode()\n      itemsState.value = loadShowsCase.loadShows(searchQuery ?: \"\")\n      scrollState.value = Event(resetScroll)\n    }\n  }\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType) {\n    viewModelScope.launch {\n      sortOrderCase.setSortOrder(sortOrder, sortType)\n      loadShows(resetScroll = true)\n    }\n  }\n\n  fun setFilters(isUpcoming: Boolean) {\n    viewModelScope.launch {\n      filtersCase.setIsUpcoming(isUpcoming)\n      loadShows(resetScroll = true)\n    }\n  }\n\n  fun setNextViewMode() {\n    if (settingsRepository.isPremium) {\n      viewModeState.value = viewModeCase.setNextViewMode()\n      return\n    }\n    viewModelScope.launch {\n      eventChannel.send(OpenPremium)\n    }\n  }\n\n  fun loadMissingImage(item: CollectionListItem, force: Boolean) {\n    check(item is ShowItem)\n    viewModelScope.launch {\n      updateItem(item.copy(isLoading = true))\n      try {\n        val image = imagesProvider.loadRemoteImage(item.show, item.image.type, force)\n        updateItem(item.copy(isLoading = false, image = image))\n      } catch (t: Throwable) {\n        updateItem(item.copy(isLoading = false, image = Image.createUnavailable(item.image.type)))\n      }\n    }\n  }\n\n  fun loadMissingTranslation(item: CollectionListItem) {\n    check(item is ShowItem)\n    if (item.translation != null || translationsCase.getLanguage() == Config.DEFAULT_LANGUAGE) return\n    viewModelScope.launch {\n      try {\n        val translation = translationsCase.loadTranslation(item.show, false)\n        updateItem(item.copy(translation = translation))\n      } catch (error: Throwable) {\n        Timber.e(error)\n      }\n    }\n  }\n\n  private fun updateItem(new: CollectionListItem) {\n    val currentItems = uiState.value.items.toMutableList()\n    currentItems.findReplace(new) { it.isSameAs(new) }\n    itemsState.value = currentItems\n  }\n\n  private fun onEvent(event: EventSync) =\n    when (event) {\n      is TraktSyncSuccess -> loadShows()\n      is TraktSyncError -> loadShows()\n      is TraktSyncAuthError -> loadShows()\n      is ReloadData -> loadShows()\n      else -> Unit\n    }\n\n  val uiState = combine(\n    itemsState,\n    sortOrderState,\n    scrollState,\n    viewModeState\n  ) { s1, s2, s3, s4 ->\n    WatchlistUiState(\n      items = s1,\n      sortOrder = s2,\n      resetScroll = s3,\n      viewMode = s4\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = WatchlistUiState()\n  )\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/watchlist/cases/WatchlistFiltersCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.watchlist.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass WatchlistFiltersCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setIsUpcoming(isUpcoming: Boolean) {\n    settingsRepository.filters.watchlistShowsUpcoming = isUpcoming\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/watchlist/cases/WatchlistLoadShowsCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.watchlist.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.TraktRating\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem\nimport com.michaldrabik.ui_my_shows.watchlist.helpers.WatchlistItemFilter\nimport com.michaldrabik.ui_my_shows.watchlist.helpers.WatchlistItemSorter\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport java.time.format.DateTimeFormatter\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass WatchlistLoadShowsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val ratingsCase: WatchlistRatingsCase,\n  private val sorter: WatchlistItemSorter,\n  private val filters: WatchlistItemFilter,\n  private val showsRepository: ShowsRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val settingsRepository: SettingsRepository,\n  private val imagesProvider: ShowImagesProvider,\n  private val dateFormatProvider: DateFormatProvider,\n) {\n\n  suspend fun loadShows(searchQuery: String): List<CollectionListItem> =\n    withContext(dispatchers.IO) {\n      val language = translationsRepository.getLanguage()\n      val ratings = ratingsCase.loadRatings()\n      val dateFormat = dateFormatProvider.loadFullDayFormat()\n      val translations =\n        if (language == Config.DEFAULT_LANGUAGE) emptyMap()\n        else translationsRepository.loadAllShowsLocal(language)\n      val spoilers = settingsRepository.spoilers.getAll()\n\n      val filtersItem = loadFiltersItem()\n      val filtersNetworks = filtersItem.networks\n        .flatMap { network -> network.channels.map { it } }\n      val filtersGenres = filtersItem.genres.map { it.slug.lowercase() }\n\n      val showsItems = showsRepository.watchlistShows.loadAll()\n        .map {\n          toListItemAsync(\n            show = it,\n            translation = translations[it.traktId],\n            userRating = ratings[it.ids.trakt],\n            dateFormat = dateFormat,\n            sortOrder = filtersItem.sortOrder,\n            spoilers = spoilers\n          )\n        }\n        .awaitAll()\n        .filter { item ->\n          filters.filterByQuery(item, searchQuery) &&\n            filters.filterUpcoming(item, filtersItem.isUpcoming) &&\n            filters.filterNetworks(item, filtersNetworks) &&\n            filters.filterGenres(item, filtersGenres)\n        }\n        .sortedWith(sorter.sort(filtersItem.sortOrder, filtersItem.sortType))\n\n      if (showsItems.isNotEmpty() || filtersItem.hasActiveFilters()) {\n        listOf(filtersItem) + showsItems\n      } else {\n        showsItems\n      }\n    }\n\n  private fun loadFiltersItem(): CollectionListItem.FiltersItem {\n    return CollectionListItem.FiltersItem(\n      sortOrder = settingsRepository.sorting.watchlistShowsSortOrder,\n      sortType = settingsRepository.sorting.watchlistShowsSortType,\n      networks = settingsRepository.filters.watchlistShowsNetworks,\n      genres = settingsRepository.filters.watchlistShowsGenres,\n      isUpcoming = settingsRepository.filters.watchlistShowsUpcoming\n    )\n  }\n\n  private fun CoroutineScope.toListItemAsync(\n    show: Show,\n    translation: Translation?,\n    userRating: TraktRating?,\n    dateFormat: DateTimeFormatter,\n    sortOrder: SortOrder,\n    spoilers: SpoilersSettings\n  ) = async {\n    val image = imagesProvider.findCachedImage(show, ImageType.POSTER)\n    CollectionListItem.ShowItem(\n      isLoading = false,\n      show = show,\n      image = image,\n      dateFormat = dateFormat,\n      translation = translation,\n      userRating = userRating?.rating,\n      sortOrder = sortOrder,\n      spoilers = CollectionListItem.ShowItem.Spoilers(\n        isSpoilerHidden = spoilers.isWatchlistShowsHidden,\n        isSpoilerRatingsHidden = spoilers.isWatchlistShowsRatingsHidden,\n        isSpoilerTapToReveal = spoilers.isTapToReveal\n      )\n    )\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/watchlist/cases/WatchlistRatingsCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.watchlist.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.TraktRating\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass WatchlistRatingsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val ratingsRepository: RatingsRepository,\n  private val userTraktManager: UserTraktManager,\n) {\n\n  suspend fun loadRatings(): Map<IdTrakt, TraktRating?> =\n    withContext(dispatchers.IO) {\n      if (!userTraktManager.isAuthorized()) {\n        return@withContext emptyMap()\n      }\n      ratingsRepository.shows.loadShowsRatings().associateBy { it.idTrakt }\n    }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/watchlist/cases/WatchlistSortOrderCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.watchlist.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass WatchlistSortOrderCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType) {\n    settingsRepository.sorting.watchlistShowsSortOrder = sortOrder\n    settingsRepository.sorting.watchlistShowsSortType = sortType\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/watchlist/cases/WatchlistTranslationsCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.watchlist.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.Translation\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass WatchlistTranslationsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val translationsRepository: TranslationsRepository,\n) {\n\n  fun getLanguage() = translationsRepository.getLanguage()\n\n  suspend fun loadTranslation(show: Show, onlyLocal: Boolean): Translation? =\n    withContext(dispatchers.IO) {\n      val language = getLanguage()\n      if (language == Config.DEFAULT_LANGUAGE) {\n        return@withContext Translation.EMPTY\n      }\n      translationsRepository.loadTranslation(show, language, onlyLocal)\n    }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/watchlist/cases/WatchlistViewModeCase.kt",
    "content": "package com.michaldrabik.ui_my_shows.watchlist.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.ListViewMode\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass WatchlistViewModeCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setNextViewMode(): ListViewMode {\n    val viewModes = ListViewMode.values()\n    val index = viewModes.indexOf(getListViewMode()) + 1\n    val nextIndex = if (index >= viewModes.size) 0 else index\n    settingsRepository.viewMode.watchlistShowsViewMode = viewModes[nextIndex].name\n    return viewModes[nextIndex]\n  }\n\n  fun getListViewMode(): ListViewMode {\n    if (!settingsRepository.isPremium) {\n      return ListViewMode.valueOf(Config.DEFAULT_LIST_VIEW_MODE)\n    }\n    val viewMode = settingsRepository.viewMode.watchlistShowsViewMode\n    return ListViewMode.valueOf(viewMode)\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/watchlist/helpers/WatchlistItemFilter.kt",
    "content": "package com.michaldrabik.ui_my_shows.watchlist.helpers\n\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass WatchlistItemFilter @Inject constructor() {\n\n  fun filterUpcoming(\n    item: CollectionListItem,\n    isUpcoming: Boolean,\n  ): Boolean {\n    if (isUpcoming) {\n      val releasedAt = item.getReleaseDate()\n      return releasedAt != null && releasedAt.isAfter(nowUtc())\n    }\n    return true\n  }\n\n  fun filterNetworks(\n    item: CollectionListItem,\n    networks: List<String>,\n  ): Boolean {\n    if (networks.isEmpty()) {\n      return true\n    }\n    return item.show.network in networks\n  }\n\n  fun filterGenres(\n    item: CollectionListItem,\n    genres: List<String>,\n  ): Boolean {\n    if (genres.isEmpty()) {\n      return true\n    }\n    return item.show.genres.any { genre -> genre.lowercase() in genres }\n  }\n\n  fun filterByQuery(\n    item: CollectionListItem.ShowItem,\n    query: String,\n  ): Boolean {\n    return item.show.title.contains(query, true) ||\n      item.translation?.title?.contains(query, true) == true\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/java/com/michaldrabik/ui_my_shows/watchlist/helpers/WatchlistItemSorter.kt",
    "content": "package com.michaldrabik.ui_my_shows.watchlist.helpers\n\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_ADDED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SortType.ASCENDING\nimport com.michaldrabik.ui_model.SortType.DESCENDING\nimport com.michaldrabik.ui_my_shows.common.recycler.CollectionListItem\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass WatchlistItemSorter @Inject constructor() {\n\n  fun sort(sortOrder: SortOrder, sortType: SortType) = when (sortType) {\n    ASCENDING -> sortAscending(sortOrder)\n    DESCENDING -> sortDescending(sortOrder)\n  }\n\n  private fun sortAscending(sortOrder: SortOrder) = when (sortOrder) {\n    NAME -> compareBy { getTitle(it) }\n    RATING -> compareBy { it.show.rating }\n    USER_RATING ->\n      compareByDescending<CollectionListItem.ShowItem> { it.userRating != null }\n        .thenBy { it.userRating }\n        .thenBy { getTitle(it) }\n    DATE_ADDED -> compareBy { it.show.createdAt }\n    NEWEST -> compareBy<CollectionListItem.ShowItem> { it.show.firstAired }.thenBy { it.show.year }\n    else -> throw IllegalStateException(\"Invalid sort order\")\n  }\n\n  private fun sortDescending(sortOrder: SortOrder) = when (sortOrder) {\n    NAME -> compareByDescending { getTitle(it) }\n    RATING -> compareByDescending { it.show.rating }\n    USER_RATING ->\n      compareByDescending<CollectionListItem.ShowItem> { it.userRating != null }\n        .thenByDescending { it.userRating }\n        .thenBy { getTitle(it) }\n    DATE_ADDED -> compareByDescending { it.show.createdAt }\n    NEWEST -> compareByDescending<CollectionListItem.ShowItem> { it.show.firstAired }.thenByDescending { it.show.year }\n    else -> throw IllegalStateException(\"Invalid sort order\")\n  }\n\n  private fun getTitle(item: CollectionListItem.ShowItem): String {\n    val translatedTitle =\n      if (item.translation?.hasTitle == true) item.translation.title\n      else item.show.titleNoThe\n    return translatedTitle.uppercase()\n  }\n}\n"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/fragment_followed_shows.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  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/followedShowsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.viewpager.widget.ViewPager\n    android:id=\"@+id/followedShowsPager\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:overScrollMode=\"never\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.ModeTabsView\n    android:id=\"@+id/followedShowsModeTabs\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/collectionTabsMargin\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.ScrollableTabLayout\n    android:id=\"@+id/followedShowsTabs\"\n    style=\"@style/ScrollableTabsStyle\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"36dp\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/myShowsSearchViewPadding\"\n    android:translationX=\"@dimen/myShowsTabsTranslation\"\n    app:tabTextAppearance=\"@style/ScrollableTabTextStyle\"\n    />\n\n  <FrameLayout\n    android:id=\"@+id/followedShowsIcons\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"end\"\n    android:layout_marginTop=\"@dimen/myShowsSearchViewPadding\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    tools:visibility=\"visible\"\n    >\n\n    <com.michaldrabik.ui_base.common.views.ScrollableImageView\n      android:id=\"@+id/followedShowsSearchIcon\"\n      android:layout_width=\"36dp\"\n      android:layout_height=\"36dp\"\n      android:layout_gravity=\"end\"\n      android:paddingStart=\"14dp\"\n      app:srcCompat=\"@drawable/ic_search\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n  </FrameLayout>\n\n  <com.michaldrabik.ui_base.common.views.SearchLocalView\n    android:id=\"@+id/followedShowsSearchLocalView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/searchLocalViewHeight\"\n    android:layout_marginLeft=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/myShowsSearchLocalViewPadding\"\n    android:layout_marginRight=\"@dimen/screenMarginHorizontal\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.SearchView\n    android:id=\"@+id/followedShowsSearchView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/searchViewHeight\"\n    android:layout_marginLeft=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginRight=\"@dimen/screenMarginHorizontal\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/fragment_hidden.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/hiddenContent\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/hiddenRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/transparent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/screenMarginHorizontal\"\n    android:paddingTop=\"@dimen/archiveTabsViewPadding\"\n    android:paddingEnd=\"@dimen/screenMarginHorizontal\"\n    android:paddingBottom=\"@dimen/myShowsBottomPadding\"\n    />\n\n  <include\n    android:id=\"@+id/hiddenEmptyView\"\n    layout=\"@layout/layout_archive_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/fragment_my_shows.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/myShowsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/myShowsRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/screenMarginHorizontal\"\n    android:paddingTop=\"@dimen/myShowsTabsViewPadding\"\n    android:paddingEnd=\"@dimen/screenMarginHorizontal\"\n    android:paddingBottom=\"@dimen/myShowsBottomPadding\"\n    />\n\n  <include\n    android:id=\"@+id/myShowsEmptyView\"\n    layout=\"@layout/layout_my_shows_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/fragment_watchlist.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/watchlistContent\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/watchlistRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/transparent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/screenMarginHorizontal\"\n    android:paddingTop=\"@dimen/collectionTabsViewPadding\"\n    android:paddingEnd=\"@dimen/screenMarginHorizontal\"\n    android:paddingBottom=\"@dimen/myShowsBottomPadding\"\n    />\n\n  <include\n    android:id=\"@+id/watchlistEmptyView\"\n    layout=\"@layout/layout_watchlist_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/layout_archive_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/menuHidden\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textHiddenEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/layout_my_shows_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/menuMyShows\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textMyShowsEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/layout_watchlist_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/menuWatchlist\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textWatchlistEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/view_collection_show.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/collectionShowRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    >\n\n    <ImageView\n      android:id=\"@+id/collectionShowImage\"\n      android:layout_width=\"@dimen/collectionImageWidth\"\n      android:layout_height=\"@dimen/collectionImageHeight\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionShowPlaceholder\"\n      android:layout_width=\"@dimen/collectionImageWidth\"\n      android:layout_height=\"@dimen/collectionImageHeight\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"@dimen/spaceBig\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/collectionShowProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"@id/collectionShowImage\"\n      app:layout_constraintStart_toStartOf=\"@id/collectionShowImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionShowTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionShowDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/collectionShowImage\"\n      app:layout_constraintTop_toBottomOf=\"@+id/collectionShowNetwork\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Game of Thrones\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionShowNetwork\"\n      style=\"@style/CollectionItem.Header\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@+id/collectionShowTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/collectionShowImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Netflix\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionShowStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      app:layout_constraintBottom_toTopOf=\"@+id/collectionShowTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/collectionShowRating\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionShowNetwork\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionShowRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintBottom_toTopOf=\"@+id/collectionShowTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionShowNetwork\"\n      tools:text=\"7.6\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionShowUserStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionShowTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/collectionShowUserRating\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionShowNetwork\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionShowUserRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionShowTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/collectionShowStarIcon\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionShowNetwork\"\n      tools:text=\"10\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionShowDescription\"\n      style=\"@style/CollectionItem.Description\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionShowReleaseDate\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/collectionShowImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/collectionShowTitle\"\n      tools:text=\"Lorem Ipsum\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionShowReleaseDate\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_badge\"\n      android:drawablePadding=\"@dimen/spaceTiny\"\n      android:elevation=\"@dimen/elevationTiny\"\n      android:gravity=\"start|center_vertical\"\n      android:includeFontPadding=\"false\"\n      android:paddingStart=\"4dp\"\n      android:paddingTop=\"4dp\"\n      android:paddingEnd=\"6dp\"\n      android:paddingBottom=\"4dp\"\n      android:textAlignment=\"gravity\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"13sp\"\n      android:visibility=\"gone\"\n      app:drawableStartCompat=\"@drawable/ic_clock_small\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/collectionShowImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/collectionShowDescription\"\n      tools:text=\"Wednesday, 27 June 2019\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/view_collection_show_compact.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/collectionShowRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    >\n\n    <ImageView\n      android:id=\"@+id/collectionShowImage\"\n      android:layout_width=\"@dimen/collectionImageCompactWidth\"\n      android:layout_height=\"@dimen/collectionImageCompactHeight\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionShowPlaceholder\"\n      android:layout_width=\"@dimen/collectionImageCompactWidth\"\n      android:layout_height=\"@dimen/collectionImageCompactHeight\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"@dimen/spaceMedium\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/collectionShowProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"20dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"@id/collectionShowImage\"\n      app:layout_constraintStart_toStartOf=\"@id/collectionShowImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionShowTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:textSize=\"16sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionShowReleaseDate\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/collectionShowImage\"\n      app:layout_constraintTop_toBottomOf=\"@+id/collectionShowNetwork\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Game of Thrones\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionShowNetwork\"\n      style=\"@style/CollectionItem.Header\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:textSize=\"11sp\"\n      app:layout_constraintBottom_toTopOf=\"@+id/collectionShowTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/collectionShowImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Netflix\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionShowStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      app:layout_constraintBottom_toTopOf=\"@+id/collectionShowTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/collectionShowRating\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionShowNetwork\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionShowRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"11sp\"\n      app:layout_constraintBottom_toTopOf=\"@+id/collectionShowTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionShowNetwork\"\n      tools:text=\"7.6\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionShowUserStarIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionShowTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/collectionShowUserRating\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionShowNetwork\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionShowUserRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"11sp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/collectionShowTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/collectionShowStarIcon\"\n      app:layout_constraintTop_toTopOf=\"@id/collectionShowNetwork\"\n      tools:text=\"10\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionShowReleaseDate\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:layout_marginTop=\"@dimen/spaceTiny\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_badge\"\n      android:drawablePadding=\"@dimen/spaceTiny\"\n      android:elevation=\"@dimen/elevationTiny\"\n      android:gravity=\"start|center_vertical\"\n      android:includeFontPadding=\"false\"\n      android:paddingStart=\"4dp\"\n      android:paddingTop=\"4dp\"\n      android:paddingEnd=\"6dp\"\n      android:paddingBottom=\"4dp\"\n      android:textAlignment=\"gravity\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"11sp\"\n      android:visibility=\"gone\"\n      app:drawableStartCompat=\"@drawable/ic_clock_compact\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/collectionShowImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/collectionShowTitle\"\n      tools:text=\"Wednesday, 27 June 2019\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/view_collection_show_grid.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/collectionShowRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@drawable/bg_media_view_elevation\"\n    android:elevation=\"@dimen/elevationTiny\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/collectionShowImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionShowPlaceholder\"\n      android:layout_width=\"@dimen/showTilePlaceholder\"\n      android:layout_height=\"@dimen/showTilePlaceholder\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/collectionShowProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionShowRating\"\n      style=\"@style/ImageTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"top|end\"\n      android:layout_margin=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_text_on_surface\"\n      android:drawablePadding=\"2dp\"\n      android:gravity=\"end\"\n      android:paddingStart=\"4dp\"\n      android:paddingTop=\"2dp\"\n      android:paddingEnd=\"4dp\"\n      android:paddingBottom=\"2dp\"\n      android:shadowColor=\"@color/colorTransparent\"\n      android:textColor=\"@color/colorWhite\"\n      android:textSize=\"11sp\"\n      android:visibility=\"gone\"\n      app:drawableStartCompat=\"@drawable/ic_star_small\"\n      app:drawableTint=\"@color/colorWhite\"\n      tools:text=\"9.9\"\n      tools:visibility=\"visible\"\n      />\n\n  </FrameLayout>\n\n</merge>"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/view_collection_show_grid_title.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/collectionShowRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginBottom=\"@dimen/collectionImageMargin\"\n    android:background=\"@drawable/bg_media_view_elevation\"\n    android:elevation=\"@dimen/elevationTiny\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/collectionShowImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/collectionShowPlaceholder\"\n      android:layout_width=\"@dimen/showTilePlaceholder\"\n      android:layout_height=\"@dimen/showTilePlaceholder\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/collectionShowProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      />\n\n    <TextView\n      android:id=\"@+id/collectionShowRating\"\n      style=\"@style/ImageTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"top|end\"\n      android:layout_margin=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_text_on_surface\"\n      android:drawablePadding=\"2dp\"\n      android:gravity=\"end\"\n      android:paddingStart=\"4dp\"\n      android:paddingTop=\"2dp\"\n      android:paddingEnd=\"4dp\"\n      android:paddingBottom=\"2dp\"\n      android:shadowColor=\"@color/colorTransparent\"\n      android:textColor=\"@color/colorWhite\"\n      android:textSize=\"11sp\"\n      android:visibility=\"gone\"\n      app:drawableStartCompat=\"@drawable/ic_star_small\"\n      app:drawableTint=\"@color/colorWhite\"\n      tools:text=\"9.9\"\n      tools:visibility=\"visible\"\n      />\n\n  </FrameLayout>\n\n  <TextView\n    android:id=\"@+id/collectionShowTitle\"\n    style=\"@style/ImageTitleGrid\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"bottom|start\"\n    android:layout_marginBottom=\"@dimen/collectionImageTitleMargin\"\n    android:gravity=\"start\"\n    android:maxLines=\"1\"\n    android:textAlignment=\"viewStart\"\n    android:textSize=\"@dimen/collectionImageTitleSize\"\n    android:visibility=\"visible\"\n    tools:text=\"Game Of Thrones\"\n    tools:visibility=\"visible\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/view_filters_genres.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  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/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_filters_sheet\"\n  android:orientation=\"vertical\"\n  android:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.core.widget.NestedScrollView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\"\n    >\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:clipToPadding=\"false\"\n      android:paddingStart=\"@dimen/spaceNormal\"\n      android:paddingTop=\"@dimen/spaceNormal\"\n      android:paddingEnd=\"@dimen/spaceNormal\"\n      tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n      >\n\n      <TextView\n        android:id=\"@+id/genresTitle\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/textGenres\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"18sp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintBottom_toTopOf=\"@id/genresChipGroup\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n      <com.google.android.material.chip.ChipGroup\n        android:id=\"@+id/genresChipGroup\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceTiny\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/genresTitle\"\n        app:lineSpacing=\"10dp\"\n        />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n  </androidx.core.widget.NestedScrollView>\n\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingTop=\"@dimen/spaceMedium\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceNormal\"\n    >\n\n    <ImageView\n      android:id=\"@+id/clearButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center_vertical\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_delete\"\n      app:tint=\"?android:textColorSecondary\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/applyButton\"\n      style=\"@style/RoundMaterialButton\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:backgroundTint=\"?attr/colorAccent\"\n      android:gravity=\"center\"\n      android:text=\"@string/textApply\"\n      android:textColor=\"?attr/textColorOnSurface\"\n      app:rippleColor=\"?android:attr/textColorPrimary\"\n      />\n\n  </LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/view_filters_networks.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  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/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_filters_sheet\"\n  android:orientation=\"vertical\"\n  android:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.core.widget.NestedScrollView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\"\n    >\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:clipToPadding=\"false\"\n      android:paddingStart=\"@dimen/spaceNormal\"\n      android:paddingTop=\"@dimen/spaceNormal\"\n      android:paddingEnd=\"@dimen/spaceNormal\"\n      tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n      >\n\n      <TextView\n        android:id=\"@+id/networksTitle\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/textNetworks\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"18sp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintBottom_toTopOf=\"@+id/networksChipGroup\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n      <com.google.android.material.chip.ChipGroup\n        android:id=\"@+id/networksChipGroup\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceTiny\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/networksTitle\"\n        app:lineSpacing=\"10dp\"\n        app:singleSelection=\"true\"\n        />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n  </androidx.core.widget.NestedScrollView>\n\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingTop=\"@dimen/spaceMedium\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceNormal\"\n    >\n\n    <ImageView\n      android:id=\"@+id/clearButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center_vertical\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_delete\"\n      app:tint=\"?android:textColorSecondary\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/applyButton\"\n      style=\"@style/RoundMaterialButton\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:backgroundTint=\"?attr/colorAccent\"\n      android:gravity=\"center\"\n      android:text=\"@string/textApply\"\n      android:textColor=\"?attr/textColorOnSurface\"\n      app:rippleColor=\"?android:attr/textColorPrimary\"\n      />\n\n  </LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/view_my_shows_fanart.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/myShowFanartRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/myShowFanartImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/myShowFanartPlaceholder\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:padding=\"42dp\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/myShowFanartTitle\"\n      style=\"@style/ImageTitle\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"bottom|start\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:gravity=\"start\"\n      android:maxLines=\"2\"\n      android:textAlignment=\"viewStart\"\n      android:textSize=\"14sp\"\n      android:visibility=\"gone\"\n      tools:text=\"@tools:sample/lorem/random\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/myShowFanartProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n  </FrameLayout>\n</merge>"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/view_my_shows_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/myShowsHeaderRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    >\n\n    <androidx.appcompat.widget.AppCompatTextView\n      android:id=\"@+id/myShowsHeaderLabel\"\n      style=\"@style/MyShows.Label\"\n      android:layout_marginTop=\"@dimen/spaceNormal\"\n      app:layout_constraintBottom_toTopOf=\"@id/myShowsFilterChipsScroll\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_goneMarginBottom=\"@dimen/spaceSmall\"\n      tools:text=\"Section Label\"\n      />\n\n    <HorizontalScrollView\n      android:id=\"@+id/myShowsFilterChipsScroll\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"14dp\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:clipToPadding=\"false\"\n      android:overScrollMode=\"never\"\n      android:scrollbars=\"none\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/myShowsHeaderLabel\"\n      >\n\n      <com.google.android.material.chip.ChipGroup\n        android:id=\"@+id/myShowsFilterChips\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        app:singleLine=\"true\"\n        >\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/myShowsTypeChip\"\n          style=\"@style/ShowlyChip.Filter\"\n          android:checkable=\"false\"\n          android:text=\"@string/textHeaderAll\"\n          />\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/myShowsSortChip\"\n          style=\"@style/ShowlyChip.Sort\"\n          android:text=\"@string/textSortName\"\n          android:visibility=\"gone\"\n          tools:visibility=\"visible\"\n          />\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/myShowsNetworksChip\"\n          style=\"@style/ShowlyChip.Filter\"\n          android:checkable=\"false\"\n          android:text=\"@string/textNetworks\"\n          android:visibility=\"gone\"\n          tools:visibility=\"visible\"\n          />\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/myShowsGenresChip\"\n          style=\"@style/ShowlyChip.Filter\"\n          android:checkable=\"false\"\n          android:text=\"@string/textGenres\"\n          android:visibility=\"gone\"\n          tools:visibility=\"visible\"\n          />\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/myShowsSortListViewChip\"\n          style=\"@style/ShowlyChip.Filter\"\n          android:checkable=\"false\"\n          app:chipIcon=\"@drawable/ic_view_grid\"\n          app:chipIconEnabled=\"true\"\n          app:chipIconTint=\"?android:textColorPrimary\"\n          app:iconEndPadding=\"-14dp\"\n          app:iconStartPadding=\"2dp\"\n          />\n\n      </com.google.android.material.chip.ChipGroup>\n\n    </HorizontalScrollView>\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/view_my_shows_recents.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.gridlayout.widget.GridLayout\n    android:id=\"@+id/myShowsRecentsContainer\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    app:columnCount=\"2\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/view_my_shows_type_filter_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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/viewMyShowsTypeItemRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <View\n    android:id=\"@+id/viewMyShowsTypeItemBadge\"\n    android:layout_width=\"3dp\"\n    android:layout_height=\"0dp\"\n    android:background=\"@drawable/bg_sort_item_badge\"\n    android:translationY=\"1dp\"\n    app:layout_constraintBottom_toBottomOf=\"@id/viewMyShowsTypeItemTitle\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/viewMyShowsTypeItemTitle\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewMyShowsTypeItemTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"10dp\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    android:layout_marginBottom=\"10dp\"\n    android:gravity=\"start\"\n    android:maxLines=\"1\"\n    android:text=\"@string/textHeaderAll\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"16sp\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewMyShowsTypeItemBadge\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_goneMarginStart=\"0dp\"\n    />\n\n</merge>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/view_my_shows_type_filters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  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/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_filters_sheet\"\n  android:orientation=\"vertical\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.core.widget.NestedScrollView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\"\n    >\n\n    <LinearLayout\n      android:id=\"@+id/rootItemsLayout\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceMedium\"\n      android:orientation=\"vertical\"\n      />\n\n  </androidx.core.widget.NestedScrollView>\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/applyButton\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:layout_marginBottom=\"@dimen/spaceNormal\"\n    android:backgroundTint=\"?attr/colorAccent\"\n    android:gravity=\"center\"\n    android:text=\"@string/textApply\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/layout/view_shows_filters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <HorizontalScrollView\n    android:id=\"@+id/followedShowsScroll\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:scrollbars=\"none\"\n    >\n\n    <LinearLayout\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"horizontal\"\n      >\n\n      <com.google.android.material.chip.ChipGroup\n        android:id=\"@+id/followedShowsChips\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:paddingTop=\"@dimen/spaceSmall\"\n        android:paddingBottom=\"@dimen/spaceSmall\"\n        app:singleLine=\"true\"\n        >\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/followedShowsSortingChip\"\n          style=\"@style/ShowlyChip.Sort\"\n          android:text=\"@string/textSortName\"\n          />\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/followedShowsNetworksChip\"\n          style=\"@style/ShowlyChip.Filter\"\n          android:checkable=\"false\"\n          android:text=\"@string/textNetworks\"\n          />\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/followedShowsGenresChip\"\n          style=\"@style/ShowlyChip.Filter\"\n          android:checkable=\"false\"\n          android:text=\"@string/textGenres\"\n          />\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/followedShowsUpcomingChip\"\n          style=\"@style/ShowlyChip.Filter\"\n          android:text=\"@string/textWatchlistIncoming\"\n          android:visibility=\"gone\"\n          tools:visibility=\"visible\"\n          />\n\n        <com.google.android.material.chip.Chip\n          android:id=\"@+id/followedShowsListViewChip\"\n          style=\"@style/ShowlyChip.Filter\"\n          android:checkable=\"false\"\n          app:chipIcon=\"@drawable/ic_view_grid\"\n          app:chipIconEnabled=\"true\"\n          app:chipIconTint=\"?android:textColorPrimary\"\n          app:iconEndPadding=\"-14dp\"\n          app:iconStartPadding=\"2dp\"\n          />\n\n      </com.google.android.material.chip.ChipGroup>\n\n    </LinearLayout>\n\n  </HorizontalScrollView>\n\n</merge>"
  },
  {
    "path": "ui-my-shows/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"myShowsTabsTranslation\">-7dp</dimen>\n  <dimen name=\"myShowsSearchViewPadding\">104dp</dimen>\n  <dimen name=\"myShowsTabsViewPadding\">136dp</dimen>\n\n  <dimen name=\"myShowsSearchLocalViewPadding\">150dp</dimen>\n  <dimen name=\"myShowsSearchLocalOffset\">40dp</dimen>\n\n  <dimen name=\"myShowsBottomPadding\">76dp</dimen>\n  <dimen name=\"myShowsFanartCorner\">4dp</dimen>\n  <dimen name=\"myShowsFanartHeight\">126dp</dimen>\n  <dimen name=\"myShowsPosterHeight\">120dp</dimen>\n  <dimen name=\"myShowsPosterWidth\">80dp</dimen>\n\n  <dimen name=\"myShowAllImageHeight\">120dp</dimen>\n  <dimen name=\"myShowAllImageWidth\">80dp</dimen>\n  <dimen name=\"myShowAllMarginHorizontal\">12dp</dimen>\n\n  <dimen name=\"archiveTabsViewPadding\">148dp</dimen>\n  <dimen name=\"archiveImageHeight\">120dp</dimen>\n  <dimen name=\"archiveImageWidth\">80dp</dimen>\n  <dimen name=\"archiveMarginHorizontal\">12dp</dimen>\n\n  <dimen name=\"collectionTabsViewPadding\">148dp</dimen>\n  <dimen name=\"collectionMarginHorizontal\">12dp</dimen>\n  <dimen name=\"collectionImageHeight\">120dp</dimen>\n  <dimen name=\"collectionImageWidth\">80dp</dimen>\n  <dimen name=\"collectionImageCompactHeight\">64dp</dimen>\n  <dimen name=\"collectionImageCompactWidth\">43dp</dimen>\n  <dimen name=\"collectionImageMargin\">23dp</dimen>\n  <dimen name=\"collectionImageTitleMargin\">4dp</dimen>\n  <dimen name=\"collectionImageTitleSize\">11sp</dimen>\n  <dimen name=\"collectionFiltersPaddingHorizontal\">3dp</dimen>\n  <dimen name=\"collectionFiltersPaddingBottom\">5dp</dimen>\n</resources>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyShows\">My Shows</string>\n  <string name=\"menuWatchlist\">Watchlist</string>\n  <string name=\"menuHidden\">Hidden</string>\n\n  <string name=\"textMyShowsEmpty\">Your shows collection is currently empty.\\n\\nVisit <b>Discover</b> tab to add your first show.</string>\n  <string name=\"textWatchlistEmpty\">Your <b>Watchlist</b> is currently empty.\\n\\nVisit <b>Discover</b> tab to add shows you might want to watch in the future.</string>\n  <string name=\"textHiddenEmpty\">Your <b>Hidden</b> list is currently empty.\\n\\n<b>Hidden</b> is a list of items that you are not interested in.\\n\\nHidden items will no longer appear in <b>Progress</b>, <b>Discover</b> and other sections.</string>\n</resources>"
  },
  {
    "path": "ui-my-shows/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <style name=\"MyShows\" />\n\n  <style name=\"MyShows.Label\" parent=\"MyShows\">\n    <item name=\"android:layout_width\">0dp</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:textColor\">?android:attr/textColorPrimary</item>\n    <item name=\"android:textStyle\">bold</item>\n    <item name=\"android:textAlignment\">viewStart</item>\n    <item name=\"android:textSize\">22sp</item>\n    <item name=\"android:gravity\">start|center_vertical</item>\n    <item name=\"android:maxLines\">1</item>\n  </style>\n\n</resources>"
  },
  {
    "path": "ui-my-shows/src/main/res/values-ar/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"myShowsTabsTranslation\">6dp</dimen>\n</resources>"
  },
  {
    "path": "ui-my-shows/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyShows\">مسلسلاتي</string>\n  <string name=\"menuWatchlist\">قائمة المشاهدة</string>\n  <string name=\"menuHidden\">المخفية</string>\n  <string name=\"textMyShowsEmpty\">مجموعة مسلسلاتك فارغة حالياً.\\n\\nإذهب إلى صفحة <b>إكتشف</b> وأضف مسلسلك الأول.</string>\n  <string name=\"textWatchlistEmpty\">مجموعة <b>قائمة المشاهدة</b> فارغة حالياً.\\n\\nإذهب إلى صفحة <b>إكتشف</b> وأضف ما قد ترغب بمشاهدته في المستقبل.</string>\n  <string name=\"textHiddenEmpty\">قائمة <b>المخفية</b> فارغة حاليًا.\\n\\n<b>المخفية</b> هي قائمة تحتوى على محتويات أنت لا تهتم بها.\\n\\nالمحتويات المخفية لن تظهر في قوائم <b>مستوى التقدم</b> و <b>اكتشف</b> وفي القوائم الأخرى.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/values-de/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"myShowsTabsTranslation\">-8dp</dimen>\n</resources>"
  },
  {
    "path": "ui-my-shows/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyShows\">Meine Serien</string>\n  <string name=\"menuWatchlist\">Watchlist</string>\n  <string name=\"menuHidden\">Versteckt</string>\n  <string name=\"textMyShowsEmpty\">Deine Seriensammlung ist derzeit leer.\\n\\nGehe auf den <b>Entdecken</b>Tab um deine erste Serie hinzuzufügen.</string>\n  <string name=\"textWatchlistEmpty\">Deine <b>Watchlist</b> ist zurzeit leer.\\n\\nGehe auf den <b>Entdecken</b> Tab um Serien hinzuzufügen, die du später anschauen möchtest.</string>\n  <string name=\"textHiddenEmpty\">Deine <b>Versteckt</b> Liste ist derzeit leer.\\n\\n<b>Versteckt</b> ist eine Liste von Elementen, die Sie nicht interessieren.\\n\\nVersteckte Elemente werden nicht mehr in <b>Fortschritt</b>, <b>Entdecke</b> und andere Abschnitte.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyShows\">Mis Series</string>\n  <string name=\"menuWatchlist\">Pendientes</string>\n  <string name=\"menuHidden\">Ocultos</string>\n  <string name=\"textMyShowsEmpty\">Tu colección de programas está actualmente vacía.\\n\\nVisita la pestaña <b>Descubrir</b> para agregar tu primera serie.</string>\n  <string name=\"textWatchlistEmpty\">Tu lista <b>Pendientes</b> actualmente está vacía.\\n\\nVisita la pestaña<b>Descubrir</b> para añadir series que quizá desees ver en el futuro.</string>\n  <string name=\"textHiddenEmpty\">Tu lista de <b>Ocultos</b> está actualmente vacía.\\n\\n<b>Ocultos</b> es una lista de elementos que no te interesan.\\n\\nLos elementos ocultos ya no aparecerán en <b>Progreso</b>, <b>Descubrir</b> y otras secciones.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyShows\">Omat sarjat</string>\n  <string name=\"menuWatchlist\">Katselulista</string>\n  <string name=\"menuHidden\">Piilotetut</string>\n  <string name=\"textMyShowsEmpty\">Sarjakokoelmasi on tyhjä.\\n\\nLisää ensimmäinen sarjasi <b>Etsi</b>-osiosta.</string>\n  <string name=\"textWatchlistEmpty\"><b>Katselulista</b> on tyhjä.\\n\\nLisää <b>Etsi</b>-osiosta sarjoja, joita haluat katsella tulevaisuudessa.</string>\n  <string name=\"textHiddenEmpty\"><b>Piilotetut</b>-lista on tyhjä.\\n\\n<b>Piilotetut</b> on lista kohteista, joista et ole kiinnostunut.\\n\\nPiilotettuja kohteita ei enää näytetä mm. <b>Katselutila</b>- ja <b>Etsi</b>-osioissa.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyShows\">Mes séries</string>\n  <string name=\"menuWatchlist\">Watchlist</string>\n  <string name=\"menuHidden\">Masqué</string>\n  <string name=\"textMyShowsEmpty\">Votre collection de séries est actuellement vide.\\n\\nVisitez l\\'onglet <b>Découvrir</b> pour ajouter votre première série.</string>\n  <string name=\"textWatchlistEmpty\">Votre liste <b>Watchlist</b> est présentement vide.\\n\\nVisitez l\\'onglet <b>Découvrir</b> pour ajouter des émissions que vous souhaitez visionner plus tard.</string>\n  <string name=\"textHiddenEmpty\">Votre liste <b>masqué</b> est actuellement vide.\\n\\n<b>Masqué</b> est une liste d\\'éléments qui ne vous intéressent pas.\\n\\nLes éléments masqués n\\'apparaîtront plus dans <b>Progression</b>, <b>Découvrir</b> et d\\'autres sections.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyShows\">I miei show</string>\n  <string name=\"menuWatchlist\">Da Vedere</string>\n  <string name=\"menuHidden\">Nascosti</string>\n  <string name=\"textMyShowsEmpty\">La tua raccolta di show è vuota.\\n\\nVisita la pagina <b>Scopri</b> per aggiungere il tuo primo show.</string>\n  <string name=\"textWatchlistEmpty\">La tua lista <b>Da vedere</b> è vuota.\\n\\nVisita la pagina <b>Scopri</b> per aggiungere gli show che hai intenzione di vedere più avanti.</string>\n  <string name=\"textHiddenEmpty\">La tua lista <b>Nascosti</b> è attualmente vuota.\\n\\n<b>Nascosti</b> è un elenco di elementi a cui non sei interessato.\\n\\nGli elementi nascosti non appariranno più in <b>Progressi</b>, <b>Scopri</b> e altre sezioni.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/values-pl/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"myShowsTabsTranslation\">-8dp</dimen>\n</resources>"
  },
  {
    "path": "ui-my-shows/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyShows\">Moje Seriale</string>\n  <string name=\"menuWatchlist\">Na Później</string>\n  <string name=\"menuHidden\">Ukryte</string>\n  <string name=\"textMyShowsEmpty\">Kolekcja jest obecnie pusta.\\n\\nWybierz zakładkę <b>Odkrywaj</b> aby dodać swój pierwszy serial.</string>\n  <string name=\"textWatchlistEmpty\">Kolekcja <b>Na Później</b> jest obecnie pusta.\\n\\nWybierz zakładkę <b>Odkrywaj</b> aby dodać seriale które chcesz sprawdzić w przyszłości.</string>\n  <string name=\"textHiddenEmpty\">Twoja lista <b>Ukrytych</b> jest obecnie pusta.\\n\\n<b>Ukryte</b> to lista elementów, którymi nie jesteś zainteresowany.\\n\\nUkryte przedmioty nie pojawią się już w sekcjach <b>Postępu</b>, <b>Odkrywania</b> i innych.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyShows\">Minhas Séries</string>\n  <string name=\"menuWatchlist\">Interesses</string>\n  <string name=\"menuHidden\">Oculto</string>\n  <string name=\"textMyShowsEmpty\">Sua coleção de séries está vazia no momento.\\n\\nVisite a guia <b>Explorar</b> para adicionar sua primeira série.</string>\n  <string name=\"textWatchlistEmpty\">Seus <b>Interesses</b> está vazia no momento.\\n\\nAcesse a guia <b>Explorar</b> para adicionar séries que você pode querer assistir no futuro.</string>\n  <string name=\"textHiddenEmpty\">Sua lista <b>Oculta</b> está vazia no momento.\\n\\n<b>Oculto</b> é uma lista de itens nos quais você não tem interesse.\\n\\nItens escondidos não aparecerão mais em <b>Progresso</b>, <b>Explorar</b> e outras seções.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyShows\">Мои сериалы</string>\n  <string name=\"menuWatchlist\">Буду смотреть</string>\n  <string name=\"menuHidden\">Скрытое</string>\n  <string name=\"textMyShowsEmpty\">В настоящее время коллекция сериалов пуста.\\n\\nПосетите вкладку <b>Открытия</b> и добавьте свой первый сериал.</string>\n  <string name=\"textWatchlistEmpty\">Ваш список <b>Буду смотреть</b> в настоящее время пуст.\\n\\nПосетите вкладку <b>Открытия</b>, чтобы добавить фильмы, которые вы хотите посмотреть в будущем.</string>\n  <string name=\"textHiddenEmpty\">Ваш список <b>Скрытое</b> в настоящее время пуст.\\n\\n<b>Скрытое</b> это список элементов, которые вас больше не интересуют.\\n\\nСкрытые элементы не появятся в <b>Прогресс</b>, <b>Открытия</b> и других разделах.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"myShowsFanartHeight\">176dp</dimen>\n  <dimen name=\"myShowsFanartCorner\">6dp</dimen>\n  <dimen name=\"myShowsBottomPadding\">86dp</dimen>\n\n  <dimen name=\"collectionImageMargin\">29dp</dimen>\n  <dimen name=\"collectionImageTitleMargin\">8dp</dimen>\n  <dimen name=\"collectionImageTitleSize\">12sp</dimen>\n</resources>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyShows\">Dizilerim</string>\n  <string name=\"menuWatchlist\">İstek Listesi</string>\n  <string name=\"menuHidden\">Gizlenmiş</string>\n  <string name=\"textMyShowsEmpty\">Dizi koleksiyonunuz şu anda boştur.\\n\\nİlk dizinizi eklemek için <b>Keşfet</b> sekmesini ziyaret edin.</string>\n  <string name=\"textWatchlistEmpty\"><b>İstek Listeniz</b> şu anda boştur.\\n\\nGelecekte izlemek isteyebileceğiniz dizileri eklemek için <b>Keşfet</b> sekmesini ziyaret edin.</string>\n  <string name=\"textHiddenEmpty\">&lt;b&gt;Gizli&lt;/b&gt; listeniz şu anda boş.\\n\\n&lt;b&gt;Gizli&lt;/b&gt;, ilgilenmediğiniz öğelerin listesidir.\\n\\nGizli öğeler artık &lt;b içinde görünmeyecek &gt;İlerleme&lt;/b&gt;, &lt;b&gt;Keşfet&lt;/b&gt; ve diğer bölümler.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyShows\">Мої Серіали</string>\n  <string name=\"menuWatchlist\">На потім</string>\n  <string name=\"menuHidden\">Приховане</string>\n  <string name=\"textMyShowsEmpty\">Ваша колекція серіалів наразі порожня.\\n\\nВідвідайте <b>Огляд</b>, щоб додати свій перший серіал.</string>\n  <string name=\"textWatchlistEmpty\">Ваш список <b>На потім</b> зараз порожній.\\n\\nВідвідайте вкладку <b>Огляд</b>, щоб додати серіали, які ви хочете переглянути у майбутньому.</string>\n  <string name=\"textHiddenEmpty\">Наразі Ваш список <b>Приховане</b> порожній.\\n\\n<b>Приховане</b> це список елементів, які вас не цікавлять.\\n\\nПриховані елементи більше не з\\'являтимуться в <b>Прогрес</b>, <b>Огляд</b> та інших розділах.</string>\n</resources>\n"
  },
  {
    "path": "ui-my-shows/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyShows\">Chương trình của tôi</string>\n  <string name=\"menuWatchlist\">Danh sách theo dõi</string>\n  <string name=\"menuHidden\">Ẩn</string>\n\n  <string name=\"textMyShowsEmpty\">Bộ sưu tập chương trình của bạn hiện trống.\\n\\nTruy cập tab <b>Khám phá</b> để thêm chương trình đầu tiên của bạn.</string>\n  <string name=\"textWatchlistEmpty\"><b>Danh sách theo dõi</b> của bạn hiện trống.\\n\\nTruy cập tab <b>Khám phá</b> để thêm các chương trình mà bạn có thể muốn xem trong tương lai.</string>\n  <string name=\"textHiddenEmpty\">Danh sách <b>Ẩn</b> của bạn hiện trống.\\n\\n<b>Ẩn</b> là danh sách các mục mà bạn không quan tâm.\\n\\nCác mục ẩn sẽ không còn xuất hiện trong <b >Tiến độ</b>, <b>Khám phá</b> và các mục khác.</string>\n</resources>"
  },
  {
    "path": "ui-my-shows/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuMyShows\">我的剧集</string>\n  <string name=\"menuWatchlist\">待看列表</string>\n  <string name=\"menuHidden\">已隐藏</string>\n  <string name=\"textMyShowsEmpty\">当前您的剧集合集为空。\\n\\n访问 <b>发现</b> 选项卡来添加您的第一个剧集。</string>\n  <string name=\"textWatchlistEmpty\">您的 <b>待看列表</b> 目前为空。\\n\\n访问 <b>发现</b> 选项卡添加您想看的剧集。</string>\n  <string name=\"textHiddenEmpty\">您的 <b>隐藏</b> 列表目前是空的。\\n\\n<b>隐藏</b> 列表包含您不感兴趣的项目。\\n\\n隐藏的项目将不再出现在 <b>进度</b>, <b>发现</b> 和其它模块中。</string>\n</resources>\n"
  },
  {
    "path": "ui-navigation/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-navigation/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_navigation'\n}\n\ndependencies {\n  api libs.bundles.android.navigation\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-navigation/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "ui-navigation/src/main/java/com.michaldrabik.ui_navigation.java/NavigationArgs.kt",
    "content": "package com.michaldrabik.ui_navigation.java\n\nobject NavigationArgs {\n  const val ARG_ID = \"ARG_ID\"\n  const val ARG_SHOW_ID = \"ARG_SHOW_ID\"\n  const val ARG_MOVIE_ID = \"ARG_MOVIE_ID\"\n  const val ARG_EPISODE_ID = \"ARG_EPISODE_ID\"\n  const val ARG_COMMENT_ID = \"ARG_COMMENT_ID\"\n  const val ARG_COLLECTION_ID = \"ARG_COLLECTION_ID\"\n  const val ARG_PERSON = \"ARG_PERSON\"\n  const val ARG_PERSON_ARGS = \"ARG_PERSON_ARGS\"\n  const val ARG_OPTIONS = \"ARG_OPTIONS\"\n  const val ARG_TITLE = \"ARG_TITLE\"\n  const val ARG_ITEM = \"ARG_ITEM\"\n\n  const val ARG_LIST = \"ARG_LIST\"\n  const val ARG_COMMENT = \"ARG_COMMENT\"\n  const val ARG_COMMENT_ACTION = \"ARG_COMMENT_ACTION\"\n  const val ARG_FAMILY = \"ARG_FAMILY\"\n  const val ARG_TYPE = \"ARG_TYPE\"\n  const val ARG_DEPARTMENT = \"ARG_DEPARTMENT\"\n  const val ARG_PICK_MODE = \"ARG_PICK_MODE\"\n  const val ARG_CUSTOM_IMAGE_CLEARED = \"ARG_CUSTOM_IMAGE_CLEARED\"\n  const val ARG_REPLY_USER = \"ARG_REPLY_USER\"\n  const val ARG_REQUEST_KEY = \"ARG_REQUEST_KEY\"\n\n  const val ARG_SORT_ORDERS = \"ARG_SORT_ORDERS\"\n  const val ARG_SELECTED_SORT_ORDER = \"ARG_SELECTED_SORT_ORDER\"\n  const val ARG_SELECTED_SORT_TYPE = \"ARG_SELECTED_SORT_TYPE\"\n  const val ARG_SELECTED_NEW_AT_TOP = \"ARG_SELECTED_NEW_AT_TOP\"\n\n  const val REQUEST_CUSTOM_IMAGE = \"REQUEST_CUSTOM_IMAGE\"\n  const val REQUEST_COMMENT = \"REQUEST_COMMENT\"\n  const val REQUEST_EPISODE_DETAILS = \"REQUEST_EPISODE_DETAILS\"\n  const val REQUEST_DETAILS = \"REQUEST_DETAILS\"\n  const val REQUEST_SORT_ORDER = \"REQUEST_SORT_ORDER\"\n  const val REQUEST_RATING = \"REQUEST_RATING\"\n  fun requestSortOrderSection(section: String) = \"REQUEST_SORT_ORDER_SECTION_$section\"\n\n  const val ACTION_NEW_COMMENT = \"ACTION_NEW_COMMENT\"\n  const val ACTION_RATING_CHANGED = \"ACTION_RATING_CHANGED\"\n  const val ACTION_EPISODE_WATCHED = \"ACTION_EPISODE_WATCHED\"\n  const val ACTION_EPISODE_TAB_SELECTED = \"ACTION_EPISODE_TAB_SELECTED\"\n\n  const val REQUEST_CREATE_LIST = \"REQUEST_CREATE_LIST\"\n  const val REQUEST_MANAGE_LISTS = \"REQUEST_MANAGE_LISTS\"\n  const val REQUEST_REMOVE_TRAKT = \"REQUEST_REMOVE_TRAKT\"\n  const val REQUEST_ITEM_MENU = \"REQUEST_ITEM_MENU\"\n\n  const val RESULT = \"RESULT\"\n}\n"
  },
  {
    "path": "ui-navigation/src/main/res/anim/anim_fade_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:duration=\"200\"\n  android:fromAlpha=\"0.0\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:toAlpha=\"1.0\"\n  />"
  },
  {
    "path": "ui-navigation/src/main/res/anim/anim_fade_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:duration=\"250\"\n  android:fromAlpha=\"1.0\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:toAlpha=\"0.0\"\n  />"
  },
  {
    "path": "ui-navigation/src/main/res/anim/anim_in_from_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <translate\n    android:duration=\"250\"\n    android:fromXDelta=\"-15%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"0%\"\n    android:toYDelta=\"0%\"\n    />\n</set>\n"
  },
  {
    "path": "ui-navigation/src/main/res/anim/anim_in_from_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <alpha\n    android:duration=\"250\"\n    android:fromAlpha=\"0.0\"\n    android:toAlpha=\"1.0\"\n    />\n  <translate\n    android:duration=\"250\"\n    android:fromXDelta=\"100%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"0%\"\n    android:toYDelta=\"0%\"\n    />\n</set>\n"
  },
  {
    "path": "ui-navigation/src/main/res/anim/anim_out_from_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <translate\n    android:duration=\"250\"\n    android:fromXDelta=\"0%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"100%\"\n    android:toYDelta=\"0%\"\n    />\n  <alpha\n    android:duration=\"250\"\n    android:fromAlpha=\"1.0\"\n    android:toAlpha=\"0.0\"\n    />\n</set>\n"
  },
  {
    "path": "ui-navigation/src/main/res/anim/anim_out_from_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <translate\n    android:duration=\"250\"\n    android:fromXDelta=\"0%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"-15%\"\n    android:toYDelta=\"0%\"\n    />\n  <alpha\n    android:duration=\"200\"\n    android:fromAlpha=\"1.0\"\n    android:toAlpha=\"0.0\"\n    />\n</set>\n"
  },
  {
    "path": "ui-navigation/src/main/res/anim-ar/anim_in_from_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <translate\n    android:duration=\"250\"\n    android:fromXDelta=\"15%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"0%\"\n    android:toYDelta=\"0%\"\n    />\n</set>\n"
  },
  {
    "path": "ui-navigation/src/main/res/anim-ar/anim_in_from_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <alpha\n    android:duration=\"250\"\n    android:fromAlpha=\"0.0\"\n    android:toAlpha=\"1.0\"\n    />\n  <translate\n    android:duration=\"250\"\n    android:fromXDelta=\"-100%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"0%\"\n    android:toYDelta=\"0%\"\n    />\n</set>\n"
  },
  {
    "path": "ui-navigation/src/main/res/anim-ar/anim_out_from_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:duration=\"250\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <translate\n    android:fromXDelta=\"0%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"-100%\"\n    android:toYDelta=\"0%\"\n    />\n  <alpha\n    android:duration=\"250\"\n    android:fromAlpha=\"1.0\"\n    android:toAlpha=\"0.0\"\n    />\n</set>\n"
  },
  {
    "path": "ui-navigation/src/main/res/anim-ar/anim_out_from_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:duration=\"250\"\n  android:interpolator=\"@android:anim/decelerate_interpolator\"\n  android:shareInterpolator=\"true\"\n  >\n  <translate\n    android:fromXDelta=\"0%\"\n    android:fromYDelta=\"0%\"\n    android:toXDelta=\"15%\"\n    android:toYDelta=\"0%\"\n    />\n  <alpha\n    android:duration=\"200\"\n    android:fromAlpha=\"1.0\"\n    android:toAlpha=\"0.0\"\n    />\n</set>\n"
  },
  {
    "path": "ui-navigation/src/main/res/navigation/navigation_graph.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--suppress AndroidDomInspection -->\n<!-- Start destination is set programmatically -->\n<navigation\n  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/navigationGraph\"\n  tools:ignore=\"InvalidNavigation\"\n  >\n\n  <action\n    android:id=\"@+id/actionNavigateShowDetailsFragment\"\n    app:destination=\"@id/showDetailsFragment\"\n    />\n\n  <action\n    android:id=\"@+id/actionNavigateMovieDetailsFragment\"\n    app:destination=\"@id/movieDetailsFragment\"\n    />\n\n  <action\n    android:id=\"@+id/actionNavigateProgressFragment\"\n    app:destination=\"@id/progressMainFragment\"\n    app:launchSingleTop=\"true\"\n    app:popUpTo=\"@id/navigationGraph\"\n    app:popUpToInclusive=\"true\"\n    />\n\n  <action\n    android:id=\"@+id/actionNavigateProgressMoviesFragment\"\n    app:destination=\"@id/progressMoviesMainFragment\"\n    app:launchSingleTop=\"true\"\n    app:popUpTo=\"@id/navigationGraph\"\n    app:popUpToInclusive=\"true\"\n    />\n\n  <action\n    android:id=\"@+id/actionNavigateDiscoverFragment\"\n    app:destination=\"@id/discoverFragment\"\n    app:launchSingleTop=\"true\"\n    app:popUpTo=\"@id/navigationGraph\"\n    app:popUpToInclusive=\"true\"\n    />\n\n  <action\n    android:id=\"@+id/actionNavigateDiscoverMoviesFragment\"\n    app:destination=\"@id/discoverMoviesFragment\"\n    app:launchSingleTop=\"true\"\n    app:popUpTo=\"@id/navigationGraph\"\n    app:popUpToInclusive=\"true\"\n    />\n\n  <action\n    android:id=\"@+id/actionNavigateFollowedShowsFragment\"\n    app:destination=\"@id/followedShowsFragment\"\n    app:launchSingleTop=\"true\"\n    app:popUpTo=\"@id/navigationGraph\"\n    app:popUpToInclusive=\"true\"\n    />\n\n  <action\n    android:id=\"@+id/actionNavigateFollowedMoviesFragment\"\n    app:destination=\"@id/followedMoviesFragment\"\n    app:launchSingleTop=\"true\"\n    app:popUpTo=\"@id/navigationGraph\"\n    app:popUpToInclusive=\"true\"\n    />\n\n  <action\n    android:id=\"@+id/actionNavigateListsFragment\"\n    app:destination=\"@id/listsFragment\"\n    app:launchSingleTop=\"true\"\n    app:popUpTo=\"@id/navigationGraph\"\n    app:popUpToInclusive=\"true\"\n    />\n\n  <action\n    android:id=\"@+id/actionNavigateNewsFragment\"\n    app:destination=\"@id/newsFragment\"\n    app:launchSingleTop=\"true\"\n    app:popUpTo=\"@id/navigationGraph\"\n    app:popUpToInclusive=\"true\"\n    />\n\n  <fragment\n    android:id=\"@+id/discoverFragment\"\n    android:name=\"com.michaldrabik.ui_discover.DiscoverFragment\"\n    android:label=\"fragmentDiscover\"\n    >\n    <action\n      android:id=\"@+id/actionDiscoverFragmentToShowDetailsFragment\"\n      app:destination=\"@id/showDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionDiscoverFragmentToSearchFragment\"\n      app:destination=\"@id/searchFragment\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionDiscoverFragmentToSettingsFragment\"\n      app:destination=\"@id/settingsFragment\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionDiscoverFragmentToItemMenu\"\n      app:destination=\"@id/showItemContextDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionDiscoverFragmentToFiltersGenres\"\n      app:destination=\"@id/discoverFiltersGenresDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionDiscoverFragmentToFiltersNetworks\"\n      app:destination=\"@id/discoverFiltersNetworksDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionDiscoverFragmentToFiltersFeed\"\n      app:destination=\"@id/discoverFiltersFeedDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionDiscoverFragmentToPremium\"\n      app:destination=\"@id/premiumFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/discoverMoviesFragment\"\n    android:name=\"com.michaldrabik.ui_discover_movies.DiscoverMoviesFragment\"\n    android:label=\"fragmentDiscoverMovies\"\n    >\n    <action\n      android:id=\"@+id/actionDiscoverMoviesFragmentToMovieDetailsFragment\"\n      app:destination=\"@id/movieDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionDiscoverMoviesFragmentToSearchFragment\"\n      app:destination=\"@id/searchFragment\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionDiscoverMoviesFragmentToSettingsFragment\"\n      app:destination=\"@id/settingsFragment\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionDiscoverMoviesFragmentToItemMenu\"\n      app:destination=\"@id/movieItemContextDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionDiscoverMoviesFragmentToFiltersFeed\"\n      app:destination=\"@id/discoverMoviesFiltersFeedDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionDiscoverMoviesFragmentToFiltersGenres\"\n      app:destination=\"@id/discoverMoviesFiltersGenresDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionDiscoverMoviesFragmentToPremium\"\n      app:destination=\"@id/premiumFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/progressMainFragment\"\n    android:name=\"com.michaldrabik.ui_progress.main.ProgressMainFragment\"\n    android:label=\"fragmentProgressMain\"\n    >\n    <action\n      android:id=\"@+id/actionProgressFragmentToShowDetailsFragment\"\n      app:destination=\"@id/showDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionProgressFragmentToMovieDetailsFragment\"\n      app:destination=\"@id/movieDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionProgressFragmentToSettingsFragment\"\n      app:destination=\"@id/settingsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionProgressFragmentToTraktSyncFragment\"\n      app:destination=\"@id/traktSyncFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionProgressFragmentToEpisodeDetails\"\n      app:destination=\"@id/episodeDetailsDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionProgressFragmentToSortOrder\"\n      app:destination=\"@id/sortOrderDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionProgressFragmentToSearch\"\n      app:destination=\"@id/searchFragment\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionProgressFragmentToItemMenu\"\n      app:destination=\"@id/showItemContextDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionProgressFragmentToRating\"\n      app:destination=\"@id/ratingDialog\"\n      app:launchSingleTop=\"true\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/progressMoviesMainFragment\"\n    android:name=\"com.michaldrabik.ui_progress_movies.main.ProgressMoviesMainFragment\"\n    android:label=\"fragmentProgressMain\"\n    >\n    <action\n      android:id=\"@+id/actionProgressMoviesFragmentToMovieDetailsFragment\"\n      app:destination=\"@id/movieDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionProgressMoviesFragmentToShowDetailsFragment\"\n      app:destination=\"@id/showDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionProgressMoviesFragmentToSettingsFragment\"\n      app:destination=\"@id/settingsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionProgressMoviesFragmentToTraktSyncFragment\"\n      app:destination=\"@id/traktSyncFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionProgressMoviesFragmentToSortOrder\"\n      app:destination=\"@id/sortOrderDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionProgressMoviesFragmentToSearch\"\n      app:destination=\"@id/searchFragment\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionProgressMoviesFragmentToItemMenu\"\n      app:destination=\"@id/movieItemContextDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionProgressMoviesFragmentToRating\"\n      app:destination=\"@id/ratingDialog\"\n      app:launchSingleTop=\"true\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/showDetailsFragment\"\n    android:name=\"com.michaldrabik.ui_show.ShowDetailsFragment\"\n    android:label=\"fragmentShowDetails\"\n    >\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToSelf\"\n      app:destination=\"@id/showDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:exitAnim=\"@anim/anim_fade_out\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToArtGallery\"\n      app:destination=\"@id/artGalleryFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:exitAnim=\"@anim/anim_fade_out\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToCustomImages\"\n      app:destination=\"@id/customImagesDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToManageLists\"\n      app:destination=\"@id/manageListsDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToPostComment\"\n      app:destination=\"@id/postCommentDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentEpisodeDetails\"\n      app:destination=\"@id/episodeDetailsDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToPremium\"\n      app:destination=\"@id/premiumFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToRemoveTraktHidden\"\n      app:destination=\"@id/removeTraktHiddenDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToRemoveTraktWatchlist\"\n      app:destination=\"@id/removeTraktWatchlistDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToRemoveTraktProgress\"\n      app:destination=\"@id/removeTraktProgressDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToLinks\"\n      app:destination=\"@id/linksDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToPerson\"\n      app:destination=\"@id/personDetailsDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToPeopleList\"\n      app:destination=\"@id/peopleListDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToRating\"\n      app:destination=\"@id/ratingDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToComments\"\n      app:destination=\"@id/commentsFragment\"\n      app:enterAnim=\"@anim/anim_in_from_right\"\n      app:exitAnim=\"@anim/anim_out_from_right\"\n      app:launchSingleTop=\"true\"\n      app:popEnterAnim=\"@anim/anim_in_from_left\"\n      app:popExitAnim=\"@anim/anim_out_from_left\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToEpisodes\"\n      app:destination=\"@id/showDetailsEpisodesFragment\"\n      app:enterAnim=\"@anim/anim_in_from_right\"\n      app:exitAnim=\"@anim/anim_out_from_right\"\n      app:launchSingleTop=\"true\"\n      app:popEnterAnim=\"@anim/anim_in_from_left\"\n      app:popExitAnim=\"@anim/anim_out_from_left\"\n      />\n    <action\n      android:id=\"@+id/actionShowDetailsFragmentToContext\"\n      app:destination=\"@id/showItemContextDialog\"\n      app:launchSingleTop=\"true\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/showDetailsEpisodesFragment\"\n    android:name=\"com.michaldrabik.ui_show.episodes.ShowDetailsEpisodesFragment\"\n    android:label=\"fragmentEpisodes\"\n    >\n    <action\n      android:id=\"@+id/actionEpisodesFragmentToRemoveTraktProgress\"\n      app:destination=\"@id/removeTraktProgressDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionEpisodesFragmentToEpisodesDetails\"\n      app:destination=\"@id/episodeDetailsDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionEpisodesFragmentToRating\"\n      app:destination=\"@id/ratingDialog\"\n      app:launchSingleTop=\"true\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/movieDetailsFragment\"\n    android:name=\"com.michaldrabik.ui_movie.MovieDetailsFragment\"\n    android:label=\"fragmentMovieDetails\"\n    >\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToSelf\"\n      app:destination=\"@id/movieDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:exitAnim=\"@anim/anim_fade_out\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToArtGallery\"\n      app:destination=\"@id/artGalleryFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:exitAnim=\"@anim/anim_fade_out\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToCustomImages\"\n      app:destination=\"@id/customImagesDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToManageLists\"\n      app:destination=\"@id/manageListsDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToPostComment\"\n      app:destination=\"@id/postCommentDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToPremium\"\n      app:destination=\"@id/premiumFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToRemoveTraktWatchlist\"\n      app:destination=\"@id/removeTraktWatchlistDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToRemoveTraktProgress\"\n      app:destination=\"@id/removeTraktProgressDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToRemoveTraktHidden\"\n      app:destination=\"@id/removeTraktHiddenDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToLinks\"\n      app:destination=\"@id/linksDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToPerson\"\n      app:destination=\"@id/personDetailsDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToPeopleList\"\n      app:destination=\"@id/peopleListDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToRating\"\n      app:destination=\"@id/ratingDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToComments\"\n      app:destination=\"@id/commentsFragment\"\n      app:enterAnim=\"@anim/anim_in_from_right\"\n      app:exitAnim=\"@anim/anim_out_from_right\"\n      app:launchSingleTop=\"true\"\n      app:popEnterAnim=\"@anim/anim_in_from_left\"\n      app:popExitAnim=\"@anim/anim_out_from_left\"\n      />\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToCollection\"\n      app:destination=\"@id/movieCollectionDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionMovieDetailsFragmentToContext\"\n      app:destination=\"@id/movieItemContextDialog\"\n      app:launchSingleTop=\"true\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/artGalleryFragment\"\n    android:name=\"com.michaldrabik.ui_gallery.fanart.ArtGalleryFragment\"\n    android:label=\"fragmentArtGallery\"\n    />\n\n  <fragment\n    android:id=\"@+id/personGalleryFragment\"\n    android:name=\"com.michaldrabik.ui_people.gallery.PersonGalleryFragment\"\n    android:label=\"fragmentPersonGallery\"\n    />\n\n  <fragment\n    android:id=\"@+id/followedShowsFragment\"\n    android:name=\"com.michaldrabik.ui_my_shows.main.FollowedShowsFragment\"\n    android:label=\"fragmentFollowedShows\"\n    >\n    <action\n      android:id=\"@+id/actionFollowedShowsFragmentToShowDetailsFragment\"\n      app:destination=\"@id/showDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedShowsFragmentToSettingsFragment\"\n      app:destination=\"@id/settingsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedShowsFragmentToStatisticsFragment\"\n      app:destination=\"@id/statisticsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedShowsFragmentToSortOrder\"\n      app:destination=\"@id/sortOrderDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedShowsFragmentToNetworks\"\n      app:destination=\"@id/collectionFiltersNetworkDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedShowsFragmentToGenres\"\n      app:destination=\"@id/collectionFiltersGenreDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedShowsFragmentToSearch\"\n      app:destination=\"@id/searchFragment\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedShowsFragmentToItemMenu\"\n      app:destination=\"@id/showItemContextDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedShowsFragmentToMyShowsFilters\"\n      app:destination=\"@id/myShowsFiltersDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedShowsFragmentToPremium\"\n      app:destination=\"@id/premiumFragment\"\n      app:launchSingleTop=\"true\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/followedMoviesFragment\"\n    android:name=\"com.michaldrabik.ui_my_movies.main.FollowedMoviesFragment\"\n    android:label=\"fragmentFollowedMovies\"\n    >\n    <action\n      android:id=\"@+id/actionFollowedMoviesFragmentToMovieDetailsFragment\"\n      app:destination=\"@id/movieDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedMoviesFragmentToSettingsFragment\"\n      app:destination=\"@id/settingsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedMoviesFragmentToStatisticsFragment\"\n      app:destination=\"@id/statisticsMoviesFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedMoviesFragmentToSortOrder\"\n      app:destination=\"@id/sortOrderDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedMoviesFragmentToSearch\"\n      app:destination=\"@id/searchFragment\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedMoviesFragmentToItemMenu\"\n      app:destination=\"@id/movieItemContextDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedMoviesFragmentToPremium\"\n      app:destination=\"@id/premiumFragment\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionFollowedMoviesFragmentToGenres\"\n      app:destination=\"@id/collectionMoviesFiltersGenreDialog\"\n      app:launchSingleTop=\"true\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/listsFragment\"\n    android:name=\"com.michaldrabik.ui_lists.lists.ListsFragment\"\n    android:label=\"fragmentLists\"\n    >\n    <action\n      android:id=\"@+id/actionListsFragmentToDetailsFragment\"\n      app:destination=\"@id/listDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionListsFragmentToSettingsFragment\"\n      app:destination=\"@id/settingsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionListsFragmentToCreateListDialog\"\n      app:destination=\"@id/createListDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionListsFragmentToSortOrder\"\n      app:destination=\"@id/sortOrderDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionListsFragmentToSearch\"\n      app:destination=\"@id/searchFragment\"\n      app:launchSingleTop=\"true\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/listDetailsFragment\"\n    android:name=\"com.michaldrabik.ui_lists.details.ListDetailsFragment\"\n    android:label=\"fragmentListDetails\"\n    >\n    <action\n      android:id=\"@+id/actionListDetailsFragmentToEditListDialog\"\n      app:destination=\"@id/createListDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionListDetailsFragmentToShowDetailsFragment\"\n      app:destination=\"@id/showDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionListDetailsFragmentToMovieDetailsFragment\"\n      app:destination=\"@id/movieDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionListDetailsFragmentToSortOrder\"\n      app:destination=\"@id/sortOrderDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionListDetailsFragmentToPremium\"\n      app:destination=\"@id/premiumFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/searchFragment\"\n    android:name=\"com.michaldrabik.ui_search.SearchFragment\"\n    android:label=\"fragmentSearch\"\n    >\n    <action\n      android:id=\"@+id/actionSearchFragmentToShowDetailsFragment\"\n      app:destination=\"@id/showDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionSearchFragmentToMovieDetailsFragment\"\n      app:destination=\"@id/movieDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionSearchFragmentToSortOrder\"\n      app:destination=\"@id/sortOrderDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionSearchFragmentToShowItemMenu\"\n      app:destination=\"@id/showItemContextDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionSearchFragmentToMovieItemMenu\"\n      app:destination=\"@id/movieItemContextDialog\"\n      app:launchSingleTop=\"true\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/settingsFragment\"\n    android:name=\"com.michaldrabik.ui_settings.SettingsFragment\"\n    android:label=\"fragmentSettings\"\n    >\n    <action\n      android:id=\"@+id/actionSettingsFragmentToTraktSync\"\n      app:destination=\"@id/traktSyncFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionSettingsFragmentToPremium\"\n      app:destination=\"@id/premiumFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionSettingsFragmentToSpoilersShows\"\n      app:destination=\"@id/settingsSpoilersShowsDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionSettingsFragmentToSpoilersMovies\"\n      app:destination=\"@id/settingsSpoilersMoviesDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionSettingsFragmentToSpoilersEpisodes\"\n      app:destination=\"@id/settingsSpoilersEpisodesDialog\"\n      app:launchSingleTop=\"true\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/statisticsFragment\"\n    android:name=\"com.michaldrabik.ui_statistics.StatisticsFragment\"\n    android:label=\"fragmentStatistics\"\n    >\n    <action\n      android:id=\"@+id/actionStatisticsFragmentToShowDetailsFragment\"\n      app:destination=\"@id/showDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/statisticsMoviesFragment\"\n    android:name=\"com.michaldrabik.ui_statistics_movies.StatisticsMoviesFragment\"\n    android:label=\"fragmentStatisticsMovies\"\n    >\n    <action\n      android:id=\"@+id/actionStatisticsMoviesFragmentToMovieDetailsFragment\"\n      app:destination=\"@id/movieDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/commentsFragment\"\n    android:name=\"com.michaldrabik.ui_comments.fragment.CommentsFragment\"\n    android:label=\"fragmentComments\"\n    >\n    <action\n      android:id=\"@+id/actionCommentsFragmentToPostComment\"\n      app:destination=\"@id/postCommentDialog\"\n      app:launchSingleTop=\"true\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/newsFragment\"\n    android:name=\"com.michaldrabik.ui_news.NewsFragment\"\n    android:label=\"fragmentNews\"\n    >\n    <action\n      android:id=\"@+id/actionNewsFragmentToSettingsFragment\"\n      app:destination=\"@id/settingsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n  </fragment>\n\n  <fragment\n    android:id=\"@+id/traktSyncFragment\"\n    android:name=\"com.michaldrabik.ui_trakt_sync.TraktSyncFragment\"\n    android:label=\"fragmentTraktSync\"\n    />\n\n  <fragment\n    android:id=\"@+id/premiumFragment\"\n    android:name=\"com.michaldrabik.ui_premium.PremiumFragment\"\n    android:label=\"fragmentPremium\"\n    />\n\n  <dialog\n    android:id=\"@+id/personDetailsDialog\"\n    android:name=\"com.michaldrabik.ui_people.details.PersonDetailsBottomSheet\"\n    android:label=\"dialogPersonDetails\"\n    >\n    <action\n      android:id=\"@+id/actionPersonDetailsDialogToLinks\"\n      app:destination=\"@id/personLinksDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionPersonDetailsDialogToShow\"\n      app:destination=\"@id/showDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionPersonDetailsDialogToMovie\"\n      app:destination=\"@id/movieDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionPersonDetailsDialogToGallery\"\n      app:destination=\"@id/personGalleryFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n  </dialog>\n\n  <dialog\n    android:id=\"@+id/peopleListDialog\"\n    android:name=\"com.michaldrabik.ui_people.list.PeopleListBottomSheet\"\n    android:label=\"dialogPeopleList\"\n    >\n    <action\n      android:id=\"@+id/actionPeopleListDialogToDetails\"\n      app:destination=\"@id/personDetailsDialog\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n  </dialog>\n\n  <dialog\n    android:id=\"@+id/customImagesDialog\"\n    android:name=\"com.michaldrabik.ui_gallery.custom.CustomImagesBottomSheet\"\n    android:label=\"dialogCustomImages\"\n    >\n    <action\n      android:id=\"@+id/actionCustomImagesDialogToArtGallery\"\n      app:destination=\"@id/artGalleryFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:launchSingleTop=\"true\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n  </dialog>\n\n  <dialog\n    android:id=\"@+id/episodeDetailsDialog\"\n    android:name=\"com.michaldrabik.ui_episodes.details.EpisodeDetailsBottomSheet\"\n    android:label=\"dialogEpisodeDetails\"\n    >\n    <action\n      android:id=\"@+id/actionEpisodeDetailsDialogToPostComment\"\n      app:destination=\"@id/postCommentDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionEpisodeDetailsDialogToRate\"\n      app:destination=\"@id/ratingDialog\"\n      app:launchSingleTop=\"true\"\n      />\n  </dialog>\n\n  <dialog\n    android:id=\"@+id/postCommentDialog\"\n    android:name=\"com.michaldrabik.ui_comments.post.PostCommentBottomSheet\"\n    android:label=\"dialogPostComment\"\n    />\n\n  <dialog\n    android:id=\"@+id/createListDialog\"\n    android:name=\"com.michaldrabik.ui_lists.create.CreateListBottomSheet\"\n    android:label=\"dialogCreateList\"\n    />\n\n  <dialog\n    android:id=\"@+id/manageListsDialog\"\n    android:name=\"com.michaldrabik.ui_lists.manage.ManageListsBottomSheet\"\n    android:label=\"dialogManageLists\"\n    >\n    <action\n      android:id=\"@+id/actionManageListsDialogToCreateListDialog\"\n      app:destination=\"@id/createListDialog\"\n      app:launchSingleTop=\"true\"\n      />\n  </dialog>\n\n  <dialog\n    android:id=\"@+id/removeTraktHiddenDialog\"\n    android:name=\"com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_hidden.RemoveTraktHiddenBottomSheet\"\n    android:label=\"dialogRemoveTraktHidden\"\n    />\n\n  <dialog\n    android:id=\"@+id/removeTraktWatchlistDialog\"\n    android:name=\"com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_watchlist.RemoveTraktWatchlistBottomSheet\"\n    android:label=\"dialogRemoveTraktWatchlist\"\n    />\n\n  <dialog\n    android:id=\"@+id/removeTraktProgressDialog\"\n    android:name=\"com.michaldrabik.ui_base.common.sheets.remove_trakt.remove_trakt_progress.RemoveTraktProgressBottomSheet\"\n    android:label=\"dialogRemoveTraktProgress\"\n    />\n\n  <dialog\n    android:id=\"@+id/sortOrderDialog\"\n    android:name=\"com.michaldrabik.ui_base.common.sheets.sort_order.SortOrderBottomSheet\"\n    android:label=\"dialogSortOrder\"\n    />\n\n  <dialog\n    android:id=\"@+id/linksDialog\"\n    android:name=\"com.michaldrabik.ui_base.common.sheets.links.LinksBottomSheet\"\n    android:label=\"dialogLinks\"\n    />\n\n  <dialog\n    android:id=\"@+id/personLinksDialog\"\n    android:name=\"com.michaldrabik.ui_people.details.links.PersonLinksBottomSheet\"\n    android:label=\"dialogPersonLinks\"\n    />\n\n  <dialog\n    android:id=\"@+id/showItemContextDialog\"\n    android:name=\"com.michaldrabik.ui_base.common.sheets.context_menu.show.ShowContextMenuBottomSheet\"\n    android:label=\"dialogShowItemContext\"\n    >\n    <action\n      android:id=\"@+id/actionShowItemContextDialogToRemoveTraktHidden\"\n      app:destination=\"@id/removeTraktHiddenDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionShowItemContextDialogToRemoveTraktWatchlist\"\n      app:destination=\"@id/removeTraktWatchlistDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionShowItemContextDialogToRemoveTraktProgress\"\n      app:destination=\"@id/removeTraktProgressDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionShowItemContextDialogToShowDetails\"\n      app:destination=\"@id/showDetailsFragment\"\n      app:launchSingleTop=\"true\"\n      />\n  </dialog>\n\n  <dialog\n    android:id=\"@+id/movieItemContextDialog\"\n    android:name=\"com.michaldrabik.ui_base.common.sheets.context_menu.movie.MovieContextMenuBottomSheet\"\n    android:label=\"dialogMovieItemContext\"\n    >\n    <action\n      android:id=\"@+id/actionMovieItemContextDialogToRemoveTraktHidden\"\n      app:destination=\"@id/removeTraktHiddenDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionMovieItemContextDialogToRemoveTraktWatchlist\"\n      app:destination=\"@id/removeTraktWatchlistDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionMovieItemContextDialogToRemoveTraktProgress\"\n      app:destination=\"@id/removeTraktProgressDialog\"\n      app:launchSingleTop=\"true\"\n      />\n    <action\n      android:id=\"@+id/actionMovieItemContextDialogToMovieDetails\"\n      app:destination=\"@id/movieDetailsFragment\"\n      app:launchSingleTop=\"true\"\n      />\n  </dialog>\n\n  <dialog\n    android:id=\"@+id/ratingDialog\"\n    android:name=\"com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet\"\n    android:label=\"dialogRating\"\n    />\n\n  <dialog\n    android:id=\"@+id/discoverFiltersFeedDialog\"\n    android:name=\"com.michaldrabik.ui_discover.filters.feed.DiscoverFiltersFeedBottomSheet\"\n    android:label=\"dialogMoviesFilters\"\n    />\n\n  <dialog\n    android:id=\"@+id/discoverMoviesFiltersFeedDialog\"\n    android:name=\"com.michaldrabik.ui_discover_movies.filters.feed.DiscoverMoviesFiltersFeedBottomSheet\"\n    android:label=\"dialogDiscoverMoviesFilters\"\n    />\n\n  <dialog\n    android:id=\"@+id/discoverFiltersGenresDialog\"\n    android:name=\"com.michaldrabik.ui_discover.filters.genres.DiscoverFiltersGenresBottomSheet\"\n    android:label=\"dialogDiscoverFiltersGenres\"\n    />\n\n  <dialog\n    android:id=\"@+id/discoverFiltersNetworksDialog\"\n    android:name=\"com.michaldrabik.ui_discover.filters.networks.DiscoverFiltersNetworksBottomSheet\"\n    android:label=\"dialogDiscoverFiltersNetworks\"\n    />\n\n  <dialog\n    android:id=\"@+id/discoverMoviesFiltersGenresDialog\"\n    android:name=\"com.michaldrabik.ui_discover_movies.filters.genres.DiscoverMoviesFiltersGenresBottomSheet\"\n    android:label=\"dialogDiscoverMoviesFiltersGenres\"\n    />\n\n  <dialog\n    android:id=\"@+id/myShowsFiltersDialog\"\n    android:name=\"com.michaldrabik.ui_my_shows.myshows.filters.MyShowsFiltersBottomSheet\"\n    android:label=\"dialogMyShowsFilters\"\n    />\n\n  <dialog\n    android:id=\"@+id/collectionFiltersNetworkDialog\"\n    android:name=\"com.michaldrabik.ui_my_shows.common.filters.network.CollectionFiltersNetworkBottomSheet\"\n    android:label=\"dialogCollectionFiltersNetwork\"\n    />\n\n  <dialog\n    android:id=\"@+id/collectionFiltersGenreDialog\"\n    android:name=\"com.michaldrabik.ui_my_shows.common.filters.genre.CollectionFiltersGenreBottomSheet\"\n    android:label=\"dialogCollectionFiltersGenre\"\n    />\n\n  <dialog\n    android:id=\"@+id/collectionMoviesFiltersGenreDialog\"\n    android:name=\"com.michaldrabik.ui_my_movies.filters.genre.CollectionFiltersGenreBottomSheet\"\n    android:label=\"dialogCollectionFiltersGenre\"\n    />\n\n  <dialog\n    android:id=\"@+id/settingsSpoilersShowsDialog\"\n    android:name=\"com.michaldrabik.ui_settings.sections.spoilers.shows.SpoilersShowsBottomSheet\"\n    android:label=\"dialogSettingsSpoilersShows\"\n    />\n\n  <dialog\n    android:id=\"@+id/settingsSpoilersMoviesDialog\"\n    android:name=\"com.michaldrabik.ui_settings.sections.spoilers.movies.SpoilersMoviesBottomSheet\"\n    android:label=\"dialogSettingsSpoilersMovies\"\n    />\n\n  <dialog\n    android:id=\"@+id/settingsSpoilersEpisodesDialog\"\n    android:name=\"com.michaldrabik.ui_settings.sections.spoilers.episodes.SpoilersEpisodesBottomSheet\"\n    android:label=\"dialogSettingsSpoilersEpisodes\"\n    />\n\n  <dialog\n    android:id=\"@+id/movieCollectionDialog\"\n    android:name=\"com.michaldrabik.ui_movie.sections.collections.details.MovieDetailsCollectionBottomSheet\"\n    android:label=\"dialogMovieCollection\"\n    >\n    <action\n      android:id=\"@+id/actionMovieCollectionDialogToMovie\"\n      app:destination=\"@id/movieDetailsFragment\"\n      app:enterAnim=\"@anim/anim_fade_in\"\n      app:popExitAnim=\"@anim/anim_fade_out\"\n      />\n    <action\n      android:id=\"@+id/actionMovieCollectionDialogToContextDialog\"\n      app:destination=\"@id/movieItemContextDialog\"\n      app:launchSingleTop=\"true\"\n      />\n  </dialog>\n\n</navigation>\n"
  },
  {
    "path": "ui-news/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-news/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_news'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-local')\n  implementation project(':data-remote')\n  implementation project(':repository')\n  implementation project(':ui-base')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  implementation libs.android.browser\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-news/src/main/AndroidManifest.xml",
    "content": "<manifest\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n  <application android:theme=\"@style/AppTheme\" />\n</manifest>\n"
  },
  {
    "path": "ui-news/src/main/java/com/michaldrabik/ui_news/NewsFragment.kt",
    "content": "package com.michaldrabik.ui_news\n\nimport android.content.ComponentName\nimport android.content.ServiceConnection\nimport android.content.res.Configuration\nimport android.graphics.BitmapFactory\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport androidx.activity.addCallback\nimport androidx.browser.customtabs.CustomTabColorSchemeParams\nimport androidx.browser.customtabs.CustomTabsClient\nimport androidx.browser.customtabs.CustomTabsIntent\nimport androidx.browser.customtabs.CustomTabsServiceConnection\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.OnTabReselectedListener\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.addDivider\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.enableUi\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.openWebUrl\nimport com.michaldrabik.ui_base.utilities.extensions.updateTopMargin\nimport com.michaldrabik.ui_base.utilities.ui.EqualSpacingItemDecoration\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.NewsItem\nimport com.michaldrabik.ui_news.databinding.FragmentNewsBinding\nimport com.michaldrabik.ui_news.providers.NewsLayoutManagerProvider\nimport com.michaldrabik.ui_news.recycler.NewsAdapter\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass NewsFragment :\n  BaseFragment<NewsViewModel>(R.layout.fragment_news),\n  OnTabReselectedListener {\n\n  companion object {\n    private const val ARG_HEADER_POSITION = \"ARG_HEADER_POSITION\"\n  }\n\n  @Inject lateinit var settingsRepository: SettingsViewModeRepository\n\n  override val viewModel by viewModels<NewsViewModel>()\n  private val binding by viewBinding(FragmentNewsBinding::bind)\n\n  private var tabsService: ServiceConnection? = null\n  private var tabsClient: CustomTabsClient? = null\n  private var adapter: NewsAdapter? = null\n  private var layoutManager: LayoutManager? = null\n\n  private var headerTranslation = 0F\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    savedInstanceState?.let {\n      headerTranslation = it.getFloat(ARG_HEADER_POSITION)\n    }\n  }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupStatusBar()\n    setupRecycler()\n    setupSwipeRefresh()\n    setupCustomTabs()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.messageFlow.collect { showSnack(it) } }\n    )\n  }\n\n  override fun onResume() {\n    super.onResume()\n    showNavigation()\n  }\n\n  override fun onSaveInstanceState(outState: Bundle) {\n    super.onSaveInstanceState(outState)\n    outState.putFloat(ARG_HEADER_POSITION, headerTranslation)\n  }\n\n  override fun onPause() {\n    enableUi()\n    headerTranslation = binding.fragmentNewsHeaderView.translationY\n    super.onPause()\n  }\n\n  private fun setupView() {\n    with(binding.fragmentNewsHeaderView) {\n      onSettingsClickListener = { openSettings() }\n      onViewTypeClickListener = { viewModel.toggleViewType() }\n      translationY = headerTranslation\n    }\n    with(binding.fragmentNewsFiltersView) {\n      onChipsChangeListener = { viewModel.loadItems(false, it) }\n      translationY = headerTranslation\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      fragmentNewsRoot.doOnApplyWindowInsets { _, insets, _, _ ->\n        val statusBarSize = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top\n        fragmentNewsRecycler.updatePadding(top = dimenToPx(R.dimen.newsRecyclerTopPadding) + statusBarSize)\n        fragmentNewsHeaderView.updateTopMargin(dimenToPx(R.dimen.spaceSmall) + statusBarSize)\n        fragmentNewsFiltersView.updateTopMargin(dimenToPx(R.dimen.newsFiltersTopPadding) + statusBarSize)\n        fragmentNewsSwipeRefresh.setProgressViewOffset(true, 0, dimenToPx(R.dimen.newsSwipeRefreshEndOffset) + statusBarSize)\n      }\n    }\n  }\n\n  private fun setupRecycler() {\n    layoutManager = NewsLayoutManagerProvider.provideLayoutManger(requireContext(), settingsRepository)\n    adapter = NewsAdapter(\n      itemClickListener = { openLink(it.item) },\n      listChangeListener = { scrollToTop(false) }\n    ).apply {\n      stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY\n    }\n    binding.fragmentNewsRecycler.apply {\n      adapter = this@NewsFragment.adapter\n      layoutManager = this@NewsFragment.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      setHasFixedSize(true)\n      if (requireContext().isTablet()) {\n        addItemDecoration(EqualSpacingItemDecoration(dimenToPx(R.dimen.spaceNormal)))\n      } else {\n        addDivider(R.drawable.divider_news, VERTICAL)\n      }\n    }\n  }\n\n  private fun setupSwipeRefresh() =\n    with(binding.fragmentNewsSwipeRefresh) {\n      val color = requireContext().colorFromAttr(R.attr.colorAccent)\n      setProgressBackgroundColorSchemeColor(requireContext().colorFromAttr(R.attr.colorSearchViewBackground))\n      setColorSchemeColors(color, color, color)\n      setOnRefreshListener {\n        viewModel.loadItems(forceRefresh = true)\n      }\n    }\n\n  private fun setupCustomTabs() {\n    tabsService = object : CustomTabsServiceConnection() {\n      override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {\n        tabsClient = client\n        tabsClient?.warmup(0)\n      }\n\n      override fun onServiceDisconnected(name: ComponentName?) {\n        tabsClient = null\n      }\n    }\n    CustomTabsClient.bindCustomTabsService(\n      requireActivity(),\n      \"com.android.chrome\",\n      (tabsService as CustomTabsServiceConnection)\n    )\n  }\n\n  override fun setupBackPressed() {\n    val dispatcher = requireActivity().onBackPressedDispatcher\n    dispatcher.addCallback(viewLifecycleOwner) {\n      isEnabled = false\n      activity?.onBackPressed()\n    }\n  }\n\n  private fun openLink(item: NewsItem) {\n    if (item.isVideo) {\n      openWebUrl(item.url) ?: showSnack(MessageEvent.Info(R.string.errorCouldNotFindApp))\n    } else {\n      val context = requireActivity()\n      val tabColor = context.colorFromAttr(R.attr.colorBottomMenuBackground)\n\n      val params = CustomTabColorSchemeParams.Builder()\n        .setToolbarColor(tabColor)\n        .setNavigationBarColor(tabColor)\n        .build()\n\n      val closeButton = when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {\n        Configuration.UI_MODE_NIGHT_YES -> R.drawable.ic_arrow_back_tabs\n        Configuration.UI_MODE_NIGHT_NO -> R.drawable.ic_arrow_back_tabs_black\n        else -> R.drawable.ic_arrow_back_tabs\n      }\n\n      val tabsIntent = CustomTabsIntent.Builder()\n        .setCloseButtonIcon(BitmapFactory.decodeResource(resources, closeButton))\n        .setDefaultColorSchemeParams(params)\n        .setStartAnimations(context, R.anim.anim_slide_in_from_right, R.anim.anim_slide_out_from_right)\n        .setExitAnimations(context, R.anim.anim_slide_in_from_left, R.anim.anim_slide_out_from_left)\n        .build()\n\n      tabsIntent.launchUrl(context, Uri.parse(item.url))\n    }\n  }\n\n  private fun openSettings() {\n    hideNavigation()\n    navigateTo(R.id.actionNewsFragmentToSettingsFragment)\n  }\n\n  private fun render(ui: NewsUiState) {\n    with(ui) {\n      adapter?.run {\n        setViewType(viewType)\n        setItems(items)\n      }\n\n      with(binding) {\n        fragmentNewsRecycler.fadeIf(items.isNotEmpty())\n        fragmentNewsFiltersView.setFilters(filters)\n        fragmentNewsFiltersView.fadeIf(items.isNotEmpty())\n        fragmentNewsEmptyView.root.fadeIf(items.isEmpty() && !isLoading)\n        fragmentNewsHeaderView.setViewType(viewType)\n\n        fragmentNewsSwipeRefresh.isRefreshing = isLoading\n        fragmentNewsFiltersView.isEnabled = !isLoading\n      }\n    }\n  }\n\n  private fun scrollToTop(smooth: Boolean = true) {\n    if (view == null) return\n    with(binding) {\n      fragmentNewsHeaderView.animate().translationY(0F).start()\n      fragmentNewsFiltersView.animate().translationY(0F).start()\n      when {\n        smooth -> fragmentNewsRecycler.smoothScrollToPosition(0)\n        else -> fragmentNewsRecycler.scrollToPosition(0)\n      }\n    }\n  }\n\n  override fun onTabReselected() = scrollToTop()\n\n  override fun onDestroyView() {\n    adapter = null\n    layoutManager = null\n    tabsClient = null\n    if (tabsService != null) {\n      activity?.unbindService(tabsService!!)\n      tabsService = null\n    }\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-news/src/main/java/com/michaldrabik/ui_news/NewsUiState.kt",
    "content": "package com.michaldrabik.ui_news\n\nimport com.michaldrabik.ui_model.NewsItem\nimport com.michaldrabik.ui_news.recycler.NewsListItem\nimport com.michaldrabik.ui_news.views.item.NewsItemViewType\n\ndata class NewsUiState(\n  val items: List<NewsListItem> = emptyList(),\n  val filters: List<NewsItem.Type> = emptyList(),\n  val viewType: NewsItemViewType = NewsItemViewType.ROW,\n  val isLoading: Boolean = false,\n)\n"
  },
  {
    "path": "ui-news/src/main/java/com/michaldrabik/ui_news/NewsViewModel.kt",
    "content": "package com.michaldrabik.ui_news\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.launchDelayed\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.NewsItem\nimport com.michaldrabik.ui_news.cases.NewsFiltersCase\nimport com.michaldrabik.ui_news.cases.NewsLoadItemsCase\nimport com.michaldrabik.ui_news.cases.NewsViewTypeCase\nimport com.michaldrabik.ui_news.recycler.NewsListItem\nimport com.michaldrabik.ui_news.views.item.NewsItemViewType\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\n\n@HiltViewModel\nclass NewsViewModel @Inject constructor(\n  private val loadNewsCase: NewsLoadItemsCase,\n  private val filtersCase: NewsFiltersCase,\n  private val viewTypeCase: NewsViewTypeCase,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private var previousRefresh = 0L\n\n  private val itemsState = MutableStateFlow(emptyList<NewsListItem>())\n  private val filtersState = MutableStateFlow(emptyList<NewsItem.Type>())\n  private val viewTypeState = MutableStateFlow(NewsItemViewType.ROW)\n  private val loadingState = MutableStateFlow(false)\n\n  init {\n    viewTypeState.value = viewTypeCase.loadViewType()\n    loadItems(filters = filtersCase.loadFilters())\n  }\n\n  fun loadItems(\n    forceRefresh: Boolean = false,\n    filters: List<NewsItem.Type>? = null,\n  ) {\n    if (filters != null) {\n      filtersCase.saveFilters(filters.toList())\n    }\n\n    if (forceRefresh) {\n      loadingState.value = true\n    }\n\n    viewModelScope.launch {\n      val progressJob = launchDelayed(700) {\n        loadingState.value = true\n      }\n      try {\n        if (forceRefresh && nowUtcMillis() - previousRefresh < TimeUnit.SECONDS.toMillis(30)) {\n          loadingState.value = false\n          return@launch\n        }\n\n        if (forceRefresh) {\n          loadingState.value = true\n        } else {\n          val currentFilters = filtersCase.loadFilters()\n          val cachedItems = loadNewsCase.preloadItems(currentFilters)\n          itemsState.value = cachedItems\n          filtersState.value = currentFilters\n        }\n\n        val currentFilters = filtersCase.loadFilters()\n        val items = loadNewsCase.loadItems(forceRefresh, currentFilters)\n        itemsState.value = items\n        filtersState.value = currentFilters\n        loadingState.value = false\n\n        if (forceRefresh) {\n          previousRefresh = nowUtcMillis()\n        }\n      } catch (error: Throwable) {\n        Logger.record(error, \"NewsViewModel::loadItems()\")\n        messageChannel.send(MessageEvent.Error(R.string.errorGeneral))\n        loadingState.value = false\n        rethrowCancellation(error)\n      } finally {\n        progressJob.cancel()\n      }\n    }\n  }\n\n  fun toggleViewType() {\n    viewModelScope.launch {\n      val newType = viewTypeCase.toggleViewType()\n      viewTypeState.value = newType\n    }\n  }\n\n  val uiState = combine(\n    itemsState,\n    filtersState,\n    viewTypeState,\n    loadingState\n  ) { items, filters, viewType, loading ->\n    NewsUiState(\n      items = items,\n      filters = filters,\n      viewType = viewType,\n      isLoading = loading\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = NewsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-news/src/main/java/com/michaldrabik/ui_news/cases/NewsFiltersCase.kt",
    "content": "package com.michaldrabik.ui_news.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.NewsItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass NewsFiltersCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun loadFilters() = settingsRepository.newsFilters\n\n  fun saveFilters(filters: List<NewsItem.Type>) {\n    settingsRepository.newsFilters = filters\n  }\n}\n"
  },
  {
    "path": "ui-news/src/main/java/com/michaldrabik/ui_news/cases/NewsLoadItemsCase.kt",
    "content": "package com.michaldrabik.ui_news.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.repository.NewsRepository\nimport com.michaldrabik.repository.UserRedditManager\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_model.NewsItem\nimport com.michaldrabik.ui_model.NewsItem.Type\nimport com.michaldrabik.ui_news.recycler.NewsListItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport java.time.format.DateTimeFormatter\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass NewsLoadItemsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val newsRepository: NewsRepository,\n  private val dateFormatProvider: DateFormatProvider,\n  private val userManager: UserRedditManager,\n) {\n\n  suspend fun preloadItems(types: List<Type>) = withContext(dispatchers.IO) {\n    val showsNewsAsync = async {\n      when {\n        types.contains(Type.SHOW) || types.isEmpty() -> newsRepository.getCachedNews(Type.SHOW)\n        else -> emptyList()\n      }\n    }\n    val moviesNewsAsync = async {\n      when {\n        types.contains(Type.MOVIE) || types.isEmpty() -> newsRepository.getCachedNews(Type.MOVIE)\n        else -> emptyList()\n      }\n    }\n\n    val (showsNews, moviesNews) = awaitAll(showsNewsAsync, moviesNewsAsync)\n    val dateFormat = dateFormatProvider.loadShortDayFormat()\n\n    prepareListItems(showsNews, moviesNews, dateFormat)\n  }\n\n  suspend fun loadItems(\n    forceRefresh: Boolean,\n    types: List<Type>,\n  ) = withContext(dispatchers.IO) {\n    val token = userManager.checkAuthorization()\n\n    val showsNewsAsync = async {\n      when {\n        types.contains(Type.SHOW) || types.isEmpty() -> newsRepository.loadShowsNews(token, forceRefresh)\n        else -> emptyList()\n      }\n    }\n\n    val moviesNewsAsync = async {\n      when {\n        types.contains(Type.MOVIE) || types.isEmpty() -> newsRepository.loadMoviesNews(token, forceRefresh)\n        else -> emptyList()\n      }\n    }\n\n    val (showsNews, moviesNews) = awaitAll(showsNewsAsync, moviesNewsAsync)\n    val dateFormat = dateFormatProvider.loadShortDayFormat()\n\n    prepareListItems(showsNews, moviesNews, dateFormat)\n  }\n\n  private fun prepareListItems(\n    showsNews: List<NewsItem>,\n    moviesNews: List<NewsItem>,\n    dateFormat: DateTimeFormatter,\n  ): List<NewsListItem> {\n    val timeThreshold = nowUtc().minusMinutes(5)\n    return (showsNews + moviesNews)\n      .asSequence()\n      .distinctBy { it.url }\n      .filter { it.isWebLink && it.datedAt.isBefore(timeThreshold) }\n      .sortedByDescending { it.datedAt }\n      .groupBy { it.datedAt.toLocalZone().dayOfYear }\n      .map { news -> news.value.sortedByDescending { it.score } }\n      .flatten()\n      .map { NewsListItem(it, dateFormat) }\n      .toList()\n  }\n}\n"
  },
  {
    "path": "ui-news/src/main/java/com/michaldrabik/ui_news/cases/NewsViewTypeCase.kt",
    "content": "package com.michaldrabik.ui_news.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_news.views.item.NewsItemViewType\nimport com.michaldrabik.ui_news.views.item.NewsItemViewType.CARD\nimport com.michaldrabik.ui_news.views.item.NewsItemViewType.ROW\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass NewsViewTypeCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun loadViewType(): NewsItemViewType {\n    val viewType = settingsRepository.newsViewType\n    return NewsItemViewType.valueOf(viewType)\n  }\n\n  fun toggleViewType(): NewsItemViewType {\n    val newType = when (loadViewType()) {\n      ROW -> CARD\n      CARD -> ROW\n    }\n    settingsRepository.newsViewType = newType.name\n    return newType\n  }\n}\n"
  },
  {
    "path": "ui-news/src/main/java/com/michaldrabik/ui_news/providers/NewsLayoutManagerProvider.kt",
    "content": "package com.michaldrabik.ui_news.providers\n\nimport android.content.Context\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport androidx.recyclerview.widget.StaggeredGridLayoutManager\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\n\ninternal object NewsLayoutManagerProvider {\n\n  fun provideLayoutManger(\n    context: Context,\n    settings: SettingsViewModeRepository,\n  ): LayoutManager {\n    return if (context.isTablet()) {\n      StaggeredGridLayoutManager(settings.tabletGridSpanSize, VERTICAL).apply {\n        gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_NONE\n      }\n    } else {\n      LinearLayoutManager(context, VERTICAL, false)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-news/src/main/java/com/michaldrabik/ui_news/recycler/NewsAdapter.kt",
    "content": "package com.michaldrabik.ui_news.recycler\n\nimport android.annotation.SuppressLint\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_news.views.item.NewsItemCardView\nimport com.michaldrabik.ui_news.views.item.NewsItemRowView\nimport com.michaldrabik.ui_news.views.item.NewsItemViewType\nimport com.michaldrabik.ui_news.views.item.NewsItemViewType.CARD\nimport com.michaldrabik.ui_news.views.item.NewsItemViewType.ROW\n\nclass NewsAdapter(\n  val itemClickListener: (NewsListItem) -> Unit,\n  val listChangeListener: () -> Unit,\n) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), AsyncListDiffer.ListListener<NewsListItem> {\n\n  private val asyncDiffer = AsyncListDiffer(this, NewsListItemDiffCallback())\n  private var viewType: NewsItemViewType = ROW\n\n  @SuppressLint(\"NotifyDataSetChanged\")\n  fun setViewType(viewType: NewsItemViewType) {\n    if (this.viewType != viewType) {\n      this.viewType = viewType\n      notifyDataSetChanged()\n    }\n  }\n\n  fun setItems(newItems: List<NewsListItem>) {\n    with(asyncDiffer) {\n      removeListListener(this@NewsAdapter)\n      addListListener(this@NewsAdapter)\n      submitList(newItems)\n    }\n  }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {\n    val itemView = when (viewType) {\n      ROW.ordinal -> NewsItemRowView(parent.context).apply {\n        itemClickListener = { this@NewsAdapter.itemClickListener(it) }\n      }\n      CARD.ordinal -> NewsItemCardView(parent.context).apply {\n        itemClickListener = { this@NewsAdapter.itemClickListener(it) }\n      }\n      else -> throw IllegalStateException()\n    }\n    return ViewHolder(itemView)\n  }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    when (holder.itemViewType) {\n      ROW.ordinal -> (holder.itemView as NewsItemRowView).bind(item)\n      CARD.ordinal -> (holder.itemView as NewsItemCardView).bind(item)\n    }\n  }\n\n  override fun getItemViewType(position: Int): Int = this.viewType.ordinal\n\n  override fun getItemCount() = asyncDiffer.currentList.size\n\n  override fun onCurrentListChanged(\n    previousList: MutableList<NewsListItem>,\n    currentList: MutableList<NewsListItem>,\n  ) {\n    listChangeListener()\n  }\n\n  class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-news/src/main/java/com/michaldrabik/ui_news/recycler/NewsListItem.kt",
    "content": "package com.michaldrabik.ui_news.recycler\n\nimport com.michaldrabik.ui_model.NewsItem\nimport java.time.format.DateTimeFormatter\n\ndata class NewsListItem(\n  val item: NewsItem,\n  val dateFormat: DateTimeFormatter,\n)\n"
  },
  {
    "path": "ui-news/src/main/java/com/michaldrabik/ui_news/recycler/NewsListItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_news.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass NewsListItemDiffCallback : DiffUtil.ItemCallback<NewsListItem>() {\n\n  override fun areItemsTheSame(\n    oldItem: NewsListItem,\n    newItem: NewsListItem,\n  ) =\n    oldItem.item.id == newItem.item.id &&\n      oldItem.item.type == newItem.item.type\n\n  override fun areContentsTheSame(\n    oldItem: NewsListItem,\n    newItem: NewsListItem,\n  ) =\n    oldItem.item == newItem.item\n}\n"
  },
  {
    "path": "ui-news/src/main/java/com/michaldrabik/ui_news/views/NewsFiltersView.kt",
    "content": "package com.michaldrabik.ui_news.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\nimport com.michaldrabik.ui_model.NewsItem\nimport com.michaldrabik.ui_news.databinding.ViewNewsFiltersBinding\n\nclass NewsFiltersView : FrameLayout, CoordinatorLayout.AttachedBehavior {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewNewsFiltersBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    setupListeners()\n    clipChildren = false\n  }\n\n  var onChipsChangeListener: ((List<NewsItem.Type>) -> Unit)? = null\n\n  private fun onChipCheckChange() {\n    val ids = binding.viewNewsFiltersChipGroup.checkedChipIds.map {\n      when (it) {\n        binding.viewNewsFiltersShowsChip.id -> NewsItem.Type.SHOW\n        binding.viewNewsFiltersMoviesChip.id -> NewsItem.Type.MOVIE\n        else -> throw IllegalStateException()\n      }\n    }\n    onChipsChangeListener?.invoke(ids)\n  }\n\n  fun setFilters(filters: List<NewsItem.Type>) {\n    clearListeners()\n\n    binding.viewNewsFiltersShowsChip.isChecked = filters.contains(NewsItem.Type.SHOW)\n    binding.viewNewsFiltersMoviesChip.isChecked = filters.contains(NewsItem.Type.MOVIE)\n\n    setupListeners()\n  }\n\n  private fun setupListeners() {\n    binding.viewNewsFiltersShowsChip.setOnCheckedChangeListener { _, _ -> onChipCheckChange() }\n    binding.viewNewsFiltersMoviesChip.setOnCheckedChangeListener { _, _ -> onChipCheckChange() }\n  }\n\n  private fun clearListeners() {\n    binding.viewNewsFiltersShowsChip.setOnCheckedChangeListener(null)\n    binding.viewNewsFiltersMoviesChip.setOnCheckedChangeListener(null)\n  }\n\n  override fun setEnabled(enabled: Boolean) {\n    binding.viewNewsFiltersShowsChip.isEnabled = enabled\n    binding.viewNewsFiltersMoviesChip.isEnabled = enabled\n  }\n\n  override fun getBehavior() = ScrollableViewBehaviour()\n}\n"
  },
  {
    "path": "ui-news/src/main/java/com/michaldrabik/ui_news/views/NewsHeaderView.kt",
    "content": "package com.michaldrabik.ui_news.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_news.R\nimport com.michaldrabik.ui_news.databinding.ViewNewsHeaderBinding\nimport com.michaldrabik.ui_news.views.item.NewsItemViewType\nimport com.michaldrabik.ui_news.views.item.NewsItemViewType.CARD\nimport com.michaldrabik.ui_news.views.item.NewsItemViewType.ROW\n\nclass NewsHeaderView : FrameLayout, CoordinatorLayout.AttachedBehavior {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  var onSettingsClickListener: (() -> Unit)? = null\n  var onViewTypeClickListener: (() -> Unit)? = null\n\n  private val binding = ViewNewsHeaderBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    binding.viewNewsHeaderSettingsIcon.onClick { onSettingsClickListener?.invoke() }\n    binding.viewNewsHeaderViewTypeIcon.onClick { onViewTypeClickListener?.invoke() }\n  }\n\n  fun setViewType(itemViewType: NewsItemViewType) {\n    binding.viewNewsHeaderViewTypeIcon.setImageResource(\n      when (itemViewType) {\n        ROW -> R.drawable.ic_view_cards\n        CARD -> R.drawable.iv_view_list\n      }\n    )\n  }\n\n  override fun getBehavior() = ScrollableViewBehaviour()\n}\n"
  },
  {
    "path": "ui-news/src/main/java/com/michaldrabik/ui_news/views/item/NewsItemCardView.kt",
    "content": "package com.michaldrabik.ui_news.views.item\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.os.Build\nimport android.os.Build.VERSION_CODES\nimport android.text.Html\nimport android.text.format.DateUtils\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.invisible\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_model.NewsItem.Type\nimport com.michaldrabik.ui_news.R\nimport com.michaldrabik.ui_news.databinding.ViewNewsItemCardBinding\nimport com.michaldrabik.ui_news.recycler.NewsListItem\nimport java.util.Locale\n\n@SuppressLint(\"SetTextI18n\")\nclass NewsItemCardView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.newsCardCornerRadius) }\n\n  var itemClickListener: ((NewsListItem) -> Unit)? = null\n\n  private val binding = ViewNewsItemCardBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    clipChildren = false\n    clipToPadding = false\n\n    binding.newsItemRoot.onClick { itemClickListener?.invoke(item) }\n  }\n\n  private lateinit var item: NewsListItem\n\n  @Suppress(\"DEPRECATION\")\n  fun bind(item: NewsListItem) {\n    clear()\n    this.item = item\n\n    val icon = when (item.item.type) {\n      Type.SHOW -> R.drawable.ic_television\n      Type.MOVIE -> R.drawable.ic_film\n    }\n\n    with(binding) {\n      newsItemHeaderIcon.setImageResource(icon)\n      if (icon == R.drawable.ic_television) {\n        newsItemHeaderIcon.translationY = -2F\n      }\n      newsItemPlaceholder.setImageResource(icon)\n\n      newsItemTitle.text = when {\n        Build.VERSION.SDK_INT >= VERSION_CODES.N -> Html.fromHtml(item.item.title, Html.FROM_HTML_MODE_LEGACY)\n        else -> Html.fromHtml(item.item.title)\n      }\n\n      val relativeTime = DateUtils.getRelativeTimeSpanString(item.item.datedAt.toMillis()).toString().lowercase(Locale.ROOT)\n      newsItemHeader.text = item.dateFormat.format(item.item.datedAt.toLocalZone()).capitalizeWords()\n      newsItemSubheader.text = \"~ $relativeTime\"\n    }\n\n    loadImage(item)\n  }\n\n  private fun loadImage(item: NewsListItem) {\n    if (item.item.image == null) {\n      binding.newsItemPlaceholder.visible()\n      binding.newsItemImage.invisible()\n      return\n    }\n\n    Glide.with(this@NewsItemCardView)\n      .load(item.item.image)\n      .transform(CenterCrop(), RoundedCorners(cornerRadius))\n      .transition(withCrossFade(Config.IMAGE_FADE_DURATION_MS))\n      .withSuccessListener {\n        binding.newsItemPlayIcon.visibleIf(item.item.isVideo)\n      }\n      .withFailListener {\n        binding.newsItemPlaceholder.fadeIn()\n        binding.newsItemImage.invisible()\n      }\n      .into(binding.newsItemImage)\n  }\n\n  private fun clear() {\n    with(binding) {\n      Glide.with(this@NewsItemCardView).clear(newsItemImage)\n      newsItemPlayIcon.gone()\n      newsItemPlaceholder.gone()\n      newsItemImage.visible()\n      newsItemHeaderIcon.translationY = 0f\n    }\n  }\n}\n"
  },
  {
    "path": "ui-news/src/main/java/com/michaldrabik/ui_news/views/item/NewsItemRowView.kt",
    "content": "package com.michaldrabik.ui_news.views.item\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.os.Build\nimport android.os.Build.VERSION_CODES\nimport android.text.Html\nimport android.text.format.DateUtils\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.invisible\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.setOutboundRipple\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_model.NewsItem.Type\nimport com.michaldrabik.ui_news.R\nimport com.michaldrabik.ui_news.databinding.ViewNewsItemBinding\nimport com.michaldrabik.ui_news.recycler.NewsListItem\nimport java.util.Locale\n\n@SuppressLint(\"SetTextI18n\")\nclass NewsItemRowView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.mediaTileCorner) }\n\n  var itemClickListener: ((NewsListItem) -> Unit)? = null\n\n  private val binding = ViewNewsItemBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    clipChildren = false\n    clipToPadding = false\n\n    with(binding) {\n      newsItemPlaceholder.onClick { itemClickListener?.invoke(item) }\n      newsItemImage.onClick { itemClickListener?.invoke(item) }\n      newsItemRoot.onClick { itemClickListener?.invoke(item) }\n      newsItemRoot.setOutboundRipple(\n        size = (context.dimenToPx(R.dimen.collectionItemRippleSpace)).toFloat(),\n        corner = context.dimenToPx(R.dimen.mediaTileCorner).toFloat()\n      )\n    }\n  }\n\n  private lateinit var item: NewsListItem\n\n  @Suppress(\"DEPRECATION\")\n  fun bind(item: NewsListItem) {\n    clear()\n    this.item = item\n\n    val icon = when (item.item.type) {\n      Type.SHOW -> R.drawable.ic_television\n      Type.MOVIE -> R.drawable.ic_film\n    }\n\n    with(binding) {\n      newsItemHeaderIcon.setImageResource(icon)\n      newsItemPlaceholder.setImageResource(icon)\n\n      newsItemTitle.text = when {\n        Build.VERSION.SDK_INT >= VERSION_CODES.N -> Html.fromHtml(item.item.title, Html.FROM_HTML_MODE_LEGACY)\n        else -> Html.fromHtml(item.item.title)\n      }\n\n      val relativeTime = DateUtils.getRelativeTimeSpanString(item.item.datedAt.toMillis()).toString().lowercase(Locale.ROOT)\n      newsItemHeader.text = item.dateFormat.format(item.item.datedAt.toLocalZone()).capitalizeWords()\n      newsItemSubheader.text = \"~ $relativeTime\"\n    }\n\n    loadImage(item)\n  }\n\n  private fun loadImage(item: NewsListItem) {\n    if (item.item.image == null) {\n      binding.newsItemPlaceholder.visible()\n      binding.newsItemImage.invisible()\n      return\n    }\n\n    Glide.with(this@NewsItemRowView)\n      .load(item.item.image)\n      .transform(CenterCrop(), RoundedCorners(cornerRadius))\n      .transition(withCrossFade(Config.IMAGE_FADE_DURATION_MS))\n      .withSuccessListener {\n        binding.newsItemPlayIcon.visibleIf(item.item.isVideo)\n      }\n      .withFailListener {\n        binding.newsItemPlaceholder.fadeIn()\n        binding.newsItemImage.invisible()\n      }\n      .into(binding.newsItemImage)\n  }\n\n  private fun clear() {\n    Glide.with(this@NewsItemRowView).clear(binding.newsItemImage)\n    binding.newsItemPlayIcon.gone()\n    binding.newsItemPlaceholder.gone()\n    binding.newsItemImage.visible()\n  }\n}\n"
  },
  {
    "path": "ui-news/src/main/java/com/michaldrabik/ui_news/views/item/NewsItemViewType.kt",
    "content": "package com.michaldrabik.ui_news.views.item\n\nenum class NewsItemViewType {\n  ROW,\n  CARD\n}\n"
  },
  {
    "path": "ui-news/src/main/res/drawable/bg_news_card_view_elevation.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <solid android:color=\"?android:windowBackground\" />\n  <corners android:radius=\"@dimen/newsCardCornerRadius\" />\n</shape>"
  },
  {
    "path": "ui-news/src/main/res/drawable/bg_news_card_view_placeholder.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <solid android:color=\"?attr/colorPlaceholderBackground\" />\n  <corners android:radius=\"@dimen/newsCardCornerRadius\" />\n  <stroke\n    android:width=\"1dp\"\n    android:color=\"?attr/colorPlaceholderStroke\"\n    />\n</shape>"
  },
  {
    "path": "ui-news/src/main/res/drawable/bg_play_circle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"oval\"\n  >\n  <solid android:color=\"#66000000\" />\n</shape>"
  },
  {
    "path": "ui-news/src/main/res/drawable/divider_news.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <size\n    android:width=\"18dp\"\n    android:height=\"18dp\"\n    />\n</shape>"
  },
  {
    "path": "ui-news/src/main/res/drawable/ic_play_arrow.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M8,5v14l11,-7z\"/>\n</vector>\n"
  },
  {
    "path": "ui-news/src/main/res/drawable/ic_view_cards.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M4,18h17v-6H4v6zM4,5v6h17V5H4z\"/>\n</vector>\n"
  },
  {
    "path": "ui-news/src/main/res/drawable/iv_view_list.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M4,14h4v-4L4,10v4zM4,19h4v-4L4,15v4zM4,9h4L8,5L4,5v4zM9,14h12v-4L9,10v4zM9,19h12v-4L9,15v4zM9,5v4h12L21,5L9,5z\"/>\n</vector>\n"
  },
  {
    "path": "ui-news/src/main/res/layout/fragment_news.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/fragmentNewsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.swiperefreshlayout.widget.SwipeRefreshLayout\n    android:id=\"@+id/fragmentNewsSwipeRefresh\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    >\n\n    <androidx.recyclerview.widget.RecyclerView\n      android:id=\"@+id/fragmentNewsRecycler\"\n      style=\"@style/ScrollbarsStyle\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:background=\"@android:color/transparent\"\n      android:clipChildren=\"false\"\n      android:clipToPadding=\"false\"\n      android:overScrollMode=\"never\"\n      android:paddingStart=\"@dimen/newsRecyclerHorizontalPadding\"\n      android:paddingTop=\"@dimen/newsRecyclerTopPadding\"\n      android:paddingEnd=\"@dimen/newsRecyclerHorizontalPadding\"\n      android:paddingBottom=\"@dimen/newsRecyclerBottomPadding\"\n      android:scrollbars=\"vertical\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>\n\n  <include\n    android:id=\"@+id/fragmentNewsEmptyView\"\n    layout=\"@layout/layout_news_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.michaldrabik.ui_news.views.NewsHeaderView\n    android:id=\"@+id/fragmentNewsHeaderView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/searchViewHeight\"\n    android:layout_marginLeft=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginRight=\"@dimen/screenMarginHorizontal\"\n    />\n\n  <com.michaldrabik.ui_news.views.NewsFiltersView\n    android:id=\"@+id/fragmentNewsFiltersView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/newsFiltersTopPadding\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "ui-news/src/main/res/layout/layout_news_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/menuNews\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textNewsEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-news/src/main/res/layout/view_news_filters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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/viewNewsFiltersRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <com.google.android.material.chip.ChipGroup\n    android:id=\"@+id/viewNewsFiltersChipGroup\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    >\n\n    <com.google.android.material.chip.Chip\n      android:id=\"@+id/viewNewsFiltersShowsChip\"\n      style=\"@style/ShowlyChip.Filter\"\n      android:text=\"@string/textShows\"\n      app:chipBackgroundColor=\"@color/selector_chip_background\"\n      app:chipStrokeColor=\"@color/selector_chip_stroke\"\n      />\n\n    <com.google.android.material.chip.Chip\n      android:id=\"@+id/viewNewsFiltersMoviesChip\"\n      style=\"@style/ShowlyChip.Filter\"\n      android:text=\"@string/textMovies\"\n      android:textColor=\"@color/selector_chip_text\"\n      app:chipBackgroundColor=\"@color/selector_chip_background\"\n      app:chipStrokeColor=\"@color/selector_chip_stroke\"\n      />\n\n  </com.google.android.material.chip.ChipGroup>\n\n</merge>\n"
  },
  {
    "path": "ui-news/src/main/res/layout/view_news_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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/viewNewsHeaderRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <TextView\n    android:id=\"@+id/viewNewsHeaderText\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:gravity=\"center_vertical|start\"\n    android:text=\"@string/menuNews\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"24sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <ImageView\n    android:id=\"@+id/viewNewsHeaderViewTypeIcon\"\n    android:layout_width=\"22dp\"\n    android:layout_height=\"match_parent\"\n    android:layout_gravity=\"end\"\n    android:layout_marginEnd=\"40dp\"\n    app:srcCompat=\"@drawable/ic_view_cards\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n  <ImageView\n    android:id=\"@+id/viewNewsHeaderSettingsIcon\"\n    android:layout_width=\"22dp\"\n    android:layout_height=\"match_parent\"\n    android:layout_gravity=\"end\"\n    app:srcCompat=\"@drawable/ic_settings\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n</merge>\n"
  },
  {
    "path": "ui-news/src/main/res/layout/view_news_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:background=\"?android:windowBackground\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/newsItemRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipToPadding=\"false\"\n    >\n\n    <ImageView\n      android:id=\"@+id/newsItemImage\"\n      android:layout_width=\"@dimen/newsItemImageSize\"\n      android:layout_height=\"@dimen/newsItemImageSize\"\n      android:adjustViewBounds=\"true\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/newsItemHeader\"\n      app:layout_constraintVertical_bias=\"0\"\n      />\n\n    <ImageView\n      android:id=\"@+id/newsItemPlaceholder\"\n      android:layout_width=\"@dimen/newsItemImageSize\"\n      android:layout_height=\"@dimen/newsItemImageSize\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"30dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/newsItemHeader\"\n      app:layout_constraintVertical_bias=\"0\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/newsItemPlayIcon\"\n      android:layout_width=\"42dp\"\n      android:layout_height=\"42dp\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_play_circle\"\n      android:padding=\"6dp\"\n      android:translationZ=\"@dimen/elevationNormal\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@id/newsItemImage\"\n      app:layout_constraintEnd_toEndOf=\"@id/newsItemImage\"\n      app:layout_constraintStart_toStartOf=\"@id/newsItemImage\"\n      app:layout_constraintTop_toTopOf=\"@id/newsItemImage\"\n      app:srcCompat=\"@drawable/ic_play_arrow\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/newsItemTitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:layout_marginTop=\"@dimen/spaceTiny\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start\"\n      android:maxLines=\"10\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/newsItemImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/newsItemHeader\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:targetApi=\"o\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <ImageView\n      android:id=\"@+id/newsItemHeaderIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:translationX=\"-1dp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/newsItemHeader\"\n      app:layout_constraintStart_toEndOf=\"@id/newsItemImage\"\n      app:layout_constraintTop_toTopOf=\"@id/newsItemHeader\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <TextView\n      android:id=\"@+id/newsItemHeader\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:layout_marginBottom=\"@dimen/spaceMicro\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?attr/colorAccent\"\n      android:textSize=\"12sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/newsItemTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/newsItemSubheader\"\n      app:layout_constraintStart_toEndOf=\"@id/newsItemHeaderIcon\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"~ 1 hour ago\"\n      />\n\n    <TextView\n      android:id=\"@+id/newsItemSubheader\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"0dp\"\n      android:ellipsize=\"end\"\n      android:gravity=\"end|center_vertical\"\n      android:maxLines=\"2\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/newsItemHeader\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/newsItemHeader\"\n      tools:text=\"Lorem Ipsum\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-news/src/main/res/layout/view_news_item_card.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:background=\"?android:windowBackground\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <com.google.android.material.card.MaterialCardView\n    android:id=\"@+id/newsItemRoot\"\n    style=\"?attr/materialCardViewElevatedStyle\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    app:cardBackgroundColor=\"?colorCardBackground\"\n    app:cardCornerRadius=\"@dimen/newsCardCornerRadius\"\n    >\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      >\n\n      <ImageView\n        android:id=\"@+id/newsItemImage\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"@dimen/newsCardImageHeight\"\n        android:adjustViewBounds=\"true\"\n        android:background=\"@drawable/bg_news_card_view_elevation\"\n        android:elevation=\"0dp\"\n        app:layout_constraintBottom_toTopOf=\"@id/newsItemTitle\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:background=\"@color/colorGrayLight\"\n        tools:visibility=\"invisible\"\n        />\n\n      <ImageView\n        android:id=\"@+id/newsItemPlaceholder\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/newsCardImageHeight\"\n        android:layout_gravity=\"center\"\n        android:background=\"@drawable/bg_news_card_view_placeholder\"\n        android:elevation=\"@dimen/elevationSmall\"\n        android:padding=\"80dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toTopOf=\"@id/newsItemTitle\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_bias=\"0\"\n        app:srcCompat=\"@drawable/ic_television\"\n        app:tint=\"?attr/colorPlaceholderIcon\"\n        tools:visibility=\"visible\"\n        />\n\n      <ImageView\n        android:id=\"@+id/newsItemPlayIcon\"\n        android:layout_width=\"42dp\"\n        android:layout_height=\"42dp\"\n        android:layout_gravity=\"center\"\n        android:background=\"@drawable/bg_play_circle\"\n        android:padding=\"6dp\"\n        android:translationZ=\"@dimen/elevationNormal\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"@id/newsItemImage\"\n        app:layout_constraintEnd_toEndOf=\"@id/newsItemImage\"\n        app:layout_constraintStart_toStartOf=\"@id/newsItemImage\"\n        app:layout_constraintTop_toTopOf=\"@id/newsItemImage\"\n        app:srcCompat=\"@drawable/ic_play_arrow\"\n        tools:visibility=\"visible\"\n        />\n\n      <TextView\n        android:id=\"@+id/newsItemTitle\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"@dimen/spaceNormal\"\n        android:ellipsize=\"end\"\n        android:gravity=\"start\"\n        android:maxLines=\"10\"\n        android:textAlignment=\"viewStart\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"16sp\"\n        app:layout_constraintBottom_toTopOf=\"@+id/newsItemHeader\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/newsItemImage\"\n        app:layout_constraintVertical_chainStyle=\"packed\"\n        tools:targetApi=\"o\"\n        tools:text=\"@tools:sample/lorem/random\"\n        />\n\n      <ImageView\n        android:id=\"@+id/newsItemHeaderIcon\"\n        android:layout_width=\"14dp\"\n        android:layout_height=\"0dp\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        app:layout_constraintBottom_toBottomOf=\"@id/newsItemHeader\"\n        app:layout_constraintEnd_toStartOf=\"@id/newsItemHeader\"\n        app:layout_constraintHorizontal_bias=\"0\"\n        app:layout_constraintHorizontal_chainStyle=\"packed\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@id/newsItemHeader\"\n        app:srcCompat=\"@drawable/ic_television\"\n        app:tint=\"?attr/colorAccent\"\n        />\n\n      <TextView\n        android:id=\"@+id/newsItemHeader\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceTiny\"\n        android:layout_marginEnd=\"@dimen/spaceSmall\"\n        android:layout_marginBottom=\"@dimen/spaceNormal\"\n        android:ellipsize=\"end\"\n        android:gravity=\"start\"\n        android:maxLines=\"1\"\n        android:textAlignment=\"viewStart\"\n        android:textColor=\"?attr/colorAccent\"\n        android:textSize=\"12sp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toStartOf=\"@id/newsItemSubheader\"\n        app:layout_constraintHorizontal_bias=\"0\"\n        app:layout_constraintStart_toEndOf=\"@id/newsItemHeaderIcon\"\n        app:layout_constraintTop_toBottomOf=\"@+id/newsItemTitle\"\n        app:layout_constraintVertical_chainStyle=\"packed\"\n        tools:ignore=\"SmallSp\"\n        tools:text=\"15 Jul 2022\"\n        />\n\n      <TextView\n        android:id=\"@+id/newsItemSubheader\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:ellipsize=\"end\"\n        android:gravity=\"end|center_vertical\"\n        android:maxLines=\"2\"\n        android:textAlignment=\"viewStart\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"12sp\"\n        app:layout_constraintBaseline_toBaselineOf=\"@id/newsItemHeader\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/newsItemHeader\"\n        tools:text=\"~ 2 hours ago\"\n        />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n  </com.google.android.material.card.MaterialCardView>\n\n</merge>\n"
  },
  {
    "path": "ui-news/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"newsRecyclerTopPadding\">108dp</dimen>\n  <dimen name=\"newsRecyclerBottomPadding\">74dp</dimen>\n  <dimen name=\"newsRecyclerHorizontalPadding\">12dp</dimen>\n  <dimen name=\"newsFiltersTopPadding\">60dp</dimen>\n  <dimen name=\"newsSwipeRefreshEndOffset\">120dp</dimen>\n\n  <dimen name=\"newsItemImageSize\">92dp</dimen>\n\n  <dimen name=\"newsCardCornerRadius\">10dp</dimen>\n  <dimen name=\"newsCardImageHeight\">210dp</dimen>\n</resources>"
  },
  {
    "path": "ui-news/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuNews\">News</string>\n  <string name=\"textNewsEmpty\">There are no news at the moment.</string>\n</resources>"
  },
  {
    "path": "ui-news/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuNews\">الأخبار</string>\n  <string name=\"textNewsEmpty\">لا توجد أخبار جديدة.</string>\n</resources>\n"
  },
  {
    "path": "ui-news/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuNews\">News</string>\n  <string name=\"textNewsEmpty\">Derzeit gibt es keine News.</string>\n</resources>\n"
  },
  {
    "path": "ui-news/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuNews\">Noticias</string>\n  <string name=\"textNewsEmpty\">No hay noticias en este momento.</string>\n</resources>\n"
  },
  {
    "path": "ui-news/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuNews\">Uutiset</string>\n  <string name=\"textNewsEmpty\">Uuutisia ei juuri nyt ole.</string>\n</resources>\n"
  },
  {
    "path": "ui-news/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuNews\">Actualités</string>\n  <string name=\"textNewsEmpty\">Il n\\'y a aucune actualité pour le moment.</string>\n</resources>\n"
  },
  {
    "path": "ui-news/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuNews\">Notizie</string>\n  <string name=\"textNewsEmpty\">Non ci sono notizie al momento.</string>\n</resources>\n"
  },
  {
    "path": "ui-news/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuNews\">Newsy</string>\n  <string name=\"textNewsEmpty\">W tej chwili nie ma żadnych wiadomości.</string>\n</resources>\n"
  },
  {
    "path": "ui-news/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuNews\">Notícias</string>\n  <string name=\"textNewsEmpty\">Não há notícias no momento.</string>\n</resources>\n"
  },
  {
    "path": "ui-news/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuNews\">Новости</string>\n  <string name=\"textNewsEmpty\">На данный момент новостей нет.</string>\n</resources>\n"
  },
  {
    "path": "ui-news/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"newsRecyclerTopPadding\">92dp</dimen>\n  <dimen name=\"newsRecyclerHorizontalPadding\">8dp</dimen>\n  <dimen name=\"newsItemImageSize\">104dp</dimen>\n</resources>"
  },
  {
    "path": "ui-news/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuNews\">Haberler</string>\n  <string name=\"textNewsEmpty\">Şu anda hiçbir haber yok.</string>\n</resources>\n"
  },
  {
    "path": "ui-news/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuNews\">Новини</string>\n  <string name=\"textNewsEmpty\">Наразі немає новин.</string>\n</resources>\n"
  },
  {
    "path": "ui-news/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuNews\">Tin tức</string>\n  <string name=\"textNewsEmpty\">Không có tin tức vào lúc này.</string>\n</resources>"
  },
  {
    "path": "ui-news/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuNews\">新闻</string>\n  <string name=\"textNewsEmpty\">目前暂无新闻。</string>\n</resources>\n"
  },
  {
    "path": "ui-news/src/test/java/com/michaldrabik/ui_news/ExampleUnitTest.kt",
    "content": "package com.michaldrabik.ui_news\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"
  },
  {
    "path": "ui-people/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-people/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-parcelize'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_people'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-local')\n  implementation project(':data-remote')\n  implementation project(':ui-base')\n  implementation project(':ui-navigation')\n  implementation project(':ui-model')\n  implementation project(':repository')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  implementation libs.circleIndicator\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-people/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n  <application android:theme=\"@style/AppTheme\" />\n\n</manifest>\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/PersonDetailsArgs.kt",
    "content": "package com.michaldrabik.ui_people.details\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\ndata class PersonDetailsArgs(\n  val isExpanded: Boolean = false,\n  val firstVisibleItemPosition: Int = 0,\n  val isUpButtonVisible: Boolean = false\n) : Parcelable\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/PersonDetailsBottomSheet.kt",
    "content": "package com.michaldrabik.ui_people.details\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport androidx.navigation.fragment.findNavController\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetDialog\nimport com.google.android.material.snackbar.Snackbar\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.common.FastLinearLayoutManager\nimport com.michaldrabik.ui_base.utilities.TipsHost\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.requireParcelable\nimport com.michaldrabik.ui_base.utilities.extensions.screenHeight\nimport com.michaldrabik.ui_base.utilities.extensions.showErrorSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_model.Tip\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_PERSON\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_PERSON_ARGS\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_DETAILS\nimport com.michaldrabik.ui_people.R\nimport com.michaldrabik.ui_people.databinding.ViewPersonDetailsBinding\nimport com.michaldrabik.ui_people.details.PersonDetailsUiEvent.ScrollToPosition\nimport com.michaldrabik.ui_people.details.links.PersonLinksBottomSheet\nimport com.michaldrabik.ui_people.details.recycler.PersonDetailsAdapter\nimport com.michaldrabik.ui_people.details.recycler.PersonDetailsItem\nimport com.michaldrabik.ui_people.gallery.PersonGalleryFragment\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass PersonDetailsBottomSheet : BaseBottomSheetFragment(R.layout.view_person_details) {\n\n  companion object {\n    const val SHOW_BACK_UP_BUTTON_THRESHOLD = 25\n\n    fun createBundle(\n      person: Person,\n      sourceId: IdTrakt,\n      personArgs: PersonDetailsArgs?,\n    ): Bundle {\n      return bundleOf(\n        ARG_PERSON to person,\n        ARG_PERSON_ARGS to (personArgs ?: PersonDetailsArgs()),\n        ARG_ID to sourceId\n      )\n    }\n  }\n\n  private val viewModel by viewModels<PersonDetailsViewModel>()\n  private val binding by viewBinding(ViewPersonDetailsBinding::bind)\n\n  private val personArgs by lazy { requireParcelable<PersonDetailsArgs>(ARG_PERSON_ARGS) }\n  private val person by lazy { requireParcelable<Person>(ARG_PERSON) }\n  private val sourceId by lazy { requireParcelable<IdTrakt>(ARG_ID) }\n\n  private var adapter: PersonDetailsAdapter? = null\n  private var layoutManager: LinearLayoutManager? = null\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    setupView()\n    setupTips()\n    setupRecycler()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      { viewModel.messageFlow.collect { renderSnackbar(it) } },\n      doAfterLaunch = { viewModel.loadDetails(person, personArgs) }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior\n      with(behavior) {\n        peekHeight = (screenHeight() * 0.55).toInt()\n        skipCollapsed = true\n        state = BottomSheetBehavior.STATE_COLLAPSED\n      }\n      personDetailsRecyclerFab.onClick {\n        personDetailsRecyclerFab.fadeOut(150)\n        personDetailsRecycler.smoothScrollToPosition(0)\n      }\n    }\n  }\n\n  private fun setupTips() {\n    val isShown = (requireActivity() as TipsHost).isTipShown(Tip.PERSON_DETAILS_GALLERY)\n    if (!isShown && !person.imagePath.isNullOrBlank()) {\n      val message = getString(Tip.PERSON_DETAILS_GALLERY.textResId)\n      binding.personDetailsSnackHost.showInfoSnackbar(message, length = Snackbar.LENGTH_INDEFINITE) {\n        (requireActivity() as TipsHost).setTipShow(Tip.PERSON_DETAILS_GALLERY)\n      }\n    }\n  }\n\n  private fun setupRecycler() {\n    layoutManager = FastLinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)\n    adapter = PersonDetailsAdapter(\n      onItemClickListener = { openDetails(it) },\n      onLinksClickListener = { openLinksSheet(it) },\n      onImageClickListener = { openGallery() },\n      onImageMissingListener = { item, force -> viewModel.loadMissingImage(item, force) },\n      onTranslationMissingListener = { item -> viewModel.loadMissingTranslation(item) },\n      onFiltersChangeListener = { filters -> viewModel.loadCredits(person, null, filters) }\n    )\n    with(binding.personDetailsRecycler) {\n      adapter = this@PersonDetailsBottomSheet.adapter\n      layoutManager = this@PersonDetailsBottomSheet.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      removeOnScrollListener(recyclerScrollListener)\n      addOnScrollListener(recyclerScrollListener)\n    }\n  }\n\n  private fun openDetails(item: PersonDetailsItem) {\n    val personBundle = bundleOf(\n      ARG_PERSON to person,\n      ARG_PERSON_ARGS to PersonDetailsArgs(\n        isExpanded = isSheetExpanded(),\n        isUpButtonVisible = binding.personDetailsRecyclerFab.isVisible,\n        firstVisibleItemPosition = (layoutManager?.findLastVisibleItemPosition() ?: 0)\n      ),\n    )\n    if (item is PersonDetailsItem.CreditsShowItem && item.show.traktId != sourceId.id) {\n      setFragmentResult(REQUEST_DETAILS, personBundle)\n      val bundle = bundleOf(NavigationArgs.ARG_SHOW_ID to item.show.traktId)\n      requireParentFragment()\n        .findNavController()\n        .navigate(R.id.actionPersonDetailsDialogToShow, bundle)\n    }\n    if (item is PersonDetailsItem.CreditsMovieItem && item.movie.traktId != sourceId.id) {\n      setFragmentResult(REQUEST_DETAILS, personBundle)\n      val bundle = bundleOf(NavigationArgs.ARG_MOVIE_ID to item.movie.traktId)\n      requireParentFragment()\n        .findNavController()\n        .navigate(R.id.actionPersonDetailsDialogToMovie, bundle)\n    }\n  }\n\n  private fun openGallery() {\n    val personBundle = bundleOf(ARG_PERSON to person)\n    setFragmentResult(REQUEST_DETAILS, personBundle)\n    val options = PersonGalleryFragment.createBundle(person)\n    requireParentFragment()\n      .findNavController()\n      .navigate(R.id.actionPersonDetailsDialogToGallery, options)\n  }\n\n  private fun openLinksSheet(it: Person) {\n    val options = PersonLinksBottomSheet.createBundle(it)\n    navigateTo(R.id.actionPersonDetailsDialogToLinks, options)\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun render(uiState: PersonDetailsUiState) {\n    uiState.run {\n      personDetailsItems?.let { adapter?.setItems(it) }\n    }\n  }\n\n  private fun renderSnackbar(message: MessageEvent) {\n    when (message) {\n      is MessageEvent.Info -> binding.viewPersonDetailsRoot.showInfoSnackbar(getString(message.textRestId))\n      is MessageEvent.Error -> binding.viewPersonDetailsRoot.showErrorSnackbar(getString(message.textRestId))\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is ScrollToPosition -> {\n        if (event.isSheetExpanded) expandSheet()\n        with(binding) {\n          if (event.isUpButtonVisible) personDetailsRecyclerFab.fadeIn(150)\n          personDetailsRecycler.postDelayed({\n            personDetailsRecycler.scrollToPosition(event.position)\n          }, 100)\n        }\n      }\n    }\n  }\n\n  override fun onDestroyView() {\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n\n  private val recyclerScrollListener = object : RecyclerView.OnScrollListener() {\n    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {\n      if (newState != RecyclerView.SCROLL_STATE_IDLE) return\n      if ((layoutManager?.findFirstVisibleItemPosition() ?: 0) >= SHOW_BACK_UP_BUTTON_THRESHOLD) {\n        binding.personDetailsRecyclerFab.fadeIn(150)\n      } else {\n        binding.personDetailsRecyclerFab.fadeOut(150)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/PersonDetailsUiEvent.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_people.details\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\ninternal sealed class PersonDetailsUiEvent<T>(action: T) : Event<T>(action) {\n  data class ScrollToPosition(\n    val position: Int,\n    val isSheetExpanded: Boolean,\n    val isUpButtonVisible: Boolean\n  ) : PersonDetailsUiEvent<Int>(position)\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/PersonDetailsUiState.kt",
    "content": "package com.michaldrabik.ui_people.details\n\nimport com.michaldrabik.ui_people.details.recycler.PersonDetailsItem\n\ndata class PersonDetailsUiState(\n  val personDetailsItems: List<PersonDetailsItem>? = null,\n)\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/PersonDetailsViewModel.kt",
    "content": "package com.michaldrabik.ui_people.details\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.utilities.extensions.launchDelayed\nimport com.michaldrabik.ui_base.utilities.extensions.replaceItem\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_people.R\nimport com.michaldrabik.ui_people.details.cases.PersonDetailsCreditsCase\nimport com.michaldrabik.ui_people.details.cases.PersonDetailsImagesCase\nimport com.michaldrabik.ui_people.details.cases.PersonDetailsLoadCase\nimport com.michaldrabik.ui_people.details.cases.PersonDetailsTranslationsCase\nimport com.michaldrabik.ui_people.details.recycler.PersonDetailsItem\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@HiltViewModel\nclass PersonDetailsViewModel @Inject constructor(\n  private val loadDetailsCase: PersonDetailsLoadCase,\n  private val loadCreditsCase: PersonDetailsCreditsCase,\n  private val loadImagesCase: PersonDetailsImagesCase,\n  private val loadTranslationsCase: PersonDetailsTranslationsCase,\n  private val settingsRepository: SettingsRepository,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val personDetailsItemsState = MutableStateFlow<List<PersonDetailsItem>?>(null)\n\n  private var mainProgressJob: Job? = null\n  private var creditsJob: Job? = null\n  private var creditsProgressJob: Job? = null\n  private var imagesJobs = mutableMapOf<String, Boolean>()\n  private var translationsJobs = mutableMapOf<String, Boolean>()\n\n  fun loadDetails(person: Person, personArgs: PersonDetailsArgs) {\n    viewModelScope.launch {\n      mainProgressJob = launchDelayed(750) { setMainLoading(true) }\n      try {\n        val dateFormat = loadDetailsCase.loadDateFormat()\n        personDetailsItemsState.value = mutableListOf<PersonDetailsItem>().apply {\n          add(PersonDetailsItem.MainInfo(person, dateFormat, false))\n          if (!person.bio.isNullOrBlank()) {\n            add(PersonDetailsItem.MainBio(person.bio, person.bioTranslation))\n          }\n        }\n\n        val details = loadDetailsCase.loadDetails(person)\n        personDetailsItemsState.value = mutableListOf<PersonDetailsItem>().apply {\n          add(PersonDetailsItem.MainInfo(details, dateFormat, false))\n          add(PersonDetailsItem.MainBio(details.bio, details.bioTranslation))\n        }\n        mainProgressJob?.cancelAndJoin()\n\n        loadCredits(details, personArgs)\n      } catch (error: Throwable) {\n        messageChannel.send(MessageEvent.Error(R.string.errorGeneral))\n        Timber.e(error)\n        rethrowCancellation(error)\n      } finally {\n        setMainLoading(false)\n      }\n    }\n  }\n\n  fun loadCredits(\n    person: Person,\n    personArgs: PersonDetailsArgs?,\n    filters: List<Mode> = emptyList(),\n  ) {\n    creditsJob?.cancel()\n    creditsJob = viewModelScope.launch {\n      creditsProgressJob = launchDelayed(500) { setCreditsLoading(true) }\n      try {\n        val credits = loadCreditsCase.loadCredits(person, filters)\n\n        setCreditsLoading(false)\n\n        val current = personDetailsItemsState.value?.toMutableList()\n        current?.let { currentValue ->\n          val filtersItem = PersonDetailsItem.CreditsFiltersItem(filters)\n          if (currentValue.none { it is PersonDetailsItem.CreditsFiltersItem }) {\n            currentValue.add(filtersItem)\n          } else {\n            currentValue.findReplace(filtersItem) { it is PersonDetailsItem.CreditsFiltersItem }\n          }\n          currentValue.removeIf { it.isCreditsItem() }\n          credits.forEach { (year, credit) ->\n            currentValue.add(PersonDetailsItem.CreditsHeader(year))\n            currentValue.addAll(credit)\n          }\n\n          personDetailsItemsState.value = currentValue\n\n          personArgs?.let {\n            eventChannel.send(\n              PersonDetailsUiEvent.ScrollToPosition(\n                position = it.firstVisibleItemPosition,\n                isSheetExpanded = it.isExpanded,\n                isUpButtonVisible = it.isUpButtonVisible\n              )\n            )\n          }\n        }\n      } catch (error: Throwable) {\n        messageChannel.send(MessageEvent.Error(R.string.errorGeneral))\n        Timber.e(error)\n        rethrowCancellation(error)\n      } finally {\n        setCreditsLoading(false)\n      }\n    }\n  }\n\n  fun loadMissingImage(item: PersonDetailsItem, force: Boolean) {\n    if (item.getId() in imagesJobs.keys) {\n      return\n    }\n    imagesJobs[item.getId()] = true\n    viewModelScope.launch {\n      (item as? PersonDetailsItem.CreditsShowItem)?.let {\n        updateItem(it.copy(isLoading = true))\n        val updatedItem = loadImagesCase.loadMissingImage(it, force)\n        updateItem(updatedItem)\n      }\n      (item as? PersonDetailsItem.CreditsMovieItem)?.let {\n        updateItem(it.copy(isLoading = true))\n        val updatedItem = loadImagesCase.loadMissingImage(it, force)\n        updateItem(updatedItem)\n      }\n    }\n  }\n\n  fun loadMissingTranslation(item: PersonDetailsItem) {\n    val language = settingsRepository.language\n    if (language == Config.DEFAULT_LANGUAGE || item.getId() in translationsJobs.keys) {\n      return\n    }\n    translationsJobs[item.getId()] = true\n    viewModelScope.launch {\n      (item as? PersonDetailsItem.CreditsShowItem)?.let {\n        val updatedItem = loadTranslationsCase.loadMissingTranslation(it, language)\n        updateItem(updatedItem)\n      }\n      (item as? PersonDetailsItem.CreditsMovieItem)?.let {\n        val updatedItem = loadTranslationsCase.loadMissingTranslation(it, language)\n        updateItem(updatedItem)\n      }\n    }\n  }\n\n  private fun setMainLoading(isLoading: Boolean) {\n    if (!isLoading) mainProgressJob?.cancel()\n\n    val current = personDetailsItemsState.value?.toMutableList()\n    current?.let { currentValue ->\n      val mainInfoItem = currentValue.first { it is PersonDetailsItem.MainInfo } as PersonDetailsItem.MainInfo\n      val value = mainInfoItem.copy(isLoading = isLoading)\n      currentValue.replaceItem(mainInfoItem, value)\n      personDetailsItemsState.value = currentValue\n    }\n  }\n\n  private fun setCreditsLoading(isLoading: Boolean) {\n    if (!isLoading) creditsProgressJob?.cancel()\n\n    val current = personDetailsItemsState.value?.toMutableList()\n    current?.let { currentValue ->\n      if (isLoading) {\n        currentValue.add(PersonDetailsItem.CreditsLoadingItem)\n      } else {\n        currentValue.remove(PersonDetailsItem.CreditsLoadingItem)\n      }\n      personDetailsItemsState.value = currentValue\n    }\n  }\n\n  private fun updateItem(newItem: PersonDetailsItem) {\n    val currentItems = personDetailsItemsState.value?.toMutableList()\n    currentItems?.findReplace(newItem) { it.getId() == newItem.getId() }\n    personDetailsItemsState.value = currentItems\n  }\n\n  val uiState = combine(\n    personDetailsItemsState\n  ) { s1 ->\n    PersonDetailsUiState(\n      personDetailsItems = s1[0]\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = PersonDetailsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/cases/PersonDetailsCreditsCase.kt",
    "content": "package com.michaldrabik.ui_people.details.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.PeopleRepository\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_model.PersonCredit\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_people.details.recycler.PersonDetailsItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass PersonDetailsCreditsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val peopleRepository: PeopleRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val settingsRepository: SettingsRepository,\n  private val showsRepository: ShowsRepository,\n  private val moviesRepository: MoviesRepository,\n  private val showImagesProvider: ShowImagesProvider,\n  private val movieImagesProvider: MovieImagesProvider,\n) {\n\n  suspend fun loadCredits(person: Person, filters: List<Mode>) =\n    withContext(dispatchers.IO) {\n      val myShowsIdsAsync = async { showsRepository.myShows.loadAllIds() }\n      val myMoviesIdsAsync = async { moviesRepository.myMovies.loadAllIds() }\n      val watchlistShowsIdsAsync = async { showsRepository.watchlistShows.loadAllIds() }\n      val watchlistMoviesIdsAsync = async { moviesRepository.watchlistMovies.loadAllIds() }\n      val spoilers = settingsRepository.spoilers.getAll()\n\n      val (myShowsIds, myMoviesIds, watchlistShowsId, watchlistMoviesIds) = awaitAll(\n        myShowsIdsAsync,\n        myMoviesIdsAsync,\n        watchlistShowsIdsAsync,\n        watchlistMoviesIdsAsync\n      )\n\n      val credits = peopleRepository.loadCredits(person)\n      credits\n        .filter {\n          when {\n            filters.isEmpty() || filters.containsAll(Mode.values().toList()) -> true\n            filters.contains(Mode.SHOWS) -> it.show != null\n            filters.contains(Mode.MOVIES) -> it.movie != null\n            else -> true\n          }\n        }\n        .filter { it.releaseDate != null || (it.releaseDate == null && it.isUpcoming) }\n        .sortedWith(\n          compareByDescending<PersonCredit> { it.releaseDate == null }.thenByDescending { it.releaseDate?.toEpochDay() }\n        )\n        .map {\n          async {\n            when {\n              it.show != null -> createShowItem(\n                show = it.requireShow(),\n                myShowsIds = myShowsIds,\n                watchlistShowsId = watchlistShowsId,\n                spoilersSettings = spoilers\n              )\n              it.movie != null -> createMovieItem(\n                movie = it.requireMovie(),\n                myMoviesIds = myMoviesIds,\n                watchlistMoviesId = watchlistMoviesIds,\n                spoilersSettings = spoilers\n              )\n              else -> throw IllegalStateException()\n            }\n          }\n        }\n        .awaitAll()\n        .groupBy { it.getReleaseDate()?.year }\n    }\n\n  private suspend fun createShowItem(\n    show: Show,\n    myShowsIds: List<Long>,\n    watchlistShowsId: List<Long>,\n    spoilersSettings: SpoilersSettings\n  ) = show.let {\n    val isMy = it.traktId in myShowsIds\n    val isWatchlist = it.traktId in watchlistShowsId\n    val image = showImagesProvider.findCachedImage(it, ImageType.POSTER)\n    val translation = when (val language = translationsRepository.getLanguage()) {\n      Config.DEFAULT_LANGUAGE -> null\n      else -> translationsRepository.loadTranslation(it, language, onlyLocal = true)\n    }\n    PersonDetailsItem.CreditsShowItem(\n      show = it,\n      image = image,\n      isMy = isMy,\n      isWatchlist = isWatchlist,\n      translation = translation,\n      spoilers = spoilersSettings\n    )\n  }\n\n  private suspend fun createMovieItem(\n    movie: Movie,\n    myMoviesIds: List<Long>,\n    watchlistMoviesId: List<Long>,\n    spoilersSettings: SpoilersSettings\n  ) = movie.let {\n    val isMy = it.traktId in myMoviesIds\n    val isWatchlist = it.traktId in watchlistMoviesId\n    val image = movieImagesProvider.findCachedImage(it, ImageType.POSTER)\n    val translation = when (val language = translationsRepository.getLanguage()) {\n      Config.DEFAULT_LANGUAGE -> null\n      else -> translationsRepository.loadTranslation(it, language, onlyLocal = true)\n    }\n    PersonDetailsItem.CreditsMovieItem(\n      movie = it,\n      image = image,\n      isMy = isMy,\n      isWatchlist = isWatchlist,\n      translation = translation,\n      spoilers = spoilersSettings,\n      moviesEnabled = settingsRepository.isMoviesEnabled\n    )\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/cases/PersonDetailsImagesCase.kt",
    "content": "package com.michaldrabik.ui_people.details.cases\n\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_people.details.recycler.PersonDetailsItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass PersonDetailsImagesCase @Inject constructor(\n  private val showImagesProvider: ShowImagesProvider,\n  private val movieImagesProvider: MovieImagesProvider\n) {\n\n  suspend fun loadMissingImage(item: PersonDetailsItem.CreditsShowItem, force: Boolean) =\n    try {\n      val image = showImagesProvider.loadRemoteImage(item.show, item.image.type, force)\n      item.copy(isLoading = false, image = image)\n    } catch (t: Throwable) {\n      Timber.w(t)\n      item.copy(isLoading = false, image = Image.createUnavailable(item.image.type))\n    }\n\n  suspend fun loadMissingImage(item: PersonDetailsItem.CreditsMovieItem, force: Boolean) =\n    try {\n      val image = movieImagesProvider.loadRemoteImage(item.movie, item.image.type, force)\n      item.copy(isLoading = false, image = image)\n    } catch (t: Throwable) {\n      Timber.w(t)\n      item.copy(isLoading = false, image = Image.createUnavailable(item.image.type))\n    }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/cases/PersonDetailsLoadCase.kt",
    "content": "package com.michaldrabik.ui_people.details.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.PeopleRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_model.Person\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass PersonDetailsLoadCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val peopleRepository: PeopleRepository,\n  private val dateFormatProvider: DateFormatProvider\n) {\n\n  suspend fun loadDetails(person: Person) =\n    withContext(dispatchers.IO) {\n      peopleRepository.loadDetails(person)\n        .copy(characters = person.characters)\n    }\n\n  fun loadDateFormat() = dateFormatProvider.loadShortDayFormat()\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/cases/PersonDetailsTranslationsCase.kt",
    "content": "package com.michaldrabik.ui_people.details.cases\n\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_people.details.recycler.PersonDetailsItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass PersonDetailsTranslationsCase @Inject constructor(\n  private val translationsRepository: TranslationsRepository,\n) {\n\n  suspend fun loadMissingTranslation(item: PersonDetailsItem.CreditsShowItem, language: String) =\n    try {\n      val translation = translationsRepository.loadTranslation(item.show, language) ?: Translation.EMPTY\n      item.copy(isLoading = false, translation = translation)\n    } catch (error: Throwable) {\n      Timber.w(error)\n      item.copy(isLoading = false, translation = Translation.EMPTY)\n    }\n\n  suspend fun loadMissingTranslation(item: PersonDetailsItem.CreditsMovieItem, language: String) =\n    try {\n      val translation = translationsRepository.loadTranslation(item.movie, language) ?: Translation.EMPTY\n      item.copy(isLoading = false, translation = translation)\n    } catch (error: Throwable) {\n      Timber.w(error)\n      item.copy(isLoading = false, translation = Translation.EMPTY)\n    }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/links/PersonLinksBottomSheet.kt",
    "content": "package com.michaldrabik.ui_people.details.links\n\nimport android.content.ActivityNotFoundException\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport android.os.Parcelable\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.openWebUrl\nimport com.michaldrabik.ui_base.utilities.extensions.requireParcelable\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_people.R\nimport com.michaldrabik.ui_people.databinding.ViewPersonLinksBinding\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.parcelize.Parcelize\n\n@AndroidEntryPoint\nclass PersonLinksBottomSheet : BaseBottomSheetFragment(R.layout.view_person_links) {\n\n  @Parcelize\n  data class Options(\n    val ids: Ids,\n    val name: String,\n    val website: String?,\n  ) : Parcelable\n\n  companion object {\n    fun createBundle(person: Person): Bundle {\n      val options = Options(person.ids, person.name, person.homepage)\n      return bundleOf(NavigationArgs.ARG_OPTIONS to options)\n    }\n  }\n\n  private val binding by viewBinding(ViewPersonLinksBinding::bind)\n\n  private val options by lazy { requireParcelable<Options>(NavigationArgs.ARG_OPTIONS) }\n  private val ids by lazy { options.ids }\n  private val name by lazy { options.name }\n  private val website by lazy { options.website }\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n  }\n\n  private fun setupView() {\n    with(binding) {\n      viewPersonLinksYouTube.onClick {\n        openWebUrl(\"https://www.youtube.com/results?search_query=$name\")\n      }\n      viewPersonLinksWiki.onClick {\n        openWebUrl(\"https://en.wikipedia.org/w/index.php?search=$name\")\n      }\n      viewPersonLinksGoogle.onClick {\n        openWebUrl(\"https://www.google.com/search?q=$name\")\n      }\n      viewPersonLinksDuckDuck.onClick {\n        openWebUrl(\"https://duckduckgo.com/?q=$name\")\n      }\n      viewPersonLinksTwitter.onClick {\n        openWebUrl(\"https://twitter.com/search?q=$name&src=typed_query&f=user\")\n      }\n      viewPersonLinksButtonClose.onClick { closeSheet() }\n    }\n    setWebLink()\n    setTmdbLink()\n    setImdbLink()\n  }\n\n  private fun setWebLink() {\n    binding.viewPersonLinksWebsite.run {\n      if (website.isNullOrBlank()) {\n        alpha = 0.5F\n        isEnabled = false\n      } else {\n        onClick { openWebUrl(website ?: \"\") }\n      }\n    }\n  }\n\n  private fun setTmdbLink() {\n    binding.viewPersonLinksTmdb.run {\n      if (ids.tmdb.id == -1L) {\n        alpha = 0.5F\n        isEnabled = false\n      } else {\n        onClick {\n          openWebUrl(\"https://www.themoviedb.org/person/${ids.tmdb.id}\")\n        }\n      }\n    }\n  }\n\n  private fun setImdbLink() {\n    binding.viewPersonLinksImdb.run {\n      if (ids.imdb.id.isBlank()) {\n        alpha = 0.5F\n        isEnabled = false\n      } else {\n        onClick {\n          val i = Intent(Intent.ACTION_VIEW)\n          i.data = Uri.parse(\"imdb:///name/${ids.imdb.id}\")\n          try {\n            startActivity(i)\n          } catch (e: ActivityNotFoundException) {\n            // IMDb App not installed. Start in web browser\n            openWebUrl(\"https://www.imdb.com/name/${ids.imdb.id}\")\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/recycler/PersonDetailsAdapter.kt",
    "content": "package com.michaldrabik.ui_people.details.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_people.details.recycler.views.PersonDetailsBioView\nimport com.michaldrabik.ui_people.details.recycler.views.PersonDetailsCreditsItemView\nimport com.michaldrabik.ui_people.details.recycler.views.PersonDetailsFiltersView\nimport com.michaldrabik.ui_people.details.recycler.views.PersonDetailsHeaderView\nimport com.michaldrabik.ui_people.details.recycler.views.PersonDetailsInfoView\nimport com.michaldrabik.ui_people.details.recycler.views.PersonDetailsLoadingView\n\nclass PersonDetailsAdapter(\n  var onItemClickListener: (PersonDetailsItem) -> Unit,\n  val onImageMissingListener: (PersonDetailsItem, Boolean) -> Unit,\n  val onTranslationMissingListener: (PersonDetailsItem) -> Unit,\n  val onLinksClickListener: (Person) -> Unit,\n  val onImageClickListener: () -> Unit,\n  var onFiltersChangeListener: ((List<Mode>) -> Unit)\n) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n  companion object {\n    private const val VIEW_TYPE_INFO = 1\n    private const val VIEW_TYPE_BIO = 2\n    private const val VIEW_TYPE_CREDIT_ITEM = 3\n    private const val VIEW_TYPE_CREDIT_HEADER = 4\n    private const val VIEW_TYPE_CREDIT_LOADING = 5\n    private const val VIEW_TYPE_CREDIT_FILTERS = 6\n  }\n\n  private val asyncDiffer = AsyncListDiffer(this, PersonDetailsItemDiffCallback())\n\n  fun setItems(items: List<PersonDetailsItem>) = asyncDiffer.submitList(items)\n\n  override fun getItemViewType(position: Int) =\n    when (asyncDiffer.currentList[position]) {\n      is PersonDetailsItem.MainInfo -> VIEW_TYPE_INFO\n      is PersonDetailsItem.MainBio -> VIEW_TYPE_BIO\n      is PersonDetailsItem.CreditsShowItem -> VIEW_TYPE_CREDIT_ITEM\n      is PersonDetailsItem.CreditsMovieItem -> VIEW_TYPE_CREDIT_ITEM\n      is PersonDetailsItem.CreditsHeader -> VIEW_TYPE_CREDIT_HEADER\n      is PersonDetailsItem.CreditsLoadingItem -> VIEW_TYPE_CREDIT_LOADING\n      is PersonDetailsItem.CreditsFiltersItem -> VIEW_TYPE_CREDIT_FILTERS\n      else -> throw IllegalStateException()\n    }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    when (viewType) {\n      VIEW_TYPE_INFO -> BaseViewHolder(\n        PersonDetailsInfoView(parent.context).apply {\n          onLinksClickListener = this@PersonDetailsAdapter.onLinksClickListener\n          onImageClickListener = this@PersonDetailsAdapter.onImageClickListener\n        }\n      )\n      VIEW_TYPE_BIO -> BaseViewHolder(PersonDetailsBioView(parent.context))\n      VIEW_TYPE_CREDIT_ITEM -> BaseViewHolder(\n        PersonDetailsCreditsItemView(parent.context).apply {\n          onItemClickListener = this@PersonDetailsAdapter.onItemClickListener\n          onImageMissingListener = this@PersonDetailsAdapter.onImageMissingListener\n          onTranslationMissingListener = this@PersonDetailsAdapter.onTranslationMissingListener\n        }\n      )\n      VIEW_TYPE_CREDIT_HEADER -> BaseViewHolder(PersonDetailsHeaderView(parent.context))\n      VIEW_TYPE_CREDIT_LOADING -> BaseViewHolder(PersonDetailsLoadingView(parent.context))\n      VIEW_TYPE_CREDIT_FILTERS -> BaseViewHolder(\n        PersonDetailsFiltersView(parent.context).apply {\n          onChipsChangeListener = this@PersonDetailsAdapter.onFiltersChangeListener\n        }\n      )\n      else -> throw IllegalStateException()\n    }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) =\n    when (val item = asyncDiffer.currentList[position]) {\n      is PersonDetailsItem.MainInfo -> (holder.itemView as PersonDetailsInfoView).bind(item)\n      is PersonDetailsItem.MainBio -> (holder.itemView as PersonDetailsBioView).bind(item)\n      is PersonDetailsItem.CreditsHeader -> (holder.itemView as PersonDetailsHeaderView).bind(item)\n      is PersonDetailsItem.CreditsShowItem -> (holder.itemView as PersonDetailsCreditsItemView).bind(item)\n      is PersonDetailsItem.CreditsMovieItem -> (holder.itemView as PersonDetailsCreditsItemView).bind(item)\n      is PersonDetailsItem.CreditsFiltersItem -> (holder.itemView as PersonDetailsFiltersView).bind(item.filters)\n      is PersonDetailsItem.CreditsLoadingItem -> Unit\n    }\n\n  override fun getItemCount() = asyncDiffer.currentList.size\n\n  class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/recycler/PersonDetailsItem.kt",
    "content": "package com.michaldrabik.ui_people.details.recycler\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.Translation\nimport java.time.LocalDate\nimport java.time.ZonedDateTime\nimport java.time.format.DateTimeFormatter\nimport java.util.UUID\n\nsealed class PersonDetailsItem {\n\n  open fun getId(): String = UUID.randomUUID().toString()\n\n  open fun getReleaseDate(): LocalDate? = null\n\n  fun isCreditsItem() = this is CreditsHeader || this is CreditsMovieItem || this is CreditsShowItem\n\n  data class MainInfo(\n    val person: Person,\n    val dateFormat: DateTimeFormatter?,\n    val isLoading: Boolean = false,\n  ) : PersonDetailsItem()\n\n  data class MainBio(\n    val biography: String?,\n    val biographyTranslation: String?,\n  ) : PersonDetailsItem()\n\n  data class CreditsHeader(\n    val year: Int?,\n  ) : PersonDetailsItem() {\n    override fun getId() = year?.toString() ?: \"\"\n  }\n\n  data class CreditsShowItem(\n    val show: Show,\n    val image: Image,\n    val isMy: Boolean,\n    val isWatchlist: Boolean,\n    val translation: Translation?,\n    val spoilers: SpoilersSettings,\n    val isLoading: Boolean = false,\n  ) : PersonDetailsItem() {\n    override fun getId() = \"${show.traktId}show\"\n    override fun getReleaseDate() =\n      if (show.firstAired.isNotBlank()) {\n        ZonedDateTime.parse(show.firstAired).toLocalDate()\n      } else {\n        null\n      }\n  }\n\n  data class CreditsMovieItem(\n    val movie: Movie,\n    val image: Image,\n    val isMy: Boolean,\n    val isWatchlist: Boolean,\n    val translation: Translation?,\n    val spoilers: SpoilersSettings,\n    val moviesEnabled: Boolean,\n    val isLoading: Boolean = false,\n  ) : PersonDetailsItem() {\n    override fun getId() = \"${movie.traktId}movie\"\n    override fun getReleaseDate() = movie.released\n  }\n\n  data class CreditsFiltersItem(\n    val filters: List<Mode>,\n  ) : PersonDetailsItem()\n\n  data object CreditsLoadingItem : PersonDetailsItem()\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/recycler/PersonDetailsItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_people.details.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass PersonDetailsItemDiffCallback : DiffUtil.ItemCallback<PersonDetailsItem>() {\n\n  override fun areItemsTheSame(oldItem: PersonDetailsItem, newItem: PersonDetailsItem) =\n    when {\n      oldItem is PersonDetailsItem.MainInfo && newItem is PersonDetailsItem.MainInfo -> true\n      oldItem is PersonDetailsItem.MainBio && newItem is PersonDetailsItem.MainBio -> true\n      oldItem is PersonDetailsItem.CreditsLoadingItem && newItem is PersonDetailsItem.CreditsLoadingItem -> true\n      oldItem is PersonDetailsItem.CreditsFiltersItem && newItem is PersonDetailsItem.CreditsFiltersItem -> true\n      oldItem is PersonDetailsItem.CreditsHeader && newItem is PersonDetailsItem.CreditsHeader && oldItem.getId() == newItem.getId() -> true\n      oldItem is PersonDetailsItem.CreditsShowItem && newItem is PersonDetailsItem.CreditsShowItem && oldItem.getId() == newItem.getId() -> true\n      oldItem is PersonDetailsItem.CreditsMovieItem && newItem is PersonDetailsItem.CreditsMovieItem && oldItem.getId() == newItem.getId() -> true\n      else -> false\n    }\n\n  override fun areContentsTheSame(oldItem: PersonDetailsItem, newItem: PersonDetailsItem) =\n    when {\n      oldItem is PersonDetailsItem.MainInfo && newItem is PersonDetailsItem.MainInfo -> {\n        oldItem.person == newItem.person &&\n          oldItem.isLoading == newItem.isLoading\n      }\n      oldItem is PersonDetailsItem.MainBio && newItem is PersonDetailsItem.MainBio -> {\n        oldItem == newItem\n      }\n      oldItem is PersonDetailsItem.CreditsMovieItem && newItem is PersonDetailsItem.CreditsMovieItem -> {\n        oldItem == newItem\n      }\n      oldItem is PersonDetailsItem.CreditsShowItem && newItem is PersonDetailsItem.CreditsShowItem -> {\n        oldItem == newItem\n      }\n      oldItem is PersonDetailsItem.CreditsFiltersItem && newItem is PersonDetailsItem.CreditsFiltersItem -> {\n        oldItem.filters == newItem.filters\n      }\n      oldItem is PersonDetailsItem.CreditsHeader && newItem is PersonDetailsItem.CreditsHeader -> {\n        oldItem.year == newItem.year\n      }\n      else -> false\n    }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/recycler/views/PersonDetailsBioView.kt",
    "content": "package com.michaldrabik.ui_people.details.recycler.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_base.utilities.extensions.copyToClipboard\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_people.R\nimport com.michaldrabik.ui_people.databinding.ViewPersonDetailsBioBinding\nimport com.michaldrabik.ui_people.details.recycler.PersonDetailsItem\n\nclass PersonDetailsBioView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewPersonDetailsBioBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    with(binding) {\n      personBioText.setInitialLines(5)\n      personBioText.onLongClick {\n        context.copyToClipboard(personBioText.text.toString())\n        snackbarLayout.showInfoSnackbar(context.getString(R.string.textCopiedToClipboard), length = 1250)\n      }\n    }\n  }\n\n  fun bind(item: PersonDetailsItem.MainBio) {\n    with(binding) {\n      when {\n        item.biography.isNullOrBlank() -> personBioText.text = context.getString(R.string.textNoDescription)\n        !item.biographyTranslation.isNullOrBlank() -> personBioText.text = item.biographyTranslation\n        else -> personBioText.text = item.biography\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/recycler/views/PersonDetailsCreditsItemView.kt",
    "content": "package com.michaldrabik.ui_people.details.recycler.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_model.ImageStatus\nimport com.michaldrabik.ui_people.R\nimport com.michaldrabik.ui_people.databinding.ViewPersonDetailsCreditsItemBinding\nimport com.michaldrabik.ui_people.details.recycler.PersonDetailsItem\n\nclass PersonDetailsCreditsItemView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewPersonDetailsCreditsItemBinding.inflate(LayoutInflater.from(context), this)\n\n  var onItemClickListener: ((PersonDetailsItem) -> Unit)? = null\n  var onImageMissingListener: ((PersonDetailsItem, Boolean) -> Unit)? = null\n  var onTranslationMissingListener: ((PersonDetailsItem) -> Unit)? = null\n\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.mediaTileCorner) }\n  private val spaceNano by lazy { context.dimenToPx(R.dimen.spaceNano).toFloat() }\n  private val centerCropTransformation by lazy { CenterCrop() }\n  private val cornersTransformation by lazy { RoundedCorners(cornerRadius) }\n\n  private lateinit var item: PersonDetailsItem\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    binding.viewPersonCreditsItemRoot.onClick { onItemClickListener?.invoke(item) }\n  }\n\n  fun bind(item: PersonDetailsItem.CreditsShowItem) {\n    clear()\n    this.item = item\n    bindTitleDescription(item)\n\n    with(binding) {\n      val year = if (item.show.year > 0) item.show.year.toString() else \"TBA\"\n      viewPersonCreditsItemNetwork.text =\n        if (item.show.network.isNotBlank()) context.getString(R.string.textNetwork, year, item.show.network)\n        else String.format(\"%s\", year)\n\n      viewPersonCreditsItemPlaceholder.setImageResource(R.drawable.ic_television)\n      viewPersonCreditsItemIcon.setImageResource(R.drawable.ic_television)\n      viewPersonCreditsItemNetwork.translationY = spaceNano\n      viewPersonCreditsItemBadge.visibleIf(item.isMy)\n      viewPersonCreditsItemWatchlistBadge.visibleIf(item.isWatchlist)\n    }\n\n    if (!item.isLoading) loadImage(item)\n  }\n\n  fun bind(item: PersonDetailsItem.CreditsMovieItem) {\n    clear()\n    this.item = item\n    bindTitleDescription(item)\n\n    with(binding) {\n      viewPersonCreditsItemNetwork.text = String.format(\"%s\", item.movie.released?.year ?: \"TBA\")\n      viewPersonCreditsItemPlaceholder.setImageResource(R.drawable.ic_film)\n      viewPersonCreditsItemIcon.setImageResource(R.drawable.ic_film)\n      viewPersonCreditsItemNetwork.translationY = 0F\n      viewPersonCreditsItemRoot.alpha = if (item.moviesEnabled) 1F else 0.45F\n      viewPersonCreditsItemRoot.isEnabled = item.moviesEnabled\n      viewPersonCreditsItemBadge.visibleIf(item.isMy)\n      viewPersonCreditsItemWatchlistBadge.visibleIf(item.isWatchlist)\n    }\n\n    if (!item.isLoading) loadImage(item)\n  }\n\n  private fun bindTitleDescription(item: PersonDetailsItem.CreditsShowItem) {\n    with(binding) {\n      viewPersonCreditsItemTitle.text = when {\n        item.translation?.title?.isNotBlank() == true -> item.translation.title\n        else -> item.show.title\n      }\n      var description = when {\n        item.translation?.overview?.isNotBlank() == true -> item.translation.overview\n        item.show.overview.isNotBlank() -> item.show.overview\n        else -> context.getString(R.string.textNoDescription)\n      }\n\n      val isMyHidden = item.spoilers.isMyShowsHidden && item.isMy\n      val isWatchlistHidden = item.spoilers.isWatchlistShowsHidden && item.isWatchlist\n      val isNotCollectedHidden = item.spoilers.isNotCollectedShowsHidden && (!item.isMy && !item.isWatchlist)\n      if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n        viewPersonCreditsItemDescription.tag = description\n        description = SPOILERS_REGEX.replace(description, SPOILERS_HIDE_SYMBOL)\n\n        if (item.spoilers.isTapToReveal) {\n          viewPersonCreditsItemDescription.onClick { view ->\n            view.tag?.let {\n              viewPersonCreditsItemDescription.text = it.toString()\n            }\n            view.isClickable = false\n          }\n        }\n      }\n\n      viewPersonCreditsItemDescription.text = description\n    }\n  }\n\n  private fun bindTitleDescription(item: PersonDetailsItem.CreditsMovieItem) {\n    with(binding) {\n      viewPersonCreditsItemTitle.text = when {\n        item.translation?.title?.isNotBlank() == true -> item.translation.title\n        else -> item.movie.title\n      }\n\n      var description = when {\n        item.translation?.overview?.isNotBlank() == true -> item.translation.overview\n        item.movie.overview.isNotBlank() -> item.movie.overview\n        else -> context.getString(R.string.textNoDescription)\n      }\n\n      val isMyHidden = item.spoilers.isMyMoviesHidden && item.isMy\n      val isWatchlistHidden = item.spoilers.isWatchlistMoviesHidden && item.isWatchlist\n      val isNotCollectedHidden = item.spoilers.isNotCollectedMoviesHidden && (!item.isMy && !item.isWatchlist)\n      if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n        viewPersonCreditsItemDescription.tag = description\n        description = SPOILERS_REGEX.replace(description, SPOILERS_HIDE_SYMBOL)\n\n        if (item.spoilers.isTapToReveal) {\n          viewPersonCreditsItemDescription.onClick { view ->\n            view.tag?.let {\n              viewPersonCreditsItemDescription.text = it.toString()\n            }\n            view.isClickable = false\n          }\n        }\n      }\n\n      viewPersonCreditsItemDescription.text = description\n    }\n  }\n\n  private fun loadImage(item: PersonDetailsItem) {\n    val image = when (item) {\n      is PersonDetailsItem.CreditsShowItem -> item.image\n      is PersonDetailsItem.CreditsMovieItem -> item.image\n      else -> throw IllegalArgumentException()\n    }\n\n    with(binding) {\n      if (image.status == ImageStatus.UNAVAILABLE) {\n        viewPersonCreditsItemImage.gone()\n        viewPersonCreditsItemPlaceholder.fadeIn(Config.IMAGE_FADE_DURATION_MS.toLong())\n        return\n      }\n\n      if (image.status == ImageStatus.UNKNOWN) {\n        onImageMissingListener?.invoke(item, true)\n        return\n      }\n\n      Glide.with(this@PersonDetailsCreditsItemView)\n        .load(image.fullFileUrl)\n        .transform(centerCropTransformation, cornersTransformation)\n        .transition(DrawableTransitionOptions.withCrossFade(Config.IMAGE_FADE_DURATION_MS))\n        .withSuccessListener {\n          viewPersonCreditsItemPlaceholder.gone()\n          loadTranslation(item)\n        }\n        .withFailListener {\n          if (image.status == ImageStatus.AVAILABLE) {\n            viewPersonCreditsItemImage.gone()\n            viewPersonCreditsItemPlaceholder.fadeIn(Config.IMAGE_FADE_DURATION_MS.toLong())\n            loadTranslation(item)\n            return@withFailListener\n          }\n          onImageMissingListener?.invoke(item, false)\n        }\n        .into(viewPersonCreditsItemImage)\n    }\n  }\n\n  private fun loadTranslation(item: PersonDetailsItem) {\n    if (item is PersonDetailsItem.CreditsShowItem && item.translation == null) {\n      onTranslationMissingListener?.invoke(item)\n    }\n    if (item is PersonDetailsItem.CreditsMovieItem && item.translation == null) {\n      onTranslationMissingListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      viewPersonCreditsItemBadge.gone()\n      viewPersonCreditsItemWatchlistBadge.gone()\n      viewPersonCreditsItemRoot.alpha = 1F\n      viewPersonCreditsItemRoot.isEnabled = true\n      viewPersonCreditsItemPlaceholder.gone()\n      viewPersonCreditsItemImage.visible()\n      Glide.with(this@PersonDetailsCreditsItemView)\n        .clear(viewPersonCreditsItemImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/recycler/views/PersonDetailsFiltersView.kt",
    "content": "package com.michaldrabik.ui_people.details.recycler.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_people.databinding.ViewPersonDetailsFiltersBinding\n\nclass PersonDetailsFiltersView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewPersonDetailsFiltersBinding.inflate(LayoutInflater.from(context), this, true)\n\n  init {\n    with(binding) {\n      viewPersonDetailsFiltersShowsChip.setOnCheckedChangeListener { _, _ -> onChipCheckChange() }\n      viewPersonDetailsFiltersMoviesChip.setOnCheckedChangeListener { _, _ -> onChipCheckChange() }\n    }\n  }\n\n  var onChipsChangeListener: ((List<Mode>) -> Unit)? = null\n  private var isListenerEnabled = true\n\n  private fun onChipCheckChange() {\n    if (!isListenerEnabled) return\n    with(binding) {\n      val ids = viewPersonDetailsFiltersChipGroup.checkedChipIds.map {\n        when (it) {\n          viewPersonDetailsFiltersShowsChip.id -> Mode.SHOWS\n          viewPersonDetailsFiltersMoviesChip.id -> Mode.MOVIES\n          else -> throw IllegalStateException()\n        }\n      }\n      onChipsChangeListener?.invoke(ids)\n    }\n  }\n\n  override fun setEnabled(enabled: Boolean) {\n    with(binding) {\n      viewPersonDetailsFiltersShowsChip.isEnabled = enabled\n      viewPersonDetailsFiltersMoviesChip.isEnabled = enabled\n    }\n  }\n\n  fun bind(types: List<Mode>) {\n    with(binding) {\n      isListenerEnabled = false\n      viewPersonDetailsFiltersShowsChip.isChecked = Mode.SHOWS in types\n      viewPersonDetailsFiltersMoviesChip.isChecked = Mode.MOVIES in types\n      isListenerEnabled = true\n    }\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/recycler/views/PersonDetailsHeaderView.kt",
    "content": "package com.michaldrabik.ui_people.details.recycler.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_people.R\nimport com.michaldrabik.ui_people.databinding.ViewPersonDetailsHeaderBinding\nimport com.michaldrabik.ui_people.details.recycler.PersonDetailsItem\n\nclass PersonDetailsHeaderView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewPersonDetailsHeaderBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n  }\n\n  fun bind(item: PersonDetailsItem.CreditsHeader) {\n    binding.viewPersonDetailsHeader.text =\n      if (item.year != null) item.year.toString()\n      else context.getString(R.string.textMovieStatusInProduction)\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/recycler/views/PersonDetailsInfoView.kt",
    "content": "package com.michaldrabik.ui_people.details.recycler.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport androidx.core.view.updatePadding\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_people.R\nimport com.michaldrabik.ui_people.databinding.ViewPersonDetailsInfoBinding\nimport com.michaldrabik.ui_people.details.recycler.PersonDetailsItem\n\nclass PersonDetailsInfoView : ConstraintLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewPersonDetailsInfoBinding.inflate(LayoutInflater.from(context), this)\n\n  private val topLeftCornerRadius by lazy { context.dimenToPx(R.dimen.personImageCorner).toFloat() }\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.mediaTileCorner).toFloat() }\n  private val spaceNormal by lazy { context.dimenToPx(R.dimen.spaceNormal) }\n\n  var onLinksClickListener: ((Person) -> Unit)? = null\n  var onImageClickListener: (() -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    updatePadding(left = spaceNormal, right = spaceNormal)\n    clipToPadding = false\n  }\n\n  fun bind(item: PersonDetailsItem.MainInfo) {\n    with(binding) {\n      viewPersonDetailsTitle.text = item.person.name\n      viewPersonDetailsSubtitle.text = item.person.characters.joinToString(\", \")\n      viewPersonDetailsLinkIcon.onClick { onLinksClickListener?.invoke(item.person) }\n      viewPersonDetailsImage.onClick { onImageClickListener?.invoke() }\n      viewPersonDetailsPlaceholder.onClick { onImageClickListener?.invoke() }\n\n      item.person.birthday?.let { date ->\n        viewPersonDetailsBirthdayLabel.visible()\n        viewPersonDetailsBirthdayValue.visible()\n        viewPersonDetailsAgeLabel.visible()\n        viewPersonDetailsAgeValue.visible()\n        val birthdayText = item.dateFormat?.format(date)\n          ?.capitalizeWords()\n          ?.plus(if (!item.person.birthplace.isNullOrBlank()) \"\\n${item.person.birthplace}\" else \"\")\n        viewPersonDetailsBirthdayValue.text = birthdayText\n        viewPersonDetailsAgeValue.text = item.person.getAge().toString()\n      }\n      item.person.deathday?.let { date ->\n        viewPersonDetailsDeathdayLabel.visible()\n        viewPersonDetailsDeathdayValue.visible()\n        viewPersonDetailsDeathdayValue.text = item.dateFormat?.format(date)?.capitalizeWords()\n      }\n      viewPersonDetailsProgress.visibleIf(item.isLoading)\n    }\n    renderImage(item.person)\n  }\n\n  private fun renderImage(person: Person) {\n    with(binding) {\n      Glide.with(this@PersonDetailsInfoView).clear(viewPersonDetailsImage)\n\n      if (person.imagePath.isNullOrBlank()) {\n        viewPersonDetailsImage.gone()\n        viewPersonDetailsPlaceholder.visible()\n        return\n      }\n\n      viewPersonDetailsImage.visible()\n      viewPersonDetailsPlaceholder.gone()\n\n      Glide.with(this@PersonDetailsInfoView)\n        .load(\"${Config.TMDB_IMAGE_BASE_ACTOR_URL}${person.imagePath}\")\n        .transform(CenterCrop(), GranularRoundedCorners(topLeftCornerRadius, cornerRadius, cornerRadius, cornerRadius))\n        .transition(DrawableTransitionOptions.withCrossFade(Config.IMAGE_FADE_DURATION_MS))\n        .withFailListener {\n          viewPersonDetailsImage.gone()\n          viewPersonDetailsPlaceholder.visible()\n        }\n        .into(viewPersonDetailsImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/details/recycler/views/PersonDetailsLoadingView.kt",
    "content": "package com.michaldrabik.ui_people.details.recycler.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_people.R\n\nclass PersonDetailsLoadingView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  init {\n    inflate(context, R.layout.view_person_details_loading, this)\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/gallery/PersonGalleryFragment.kt",
    "content": "package com.michaldrabik.ui_people.gallery\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.activity.addCallback\nimport androidx.core.os.bundleOf\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.nextPage\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.openWebUrl\nimport com.michaldrabik.ui_base.utilities.extensions.requireParcelable\nimport com.michaldrabik.ui_base.utilities.extensions.updateTopMargin\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ID\nimport com.michaldrabik.ui_people.R\nimport com.michaldrabik.ui_people.databinding.FragmentPersonGalleryBinding\nimport com.michaldrabik.ui_people.gallery.recycler.PersonGalleryAdapter\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass PersonGalleryFragment : BaseFragment<PersonGalleryViewModel>(R.layout.fragment_person_gallery) {\n\n  companion object {\n    fun createBundle(person: Person): Bundle {\n      return bundleOf(ARG_ID to person.ids.tmdb)\n    }\n  }\n\n  override val viewModel by viewModels<PersonGalleryViewModel>()\n  private val binding by viewBinding(FragmentPersonGalleryBinding::bind)\n\n  private val personId by lazy { requireParcelable<IdTmdb>(ARG_ID) }\n\n  private var galleryAdapter: PersonGalleryAdapter? = null\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupStatusBar()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      doAfterLaunch = { viewModel.loadImages(personId) }\n    )\n  }\n\n  override fun onDestroyView() {\n    galleryAdapter = null\n    super.onDestroyView()\n  }\n\n  private fun setupView() {\n    with(binding) {\n      personGalleryBackArrow.onClick {\n        requireActivity().onBackPressed()\n      }\n      personGalleryBrowserIcon.onClick {\n        val currentIndex = personGalleryPager.currentItem\n        val image = galleryAdapter?.getItem(currentIndex)\n        openImageInBrowser(image?.fullFileUrl)\n      }\n      galleryAdapter = PersonGalleryAdapter(\n        onItemClickListener = { personGalleryPager.nextPage() }\n      )\n      personGalleryPager.run {\n        adapter = galleryAdapter\n        offscreenPageLimit = 2\n        personGalleryPagerIndicator.setViewPager(this)\n        adapter?.registerAdapterDataObserver(personGalleryPagerIndicator.adapterDataObserver)\n      }\n    }\n  }\n\n  private fun setupStatusBar() {\n    requireView().doOnApplyWindowInsets { _, insets, _, _ ->\n      val margin = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top\n      with(binding) {\n        personGalleryBackArrow.updateTopMargin(margin)\n        personGalleryBrowserIcon.updateTopMargin(margin)\n      }\n    }\n  }\n\n  private fun render(uiState: PersonGalleryUiState) {\n    uiState.run {\n      with(binding) {\n        images?.let {\n          galleryAdapter?.setItems(it)\n          personGalleryEmptyView.visibleIf(it.isEmpty())\n          personGalleryBrowserIcon.visibleIf(it.isNotEmpty())\n        }\n        isLoading.let {\n          personGalleryImagesProgress.visibleIf(it)\n        }\n      }\n    }\n  }\n\n  private fun openImageInBrowser(url: String?) {\n    url?.let { requireContext().openWebUrl(it) }\n  }\n\n  override fun setupBackPressed() {\n    val dispatcher = requireActivity().onBackPressedDispatcher\n    dispatcher.addCallback(viewLifecycleOwner) {\n      isEnabled = false\n      findNavControl()?.popBackStack()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/gallery/PersonGalleryUiState.kt",
    "content": "package com.michaldrabik.ui_people.gallery\n\nimport com.michaldrabik.ui_model.Image\n\ndata class PersonGalleryUiState(\n  val images: List<Image>? = null,\n  val isLoading: Boolean = false,\n)\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/gallery/PersonGalleryViewModel.kt",
    "content": "package com.michaldrabik.ui_people.gallery\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_people.gallery.cases.PersonGalleryImagesCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass PersonGalleryViewModel @Inject constructor(\n  private val imagesCase: PersonGalleryImagesCase,\n) : ViewModel() {\n\n  private val imagesState = MutableStateFlow<List<Image>?>(null)\n  private val loadingState = MutableStateFlow(false)\n\n  fun loadImages(id: IdTmdb) {\n    viewModelScope.launch {\n      try {\n        loadingState.value = true\n        val allImages = imagesCase.loadImages(id)\n        imagesState.value = allImages\n        loadingState.value = false\n      } catch (t: Throwable) {\n        loadingState.value = false\n      }\n    }\n  }\n\n  val uiState = combine(\n    imagesState,\n    loadingState\n  ) { s1, s2 ->\n    PersonGalleryUiState(\n      images = s1,\n      isLoading = s2\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = PersonGalleryUiState()\n  )\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/gallery/cases/PersonGalleryImagesCase.kt",
    "content": "package com.michaldrabik.ui_people.gallery.cases\n\nimport com.michaldrabik.repository.images.PeopleImagesProvider\nimport com.michaldrabik.ui_model.IdTmdb\nimport com.michaldrabik.ui_model.Image\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass PersonGalleryImagesCase @Inject constructor(\n  private val imagesProvider: PeopleImagesProvider\n) {\n\n  suspend fun loadImages(id: IdTmdb): List<Image> {\n    val initial = imagesProvider.loadCachedImage(id)\n    val images = imagesProvider.loadImages(id).filter { it.fileUrl != initial?.fileUrl }\n    return (listOf(initial) + images).filterNotNull()\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/gallery/recycler/ImageItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_people.gallery.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\nimport com.michaldrabik.ui_model.Image\n\nclass ImageItemDiffCallback : DiffUtil.ItemCallback<Image>() {\n\n  override fun areItemsTheSame(oldItem: Image, newItem: Image) =\n    oldItem.id == newItem.id\n\n  override fun areContentsTheSame(oldItem: Image, newItem: Image) =\n    oldItem == newItem\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/gallery/recycler/PersonGalleryAdapter.kt",
    "content": "package com.michaldrabik.ui_people.gallery.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_people.gallery.recycler.views.PersonGalleryImageView\n\nclass PersonGalleryAdapter(\n  val onItemClickListener: () -> Unit\n) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n  private val asyncDiffer = AsyncListDiffer(this, ImageItemDiffCallback())\n\n  fun setItems(items: List<Image>) {\n    asyncDiffer.submitList(items)\n  }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    ViewHolder(\n      PersonGalleryImageView(parent.context).apply {\n        onItemClickListener = this@PersonGalleryAdapter.onItemClickListener\n      }\n    )\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) =\n    (holder.itemView as PersonGalleryImageView).bind(asyncDiffer.currentList[position])\n\n  fun getItem(index: Int) = asyncDiffer.currentList.getOrNull(index)\n\n  override fun getItemCount() = asyncDiffer.currentList.size\n\n  class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/gallery/recycler/views/PersonGalleryImageView.kt",
    "content": "package com.michaldrabik.ui_people.gallery.recycler.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_people.R\nimport com.michaldrabik.ui_people.databinding.ViewPersonGalleryImageBinding\n\nclass PersonGalleryImageView : ConstraintLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewPersonGalleryImageBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n  }\n\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.galleryImageCorner) }\n  var onItemClickListener: (() -> Unit)? = null\n\n  fun bind(image: Image) {\n    clear()\n    binding.viewPersonGalleryImage.onClick { onItemClickListener?.invoke() }\n    loadImage(image)\n  }\n\n  private fun loadImage(image: Image) {\n    Glide.with(this)\n      .load(image.fullFileUrl)\n      .transform(CenterCrop(), RoundedCorners(cornerRadius))\n      .into(binding.viewPersonGalleryImage)\n  }\n\n  private fun clear() {\n    Glide.with(this).clear(binding.viewPersonGalleryImage)\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/list/PeopleListBottomSheet.kt",
    "content": "package com.michaldrabik.ui_people.list\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport androidx.navigation.fragment.findNavController\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetDialog\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.common.FastLinearLayoutManager\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.requireLong\nimport com.michaldrabik.ui_base.utilities.extensions.requireSerializable\nimport com.michaldrabik.ui_base.utilities.extensions.requireString\nimport com.michaldrabik.ui_base.utilities.extensions.screenHeight\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_DEPARTMENT\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_PERSON\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_TITLE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_TYPE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_DETAILS\nimport com.michaldrabik.ui_people.R\nimport com.michaldrabik.ui_people.databinding.ViewPeopleListBinding\nimport com.michaldrabik.ui_people.details.PersonDetailsBottomSheet\nimport com.michaldrabik.ui_people.list.recycler.PeopleListAdapter\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass PeopleListBottomSheet : BaseBottomSheetFragment(R.layout.view_people_list) {\n\n  companion object {\n    fun createBundle(\n      mediaIdTrakt: IdTrakt,\n      mediaTitle: String,\n      mode: Mode,\n      department: Person.Department,\n    ) = bundleOf(\n      ARG_ID to mediaIdTrakt.id,\n      ARG_TITLE to mediaTitle,\n      ARG_TYPE to mode.type,\n      ARG_DEPARTMENT to department,\n    )\n  }\n\n  private val viewModel by viewModels<PeopleListViewModel>()\n  private val binding by viewBinding(ViewPeopleListBinding::bind)\n\n  private val mediaIdTrakt by lazy { IdTrakt(requireLong(ARG_ID)) }\n  private val mediaTitle by lazy { requireString(ARG_TITLE) }\n  private val mode by lazy { Mode.fromType(requireString(ARG_TYPE)) }\n  private val department by lazy { requireSerializable<Person.Department>(ARG_DEPARTMENT) }\n\n  private var adapter: PeopleListAdapter? = null\n  private var layoutManager: LinearLayoutManager? = null\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    setupView()\n    setupRecycler()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      doAfterLaunch = {\n        viewModel.loadPeople(\n          mediaIdTrakt,\n          mediaTitle,\n          mode,\n          department\n        )\n      }\n    )\n  }\n\n  private fun setupView() {\n    val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior\n    with(behavior) {\n      peekHeight = (screenHeight() * 0.45).toInt()\n      skipCollapsed = true\n      state = BottomSheetBehavior.STATE_COLLAPSED\n    }\n  }\n\n  private fun setupRecycler() {\n    layoutManager = FastLinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)\n    adapter = PeopleListAdapter(\n      onItemClickListener = { openDetails(it) },\n    )\n    with(binding.viewPeopleListRecycler) {\n      adapter = this@PeopleListBottomSheet.adapter\n      layoutManager = this@PeopleListBottomSheet.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n    }\n  }\n\n  private fun openDetails(item: Person) {\n    setFragmentResult(REQUEST_DETAILS, bundleOf(ARG_PERSON to item))\n    val bundle = PersonDetailsBottomSheet.createBundle(item, mediaIdTrakt, null)\n    findNavController().navigate(R.id.actionPeopleListDialogToDetails, bundle)\n  }\n\n  private fun render(uiState: PeopleListUiState) {\n    uiState.run {\n      peopleItems?.let { adapter?.setItems(it) }\n    }\n  }\n\n  override fun onDestroyView() {\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/list/PeopleListUiState.kt",
    "content": "package com.michaldrabik.ui_people.list\n\nimport com.michaldrabik.ui_people.list.recycler.PeopleListItem\n\ndata class PeopleListUiState(\n  val peopleItems: List<PeopleListItem>? = null,\n)\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/list/PeopleListViewModel.kt",
    "content": "package com.michaldrabik.ui_people.list\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_people.list.cases.PeopleListItemsCase\nimport com.michaldrabik.ui_people.list.recycler.PeopleListItem\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass PeopleListViewModel @Inject constructor(\n  private val itemsCase: PeopleListItemsCase\n) : ViewModel() {\n\n  private val peopleListState = MutableStateFlow<List<PeopleListItem>?>(null)\n\n  fun loadPeople(\n    idTrakt: IdTrakt,\n    title: String,\n    mode: Mode,\n    department: Person.Department\n  ) {\n    viewModelScope.launch {\n      val header = PeopleListItem.HeaderItem(department, title)\n      val people = itemsCase.loadPeople(idTrakt, mode, department)\n      peopleListState.value = listOf(header) + people\n    }\n  }\n\n  val uiState = combine(\n    peopleListState\n  ) { s1 ->\n    PeopleListUiState(\n      peopleItems = s1[0]\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = PeopleListUiState()\n  )\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/list/cases/PeopleListItemsCase.kt",
    "content": "package com.michaldrabik.ui_people.list.cases\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.PeopleRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_people.list.recycler.PeopleListItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass PeopleListItemsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val peopleRepository: PeopleRepository,\n) {\n\n  suspend fun loadPeople(\n    idTrakt: IdTrakt,\n    mode: Mode,\n    department: Person.Department\n  ): List<PeopleListItem.PersonItem> = withContext(dispatchers.IO) {\n    val ids = Ids.EMPTY.copy(trakt = idTrakt)\n    val people: Map<Person.Department, List<Person>> = when (mode) {\n      Mode.SHOWS -> peopleRepository.loadAllForShow(ids)\n      Mode.MOVIES -> peopleRepository.loadAllForMovie(ids)\n    }\n    people.getOrDefault(department, emptyList()).map {\n      PeopleListItem.PersonItem(it)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/list/recycler/PeopleItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_people.list.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass PeopleItemDiffCallback : DiffUtil.ItemCallback<PeopleListItem>() {\n\n  override fun areItemsTheSame(oldItem: PeopleListItem, newItem: PeopleListItem) =\n    when {\n      oldItem is PeopleListItem.HeaderItem && newItem is PeopleListItem.HeaderItem -> true\n      oldItem is PeopleListItem.PersonItem && newItem is PeopleListItem.PersonItem -> {\n        oldItem.person.ids.tmdb.id == newItem.person.ids.tmdb.id\n      }\n      else -> false\n    }\n\n  override fun areContentsTheSame(oldItem: PeopleListItem, newItem: PeopleListItem) = when {\n    oldItem is PeopleListItem.HeaderItem && newItem is PeopleListItem.HeaderItem -> {\n      oldItem == newItem\n    }\n    oldItem is PeopleListItem.PersonItem && newItem is PeopleListItem.PersonItem -> {\n      oldItem == newItem\n    }\n    else -> false\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/list/recycler/PeopleListAdapter.kt",
    "content": "package com.michaldrabik.ui_people.list.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_people.list.recycler.views.PeopleListHeaderView\nimport com.michaldrabik.ui_people.list.recycler.views.PeopleListItemView\n\nclass PeopleListAdapter(\n  var onItemClickListener: (Person) -> Unit,\n) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n  companion object {\n    private const val VIEW_TYPE_ITEM = 1\n    private const val VIEW_TYPE_HEADER = 2\n  }\n\n  private val asyncDiffer = AsyncListDiffer(this, PeopleItemDiffCallback())\n\n  fun setItems(items: List<PeopleListItem>) = asyncDiffer.submitList(items)\n\n  override fun getItemViewType(position: Int) =\n    when (asyncDiffer.currentList[position]) {\n      is PeopleListItem.HeaderItem -> VIEW_TYPE_HEADER\n      is PeopleListItem.PersonItem -> VIEW_TYPE_ITEM\n      else -> throw IllegalStateException()\n    }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    when (viewType) {\n      VIEW_TYPE_HEADER -> BaseViewHolder(PeopleListHeaderView(parent.context))\n      VIEW_TYPE_ITEM -> BaseViewHolder(\n        PeopleListItemView(parent.context).apply {\n          onItemClickListener = this@PeopleListAdapter.onItemClickListener\n        }\n      )\n      else -> throw IllegalStateException()\n    }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) =\n    when (val item = asyncDiffer.currentList[position]) {\n      is PeopleListItem.HeaderItem -> (holder.itemView as PeopleListHeaderView).bind(item)\n      is PeopleListItem.PersonItem -> (holder.itemView as PeopleListItemView).bind(item)\n    }\n\n  override fun getItemCount() = asyncDiffer.currentList.size\n\n  class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/list/recycler/PeopleListItem.kt",
    "content": "package com.michaldrabik.ui_people.list.recycler\n\nimport com.michaldrabik.ui_model.Person\n\nsealed class PeopleListItem {\n\n  data class PersonItem(\n    val person: Person,\n  ) : PeopleListItem()\n\n  data class HeaderItem(\n    val department: Person.Department,\n    val mediaTitle: String\n  ) : PeopleListItem()\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/list/recycler/views/PeopleListHeaderView.kt",
    "content": "package com.michaldrabik.ui_people.list.recycler.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_people.R\nimport com.michaldrabik.ui_people.databinding.ViewPeopleListHeaderBinding\nimport com.michaldrabik.ui_people.list.recycler.PeopleListItem\n\nclass PeopleListHeaderView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewPeopleListHeaderBinding.inflate(LayoutInflater.from(context), this)\n\n  private lateinit var item: PeopleListItem.HeaderItem\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n  }\n\n  fun bind(item: PeopleListItem.HeaderItem) {\n    this.item = item\n    with(binding) {\n      viewPeopleListHeaderTitle.text = when (item.department) {\n        Person.Department.ACTING -> context.getString(R.string.textActing)\n        Person.Department.DIRECTING -> context.getString(R.string.textDirecting)\n        Person.Department.WRITING -> context.getString(R.string.textWriting)\n        Person.Department.SOUND -> context.getString(R.string.textMusic)\n        else -> \"-\"\n      }\n      viewPeopleListHeaderSubtitle.text = item.mediaTitle\n    }\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/java/com/michaldrabik/ui_people/list/recycler/views/PeopleListItemView.kt",
    "content": "package com.michaldrabik.ui_people.list.recycler.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_model.Person.Department\nimport com.michaldrabik.ui_model.Person.Job\nimport com.michaldrabik.ui_people.R\nimport com.michaldrabik.ui_people.databinding.ViewPeopleListItemBinding\nimport com.michaldrabik.ui_people.list.recycler.PeopleListItem\n\nclass PeopleListItemView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewPeopleListItemBinding.inflate(LayoutInflater.from(context), this)\n\n  var onItemClickListener: ((Person) -> Unit)? = null\n\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.mediaTileCorner) }\n  private val centerCropTransformation by lazy { CenterCrop() }\n  private val cornersTransformation by lazy { RoundedCorners(cornerRadius) }\n\n  private lateinit var item: PeopleListItem.PersonItem\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    binding.viewPersonItemRoot.onClick { onItemClickListener?.invoke(item.person) }\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  fun bind(item: PeopleListItem.PersonItem) {\n    clear()\n    this.item = item\n\n    with(binding) {\n      viewPersonItemTitle.text = item.person.name\n      val mainJob = item.person.jobs.firstOrNull { it != Job.UNKNOWN }\n      viewPersonItemHeader.text = when (mainJob) {\n        Job.DIRECTOR -> context.getString(R.string.textDirector)\n        Job.WRITER, Job.STORY -> context.getString(R.string.textWriting)\n        Job.SCREENPLAY -> context.getString(R.string.textScreenplay)\n        Job.MUSIC, Job.ORIGINAL_MUSIC -> context.getString(R.string.textMusic)\n        else -> when (item.person.department) {\n          Department.ACTING -> context.getString(R.string.textActing)\n          Department.DIRECTING -> context.getString(R.string.textDirector)\n          Department.WRITING -> context.getString(R.string.textWriting)\n          Department.SOUND -> context.getString(R.string.textMusic)\n          Department.UNKNOWN -> \"-\"\n        }\n      }\n      viewPersonItemDescription.visibleIf(item.person.episodesCount > 0)\n      viewPersonItemDescription.text = \"${context.getString(R.string.textEpisodes)}: ${item.person.episodesCount}\"\n    }\n\n    loadImage(item.person.imagePath)\n  }\n\n  private fun loadImage(imagePath: String?) {\n    with(binding) {\n      if (imagePath.isNullOrBlank()) {\n        viewPersonItemImage.gone()\n        viewPersonItemPlaceholder.visible()\n        return\n      }\n      Glide.with(this@PeopleListItemView)\n        .load(\"${Config.TMDB_IMAGE_BASE_PROFILE_THUMB_URL}$imagePath\")\n        .transform(centerCropTransformation, cornersTransformation)\n        .transition(DrawableTransitionOptions.withCrossFade(Config.IMAGE_FADE_DURATION_MS))\n        .withSuccessListener {\n          viewPersonItemPlaceholder.gone()\n        }\n        .withFailListener {\n          viewPersonItemImage.gone()\n          viewPersonItemPlaceholder.fadeIn(Config.IMAGE_FADE_DURATION_MS.toLong())\n        }\n        .into(viewPersonItemImage)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      viewPersonItemImage.visible()\n      viewPersonItemPlaceholder.gone()\n      Glide.with(this@PeopleListItemView).clear(viewPersonItemImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-people/src/main/res/color/selector_search_chip_background.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?android:windowBackground\" android:state_checked=\"true\" />\n  <item android:color=\"?android:windowBackground\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "ui-people/src/main/res/color/selector_search_chip_text.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?attr/textColorChipSelected\" android:state_checked=\"true\" />\n  <item android:color=\"?attr/textColorChip\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "ui-people/src/main/res/drawable/bg_indicator_circle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"oval\"\n  >\n  <solid android:color=\"@color/colorWhite\" />\n</shape>"
  },
  {
    "path": "ui-people/src/main/res/drawable/bg_person_image_elevation.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <solid android:color=\"?android:windowBackground\" />\n  <corners\n    android:radius=\"@dimen/mediaTileCorner\"\n    android:topLeftRadius=\"@dimen/personImageCorner\"\n    />\n</shape>"
  },
  {
    "path": "ui-people/src/main/res/drawable/bg_person_placeholder.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <solid android:color=\"?attr/colorPlaceholderBackground\" />\n  <corners\n    android:radius=\"@dimen/mediaTileCorner\"\n    android:topLeftRadius=\"@dimen/personImageCorner\"\n    />\n  <stroke\n    android:width=\"1dp\"\n    android:color=\"?attr/colorPlaceholderStroke\"\n    />\n</shape>"
  },
  {
    "path": "ui-people/src/main/res/drawable-notnight/bg_indicator_circle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"oval\"\n  >\n  <solid android:color=\"@color/colorBlue\" />\n</shape>"
  },
  {
    "path": "ui-people/src/main/res/layout/fragment_person_gallery.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/personGalleryRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  >\n\n  <androidx.viewpager2.widget.ViewPager2\n    android:id=\"@+id/personGalleryPager\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n  <ImageView\n    android:id=\"@+id/personGalleryBackArrow\"\n    android:layout_width=\"@dimen/backArrowSize\"\n    android:layout_height=\"@dimen/backArrowSize\"\n    android:layout_marginStart=\"@dimen/personGalleryBackArrowMargin\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:padding=\"@dimen/backArrowPadding\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_arrow_back\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n  <me.relex.circleindicator.CircleIndicator3\n    android:id=\"@+id/personGalleryPagerIndicator\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"12dp\"\n    android:layout_gravity=\"center_horizontal|bottom\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    app:ci_drawable=\"@drawable/bg_indicator_circle\"\n    app:ci_drawable_unselected=\"@drawable/bg_indicator_circle\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    />\n\n  <me.relex.circleindicator.CircleIndicator3\n    android:id=\"@+id/personGalleryPagerIndicatorWhite\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"12dp\"\n    android:layout_gravity=\"center_horizontal|bottom\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    />\n\n  <TextView\n    android:id=\"@+id/personGalleryEmptyView\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:text=\"@string/textGalleryEmpty\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"16sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <ImageView\n    android:id=\"@+id/personGalleryBrowserIcon\"\n    android:layout_width=\"@dimen/backArrowSize\"\n    android:layout_height=\"@dimen/backArrowSize\"\n    android:padding=\"16dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_open_browser\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/personGalleryImagesProgress\"\n    style=\"@style/ProgressBar\"\n    android:layout_width=\"20dp\"\n    android:layout_height=\"20dp\"\n    android:layout_margin=\"@dimen/spaceMedium\"\n    android:indeterminateTint=\"?android:attr/textColorPrimary\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "ui-people/src/main/res/layout/view_people_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/viewPeopleListRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/viewPeopleListRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@android:color/transparent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingTop=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceNormal\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "ui-people/src/main/res/layout/view_people_list_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/viewPeopleHeaderRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:clipToPadding=\"false\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewPeopleListHeaderTitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textAllCaps=\"true\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"20sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewPeopleListHeaderSubtitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:text=\"Directing\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewPeopleListHeaderSubtitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewPeopleListHeaderTitle\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-people/src/main/res/layout/view_people_list_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/viewPersonItemRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:clipToPadding=\"false\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingTop=\"@dimen/spaceSmall\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <androidx.constraintlayout.widget.Guideline\n      android:id=\"@+id/viewPersonItemGuide\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"vertical\"\n      app:layout_constraintGuide_begin=\"@dimen/personCreditsImageGuide\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewPersonItemImage\"\n      android:layout_width=\"@dimen/personCreditsImageWidth\"\n      android:layout_height=\"@dimen/personCreditsImageHeight\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewPersonItemPlaceholder\"\n      android:layout_width=\"@dimen/personCreditsImageWidth\"\n      android:layout_height=\"@dimen/personCreditsImageHeight\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"14dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_person_outline\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewPersonItemHeader\"\n      style=\"@style/CollectionItem.Header\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:textSize=\"11sp\"\n      app:layout_constraintBottom_toTopOf=\"@+id/viewPersonItemTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewPersonItemGuide\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Director\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewPersonItemTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:textSize=\"16sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewPersonItemDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewPersonItemGuide\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewPersonItemHeader\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Hans Zimmer\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewPersonItemDescription\"\n      style=\"@style/CollectionItem.Description\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:textSize=\"11sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewPersonItemGuide\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewPersonItemTitle\"\n      tools:text=\"Music\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-people/src/main/res/layout/view_person_details.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  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/viewPersonDetailsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/personDetailsRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@android:color/transparent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:minHeight=\"1000dp\"\n    android:overScrollMode=\"never\"\n    android:paddingTop=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"56dp\"\n    />\n\n  <com.google.android.material.floatingactionbutton.FloatingActionButton\n    android:id=\"@+id/personDetailsRecyclerFab\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"bottom|end\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    android:rotation=\"270\"\n    android:visibility=\"gone\"\n    app:backgroundTint=\"?attr/colorAccent\"\n    app:fabSize=\"mini\"\n    app:layout_behavior=\"com.google.android.material.floatingactionbutton.FloatingActionButton$Behavior\"\n    app:maxImageSize=\"30dp\"\n    app:srcCompat=\"@drawable/ic_arrow_right\"\n    app:tint=\"?attr/textColorOnSurface\"\n    tools:ignore=\"ContentDescription\"\n    tools:visibility=\"visible\"\n    />\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/personDetailsSnackHost\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"top\"\n    android:layout_marginTop=\"@dimen/personImageTipMargin\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "ui-people/src/main/res/layout/view_person_details_bio.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <com.michaldrabik.ui_base.common.views.FoldableTextView\n    android:id=\"@+id/personBioText\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:ellipsize=\"end\"\n    android:gravity=\"start\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"14sp\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPersonDetailsSeparator\"\n    tools:text=\"@tools:sample/lorem/random\"\n    />\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/snackbarLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"bottom\"\n    />\n\n</merge>\n\n"
  },
  {
    "path": "ui-people/src/main/res/layout/view_person_details_credits_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/viewPersonCreditsItemRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:clipToPadding=\"false\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingTop=\"@dimen/spaceSmall\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <androidx.constraintlayout.widget.Guideline\n      android:id=\"@+id/viewPersonCreditsItemGuide\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"vertical\"\n      app:layout_constraintGuide_begin=\"@dimen/personCreditsImageGuide\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewPersonCreditsItemImage\"\n      android:layout_width=\"@dimen/personCreditsImageWidth\"\n      android:layout_height=\"@dimen/personCreditsImageHeight\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewPersonCreditsItemPlaceholder\"\n      android:layout_width=\"@dimen/personCreditsImageWidth\"\n      android:layout_height=\"@dimen/personCreditsImageHeight\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"14dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewPersonCreditsItemBadge\"\n      style=\"@style/Badge\"\n      android:layout_width=\"18dp\"\n      android:layout_height=\"18dp\"\n      android:layout_marginTop=\"-5dp\"\n      android:layout_marginEnd=\"12dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewPersonCreditsItemGuide\"\n      app:layout_constraintTop_toTopOf=\"@id/viewPersonCreditsItemRoot\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewPersonCreditsItemWatchlistBadge\"\n      style=\"@style/Badge.Watchlist\"\n      android:layout_width=\"18dp\"\n      android:layout_height=\"18dp\"\n      android:layout_marginTop=\"-5dp\"\n      android:layout_marginEnd=\"12dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewPersonCreditsItemGuide\"\n      app:layout_constraintTop_toTopOf=\"@id/viewPersonCreditsItemRoot\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewPersonCreditsItemTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:textSize=\"16sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewPersonCreditsItemDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewPersonCreditsItemGuide\"\n      app:layout_constraintTop_toBottomOf=\"@+id/viewPersonCreditsItemNetwork\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Game of Thrones\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewPersonCreditsItemIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"14dp\"\n      android:layout_marginStart=\"-2dp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/viewPersonCreditsItemNetwork\"\n      app:layout_constraintStart_toEndOf=\"@id/viewPersonCreditsItemGuide\"\n      app:layout_constraintTop_toTopOf=\"@id/viewPersonCreditsItemNetwork\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewPersonCreditsItemNetwork\"\n      style=\"@style/CollectionItem.Header\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMicro\"\n      android:layout_marginBottom=\"@dimen/spaceMicro\"\n      android:textSize=\"11sp\"\n      app:layout_constraintBottom_toTopOf=\"@+id/viewPersonCreditsItemTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewPersonCreditsItemIcon\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"2006 (Netflix)\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewPersonCreditsItemDescription\"\n      style=\"@style/CollectionItem.Description\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:textSize=\"12sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewPersonCreditsItemGuide\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewPersonCreditsItemTitle\"\n      tools:text=\"Lorem Ipsum\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-people/src/main/res/layout/view_person_details_filters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n  android:id=\"@+id/viewPersonDetailsFiltersRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  >\n\n  <com.google.android.material.chip.ChipGroup\n    android:id=\"@+id/viewPersonDetailsFiltersChipGroup\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    app:chipSpacingHorizontal=\"@dimen/spaceSmall\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    >\n\n    <com.google.android.material.chip.Chip\n      android:id=\"@+id/viewPersonDetailsFiltersShowsChip\"\n      style=\"@style/Widget.MaterialComponents.Chip.Choice\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:text=\"@string/textShows\"\n      android:textColor=\"@color/selector_search_chip_text\"\n      android:textSize=\"16sp\"\n      app:checkedIconTint=\"@color/selector_search_chip_text\"\n      app:chipBackgroundColor=\"@color/selector_search_chip_background\"\n      app:chipStrokeColor=\"@color/selector_search_chip_text\"\n      app:chipStrokeWidth=\"1dp\"\n      app:ensureMinTouchTargetSize=\"false\"\n      />\n\n    <com.google.android.material.chip.Chip\n      android:id=\"@+id/viewPersonDetailsFiltersMoviesChip\"\n      style=\"@style/Widget.MaterialComponents.Chip.Choice\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:text=\"@string/textMovies\"\n      android:textColor=\"@color/selector_search_chip_text\"\n      android:textSize=\"16sp\"\n      app:checkedIconTint=\"@color/selector_search_chip_text\"\n      app:chipBackgroundColor=\"@color/selector_search_chip_background\"\n      app:chipStrokeColor=\"@color/selector_search_chip_text\"\n      app:chipStrokeWidth=\"1dp\"\n      app:ensureMinTouchTargetSize=\"false\"\n      />\n\n  </com.google.android.material.chip.ChipGroup>\n\n</FrameLayout>"
  },
  {
    "path": "ui-people/src/main/res/layout/view_person_details_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <TextView\n    android:id=\"@+id/viewPersonDetailsHeader\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginBottom=\"@dimen/spaceSmall\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"@dimen/personCreditsHeaderTextSize\"\n    android:textStyle=\"bold\"\n    tools:text=\"2021\"\n    />\n\n</merge>\n"
  },
  {
    "path": "ui-people/src/main/res/layout/view_person_details_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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/viewPersonDetailsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  tools:background=\"?android:windowBackground\"\n  tools:ignore=\"UnusedAttribute\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  tools:targetApi=\"m\"\n  >\n\n  <androidx.constraintlayout.widget.Guideline\n    android:id=\"@+id/viewPersonDetailsImageGuide1\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    app:layout_constraintGuide_begin=\"@dimen/personImageWidthPadded\"\n    />\n\n  <androidx.constraintlayout.widget.Guideline\n    android:id=\"@+id/viewPersonDetailsImageGuide2\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    app:layout_constraintGuide_begin=\"@dimen/personImageHeight\"\n    />\n\n  <ImageView\n    android:id=\"@+id/viewPersonDetailsImage\"\n    android:layout_width=\"@dimen/personImageWidth\"\n    android:layout_height=\"@dimen/personImageHeight\"\n    android:background=\"@drawable/bg_person_image_elevation\"\n    android:elevation=\"@dimen/elevationNormal\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/viewPersonDetailsImageGuide2\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/viewPersonDetailsPlaceholder\"\n    android:layout_width=\"@dimen/personImageWidth\"\n    android:layout_height=\"@dimen/personImageHeight\"\n    android:layout_gravity=\"center\"\n    android:background=\"@drawable/bg_person_placeholder\"\n    android:elevation=\"@dimen/elevationNormal\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    android:padding=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/viewPersonDetailsImageGuide2\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_person_outline\"\n    app:tint=\"?attr/colorPlaceholderIcon\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/viewPersonDetailsLinkIcon\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    android:paddingStart=\"8dp\"\n    android:paddingBottom=\"5dp\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_link\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewPersonDetailsTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:ellipsize=\"end\"\n    android:gravity=\"start\"\n    android:includeFontPadding=\"false\"\n    android:maxLines=\"2\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"@dimen/personDetailsInfoTitle\"\n    android:textStyle=\"bold\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewPersonDetailsLinkIcon\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPersonDetailsImageGuide1\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:text=\"Janet Jackson\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewPersonDetailsSubtitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:ellipsize=\"end\"\n    android:gravity=\"start\"\n    android:maxLines=\"1\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"@color/colorAccent\"\n    android:textSize=\"@dimen/personDetailsInfoSubtitle\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewPersonDetailsLinkIcon\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPersonDetailsImageGuide1\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPersonDetailsTitle\"\n    tools:text=\"Starlord, John Doe, Starlord, John Doe, Starlord, John Doe, Starlord, John Doe\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewPersonDetailsBirthdayLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:ellipsize=\"end\"\n    android:gravity=\"start\"\n    android:maxLines=\"1\"\n    android:text=\"@string/textBorn\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"0\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPersonDetailsImageGuide1\"\n    app:layout_constraintTop_toTopOf=\"@id/viewPersonDetailsBirthdayValue\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewPersonDetailsBirthdayValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceTiny\"\n    android:layout_marginBottom=\"@dimen/spaceMicro\"\n    android:ellipsize=\"end\"\n    android:gravity=\"start\"\n    android:maxLines=\"2\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"14sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewPersonDetailsDeathdayValue\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"0\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPersonDetailsBirthdayLabel\"\n    app:layout_goneMarginBottom=\"0dp\"\n    tools:text=\"@tools:sample/lorem/random\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewPersonDetailsDeathdayLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:ellipsize=\"end\"\n    android:gravity=\"start\"\n    android:maxLines=\"1\"\n    android:text=\"@string/textDied\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewPersonDetailsImageGuide2\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"0\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPersonDetailsImageGuide1\"\n    app:layout_goneMarginTop=\"@dimen/spaceMedium\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewPersonDetailsDeathdayValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceTiny\"\n    android:ellipsize=\"end\"\n    android:gravity=\"start\"\n    android:maxLines=\"1\"\n    android:text=\"-\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"14sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/viewPersonDetailsDeathdayLabel\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"0\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPersonDetailsDeathdayLabel\"\n    app:layout_constraintTop_toTopOf=\"@id/viewPersonDetailsDeathdayLabel\"\n    tools:ignore=\"HardcodedText\"\n    tools:text=\"@tools:sample/lorem/random\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewPersonDetailsAgeLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:ellipsize=\"end\"\n    android:gravity=\"start\"\n    android:maxLines=\"1\"\n    android:text=\"@string/textAge\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/viewPersonDetailsAgeValue\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"0\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPersonDetailsImageGuide1\"\n    app:layout_constraintTop_toTopOf=\"@id/viewPersonDetailsAgeValue\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewPersonDetailsAgeValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceTiny\"\n    android:layout_marginBottom=\"@dimen/spaceMicro\"\n    android:gravity=\"start\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"14sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewPersonDetailsBirthdayValue\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"0\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPersonDetailsBirthdayLabel\"\n    app:layout_goneMarginBottom=\"0dp\"\n    tools:text=\"@tools:sample/lorem\"\n    tools:visibility=\"visible\"\n    />\n\n  <View\n    android:id=\"@+id/viewPersonDetailsSeparator\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"1dp\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginBottom=\"@dimen/spaceMedium\"\n    android:background=\"?attr/colorSeparator\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPersonDetailsImageGuide2\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/viewPersonDetailsProgress\"\n    style=\"@style/ProgressBar.Accent\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewPersonDetailsImageGuide2\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPersonDetailsImageGuide1\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPersonDetailsSubtitle\"\n    tools:visibility=\"visible\"\n    />\n\n</merge>\n"
  },
  {
    "path": "ui-people/src/main/res/layout/view_person_details_loading.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <ProgressBar\n    android:id=\"@+id/viewPersonDetailsProgress\"\n    style=\"@style/ProgressBar.Accent\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewPersonDetailsImageGuide2\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPersonDetailsImageGuide1\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPersonDetailsSubtitle\"\n    />\n\n</merge>\n\n"
  },
  {
    "path": "ui-people/src/main/res/layout/view_person_gallery_image.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <ImageView\n    android:id=\"@+id/viewPersonGalleryImage\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"0dp\"\n    android:layout_gravity=\"center\"\n    android:background=\"@drawable/bg_media_view_elevation\"\n    android:elevation=\"@dimen/elevationTiny\"\n    android:scaleType=\"fitCenter\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintDimensionRatio=\"H,1:1.5\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_constraintWidth_percent=\"@dimen/personGalleryImageRatio\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-people/src/main/res/layout/view_person_links.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n  xmlns:custom=\"http://schemas.android.com/apk/res-auto\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/viewPersonLinksRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:background=\"@drawable/bg_bottom_sheet\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  android:focusableInTouchMode=\"true\"\n  android:paddingTop=\"@dimen/spaceMedium\"\n  android:paddingBottom=\"@dimen/spaceNormal\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <androidx.constraintlayout.widget.Guideline\n    android:id=\"@+id/viewPersonLinksGuideline\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    app:layout_constraintGuide_percent=\"0.5\"\n    />\n\n  <TextView\n    android:id=\"@+id/viewPersonLinksTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:gravity=\"start\"\n    android:text=\"@string/textLink\"\n    android:textAlignment=\"viewStart\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewPersonLinksWebsite\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewPersonLinksGuideline\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPersonLinksTitle\"\n    custom:icon=\"@drawable/ic_link_color\"\n    custom:text=\"Website\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewPersonLinksImdb\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewPersonLinksGuideline\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPersonLinksWebsite\"\n    custom:icon=\"@drawable/ic_imdb\"\n    custom:text=\"IMDb\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewPersonLinksTmdb\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    app:layout_constraintEnd_toStartOf=\"@id/viewPersonLinksGuideline\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPersonLinksImdb\"\n    custom:icon=\"@drawable/ic_tmdb\"\n    custom:text=\"TMDB\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewPersonLinksGoogle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPersonLinksGuideline\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPersonLinksTitle\"\n    custom:icon=\"@drawable/ic_google\"\n    custom:text=\"Google\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewPersonLinksDuckDuck\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPersonLinksGuideline\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPersonLinksGoogle\"\n    custom:icon=\"@drawable/ic_duckduck\"\n    custom:text=\"DuckDuckGo\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewPersonLinksWiki\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPersonLinksGuideline\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPersonLinksDuckDuck\"\n    custom:icon=\"@drawable/ic_wikipedia\"\n    custom:text=\"Wikipedia\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewPersonLinksYouTube\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toEndOf=\"@id/viewPersonLinksGuideline\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPersonLinksWiki\"\n    custom:icon=\"@drawable/ic_youtube\"\n    custom:text=\"YouTube\"\n    />\n\n  <com.michaldrabik.ui_base.common.sheets.links.views.LinkItemView\n    android:id=\"@+id/viewPersonLinksTwitter\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    app:layout_constraintEnd_toEndOf=\"@id/viewPersonLinksGuideline\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPersonLinksTmdb\"\n    custom:icon=\"@drawable/ic_twitter\"\n    custom:text=\"Twitter\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewPersonLinksButtonClose\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:backgroundTint=\"@color/selector_main_button\"\n    android:gravity=\"center\"\n    android:text=\"@string/textClose\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewPersonLinksTwitter\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-people/src/main/res/layout-sw600dp/view_person_details_credits_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/viewPersonCreditsItemRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:clipToPadding=\"false\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingTop=\"@dimen/spaceSmall\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <androidx.constraintlayout.widget.Guideline\n      android:id=\"@+id/viewPersonCreditsItemGuide\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"vertical\"\n      app:layout_constraintGuide_begin=\"@dimen/personCreditsImageGuide\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewPersonCreditsItemImage\"\n      android:layout_width=\"@dimen/personCreditsImageWidth\"\n      android:layout_height=\"@dimen/personCreditsImageHeight\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewPersonCreditsItemPlaceholder\"\n      android:layout_width=\"@dimen/personCreditsImageWidth\"\n      android:layout_height=\"@dimen/personCreditsImageHeight\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"20dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewPersonCreditsItemBadge\"\n      style=\"@style/Badge\"\n      android:layout_width=\"18dp\"\n      android:layout_height=\"18dp\"\n      android:layout_marginTop=\"-5dp\"\n      android:layout_marginEnd=\"18dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewPersonCreditsItemGuide\"\n      app:layout_constraintTop_toTopOf=\"@id/viewPersonCreditsItemRoot\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewPersonCreditsItemWatchlistBadge\"\n      style=\"@style/Badge.Watchlist\"\n      android:layout_width=\"18dp\"\n      android:layout_height=\"18dp\"\n      android:layout_marginTop=\"-5dp\"\n      android:layout_marginEnd=\"18dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewPersonCreditsItemGuide\"\n      app:layout_constraintTop_toTopOf=\"@id/viewPersonCreditsItemRoot\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewPersonCreditsItemTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:textSize=\"18sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewPersonCreditsItemDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewPersonCreditsItemGuide\"\n      app:layout_constraintTop_toBottomOf=\"@+id/viewPersonCreditsItemNetwork\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Game of Thrones\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewPersonCreditsItemIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"16dp\"\n      android:layout_marginStart=\"-2dp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/viewPersonCreditsItemNetwork\"\n      app:layout_constraintStart_toEndOf=\"@id/viewPersonCreditsItemGuide\"\n      app:layout_constraintTop_toTopOf=\"@id/viewPersonCreditsItemNetwork\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewPersonCreditsItemNetwork\"\n      style=\"@style/CollectionItem.Header\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:layout_marginBottom=\"@dimen/spaceMicro\"\n      android:textSize=\"13sp\"\n      app:layout_constraintBottom_toTopOf=\"@+id/viewPersonCreditsItemTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewPersonCreditsItemIcon\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"2006 (Netflix)\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewPersonCreditsItemDescription\"\n      style=\"@style/CollectionItem.Description\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewPersonCreditsItemGuide\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewPersonCreditsItemTitle\"\n      tools:text=\"Lorem Ipsum\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-people/src/main/res/values/dimens.xml",
    "content": "<resources>\n  <dimen name=\"personImageWidth\">80dp</dimen>\n  <dimen name=\"personImageWidthPadded\">96dp</dimen>\n  <dimen name=\"personImageHeight\">120dp</dimen>\n  <dimen name=\"personImageCorner\">17dp</dimen>\n  <dimen name=\"personImageTipMargin\">152dp</dimen>\n\n  <dimen name=\"personDetailsInfoTitle\">20sp</dimen>\n  <dimen name=\"personDetailsInfoSubtitle\">14sp</dimen>\n\n  <dimen name=\"personCreditsImageWidth\">53dp</dimen>\n  <dimen name=\"personCreditsImageHeight\">80dp</dimen>\n  <dimen name=\"personCreditsImageGuide\">65dp</dimen>\n  <dimen name=\"personCreditsHeaderTextSize\">20sp</dimen>\n\n  <dimen name=\"personGalleryBackArrowMargin\">0dp</dimen>\n  <item name=\"personGalleryImageRatio\" format=\"float\" type=\"dimen\">0.8</item>\n</resources>\n"
  },
  {
    "path": "ui-people/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textAge\" comment=\"Person's age\">Age:</string>\n  <string name=\"textBorn\" comment=\"For example - Born: 12 January 1990\">Born:</string>\n  <string name=\"textDied\" comment=\"For example - Died: 12 January 2020\">Died:</string>\n  <string name=\"textCredits\" comment=\"List of shows and movies the person appears on\">Credits</string>\n  <string name=\"textEpisodes\">Episodes</string>\n  <string name=\"textGalleryEmpty\">No images available</string>\n</resources>\n"
  },
  {
    "path": "ui-people/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textAge\" comment=\"Person's age\">العمر:</string>\n  <string name=\"textBorn\" comment=\"For example - Born: 12 January 1990\">الميلاد:</string>\n  <string name=\"textDied\" comment=\"For example - Died: 12 January 2020\">الوفاة:</string>\n  <string name=\"textCredits\" comment=\"List of shows and movies the person appears on\">الأعمال</string>\n  <string name=\"textEpisodes\">حلقات</string>\n  <string name=\"textGalleryEmpty\">لا توجد صور</string>\n</resources>\n"
  },
  {
    "path": "ui-people/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textAge\" comment=\"Person's age\">Alter:</string>\n  <string name=\"textBorn\" comment=\"For example - Born: 12 January 1990\">Geboren:</string>\n  <string name=\"textDied\" comment=\"For example - Died: 12 January 2020\">Gestorben:</string>\n  <string name=\"textCredits\" comment=\"List of shows and movies the person appears on\">Auftritte</string>\n  <string name=\"textEpisodes\">Folgen</string>\n  <string name=\"textGalleryEmpty\">Keine Bilder verfügbar</string>\n</resources>\n"
  },
  {
    "path": "ui-people/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textAge\" comment=\"Person's age\">Edad:</string>\n  <string name=\"textBorn\" comment=\"For example - Born: 12 January 1990\">Nacido:</string>\n  <string name=\"textDied\" comment=\"For example - Died: 12 January 2020\">Muerto:</string>\n  <string name=\"textCredits\" comment=\"List of shows and movies the person appears on\">Créditos</string>\n  <string name=\"textEpisodes\">Episodios</string>\n  <string name=\"textGalleryEmpty\">No hay imágenes disponibles</string>\n</resources>\n"
  },
  {
    "path": "ui-people/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textAge\" comment=\"Person's age\">Ikä:</string>\n  <string name=\"textBorn\" comment=\"For example - Born: 12 January 1990\">Syntynyt:</string>\n  <string name=\"textDied\" comment=\"For example - Died: 12 January 2020\">Kuollut:</string>\n  <string name=\"textCredits\" comment=\"List of shows and movies the person appears on\">Työt</string>\n  <string name=\"textEpisodes\">jaksoa</string>\n  <string name=\"textGalleryEmpty\">Kuvia ei ole saatavilla</string>\n</resources>\n"
  },
  {
    "path": "ui-people/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textAge\" comment=\"Person's age\">Âge :</string>\n  <string name=\"textBorn\" comment=\"For example - Born: 12 January 1990\">Né(e) le :</string>\n  <string name=\"textDied\" comment=\"For example - Died: 12 January 2020\">Décédé(e) :</string>\n  <string name=\"textCredits\" comment=\"List of shows and movies the person appears on\">Filmographie</string>\n  <string name=\"textEpisodes\">Épisodes</string>\n  <string name=\"textGalleryEmpty\">Aucune image disponible</string>\n</resources>\n"
  },
  {
    "path": "ui-people/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textAge\" comment=\"Person's age\">Età:</string>\n  <string name=\"textBorn\" comment=\"For example - Born: 12 January 1990\">Nascita:</string>\n  <string name=\"textDied\" comment=\"For example - Died: 12 January 2020\">Morte:</string>\n  <string name=\"textCredits\" comment=\"List of shows and movies the person appears on\">Crediti</string>\n  <string name=\"textEpisodes\">Episodi</string>\n  <string name=\"textGalleryEmpty\">Nessuna immagine disponibile</string>\n</resources>\n"
  },
  {
    "path": "ui-people/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textAge\" comment=\"Person's age\">Wiek:</string>\n  <string name=\"textBorn\" comment=\"For example - Born: 12 January 1990\">Urodziny:</string>\n  <string name=\"textDied\" comment=\"For example - Died: 12 January 2020\">Śmierć:</string>\n  <string name=\"textCredits\" comment=\"List of shows and movies the person appears on\">Produkcje</string>\n  <string name=\"textEpisodes\">Odcinki</string>\n  <string name=\"textGalleryEmpty\">Brak dostępnych obrazów</string>\n</resources>\n"
  },
  {
    "path": "ui-people/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textAge\" comment=\"Person's age\">Idade:</string>\n  <string name=\"textBorn\" comment=\"For example - Born: 12 January 1990\">Nascimento:</string>\n  <string name=\"textDied\" comment=\"For example - Died: 12 January 2020\">Morte:</string>\n  <string name=\"textCredits\" comment=\"List of shows and movies the person appears on\">Creditado</string>\n  <string name=\"textEpisodes\">Episódios</string>\n  <string name=\"textGalleryEmpty\">Nenhuma imagem disponível</string>\n</resources>\n"
  },
  {
    "path": "ui-people/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textAge\" comment=\"Person's age\">Возраст:</string>\n  <string name=\"textBorn\" comment=\"For example - Born: 12 January 1990\">Рожден:</string>\n  <string name=\"textDied\" comment=\"For example - Died: 12 January 2020\">Умер:</string>\n  <string name=\"textCredits\" comment=\"List of shows and movies the person appears on\">Авторство</string>\n  <string name=\"textEpisodes\">Эпизоды</string>\n  <string name=\"textGalleryEmpty\">Нет доступных изображений</string>\n</resources>\n"
  },
  {
    "path": "ui-people/src/main/res/values-sw600dp/dimens.xml",
    "content": "<resources>\n  <dimen name=\"personImageWidth\">100dp</dimen>\n  <dimen name=\"personImageWidthPadded\">116dp</dimen>\n  <dimen name=\"personImageHeight\">150dp</dimen>\n  <dimen name=\"personImageCorner\">18dp</dimen>\n\n  <dimen name=\"personDetailsInfoTitle\">22sp</dimen>\n  <dimen name=\"personDetailsInfoSubtitle\">16sp</dimen>\n\n  <dimen name=\"personCreditsImageWidth\">67dp</dimen>\n  <dimen name=\"personCreditsImageHeight\">100dp</dimen>\n  <dimen name=\"personCreditsImageGuide\">83dp</dimen>\n  <dimen name=\"personCreditsHeaderTextSize\">22sp</dimen>\n\n  <dimen name=\"personGalleryBackArrowMargin\">8dp</dimen>\n  <item name=\"personGalleryImageRatio\" format=\"float\" type=\"dimen\">0.67</item>\n</resources>\n"
  },
  {
    "path": "ui-people/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textAge\" comment=\"Person's age\">Yaş:</string>\n  <string name=\"textBorn\" comment=\"For example - Born: 12 January 1990\">Doğum:</string>\n  <string name=\"textDied\" comment=\"For example - Died: 12 January 2020\">Ölüm:</string>\n  <string name=\"textCredits\" comment=\"List of shows and movies the person appears on\">Katkıda Bulunanlar</string>\n  <string name=\"textEpisodes\">Bölüm</string>\n  <string name=\"textGalleryEmpty\">Kullanılabilir fotoğraf yok</string>\n</resources>\n"
  },
  {
    "path": "ui-people/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textAge\" comment=\"Person's age\">Вік:</string>\n  <string name=\"textBorn\" comment=\"For example - Born: 12 January 1990\">Народився:</string>\n  <string name=\"textDied\" comment=\"For example - Died: 12 January 2020\">Помер:</string>\n  <string name=\"textCredits\" comment=\"List of shows and movies the person appears on\">Фільмографія</string>\n  <string name=\"textEpisodes\">Серії</string>\n  <string name=\"textGalleryEmpty\">Немає доступних зображень</string>\n</resources>\n"
  },
  {
    "path": "ui-people/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textAge\" comment=\"Person's age\">Tuổi:</string>\n  <string name=\"textBorn\" comment=\"For example - Born: 12 January 1990\">Sinh:</string>\n  <string name=\"textDied\" comment=\"For example - Died: 12 January 2020\">Mất:</string>\n  <string name=\"textCredits\" comment=\"List of shows and movies the person appears on\">Ghi nhận</string>\n  <string name=\"textEpisodes\">Các tập</string>\n  <string name=\"textGalleryEmpty\">Không có hình ảnh nào</string>\n</resources>\n"
  },
  {
    "path": "ui-people/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textAge\" comment=\"Person's age\">年龄：</string>\n  <string name=\"textBorn\" comment=\"For example - Born: 12 January 1990\">出生日期：</string>\n  <string name=\"textDied\" comment=\"For example - Died: 12 January 2020\">去世日期：</string>\n  <string name=\"textCredits\" comment=\"List of shows and movies the person appears on\">出演影视</string>\n  <string name=\"textEpisodes\">集</string>\n  <string name=\"textGalleryEmpty\">暂未能获取图片</string>\n</resources>\n"
  },
  {
    "path": "ui-people/src/test/java/com/michaldrabik/ui_people/ExampleUnitTest.kt",
    "content": "package com.michaldrabik.ui_people\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"
  },
  {
    "path": "ui-premium/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-premium/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_premium'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-local')\n  implementation project(':data-remote')\n  implementation project(':ui-base')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n  implementation project(':repository')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-premium/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "ui-premium/src/main/java/com/michaldrabik/ui_premium/PremiumFragment.kt",
    "content": "package com.michaldrabik.ui_premium\n\nimport android.graphics.Rect\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.children\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.bump\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.PremiumFeature\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ITEM\nimport com.michaldrabik.ui_premium.databinding.FragmentPremiumBinding\nimport com.michaldrabik.ui_premium.views.PurchaseItemView\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass PremiumFragment : BaseFragment<PremiumViewModel>(R.layout.fragment_premium) {\n\n  override val viewModel by viewModels<PremiumViewModel>()\n  private val binding by viewBinding(FragmentPremiumBinding::bind)\n\n  private val highlightItem by lazy { arguments?.getSerializable(ARG_ITEM) as? PremiumFeature }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.messageFlow.collect { showSnack(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = {\n        highlightItem?.let { viewModel.highlightItem(it) }\n      }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      premiumToolbar.setNavigationOnClickListener { activity?.onBackPressed() }\n      premiumRoot.doOnApplyWindowInsets { view, insets, padding, _ ->\n        val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top\n        view.updatePadding(top = padding.top + inset)\n      }\n    }\n  }\n\n  private fun render(uiState: PremiumUiState) {\n    uiState.run {\n      with(binding) {\n        premiumProgress.visibleIf(isLoading)\n        premiumStatus.visibleIf(isPurchasePending)\n        renderPurchaseItems(isLoading)\n        onFinish?.let {\n          it.consume()?.let {\n            requireActivity().onBackPressed()\n          }\n        }\n      }\n    }\n  }\n\n  private fun renderPurchaseItems(isLoading: Boolean) {\n    with(binding) {\n      premiumPurchaseItems.removeAllViews()\n      premiumPurchaseItems.visibleIf(!isLoading)\n\n      val view = PurchaseItemView(requireContext()).apply {\n        bindInApp(\"Single Payment\", \"$0.00\")\n        onClick {\n          //viewModel.unlockAndFinish()\n          viewModel.sendErrorMessage()\n        }\n      }\n      premiumPurchaseItems.addView(view)\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    if (event is PremiumUiEvent.HighlightItem) {\n      highlightItem(event.item)\n    }\n  }\n\n  private fun highlightItem(item: PremiumFeature) {\n    with(binding) {\n      val scrollBounds = Rect()\n      premiumRoot.getHitRect(scrollBounds)\n\n      val targetTag = getString(item.tag)\n      val targetViews = premiumContent.children.filter { it.tag == targetTag }\n\n      if (targetViews.any { !it.getLocalVisibleRect(scrollBounds) }) {\n        premiumRoot.smoothScrollTo(0, Int.MAX_VALUE)\n      }\n\n      targetViews.forEach {\n        it.bump(duration = 450, startDelay = 300)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-premium/src/main/java/com/michaldrabik/ui_premium/PremiumUiEvent.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_premium\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\ninternal sealed class PremiumUiEvent<T>(action: T) : Event<T>(action) {\n  data class HighlightItem(val item: com.michaldrabik.ui_model.PremiumFeature) : PremiumUiEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-premium/src/main/java/com/michaldrabik/ui_premium/PremiumUiState.kt",
    "content": "package com.michaldrabik.ui_premium\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\ndata class PremiumUiState(\n  val isLoading: Boolean = false,\n  val isPurchasePending: Boolean = false,\n  val onFinish: Event<Boolean>? = null,\n)\n"
  },
  {
    "path": "ui-premium/src/main/java/com/michaldrabik/ui_premium/PremiumViewModel.kt",
    "content": "package com.michaldrabik.ui_premium\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_premium.PremiumUiEvent.HighlightItem\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass PremiumViewModel @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val purchasePendingState = MutableStateFlow(false)\n  private val loadingState = MutableStateFlow(false)\n  private val finishEvent = MutableStateFlow<Event<Boolean>?>(null)\n\n  fun sendErrorMessage() {\n    viewModelScope.launch {\n      messageChannel.send(MessageEvent.Info(R.string.textPurchaseNotAvailable))\n    }\n  }\n\n  fun unlockAndFinish() {\n    viewModelScope.launch {\n      settingsRepository.isPremium = true\n      messageChannel.send(MessageEvent.Info(R.string.textPurchaseThanks))\n      finishEvent.value = Event(true)\n    }\n  }\n\n  fun highlightItem(item: com.michaldrabik.ui_model.PremiumFeature) {\n    viewModelScope.launch {\n      delay(300)\n      eventChannel.send(HighlightItem(item))\n    }\n  }\n\n  val uiState = combine(\n    purchasePendingState,\n    loadingState,\n    finishEvent\n  ) { s2, s3, s4 ->\n    PremiumUiState(\n      isPurchasePending = s2,\n      isLoading = s3,\n      onFinish = s4\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = PremiumUiState()\n  )\n}\n"
  },
  {
    "path": "ui-premium/src/main/java/com/michaldrabik/ui_premium/views/PurchaseItemView.kt",
    "content": "package com.michaldrabik.ui_premium.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport androidx.core.content.ContextCompat\nimport com.google.android.material.card.MaterialCardView\nimport com.michaldrabik.ui_base.utilities.extensions.colorStateListFromAttr\nimport com.michaldrabik.ui_premium.R\nimport com.michaldrabik.ui_premium.databinding.ViewPurchaseItemBinding\n\n@SuppressLint(\"SetTextI18n\")\nclass PurchaseItemView : MaterialCardView {\n  private val binding = ViewPurchaseItemBinding.inflate(LayoutInflater.from(context), this)\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    strokeWidth = 0\n    setCardBackgroundColor(context.colorStateListFromAttr(R.attr.colorAccent))\n  }\n\n  fun bindInApp(title: String, price: String) {\n    with(binding) {\n      viewPurchaseItemTitle.text = title\n      viewPurchaseItemDescription.text = \"Pay once, unlock forever!\"\n      viewPurchaseItemDescriptionDetails.text =\n        \"You will unlock all bonus features with a single payment and enjoy them forever.\"\n      viewPurchaseItemPrice.text = price\n\n      val colorBlack = ContextCompat.getColor(context, R.color.colorBlack)\n      val colorWhite = ContextCompat.getColor(context, R.color.colorWhite)\n\n      viewPurchaseItemTitle.setTextColor(colorBlack)\n      viewPurchaseItemDescription.setTextColor(colorBlack)\n      viewPurchaseItemDescriptionDetails.setTextColor(colorBlack)\n      viewPurchaseItemPrice.setTextColor(colorBlack)\n      viewPurchaseItemSeparator.setBackgroundColor(colorBlack)\n      setCardBackgroundColor(colorWhite)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-premium/src/main/res/drawable/divider_purchase_items.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <size\n    android:width=\"1dp\"\n    android:height=\"@dimen/spaceNormal\"\n    />\n</shape>"
  },
  {
    "path": "ui-premium/src/main/res/drawable/ic_flash.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"64dp\"\n    android:height=\"64dp\"\n    android:viewportWidth=\"64\"\n    android:viewportHeight=\"64\">\n  <path\n      android:pathData=\"M50.9453,0.8906C50.7188,0.8984 50.4961,0.9883 50.3203,1.1602L45.3711,5.8008C38.7383,2.0195 31.0391,1.0078 23.6523,2.9492C16.1523,4.9102 9.8711,9.6797 5.9609,16.3594C2.0508,23.0508 0.9805,30.8594 2.9492,38.3477C4.8594,45.668 9.4609,51.8281 15.8906,55.75L12.7188,61.5508C12.4922,61.9805 12.6016,62.5195 12.9883,62.8203C13.1719,62.9609 13.3789,63.0313 13.5977,63.0313C13.8477,63.0313 14.0898,62.9414 14.2813,62.7695L19.7383,57.7383C23.3086,59.2383 27.1094,60.0117 30.9492,60.0117C33.418,60.0117 35.8984,59.6914 38.3477,59.0508C45.8477,57.0898 52.1289,52.3203 56.0391,45.6406C59.9492,38.9492 61.0195,31.1406 59.0508,23.6523C57.4219,17.4297 53.8594,12.0391 48.8789,8.1719L51.8906,2.3398C52.1094,1.9102 51.9883,1.3789 51.5977,1.0898C51.4063,0.9453 51.1719,0.8789 50.9453,0.8906ZM31.0508,3.9922C35.4805,3.9922 39.8594,5.0898 43.8398,7.2422L21.4023,28.2695C21.1016,28.5508 21,28.9883 21.1523,29.3711C21.3008,29.75 21.6719,30 22.0781,30L25.8281,30L16.2813,42.3906C16.0508,42.6914 16.0117,43.0977 16.1719,43.4375C16.3398,43.7813 16.6914,44 17.0703,44L22.3203,44L19.75,48.707C15.2578,45.8555 12.043,41.4922 10.6875,36.3281C9.2656,30.8984 10.0391,25.2461 12.8711,20.4023C15.7031,15.5586 20.25,12.1094 25.6758,10.6875C27.2539,10.2734 28.875,10.043 30.4883,10C31.0391,9.9883 31.4766,9.5273 31.4648,8.9766C31.4492,8.4258 30.9844,7.9648 30.4375,8C28.668,8.0469 26.8945,8.3008 25.168,8.75C19.2266,10.3086 14.2461,14.0898 11.1445,19.3906C8.043,24.6953 7.1953,30.8906 8.75,36.832C10.2461,42.5313 13.8086,47.3477 18.7852,50.4688L16.8594,53.9883C13.9688,52.2305 11.4805,49.9688 9.4805,47.3281C7.3594,44.5391 5.8008,41.3398 4.8828,37.8477C3.0508,30.8711 4.0508,23.5977 7.6875,17.3711C11.3281,11.1484 17.1797,6.7109 24.1523,4.8828C26.4414,4.2813 28.75,3.9922 31.0508,3.9922ZM47.4609,6.5781L47.25,6.9922L46.3281,8.7813L44.2383,12.8281L43.6602,13.9492L39.2305,22.5391C39.0703,22.8477 39.0781,23.2188 39.2617,23.5195C39.4414,23.8203 39.7695,24 40.1211,24L44.9102,24.0117L35.5977,34.3789C35.3398,34.6719 35.2695,35.0898 35.4375,35.4492C35.5977,35.8086 35.9492,36.0391 36.3477,36.0391L40.3398,36.0391L19.3711,55.3711L17.7891,56.8203L17.4023,57.1797L17.6406,56.7383L18.5977,54.9883L24.8906,43.4805C25.0625,43.1719 25.0508,42.7891 24.8711,42.4883C24.6875,42.1914 24.3594,42 24.0117,42L19.0977,42L28.6602,29.6094C28.8906,29.3086 28.9297,28.9023 28.7617,28.5625C28.6016,28.2188 28.25,28 27.8711,28L24.6094,28L45.6211,8.3125L47.1211,6.8984ZM34.9688,8.3398C34.4219,8.2461 33.9102,8.6094 33.8125,9.1523C33.7148,9.6953 34.082,10.2148 34.625,10.3125C35.3594,10.4414 36.0977,10.6094 36.8164,10.8164C36.9102,10.8438 37.0039,10.8594 37.0938,10.8594C37.5313,10.8594 37.9258,10.5742 38.0547,10.1367C38.207,9.6055 37.9023,9.0508 37.3711,8.8984C36.582,8.668 35.7773,8.4805 34.9688,8.3398ZM47.9492,9.9805C52.4297,13.582 55.6406,18.5 57.1211,24.1523C58.9492,31.1289 57.9492,38.4023 54.3125,44.6289C50.6719,50.8516 44.8203,55.2891 37.8477,57.1211C32.3203,58.5586 26.6094,58.2422 21.3789,56.2305L43.5781,35.7813C43.8789,35.5 43.9805,35.0586 43.8281,34.6797C43.6914,34.3008 43.3203,34.0391 42.9023,34.0391L38.5898,34.0391L47.8906,23.6797C48.1523,23.3789 48.2188,22.9609 48.0625,22.5977C47.9023,22.2383 47.5391,22.0117 47.1523,22.0117L41.7617,22L45.1289,15.4688C52.3789,22.0625 54.1406,33.0195 49.1406,41.5781C48.8594,42.0586 49.0195,42.668 49.5,42.9492C49.6602,43.0391 49.8281,43.0781 50,43.0781C50.3516,43.0781 50.6797,42.9102 50.8711,42.5898C56.4375,33.0508 54.3516,20.7891 46.0703,13.6289ZM44.8477,45.3477C44.5898,45.3477 44.3359,45.4414 44.1406,45.6367C43.7461,46.0234 43.7422,46.6563 44.1289,47.0508L45.5352,48.4727C45.7305,48.6719 45.9883,48.7695 46.2461,48.7695C46.5,48.7695 46.7539,48.6719 46.9492,48.4805C47.3438,48.0938 47.3438,47.4609 46.957,47.0664L45.5508,45.6445C45.3594,45.4492 45.1055,45.3477 44.8477,45.3477ZM40.668,48.4297C40.5391,48.4453 40.4102,48.4844 40.2891,48.5547C39.8086,48.8281 39.6445,49.4375 39.918,49.918L40.9063,51.6563C41.0898,51.9805 41.4258,52.1602 41.7773,52.1602C41.9453,52.1602 42.1133,52.1172 42.2695,52.0313C42.75,51.7578 42.918,51.1445 42.6445,50.668L41.6563,48.9258C41.4492,48.5664 41.0547,48.3828 40.668,48.4297ZM36.2148,50.3555C36.0859,50.3359 35.9531,50.3438 35.8164,50.3789C35.2852,50.5195 34.9648,51.0664 35.1055,51.5977L35.6094,53.5352C35.7305,53.9844 36.1328,54.2813 36.5781,54.2813C36.6641,54.2813 36.7461,54.2695 36.832,54.25C37.3672,54.1094 37.6875,53.5625 37.5469,53.0273L37.0391,51.0938C36.9336,50.6914 36.6016,50.4102 36.2148,50.3555ZM31.0195,50.9844C30.4727,50.9844 30.0234,51.4258 30.0195,51.9805L30.0117,53.9805C30.0078,54.5313 30.4531,54.9805 31.0039,54.9844L31.0117,54.9844C31.5625,54.9844 32.0078,54.5391 32.0117,53.9883L32.0195,51.9883C32.0234,51.4375 31.5781,50.9844 31.0273,50.9844Z\"\n      android:fillColor=\"#000000\"/>\n</vector>\n"
  },
  {
    "path": "ui-premium/src/main/res/drawable/ic_genie.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"50dp\"\n    android:height=\"50dp\"\n    android:viewportWidth=\"50\"\n    android:viewportHeight=\"50\">\n  <path\n      android:pathData=\"M38.5,5C34.9219,5 32,7.9219 32,11.5C32,13.3945 32.7305,14.8906 33.6758,15.9258C34.6211,16.9648 35.7461,17.582 36.6328,17.9297C38.6836,18.7383 40.1563,19.3711 41.0156,19.9063C41.0703,19.9414 41.0977,19.9688 41.1445,20L40.1719,20C39.9492,20 39.7344,20.0742 39.5625,20.207C37.5703,21.7344 33.4922,22.9414 28.7813,22.9883C27.5781,21.0977 25.9961,19.5117 24.2188,18.668C24.6992,18.0703 25,17.3203 25,16.5C25,14.5781 23.4219,13 21.5,13C19.5781,13 18,14.5781 18,16.5C18,17.3203 18.3008,18.0664 18.7813,18.668C16.8828,19.5625 15.2148,21.3008 13.9844,23.3594C13.1289,23.6641 12.3594,24.1563 11.7344,24.793C11.7148,24.7031 11.7148,24.6211 11.6914,24.5352C11.457,23.5 11.0664,22.418 10.3242,21.5313C9.582,20.6445 8.418,20 7,20C5.2305,20 4.0625,21.0664 3.5547,22.0313C3.043,23 3,23.9453 3,23.9453L3,24C3,25.707 3.5703,27.0977 4.4141,28.1797C5.2617,29.2617 6.3477,30.0547 7.4219,30.8164C8.7422,31.7461 9.3906,32.6055 9.5547,32.9219C9.4336,32.957 9.3906,33 9,33C8.6406,32.9961 8.3047,33.1836 8.1211,33.4961C7.9414,33.8086 7.9414,34.1914 8.1211,34.5039C8.3047,34.8164 8.6406,35.0039 9,35C9.8164,35 10.4883,34.9102 11.0625,34.4688C11.4453,34.1758 11.6406,33.6836 11.6836,33.2031C12.2148,33.8398 12.8164,34.3906 13.4219,34.8164C13.4258,34.8203 13.4258,34.8203 13.4258,34.8164C15.1055,36 17.1328,36.6563 19.6211,36.875C19.2969,37.2461 19.0898,37.707 19.0313,38.2188C18.0898,38.3789 17.2227,38.6289 16.4844,38.9688C15.8086,39.2813 15.2266,39.6641 14.7734,40.1563C14.3203,40.6484 14,41.293 14,42L14,43L29,43L29,42C29,41.293 28.6797,40.6484 28.2266,40.1563C27.7734,39.6641 27.1914,39.2813 26.5156,38.9688C25.7773,38.6289 24.9102,38.3789 23.9688,38.2188C23.9102,37.7148 23.707,37.2578 23.3906,36.8906C28.4922,36.3711 32.082,33.8242 36.6641,29.7461C39.9922,26.7852 43.5938,23.0195 46.3633,21.9336C46.8125,21.7578 47.0742,21.2852 46.9844,20.8125C46.8945,20.3398 46.4805,20 46,20L45.9063,20C45.625,18.2695 44.5898,16.2578 42.1367,15.0703C41.9805,14.9922 41.8438,14.9219 41.6875,14.8438C42.6094,14.6758 43.457,14.2813 44.0352,13.5938L44.0703,13.5508L44.1016,13.5039C44.5742,12.7969 44.8633,11.9766 44.9609,11.1211C45,10.8242 45,10.7266 45,10.5C45,7.4727 42.5273,5 39.5,5ZM38.5,7L39.5,7C41.4531,7 43,8.5469 43,10.5C43,10.4648 42.9531,11.0391 42.9805,10.8594L42.9805,10.875L42.9766,10.8906C42.918,11.4219 42.7266,11.9063 42.4648,12.3242C42.1094,12.7227 41.5938,13 41,13C40.3398,13 39.7734,12.6875 39.4023,12.2031C39.1523,11.8594 39,11.4531 39,11L39,10L38,10C36.9063,10 36,10.9063 36,12C36,12.1367 36.0156,12.2266 36.0273,12.3008C36.1133,13.1797 36.6016,13.8984 37.1914,14.4531C37.7852,15.0078 38.5039,15.4492 39.2695,15.8711L39.2734,15.8711C39.9141,16.2148 40.5938,16.5391 41.2578,16.8672L41.2617,16.8711L41.2656,16.8711C42.9922,17.707 43.5313,18.8906 43.7539,20L43.6563,20C43.5664,19.7969 43.5742,19.5547 43.4375,19.3789C43.0898,18.918 42.6289,18.5547 42.0781,18.207C40.9688,17.5195 39.4414,16.8867 37.3672,16.0703C36.6992,15.8086 35.8242,15.3164 35.1563,14.582C34.4883,13.8477 34,12.8984 34,11.5C34,9 36,7 38.5,7ZM21.5,15C22.3398,15 23,15.6602 23,16.5C23,17.3398 22.3398,18 21.5,18C20.6602,18 20,17.3398 20,16.5C20,15.6602 20.6602,15 21.5,15ZM21.5,20C22.9375,20 24.6211,21.2891 26.0742,23L16.9375,23C18.3906,21.2969 20.0625,20 21.5,20ZM7,22C7.8711,22 8.3555,22.2969 8.7891,22.8164C9.2266,23.3359 9.5508,24.1328 9.7422,24.9805C10.1289,26.668 10.0039,28.4375 10.0039,28.4375C9.9922,28.5352 9.9961,28.6406 10.0156,28.7383C10.0117,28.8281 10,28.9141 10,29C10,29.5156 10.0898,30.0156 10.2266,30.5C9.7578,30.0664 9.2188,29.6367 8.5781,29.1836C7.5273,28.4453 6.6133,27.7383 5.9922,26.9453C5.375,26.1602 5.0117,25.3008 5.0078,24.0313C5.0078,24.0078 5.0469,23.4883 5.3203,22.9688C5.6016,22.4336 5.9375,22 7,22ZM40.4258,22L42.3789,22C39.9336,23.8125 37.5664,26.2695 35.3359,28.2539C30.4648,32.5898 27.2969,34.7969 22.0664,34.9844C22.0664,34.9805 22.0664,34.9805 22.0625,34.9844C21.8516,34.9922 21.668,35 21.5,35C21.5469,35 21.5273,35 21.4414,35C21.4414,34.9961 21.4414,34.9961 21.4375,35C21.418,34.9961 21.3984,34.9961 21.375,34.9961C18.3203,34.9688 16.2578,34.3633 14.5781,33.1836C13.3477,32.3164 12,30.3867 12,29C12,27.2813 13.0742,25.832 14.5898,25.2656L14.9023,25.1758C15.0781,25.125 15.2461,25.0898 15.4102,25.0586C15.6055,25.0313 15.7969,25 16,25L29.9844,25L29.9648,24.957C34.2969,24.7617 38.0234,23.6953 40.4258,22ZM21.5,40C21.9258,40 22.3398,40.0234 22.7422,40.0625C23.9063,40.1758 24.9336,40.4453 25.6758,40.7852C25.8242,40.8555 25.8711,40.9297 25.9961,41L17.0039,41C17.1289,40.9297 17.1758,40.8555 17.3242,40.7852C18.0664,40.4453 19.0938,40.1758 20.2578,40.0625C20.6602,40.0234 21.0742,40 21.5,40Z\"\n      android:fillColor=\"#000000\"/>\n</vector>\n"
  },
  {
    "path": "ui-premium/src/main/res/drawable/ic_homer.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"50dp\"\n    android:height=\"50dp\"\n    android:viewportWidth=\"50\"\n    android:viewportHeight=\"50\">\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"M25,3C20.391,3 17.313,5.781 15.512,8.438C13.848,10.891 13.219,13.121 13.121,13.488C12.809,13.77 12,14.566 12,16L12,16.609C10.223,17.523 9,19.375 9,21.5C9,23.047 9.648,24.445 10.688,25.449C10.262,26.027 10,26.734 10,27.5L10,28.5L10.203,28.5C10.277,28.73 10.375,28.945 10.488,29.156C10.398,29.254 10.32,29.332 10.223,29.469C9.871,29.938 9.441,30.652 9.063,31.645C9.078,31.609 8.945,31.855 8.758,32.125C8.566,32.402 8.313,32.742 8.063,33.129C7.559,33.906 7,34.848 7,36C7,36.148 7.035,36.297 7.098,36.434C7.098,36.434 7.609,37.445 8.836,38.313C9.395,38.715 10.125,39.098 11.027,39.402C11.047,39.539 11.055,39.668 11.086,39.813C11.289,40.754 11.973,41.852 13.176,42.578C13.363,42.938 13.785,43.844 14.777,44.598C15.117,44.859 15.527,45.09 16,45.297L16,46C15.996,46.359 16.184,46.695 16.496,46.879C16.809,47.059 17.191,47.059 17.504,46.879C17.816,46.695 18.004,46.359 18,46L18,45.848C18.598,45.941 19.258,46 20,46C23.785,46 25.969,44.254 26.988,42.504C27.957,40.844 27.988,39.277 27.988,39.109C27.992,39.09 28.156,37.832 27.672,36.25C27.184,34.652 26.016,32.656 23.516,31.145C19.926,28.969 14.711,29 14.711,29L13.5,29C12.672,29 12,28.328 12,27.5C12,26.672 12.672,26 13.5,26L18.035,26C19.195,27.801 21.211,29 23.5,29C27.078,29 30,26.078 30,22.5C30,18.922 27.078,16 23.5,16C21.641,16 19.961,16.793 18.773,18.059C17.762,16.805 16.223,16 14.5,16C14.332,16 14.164,16.012 14,16.027L14,16C14,15.469 14.656,14.75 14.656,14.75C14.809,14.621 14.918,14.445 14.969,14.25C14.969,14.25 15.578,11.902 17.168,9.563C18.754,7.223 21.176,5.004 24.996,5C24.996,5 28.344,5.031 31.633,6.336C33.277,6.984 34.879,7.945 36.051,9.32C37.223,10.699 38,12.484 38,15C38,16.605 37.992,18.238 37.516,20.145C37.352,20.043 37.164,19.996 36.969,20C36.629,20.012 36.316,20.195 36.141,20.484L33.141,25.484C32.945,25.793 32.93,26.184 33.102,26.504C33.277,26.828 33.613,27.027 33.977,27.031C34.344,27.031 34.68,26.832 34.859,26.516L36,24.609L36,27C36,27.449 36.301,27.844 36.734,27.961C37.168,28.082 37.625,27.898 37.859,27.516L39,25.609L39,28C38.996,28.359 39.184,28.695 39.496,28.879C39.809,29.059 40.191,29.059 40.504,28.879C40.816,28.695 41.004,28.359 41,28L41,22C41,21.59 40.75,21.223 40.371,21.074C39.992,20.922 39.559,21.016 39.277,21.313C39.996,18.852 40,16.773 40,15C40,12.055 39.027,9.734 37.574,8.023C36.121,6.316 34.223,5.207 32.367,4.477C28.656,3.008 25.004,3 25.004,3C25.004,3 25,3 25,3ZM14.5,18C15.836,18 16.988,18.746 17.578,19.836C17.211,20.648 17,21.551 17,22.5C17,23.016 17.07,23.516 17.184,24L13.5,24C13.082,24 12.684,24.086 12.313,24.223C11.516,23.586 11,22.609 11,21.5C11,19.555 12.555,18 14.5,18ZM23.5,18C25.996,18 28,20.004 28,22.5C28,24.996 25.996,27 23.5,27C21.004,27 19,24.996 19,22.5C19,20.004 21.004,18 23.5,18ZM14,21C13.449,21 13,21.449 13,22C13,22.551 13.449,23 14,23C14.551,23 15,22.551 15,22C15,21.449 14.551,21 14,21ZM25,22C24.449,22 24,22.449 24,23C24,23.551 24.449,24 25,24C25.551,24 26,23.551 26,23C26,22.449 25.551,22 25,22ZM35.895,29C34.906,29 34.148,29.344 33.684,29.727C33.215,30.109 32.984,30.594 32.984,30.594C32.758,31.098 32.984,31.688 33.488,31.914C33.992,32.141 34.586,31.914 34.813,31.406C34.813,31.406 34.801,31.391 34.949,31.273C35.094,31.156 35.336,31 35.895,31C36.469,31 36.605,31.109 36.734,31.281C36.863,31.449 37,31.855 37,32.531C37,32.488 36.859,32.988 36.465,33.348C36.07,33.711 35.508,34 34.75,34C34.477,34 34.215,34.109 34.023,34.309C33.836,34.504 33.738,34.77 33.75,35.043C33.75,35.043 34.031,42.547 35.035,46.262C35.121,46.617 35.391,46.895 35.742,46.992C36.094,47.086 36.469,46.984 36.723,46.727C36.977,46.465 37.07,46.086 36.965,45.738C36.207,42.934 35.871,37.059 35.805,35.797C36.598,35.602 37.313,35.285 37.813,34.824C38.535,34.164 39,33.43 39,32.531C39,31.625 38.859,30.766 38.328,30.066C37.793,29.367 36.875,29 35.895,29ZM11.891,30.59C12.367,30.848 12.914,31 13.5,31L14.688,31C14.688,31 19.68,31.156 22.484,32.855C24.563,34.117 25.391,35.621 25.762,36.832C26.129,38.043 26.012,38.863 26.012,38.863L26,38.934L26,39C26,39 25.992,40.246 25.262,41.496C24.531,42.746 23.215,44 20,44C17.777,44 16.637,43.504 15.988,43.012C15.34,42.516 15.156,42.035 14.879,41.527L14.715,41.219L14.391,41.078C13.699,40.785 13.367,40.359 13.184,39.883C13.746,39.953 14.344,40 15,40C17.84,40 19.664,39.746 20.816,39.473C21.391,39.336 21.801,39.195 22.078,39.078C22.355,38.961 22.57,38.82 22.57,38.82C22.883,38.625 23.059,38.273 23.035,37.906C23.008,37.539 22.785,37.215 22.449,37.063C22.113,36.91 21.723,36.957 21.43,37.18C21.43,37.18 21.465,37.164 21.297,37.234C21.133,37.305 20.836,37.414 20.355,37.527C19.398,37.754 17.723,38 15,38C12.398,38 10.879,37.316 9.992,36.688C9.266,36.168 9.172,35.902 9.098,35.766C9.164,35.348 9.363,34.797 9.738,34.223C9.953,33.891 10.188,33.57 10.395,33.273C10.602,32.977 10.789,32.738 10.938,32.355C11.246,31.535 11.586,30.988 11.828,30.656C11.875,30.602 11.855,30.633 11.891,30.59Z\"/>\n</vector>\n"
  },
  {
    "path": "ui-premium/src/main/res/drawable/ic_spongebob.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"50dp\"\n    android:height=\"50dp\"\n    android:viewportWidth=\"50\"\n    android:viewportHeight=\"50\">\n  <path\n      android:pathData=\"M8.1563,2C6.4258,2 5.0347,3.4987 5.1641,5.2246L8.002,43.0742A1.0001,1.0001 0,0 0,9 44L21.5,44L24.1992,47.5996A1.0001,1.0001 0,0 0,25.8008 47.5996L28.5,44L41,44A1.0001,1.0001 0,0 0,41.998 43.0742L44.8359,5.2246C44.9652,3.4993 43.5761,2 41.8457,2L8.1563,2zM8.1563,4L41.8457,4C42.4433,4 42.8865,4.4795 42.8418,5.0762L40.748,33L37.1992,33A1.0001,1.0001 0,0 0,36.8477 33L25.127,33A1.0001,1.0001 0,0 0,25.0977 32.9961A1.0001,1.0001 0,0 0,25.0313 32.9922A1.0001,1.0001 0,0 0,24.9727 32.9922A1.0001,1.0001 0,0 0,24.8613 33L13.125,33A1.0001,1.0001 0,0 0,12.998 32.9902A1.0001,1.0001 0,0 0,12.8555 33L9.252,33L7.1582,5.0762C7.1135,4.4801 7.5587,4 8.1563,4zM19,7C15.1457,7 12,10.1457 12,14C12,17.8543 15.1457,21 19,21C19.7181,21 20.4095,20.8841 21.0645,20.6816C21.1957,21.4385 21.5348,22.1288 22.0273,22.6758A1.0001,1.0001 0,1 0,23.5137 21.3379C23.193,20.9818 23,20.5207 23,20C23,19.7996 23.0387,19.6113 23.0918,19.4297A1.0001,1.0001 0,0 0,23.0977 19.4063C23.3479,18.588 24.0923,18 25,18C25.1879,18 25.3655,18.0331 25.5371,18.0801A1.0001,1.0001 0,0 0,25.668 18.1211C26.2262,18.3159 26.6625,18.7436 26.8672,19.2969A1.0001,1.0001 0,0 0,26.9297 19.5C26.9703,19.6605 27,19.8256 27,20C27,20.5189 26.8072,20.9797 26.4883,21.334A1.0005,1.0005 0,1 0,27.9746 22.6738C28.4669,22.1269 28.8047,21.4376 28.9355,20.6816C29.5902,20.8838 30.2815,21 31,21C34.8543,21 38,17.8543 38,14C38,10.1457 34.8543,7 31,7C28.3969,7 26.2059,8.4964 25,10.6152C23.7941,8.4964 21.6031,7 19,7zM19,9C21.7737,9 24,11.2263 24,14C24,14.8508 23.7803,15.639 23.4082,16.3379C22.4851,16.7429 21.7429,17.4851 21.3379,18.4082C20.64,18.7799 19.8508,19 19,19C16.2263,19 14,16.7737 14,14C14,11.2263 16.2263,9 19,9zM31,9C33.7737,9 36,11.2263 36,14C36,16.7737 33.7737,19 31,19C30.149,19 29.3606,18.7795 28.6621,18.4082C28.2571,17.4851 27.5149,16.7429 26.5918,16.3379C26.2201,15.6395 26,14.8512 26,14C26,11.2263 28.2263,9 31,9zM20,12A2,2 0,0 0,20 16A2,2 0,0 0,20 12zM30,12A2,2 0,0 0,30 16A2,2 0,0 0,30 12zM38.25,19.9844A1.0001,1.0001 0,0 0,37.4883 20.3711C35.133,23.2827 31.5756,25 27.7891,25L27.168,25A1.0001,1.0001 0,0 0,26.8418 25L25,25L23.168,25A1.0001,1.0001 0,0 0,22.8418 25L22.2109,25C18.4244,25 14.8678,23.2826 12.5137,20.3711A1.0001,1.0001 0,0 0,11.7324 19.9883A1.0001,1.0001 0,0 0,10.959 21.6289C12.7853,23.8876 15.2637,25.4544 18,26.293L18,30A1.0001,1.0001 0,0 0,19 31L23,31A1.0001,1.0001 0,0 0,24 30L24,27L25,27L26,27L26,30A1.0001,1.0001 0,0 0,27 31L31,31A1.0001,1.0001 0,0 0,32 30L32,26.293C34.7364,25.4544 37.216,23.8875 39.043,21.6289A1.0001,1.0001 0,0 0,38.25 19.9844zM20,26.752C20.6614,26.855 21.3209,26.9665 22,26.9766L22,29L20,29L20,26.752zM30,26.752L30,29L28,29L28,26.9766C28.6791,26.9665 29.3386,26.855 30,26.752zM9.4023,35L12.6387,35L18.3594,39.7676A1.0001,1.0001 0,0 0,19.6406 39.7676L21.6504,38.0938L22.7793,39.2031L21.3809,42L9.9277,42L9.4023,35zM15.7617,35L22.2402,35L19,37.6992L15.7617,35zM27.7617,35L34.2383,35L31,37.6992L27.7617,35zM37.3613,35L40.5977,35L40.0723,42L28.6172,42L27.2148,39.1934L28.3223,38.0703L30.3594,39.7676A1.0001,1.0001 0,0 0,31.6406 39.7676L37.3613,35zM25,35.3008L26.7793,36.7852L25.582,38L24.4082,38L23.1934,36.8066L25,35.3008zM24.6172,40L25.3828,40L26.8301,42.8945L25,45.334L23.1699,42.8945L24.6172,40z\"\n      android:fillColor=\"#000000\"/>\n</vector>\n"
  },
  {
    "path": "ui-premium/src/main/res/drawable/ic_walter_white.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"50dp\"\n    android:height=\"50dp\"\n    android:viewportWidth=\"50\"\n    android:viewportHeight=\"50\">\n  <path\n      android:pathData=\"M13.5,3C12.8906,3 12.2695,3.2305 11.8594,3.6641C11.4531,4.0977 11.2734,4.6367 11.207,5.1758L11.2031,5.1953L10.2813,16L4,16C3.2695,16 2.5703,16.5469 2.3906,17.1875C2.207,17.8281 2.4063,18.5234 2.8906,19.0078L2.9922,19.1055L3.1172,19.1719C4.4883,19.9375 6.793,20.8008 10,21.5156L10,22.5234C8.3242,23.3398 7,24.8633 7,26.9023C7,28.9023 8.2617,30.5977 10,31.418L10,35C10,36.1367 10.4648,37.3789 11.2148,38.75C11.9648,40.1172 13.0352,41.5781 14.3594,42.9375C16.0781,44.6953 18.25,46.2891 20.7227,47.2031C21.5625,47.707 22.5469,48 23.5977,48L26.4023,48C27.4883,48 28.5039,47.6875 29.3594,47.1523C31.8047,46.207 33.9492,44.582 35.6445,42.8164C36.9688,41.4414 38.0352,39.9766 38.7813,38.625C39.5313,37.2773 40,36.0938 40,35L40,31.5742C41.9258,30.8359 43.4023,29.0977 43.4023,26.9023C43.4023,24.7031 41.9258,22.9648 40,22.2305L40,21.5352C43.2617,20.7891 45.5391,19.8984 46.9492,19.1953L47.0938,19.1211L47.207,19.0078C47.7148,18.5 47.8711,17.7305 47.6367,17.1094C47.3984,16.4844 46.7305,16 46,16L40.1211,16L39.1953,5.1953L39.1914,5.1758C39.1289,4.6797 39.0078,4.1641 38.6289,3.7109C38.2539,3.2539 37.6094,3 37,3ZM37.0938,4.9922C37.0898,4.9844 37.168,5.1211 37.207,5.4219L37.7695,12L12.6328,12L13.1953,5.4141C13.2305,5.168 13.2969,5.0547 13.3125,5.0391C13.332,5.0195 13.3086,5 13.5,5L37,5C37.1914,5 37.0977,4.9961 37.0938,4.9922ZM12.2031,17L38.1953,17L38.2813,18L44.6172,18C41.4492,19.3164 35.5078,21 25.3008,21C15.0273,21 8.5859,19.3242 5.4531,18L12.1211,18ZM12,21.9141C15.5078,22.5469 19.8867,23 25.3008,23C30.5156,23 34.668,22.5508 38,21.9375L38,23.7109L38.7969,23.8789C40.2734,24.1914 41.4023,25.4023 41.4023,26.9023C41.4023,28.4023 40.2734,29.6094 38.7969,29.9219L38,30.0898L38,35C38,35.4063 37.6914,36.4727 37.0352,37.6563C36.3789,38.8359 35.4063,40.1836 34.2031,41.4336C33.5078,42.1563 32.7305,42.8359 31.8984,43.4492C31.9609,43.1094 32,42.7578 32,42.4023L32,38C32,35.8008 30.1992,34 28,34L22,34C19.8008,34 18,35.8008 18,38L18,42.4023C18,42.7891 18.0391,43.168 18.1133,43.5313C17.2773,42.9297 16.4922,42.2578 15.793,41.5391C14.5938,40.3086 13.6211,38.9766 12.9688,37.7852C12.3125,36.5938 12,35.5117 12,35L12,30.0508L11.2813,29.8398C10.0039,29.4648 9,28.2188 9,26.9023C9,25.457 9.9688,24.3438 11.2813,23.957L12,23.75ZM12,24L13.5,29.3008C13.8008,30.3008 14.6992,31 15.8008,31L20.3008,31C21.3008,31 22.3008,30.3984 22.6992,29.5C22.6992,29.5 23.6992,27.6016 24.4023,26.3008C24.5,26 24.8008,25.9023 25.0977,25.9023C25.3984,25.9023 25.6992,26.1016 25.8008,26.3008L27.4023,29.5C27.9023,30.3984 28.8008,31 29.8008,31L34.3008,31C35.4023,31 36.3008,30.3008 36.5977,29.3008L38,24ZM14.5977,26L22.3008,26C21.6992,27.1992 20.9023,28.5977 20.9023,28.5977L20.9023,28.8008C20.8008,28.9023 20.6016,29.0977 20.3008,29.0977L15.8008,29.0977C15.5,29.0977 15.4023,28.9023 15.4023,28.9023ZM27.9023,26L35.4023,26L34.6992,28.6992C34.6992,28.8008 34.6016,29 34.3008,29L29.8008,29C29.6016,29 29.3008,28.8008 29.1992,28.5977ZM22,38L28,38C29.1016,38 30,38.8984 30,40L30,41L27,44L27,42C27,40.8984 26.1016,40 25,40C23.8984,40 23,40.8984 23,42L23,44L20,41L20,40C20,38.8984 20.8984,38 22,38Z\"\n      android:fillColor=\"#000000\"/>\n</vector>\n"
  },
  {
    "path": "ui-premium/src/main/res/drawable/ic_yoda.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"50dp\"\n    android:height=\"50dp\"\n    android:viewportWidth=\"50\"\n    android:viewportHeight=\"50\">\n  <path\n      android:pathData=\"M25,5C21.7157,5 19.1459,5.6422 17.3281,6.9414C17.0834,7.1163 16.8581,7.3066 16.6445,7.5059C15.8035,7.1435 14.8811,7 14,7L3,7A1.0001,1.0001 0,0 0,2.2402 8.6504C2.2402,8.6504 7.0754,14.4654 13.5469,15.7422C13.4332,16.0622 13.357,16.4001 13.2891,16.752C13.105,17.7053 13.0516,18.733 13.0273,19.6953C11.6301,21.1684 10.4669,23.2098 9.5547,25.0469C8.5209,24.7632 7.3998,25.3097 7.0176,26.3301L6.1602,28.6211C5.7557,29.701 6.315,30.9316 7.3945,31.3359L8.043,31.5781C7.8731,32.0133 7.7318,32.5074 7.6836,33.0449C7.637,33.5638 7.6853,34.1399 7.9512,34.6895C8.2171,35.239 8.7082,35.72 9.3457,36.0195C10.0941,36.371 10.8599,36.3962 11.5215,36.2266L10.2637,41.5898L10.2559,41.6406C10.0471,43.2394 11.0229,44.783 12.5742,45.2559C14.9946,45.9936 20.2518,47 25,47C29.7482,47 35.0052,45.9942 37.4258,45.2578C38.9771,44.785 39.9529,43.2394 39.7441,41.6406L39.7383,41.5918L38.4785,36.2285C39.1399,36.398 39.9061,36.3709 40.6543,36.0195C41.2918,35.72 41.7829,35.239 42.0488,34.6895C42.3147,34.1399 42.363,33.5638 42.3164,33.0449C42.2682,32.5081 42.1266,32.0148 41.957,31.5801L42.6055,31.3359C43.685,30.9316 44.2443,29.701 43.8398,28.6211L42.9824,26.3301L42.9805,26.3281C42.5973,25.3095 41.4781,24.7635 40.4453,25.0469C39.5331,23.2098 38.3699,21.1684 36.9727,19.6953C36.9484,18.733 36.895,17.7053 36.7109,16.752C36.643,16.4001 36.5668,16.0622 36.4531,15.7422C42.9246,14.4654 47.7598,8.6504 47.7598,8.6504A1.0001,1.0001 0,0 0,47 7L36,7C35.1189,7 34.1965,7.1435 33.3555,7.5059C33.1419,7.3066 32.9166,7.1163 32.6719,6.9414C30.8541,5.6422 28.2843,5 25,5zM25,7C28.0187,7 30.1758,7.6149 31.5098,8.5684C32.8438,9.5219 33.4551,10.7689 33.4551,12.5C33.4551,13.322 33.247,14.0424 32.8906,14.6934C31.1873,15.6592 29.7903,17 25,17C20.2118,17 18.8136,15.661 17.1113,14.6953C16.7537,14.0431 16.5449,13.3228 16.5449,12.5C16.5449,10.7689 17.1562,9.5219 18.4902,8.5684C19.8242,7.6149 21.9813,7 25,7zM5.6191,9L14,9C14.467,9 14.9285,9.0693 15.3535,9.1875C14.8247,10.176 14.5449,11.2996 14.5449,12.5C14.5449,12.9908 14.6131,13.4598 14.7129,13.916C10.5798,13.4246 7.39,10.6938 5.6191,9zM36,9L44.3809,9C42.6101,10.6938 39.4202,13.4246 35.2871,13.916C35.3867,13.4601 35.4551,12.9902 35.4551,12.5C35.4551,11.2996 35.1753,10.176 34.6465,9.1875C35.0715,9.0693 35.533,9 36,9zM20.9727,11C19.7864,11.0304 18.5857,11.6142 18.0664,12.6523C18.0664,13.8393 21.627,16.2128 24,13.8398C24,11.8373 22.4979,10.9609 20.9727,11zM29.0273,11C27.5021,10.9609 26,11.8373 26,13.8398C28.373,16.2128 31.9336,13.8393 31.9336,12.6523C31.4143,11.6142 30.2136,11.0304 29.0273,11zM24,15C24,15.552 24.448,16 25,16C25.552,16 26,15.552 26,15L24,15zM15.5762,16.166C16.9675,16.8013 19.062,19 25,19C25.6144,19 26.0261,18.8932 26.5625,18.8496C26.4817,19.4205 26.4812,19.9025 26.0645,20.8867C25.7541,21.6195 25.3255,22.2974 24.7969,22.7402C24.3558,23.1098 23.8202,23.2977 23.1328,23.3242C22.122,23.2728 21.3853,23.2911 19.666,23.002C18.339,22.7788 17.0253,22.4465 16.1426,22.0332C15.7012,21.8266 15.3762,21.599 15.2031,21.4141C15.03,21.2292 15,21.1347 15,21C15,19.8507 15.0374,18.2421 15.252,17.1309C15.3483,16.6318 15.4922,16.3106 15.5762,16.166zM34.4238,16.166C34.5078,16.3106 34.6517,16.6318 34.748,17.1309C34.9626,18.2421 35,19.8507 35,21C35,21.1347 34.97,21.2292 34.7969,21.4141C34.6238,21.599 34.2988,21.8266 33.8574,22.0332C32.9747,22.4465 31.661,22.7788 30.334,23.002C28.5498,23.302 27.7273,23.2882 26.7363,23.3359C27.1637,22.7863 27.6562,22.2585 27.9063,21.668C28.5476,20.1536 28.5926,19.1389 28.6406,18.6484C31.8897,18.0007 33.368,16.6482 34.4238,16.166zM13.4355,22.3906C13.5278,22.5309 13.631,22.6625 13.7422,22.7813C13.9745,23.0294 14.2428,23.2355 14.5293,23.4199L13.7813,26.6094L11.4512,25.7363C12.0373,24.5889 12.7172,23.4108 13.4355,22.3906zM36.5645,22.3906C37.2828,23.4108 37.9627,24.5889 38.5488,25.7363L36.2207,26.6094L35.4727,23.418C35.7583,23.2338 36.0261,23.0287 36.2578,22.7813C36.369,22.6625 36.4722,22.5309 36.5645,22.3906zM16.3848,24.2695C17.3118,24.5806 18.3238,24.8047 19.334,24.9746C20.5099,25.1724 20.7017,25.1138 21.668,25.1914L21.668,44.8398C18.1706,44.5294 14.7589,43.8317 13.1582,43.3438C12.5403,43.1554 12.1614,42.5581 12.2422,41.9082L16.3848,24.2695zM33.6172,24.2695L37.7598,41.9141C37.8375,42.5616 37.4598,43.156 36.8438,43.3438C35.2417,43.8312 31.8295,44.5292 28.332,44.8398L28.332,25.1914C29.2983,25.1138 29.4901,25.1724 30.666,24.9746C31.6768,24.8046 32.6896,24.5809 33.6172,24.2695zM23.668,25.377C23.9367,25.3869 24.9824,25.5 24.9824,25.5L25,25.5L25.0176,25.5C25.0176,25.5 26.0633,25.3869 26.332,25.377L26.332,44.9648C25.884,44.9829 25.4361,45 25,45C24.5639,45 24.116,44.9829 23.668,44.9648L23.668,25.377zM8.9453,26.9629C8.9692,26.9521 8.9991,26.954 9.0332,26.9668L13.3203,28.5723L12.7051,31.1895L8.0977,29.4629C8.0292,29.4372 8.0077,29.3904 8.0332,29.3223L8.8906,27.0313C8.9034,26.997 8.9214,26.9736 8.9453,26.9629zM41.0547,26.9629C41.0786,26.9736 41.0965,26.997 41.1094,27.0313L41.9668,29.3223C41.9923,29.3904 41.9708,29.4372 41.9023,29.4629L37.2969,31.1895L36.6816,28.5723L40.9668,26.9668C41.0009,26.954 41.0308,26.9521 41.0547,26.9629zM9.9297,32.2852L12.2051,33.1367C11.9912,33.5001 11.7008,33.9034 11.3926,34.125C11.0315,34.3846 10.799,34.4925 10.1953,34.209C9.9048,34.0725 9.8158,33.9543 9.75,33.8184C9.6842,33.6824 9.6501,33.4887 9.6738,33.2246C9.6992,32.9423 9.8022,32.6093 9.9297,32.2852zM40.0723,32.2852C40.1996,32.609 40.3009,32.9426 40.3262,33.2246C40.3499,33.4887 40.3158,33.6824 40.25,33.8184C40.1842,33.9543 40.0952,34.0725 39.8047,34.209C39.201,34.4925 38.9685,34.3846 38.6074,34.125C38.2998,33.9039 38.0107,33.5015 37.7969,33.1387L40.0723,32.2852z\"\n      android:fillColor=\"#000000\"/>\n</vector>\n"
  },
  {
    "path": "ui-premium/src/main/res/drawable/ic_zoidberg.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"50dp\"\n    android:height=\"50dp\"\n    android:viewportWidth=\"50\"\n    android:viewportHeight=\"50\">\n  <path\n      android:pathData=\"M20,1C16.3095,1 12.8234,2.0506 10.2305,4.2344C7.6375,6.4181 6.002,9.75 6.002,14C6.002,18.6111 6.7895,21.2785 7.5352,24.0137C8.2809,26.7488 9,29.5909 9,35L9,48A1.0001,1.0001 0,1 0,11 48L11,35C11,29.4091 10.2186,26.2512 9.4648,23.4863C8.711,20.7215 8.002,18.3889 8.002,14C8.002,10.25 9.363,7.5819 11.5195,5.7656C13.6761,3.9494 16.6905,3 20,3C24.2963,3 27.5231,4.2865 29.9648,6.4375C32.4065,8.5885 34.071,11.6495 35.0332,15.2578C35.2012,15.89 35.583,16.2911 36.0039,16.6641C36.4248,17.037 36.9147,17.3536 37.4297,17.5938C37.7005,17.7202 37.8703,17.9111 37.9492,18.1758C37.4949,18.0655 37.0124,18 36.5,18C34.75,18 33.3102,18.6923 32.377,19.7422C31.4866,20.7438 31.0547,22.0287 31.0156,23.3145A1.0001,1.0001 0,0 0,31 23.5L31,24C31,26.7667 32.1644,28.8285 33.668,30.332C35.137,31.8011 36.8963,32.8098 38.3359,33.7617A1.0001,1.0001 0,0 0,38.541 33.9023C40.7723,35.4203 42,37.2853 42,41.5C42,42.3408 41.3408,43 40.5,43C40.312,43 40.1382,42.9563 39.9727,42.8887C39.9012,40.3575 39.4331,38.5129 38.9219,37.2988C38.3683,35.9841 37.707,35.293 37.707,35.293A1.0001,1.0001 0,0 0,36.9902 34.9902A1.0001,1.0001 0,0 0,36.293 36.707C36.293,36.707 36.6317,37.0159 37.0781,38.0762C37.5246,39.1365 38,40.8712 38,43.5L38,44.4727A1.0001,1.0001 0,0 0,37.9883 44.5742C37.9482,45.3772 37.3128,46 36.5,46C35.6592,46 35,45.3408 35,44.5C35,41.6894 34.7445,39.7461 34.4785,38.4824C34.2125,37.2188 33.8945,36.5527 33.8945,36.5527A1.0001,1.0001 0,0 0,33.0156 35.9883A1.0001,1.0001 0,0 0,32.1055 37.4473C32.1055,37.4473 32.2875,37.7812 32.5215,38.8926C32.755,40.0019 32.9991,41.8051 33,44.4863A1.0001,1.0001 0,0 0,33 44.5L33,45.5C33,46.3408 32.3408,47 31.5,47C30.6592,47 30,46.3408 30,45.5L30,41.125C30,39.4558 29.7317,38.2962 29.4316,37.5273C29.1316,36.7585 28.707,36.293 28.707,36.293A1.0001,1.0001 0,0 0,27.9902 35.9902A1.0001,1.0001 0,0 0,27.293 37.707C27.293,37.707 27.3684,37.7415 27.5684,38.2539C27.7683,38.7663 28,39.6692 28,41.125L28,45.5C28,46.3408 27.3408,47 26.5,47C25.6592,47 25,46.3408 25,45.5C25,43.75 25.0053,41.715 24.7402,39.8594C24.4751,38.0038 23.9836,36.2372 22.5996,35.1992A1.0004,1.0004 0,1 0,21.4004 36.8008C22.0164,37.2628 22.5249,38.4962 22.7598,40.1406C22.9947,41.785 23,43.75 23,45.5C23,47.4212 24.5788,49 26.5,49C27.4792,49 28.3624,48.5843 29,47.9277C29.6376,48.5843 30.5208,49 31.5,49C32.7689,49 33.8392,48.2771 34.4531,47.2559C35.0367,47.6915 35.722,48 36.5,48C38.2135,48 39.5923,46.7243 39.8828,45.0918A1.0001,1.0001 0,0 0,39.9805 44.8223C40.1682,44.855 40.3024,45 40.5,45C42.4212,45 44,43.4212 44,41.5C44,37.0497 42.4419,34.3133 40,32.502L40,27.8359C40.2251,27.6575 40.4367,27.4674 40.623,27.2578C41.5563,26.2079 42,24.8472 42,23.5C42,22.1528 41.5563,20.7921 40.623,19.7422C40.4367,19.5326 40.2251,19.3425 40,19.1641L40,18.416L39.998,18.3945C39.9492,17.2908 39.3008,16.2601 38.2754,15.7813C37.9413,15.6254 37.5844,15.3933 37.3301,15.168C37.0758,14.9427 36.9548,14.697 36.9668,14.7422C35.929,10.8505 34.0935,7.4115 31.2852,4.9375C28.4769,2.4635 24.7037,1 20,1zM23,17C21.0833,17 19.5186,17.7548 18.502,18.8984C17.4853,20.0421 17,21.5278 17,23C17,24.4722 17.4853,25.9579 18.502,27.1016C19.5186,28.2452 21.0833,29 23,29C26.3019,29 29,26.3019 29,23C29,19.6981 26.3019,17 23,17zM23,19C25.221,19 27,20.779 27,23C27,25.221 25.221,27 23,27C21.5833,27 20.6481,26.5048 19.998,25.7734C19.348,25.0421 19,24.0278 19,23C19,21.9722 19.348,20.9579 19.998,20.2266C20.6481,19.4952 21.5833,19 23,19zM36.5,20C37.75,20 38.5602,20.4327 39.127,21.0703C39.6937,21.7079 40,22.5972 40,23.5C40,24.4028 39.6937,25.2921 39.127,25.9297C39.0175,26.0528 38.8888,26.1594 38.7598,26.2656A1.0001,1.0001 0,0 0,38.2129 26.6211C37.7538,26.8533 37.2014,27 36.5,27C35.25,27 34.4398,26.5673 33.873,25.9297C33.3063,25.2921 33,24.4028 33,23.5C33,22.5972 33.3063,21.7079 33.873,21.0703C34.4398,20.4327 35.25,20 36.5,20zM24,22A1,1 0,0 0,24 24A1,1 0,0 0,24 22zM38,22A1,1 0,0 0,38 24A1,1 0,0 0,38 22zM34.9766,28.8047C35.4522,28.9269 35.9594,29 36.5,29C37.0317,29 37.5311,28.929 38,28.8105L38,31.1465C36.9481,30.4584 35.9158,29.7517 35.082,28.918C35.0452,28.8812 35.0127,28.8419 34.9766,28.8047zM18.0156,28.9883A1.0001,1.0001 0,0 0,17.1055 30.4473C17.1055,30.4473 17.4726,31.1594 18.25,31.7813C19.0274,32.4031 20.2778,33 22,33C23.1667,33 24.078,32.7042 24.6973,32.3945C25.3165,32.0849 25.707,31.707 25.707,31.707A1.0001,1.0001 0,1 0,24.293 30.293C24.293,30.293 24.1835,30.4151 23.8027,30.6055C23.422,30.7958 22.8333,31 22,31C20.7222,31 19.9726,30.5969 19.5,30.2188C19.0274,29.8406 18.8945,29.5527 18.8945,29.5527A1.0001,1.0001 0,0 0,18.0156 28.9883z\"\n      android:fillColor=\"#000000\"/>\n</vector>\n"
  },
  {
    "path": "ui-premium/src/main/res/layout/fragment_premium.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView\n  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/premiumRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipToPadding=\"false\"\n  android:overScrollMode=\"never\"\n  android:scrollbars=\"none\"\n  tools:background=\"@color/colorBackground\"\n  tools:layout_marginTop=\"0dp\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/premiumContent\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:paddingBottom=\"@dimen/spaceBig\"\n    >\n\n    <com.google.android.material.appbar.MaterialToolbar\n      android:id=\"@+id/premiumToolbar\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:background=\"?android:windowBackground\"\n      android:elevation=\"@dimen/elevationNormal\"\n      app:contentInsetStartWithNavigation=\"0dp\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumCrown\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_bias=\"0\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      app:navigationIcon=\"@drawable/ic_arrow_back\"\n      app:title=\"@string/textPremium\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumTitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:gravity=\"center_vertical\"\n      android:text=\"@string/textPremiumAdFull\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/premiumCrown\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/premiumCrown\"\n      app:layout_constraintTop_toTopOf=\"@id/premiumCrown\"\n      />\n\n    <ImageView\n      android:id=\"@+id/premiumCrown\"\n      style=\"@style/Premium.Image\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumPurchaseItems\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumToolbar\"\n      app:layout_goneMarginBottom=\"36dp\"\n      app:srcCompat=\"@drawable/ic_crown\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/premiumProgress\"\n      style=\"@style/ProgressBar.Accent\"\n      android:layout_width=\"30dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_margin=\"@dimen/spaceNormal\"\n      app:layout_constraintBottom_toBottomOf=\"@id/premiumPurchaseItems\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/premiumPurchaseItems\"\n      tools:visibility=\"visible\"\n      />\n\n    <LinearLayout\n      android:id=\"@+id/premiumPurchaseItems\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_margin=\"@dimen/spaceNormal\"\n      android:divider=\"@drawable/divider_purchase_items\"\n      android:gravity=\"center\"\n      android:minHeight=\"152dp\"\n      android:orientation=\"vertical\"\n      android:showDividers=\"middle\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumLightThemeTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumCrown\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumStatus\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:gravity=\"center\"\n      android:text=\"@string/textPurchasePending\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumLightThemeTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumTitle\"\n      />\n\n    <ImageView\n      android:id=\"@+id/premiumLightThemeImage\"\n      style=\"@style/Premium.Image\"\n      android:tag=\"@string/tagTheme\"\n      app:layout_constraintBottom_toBottomOf=\"@id/premiumLightThemeDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/premiumLightThemeTitle\"\n      app:srcCompat=\"@drawable/ic_yoda\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumLightThemeTitle\"\n      style=\"@style/Premium.Title\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      android:tag=\"@string/tagTheme\"\n      android:text=\"@string/textLightThemeTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumLightThemeDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/premiumLightThemeImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumPurchaseItems\"\n      app:layout_goneMarginTop=\"36dp\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumLightThemeDescription\"\n      style=\"@style/Premium.Description\"\n      android:tag=\"@string/tagTheme\"\n      android:text=\"@string/textLightThemeDesc\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumSeparator4\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/premiumLightThemeImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumLightThemeTitle\"\n      />\n\n    <View\n      android:id=\"@+id/premiumSeparator4\"\n      style=\"@style/Premium.Separator\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumNewsTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumLightThemeDescription\"\n      />\n\n    <ImageView\n      android:id=\"@+id/premiumNewsImage\"\n      style=\"@style/Premium.Image\"\n      android:tag=\"@string/tagNews\"\n      app:layout_constraintBottom_toBottomOf=\"@id/premiumNewsDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/premiumNewsTitle\"\n      app:srcCompat=\"@drawable/ic_spongebob\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumNewsTitle\"\n      style=\"@style/Premium.Title\"\n      android:tag=\"@string/tagNews\"\n      android:text=\"@string/textNewsTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumNewsDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/premiumNewsImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumSeparator4\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumNewsDescription\"\n      style=\"@style/Premium.Description\"\n      android:tag=\"@string/tagNews\"\n      android:text=\"@string/textNewsDesc\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumSeparator1\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"1.0\"\n      app:layout_constraintStart_toEndOf=\"@id/premiumNewsImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumNewsTitle\"\n      />\n\n    <View\n      android:id=\"@+id/premiumSeparator1\"\n      style=\"@style/Premium.Separator\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumCustomImagesTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumNewsDescription\"\n      />\n\n    <ImageView\n      android:id=\"@+id/premiumCustomImagesImage\"\n      style=\"@style/Premium.Image\"\n      android:tag=\"@string/tagCustomImages\"\n      app:layout_constraintBottom_toBottomOf=\"@id/premiumCustomImagesDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/premiumCustomImagesTitle\"\n      app:srcCompat=\"@drawable/ic_walter_white\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumCustomImagesTitle\"\n      style=\"@style/Premium.Title\"\n      android:tag=\"@string/tagCustomImages\"\n      android:text=\"@string/textCustomImagesTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumCustomImagesDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/premiumCustomImagesImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumSeparator1\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumCustomImagesDescription\"\n      style=\"@style/Premium.Description\"\n      android:tag=\"@string/tagCustomImages\"\n      android:text=\"@string/textCustomImagesDesc\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumSeparator2\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/premiumCustomImagesImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumCustomImagesTitle\"\n      />\n\n    <View\n      android:id=\"@+id/premiumSeparator2\"\n      style=\"@style/Premium.Separator\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumListTypesTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumCustomImagesDescription\"\n      />\n\n    <ImageView\n      android:id=\"@+id/premiumListTypesImage\"\n      style=\"@style/Premium.Image\"\n      android:tag=\"@string/tagViewsTypes\"\n      app:layout_constraintBottom_toBottomOf=\"@id/premiumListTypesDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/premiumListTypesTitle\"\n      app:srcCompat=\"@drawable/ic_homer\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumListTypesTitle\"\n      style=\"@style/Premium.Title\"\n      android:tag=\"@string/tagViewsTypes\"\n      android:text=\"@string/textViewsTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumListTypesDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/premiumListTypesImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumSeparator2\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumListTypesDescription\"\n      style=\"@style/Premium.Description\"\n      android:tag=\"@string/tagViewsTypes\"\n      android:text=\"@string/textViewsDesc\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumSeparator6\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/premiumListTypesImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumListTypesTitle\"\n      />\n\n    <View\n      android:id=\"@+id/premiumSeparator6\"\n      style=\"@style/Premium.Separator\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumQuickRateTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumListTypesDescription\"\n      />\n\n    <ImageView\n      android:id=\"@+id/premiumQuickRateImage\"\n      style=\"@style/Premium.Image\"\n      android:tag=\"@string/tagQuickRating\"\n      app:layout_constraintBottom_toBottomOf=\"@id/premiumQuickRateDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/premiumQuickRateTitle\"\n      app:srcCompat=\"@drawable/ic_flash\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumQuickRateTitle\"\n      style=\"@style/Premium.Title\"\n      android:tag=\"@string/tagQuickRating\"\n      android:text=\"@string/textQuickRatingTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumQuickRateDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/premiumQuickRateImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumSeparator6\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumQuickRateDescription\"\n      style=\"@style/Premium.Description\"\n      android:tag=\"@string/tagQuickRating\"\n      android:text=\"@string/textQuickRatingDesc\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumSeparator3\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/premiumQuickRateImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumQuickRateTitle\"\n      />\n\n    <View\n      android:id=\"@+id/premiumSeparator3\"\n      style=\"@style/Premium.Separator\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumWidgetsTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumQuickRateDescription\"\n      />\n\n    <ImageView\n      android:id=\"@+id/premiumWidgetsImage\"\n      style=\"@style/Premium.Image\"\n      android:tag=\"@string/tagWidgetTransparency\"\n      app:layout_constraintBottom_toBottomOf=\"@id/premiumWidgetsDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/premiumWidgetsTitle\"\n      app:srcCompat=\"@drawable/ic_zoidberg\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumWidgetsTitle\"\n      style=\"@style/Premium.Title\"\n      android:tag=\"@string/tagWidgetTransparency\"\n      android:text=\"@string/textWidgetsTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumWidgetsDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/premiumWidgetsImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumSeparator3\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumWidgetsDescription\"\n      style=\"@style/Premium.Description\"\n      android:tag=\"@string/tagWidgetTransparency\"\n      android:text=\"@string/textWidgetsDesc\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumSeparator5\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"1.0\"\n      app:layout_constraintStart_toEndOf=\"@id/premiumWidgetsImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumWidgetsTitle\"\n      />\n\n    <View\n      android:id=\"@+id/premiumSeparator5\"\n      style=\"@style/Premium.Separator\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumAdsTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumWidgetsDescription\"\n      />\n\n    <ImageView\n      android:id=\"@+id/premiumAdsImage\"\n      style=\"@style/Premium.Image\"\n      app:layout_constraintBottom_toBottomOf=\"@id/premiumAdsDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/premiumAdsTitle\"\n      app:srcCompat=\"@drawable/ic_genie\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumAdsTitle\"\n      style=\"@style/Premium.Title\"\n      android:text=\"@string/textRemoveAdsTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumAdsDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/premiumAdsImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumSeparator5\"\n      />\n\n    <TextView\n      android:id=\"@+id/premiumAdsDescription\"\n      style=\"@style/Premium.Description\"\n      android:text=\"@string/textRemoveAdsDesc\"\n      app:layout_constraintBottom_toTopOf=\"@id/premiumSeparator5\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"1.0\"\n      app:layout_constraintStart_toEndOf=\"@id/premiumAdsImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/premiumAdsTitle\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</ScrollView>\n"
  },
  {
    "path": "ui-premium/src/main/res/layout/view_purchase_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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/viewPurchaseItemCard\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  app:cardBackgroundColor=\"@color/colorAccent\"\n  tools:parentTag=\"com.google.android.material.card.MaterialCardView\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/viewPurchaseItemRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:minHeight=\"70dp\"\n    android:padding=\"@dimen/spaceMedium\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewPurchaseItemTitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:ellipsize=\"end\"\n      android:gravity=\"center_vertical\"\n      android:lines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"18sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewPurchaseItemDescription\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewPurchaseItemPrice\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"3 Months Subscription\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewPurchaseItemDescription\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:drawablePadding=\"@dimen/spaceSmall\"\n      android:ellipsize=\"end\"\n      android:gravity=\"center_vertical\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewPurchaseItemSeparator\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewPurchaseItemPrice\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewPurchaseItemTitle\"\n      tools:text=\"Description\"\n      />\n\n    <View\n      android:id=\"@+id/viewPurchaseItemSeparator\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"1dp\"\n      android:layout_marginTop=\"10dp\"\n      android:layout_marginBottom=\"10dp\"\n      android:alpha=\"0.75\"\n      android:background=\"@color/colorWhite\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewPurchaseItemDescriptionDetails\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewPurchaseItemDescription\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewPurchaseItemDescriptionDetails\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:drawablePadding=\"@dimen/spaceSmall\"\n      android:ellipsize=\"end\"\n      android:gravity=\"center_vertical\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"10sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewPurchaseItemSeparator\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewPurchaseItemPrice\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:gravity=\"center\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"16sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintBottom_toBottomOf=\"@id/viewPurchaseItemDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewPurchaseItemTitle\"\n      app:layout_constraintTop_toTopOf=\"@id/viewPurchaseItemTitle\"\n      tools:text=\"USD 12.99 / year\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>\n\n"
  },
  {
    "path": "ui-premium/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPremium\">Showly Premium</string>\n  <string name=\"textPremiumAdFull\">Become Showly supporter, help its development and get access to cool bonus features!</string>\n  <string name=\"textLightThemeTitle\">Light theme</string>\n  <string name=\"textLightThemeDesc\">Join the light side of the force with an additional light theme applicable for main app and the widgets.</string>\n  <string name=\"textCustomImagesTitle\">Custom images</string>\n  <string name=\"textCustomImagesDesc\">Set your own favorite posters and fanarts for shows and movies. Select an image from gallery or upload your own URL.</string>\n  <string name=\"textQuickRatingTitle\">Quick ratings</string>\n  <string name=\"textQuickRatingDesc\">Never forget to rate a movie or show\\'s episode that you have just watched. With quick rating feature you will be able to automatically rate an item whenever you mark it as watched.</string>\n  <string name=\"textWidgetsTitle\">Transparent Widgets</string>\n  <string name=\"textWidgetsDesc\">Set widgets background transparency to better match your home screen.</string>\n  <string name=\"textRemoveAdsTitle\">Remove Ads</string>\n  <string name=\"textRemoveAdsDesc\">Remove all premium ads visible in the app.</string>\n  <string name=\"textNewsTitle\">News Section</string>\n  <string name=\"textNewsDesc\">Enable news section with the latest shows and movies articles, news and trailers. Powered by Reddit.</string>\n  <string name=\"textViewsTitle\">Lists view types</string>\n  <string name=\"textViewsDesc\">Enable additional collection view types like compact list or grid view.</string>\n\n  <string name=\"textPurchaseThanks\">You are now Showly Supporter!\\nThank you and enjoy the app!</string>\n  <string name=\"textPurchasePending\" translatable=\"false\">Your purchase is being processed by Google Play Store...</string>\n  <string name=\"textPurchaseNotAvailable\">Payments not available in the OSS version.</string>\n</resources>"
  },
  {
    "path": "ui-premium/src/main/res/values/styles.xml",
    "content": "<resources>\n\n  <style name=\"Premium\" />\n\n  <style name=\"Premium.Title\" parent=\"Premium\">\n    <item name=\"android:layout_width\">0dp</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:layout_marginStart\">@dimen/spaceNormal</item>\n    <item name=\"android:layout_marginEnd\">@dimen/spaceNormal</item>\n    <item name=\"android:textColor\">?android:attr/textColorPrimary</item>\n    <item name=\"android:textSize\">18sp</item>\n    <item name=\"android:textAllCaps\">true</item>\n    <item name=\"android:textStyle\">bold</item>\n    <item name=\"android:textAlignment\">viewStart</item>\n    <item name=\"android:drawablePadding\">@dimen/spaceSmall</item>\n  </style>\n\n  <style name=\"Premium.Description\" parent=\"Premium\">\n    <item name=\"android:layout_width\">0dp</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:layout_marginStart\">@dimen/spaceNormal</item>\n    <item name=\"android:layout_marginEnd\">@dimen/spaceNormal</item>\n    <item name=\"android:layout_marginTop\">@dimen/spaceMicro</item>\n    <item name=\"android:textColor\">?android:attr/textColorSecondary</item>\n    <item name=\"android:textSize\">13sp</item>\n    <item name=\"android:textAlignment\">viewStart</item>\n  </style>\n\n  <style name=\"Premium.Image\" parent=\"Premium\">\n    <item name=\"android:layout_width\">50dp</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:layout_marginStart\">@dimen/spaceNormal</item>\n    <item name=\"android:adjustViewBounds\">true</item>\n    <item name=\"tint\">?android:attr/textColorPrimary</item>\n  </style>\n\n  <style name=\"Premium.Separator\" parent=\"Premium\">\n    <item name=\"android:layout_width\">0dp</item>\n    <item name=\"android:layout_height\">1dp</item>\n    <item name=\"android:layout_marginStart\">@dimen/spaceNormal</item>\n    <item name=\"android:layout_marginEnd\">@dimen/spaceNormal</item>\n    <item name=\"android:layout_marginTop\">20dp</item>\n    <item name=\"android:layout_marginBottom\">20dp</item>\n    <item name=\"android:background\">?attr/colorSeparator</item>\n  </style>\n\n</resources>\n"
  },
  {
    "path": "ui-premium/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPremium\">نسخة Showly المدفوعة</string>\n  <string name=\"textPremiumAdFull\">إدعم المطور لمساعدته في تطوير التطبيق واحصل على مميزات إضافية رائعة!</string>\n  <string name=\"textLightThemeTitle\">مظهر فاتح</string>\n  <string name=\"textLightThemeDesc\">مظهر مضيء إضافي يعمل على كِلا التطبيق الأساسي والتطبيقات المصغّرة.</string>\n  <string name=\"textCustomImagesTitle\">الصور المخصصة</string>\n  <string name=\"textCustomImagesDesc\">عَيّن الملصقات والفان أرت التي تعجبك للمسلسلات والأفلام. حدد صورة من المعرض أو قم بوضع رابط تحميلها من الانترنت.</string>\n  <string name=\"textQuickRatingTitle\">التقييمات السريعة</string>\n  <string name=\"textQuickRatingDesc\">مع ميزة التقييم السريع، لن تنسى أبداً تقييم فيلم أو حلقة شاهدتها للتو، ستكون قادراً على تقييم الفيلم او الحلقة تلقائياً عند تحديد أنك شاهدتها.</string>\n  <string name=\"textWidgetsTitle\">التطبيقات المصغّرة الشفافة</string>\n  <string name=\"textWidgetsDesc\">عَيْن شفافية التطبيقات المصغّرة لتظهر في شاشتك الرئيسية بشكل أفضل.</string>\n  <string name=\"textRemoveAdsTitle\">حذف الإعلانات</string>\n  <string name=\"textRemoveAdsDesc\">حذف جميع الإعلانات في التطبيق.</string>\n  <string name=\"textNewsTitle\">قِسم الأخبار</string>\n  <string name=\"textNewsDesc\">قِسم الأخبار يتضمن آخر أخبار الأفلام والمسلسلات، وعروضهم الدعائية الجديدة؛ المِيزة تعتمد على Reddit.</string>\n  <string name=\"textViewsTitle\">طريقة عرض القوائم</string>\n  <string name=\"textViewsDesc\">تمكين طرق إضافية لعرض القوائم في المجموعة مثل القائمة المدمجة أو العرض الشبكي.</string>\n  <string name=\"textPurchaseThanks\">أنت الآن داعم لتطوير Showly!\\nشكراً لك وإستمتع بالتطبيق!</string>\n</resources>\n"
  },
  {
    "path": "ui-premium/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPremium\">Showly Premium</string>\n  <string name=\"textPremiumAdFull\">Werde Showly Unterstützer, helfe bei der Entwicklung und erhalte Zugang zu coolen Bonusfunktionen!</string>\n  <string name=\"textLightThemeTitle\">Helles Thema</string>\n  <string name=\"textLightThemeDesc\">Schließe dich der Lichtseite der Kraft mit einem zusätzlichen hellen Thema für die Hauptanwendung und die Widgets an.</string>\n  <string name=\"textCustomImagesTitle\">Benutzerdefinierte Bilder</string>\n  <string name=\"textCustomImagesDesc\">Lege deine eigenen Lieblingsposter und Fanart für Shows und Filme fest. Wähle ein Bild aus der Galerie oder lade deine eigene URL hoch.</string>\n  <string name=\"textQuickRatingTitle\">Schnelle Bewertungen</string>\n  <string name=\"textQuickRatingDesc\">Vergesse nie einen Film oder eine Episode zu bewerten, die du gerade angesehen hast. Mit der Schnellbewertungsfunktion kannst du automatisch ein Werk bewerten, wenn du es als gesehen markieren.</string>\n  <string name=\"textWidgetsTitle\">Transparente Widgets</string>\n  <string name=\"textWidgetsDesc\">Die Hintergrundtransparenz von Widgets so einstellen, dass sie besser zu deinem Startbildschirm passen.</string>\n  <string name=\"textRemoveAdsTitle\">Werbung Entfernen</string>\n  <string name=\"textRemoveAdsDesc\">Entferne alle Premium-Werbung in der App.</string>\n  <string name=\"textNewsTitle\">News</string>\n  <string name=\"textNewsDesc\">Aktiviere News mit den neuesten Serien und Film Artikeln, Nachrichten und Trailern. Powered by Reddit.</string>\n  <string name=\"textViewsTitle\">Listenansichten</string>\n  <string name=\"textViewsDesc\">Aktiviere zusätzliche Listenansichten, wie eine kompakte Liste oder Rasteransicht.</string>\n  <string name=\"textPurchaseThanks\">Du bist jetzt Showly Unterstützer!\\nVielen Dank und viel Spaß mit der App!</string>\n  <string name=\"textPurchaseNotAvailable\">Bezahlungen sind in der OSS Version nicht verfügbar.</string>\n</resources>\n"
  },
  {
    "path": "ui-premium/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPremium\">Showly Premium</string>\n  <string name=\"textPremiumAdFull\">¡Conviértete en seguidor, ayuda a su desarrollo y consigue acceso a fantásticas características adicionales!</string>\n  <string name=\"textLightThemeTitle\">Tema claro</string>\n  <string name=\"textLightThemeDesc\">Únete al lado luminoso de la fuerza con un tema claro adicional aplicable para la aplicación principal y los widgets.</string>\n  <string name=\"textCustomImagesTitle\">Imágenes personalizadas</string>\n  <string name=\"textCustomImagesDesc\">Establece tus propios carteles y fanarts favoritos para series y películas. Selecciona una imagen de la galería o sube tu propia URL.</string>\n  <string name=\"textQuickRatingTitle\">Valoraciones rápidas</string>\n  <string name=\"textQuickRatingDesc\">Nunca te olvides de puntuar una película o episodio de la serie que acabas de ver. Con la función de valoración rápida, podrás valorar automáticamente un elemento cada vez que lo marques como observado.</string>\n  <string name=\"textWidgetsTitle\">Widgets transparentes</string>\n  <string name=\"textWidgetsDesc\">Establece la transparencia de fondo de los widgets para que coincida mejor con tu pantalla de inicio.</string>\n  <string name=\"textRemoveAdsTitle\">Quitar Anuncios</string>\n  <string name=\"textRemoveAdsDesc\">Quitar todos los anuncios premium visibles en la aplicación.</string>\n  <string name=\"textNewsTitle\">Sección de Noticias</string>\n  <string name=\"textNewsDesc\">Habilita la sección de noticias con los últimos artículos de series y películas, noticias y tráileres. Impulsado por Reddit.</string>\n  <string name=\"textViewsTitle\">Listar tipos de vista</string>\n  <string name=\"textViewsDesc\">Habilitar tipos de vista de colección adicionales como lista compacta o vista de cuadrícula.</string>\n  <string name=\"textPurchaseThanks\">¡Ahora eres un Seguidor de Showly!\\n¡Gracias y disfruta de la aplicación!</string>\n</resources>\n"
  },
  {
    "path": "ui-premium/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPremium\">Showly Premium</string>\n  <string name=\"textPremiumAdFull\">Tue Showlya ja auta sen kehitystä, niin saat käyttöösi hienoja lisätoimintoja!</string>\n  <string name=\"textLightThemeTitle\">Vaalea teema</string>\n  <string name=\"textLightThemeDesc\">Liity voiman valoisalle puolelle sovelluksen ja widgettien vaalealla teemalla.</string>\n  <string name=\"textCustomImagesTitle\">Omat kuvat</string>\n  <string name=\"textCustomImagesDesc\">Aseta sarjoille ja elokuville suosikkijulisteesi ja fanitaiteesi. Valitse kuva galleriasta tai lataa se omasta URL-osoitteestasi.</string>\n  <string name=\"textQuickRatingTitle\">Pika-arviot</string>\n  <string name=\"textQuickRatingDesc\">Älä koskaan unohda arvioida juuri katselemaasi elokuvaa tai sarjan jaksoa. Pika-arviointi-toiminnolla kohteen arviointi onnistuu automaattisesti aina, kun merkitset sen katselluksi.</string>\n  <string name=\"textWidgetsTitle\">Läpinäkyvät widgetit</string>\n  <string name=\"textWidgetsDesc\">Aseta widgettien taustan läpinäkyvyys aloitusnäyttöösi sopivaksi.</string>\n  <string name=\"textRemoveAdsTitle\">Poista mainokset</string>\n  <string name=\"textRemoveAdsDesc\">Poista kaikki sovelluksessa näkyvät premium-mainokset.</string>\n  <string name=\"textNewsTitle\">Uutisosio</string>\n  <string name=\"textNewsDesc\">Ota käyttöön uutisosio, josta näet uusimmat sarjat ja elokuvat, artikkelit, uutiset ja trailerit. Voimanlähteenä Reddit.</string>\n  <string name=\"textViewsTitle\">Listausnäkymät</string>\n  <string name=\"textViewsDesc\">Käytä kompaktin listan tai ruudukon kaltaisia kokoelman esitystapoja.</string>\n  <string name=\"textPurchaseThanks\">Olet nyt Showlyn tukija!\\nKiitos ja nauti sovelluksesta!</string>\n</resources>\n"
  },
  {
    "path": "ui-premium/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPremium\">Showly Premium</string>\n  <string name=\"textPremiumAdFull\">Devenez un supporteur de Showly, aidez son développement et accédez à des super fonctionnalités en plus !</string>\n  <string name=\"textLightThemeTitle\">Thème clair</string>\n  <string name=\"textLightThemeDesc\">Rejoignez le côté lumineux de la force avec un thème clair supplémentaire pour l\\'application principale et les widgets.</string>\n  <string name=\"textCustomImagesTitle\">Images personnalisées</string>\n  <string name=\"textCustomImagesDesc\">Définissez vos propres affiches et fanarts pour les émissions et les films. Sélectionnez une image dans la galerie ou téléversez une image à partir d\\'une URL.</string>\n  <string name=\"textQuickRatingTitle\">Notations rapides</string>\n  <string name=\"textQuickRatingDesc\">N\\'oubliez jamais de noter un film ou un épisode que vous venez de regarder. Grâce à la fonctionnalité de notation rapide, vous pourrez automatiquement noter un élément chaque fois que vous le marquerez comme vu.</string>\n  <string name=\"textWidgetsTitle\">Widgets transparents</string>\n  <string name=\"textWidgetsDesc\">Définissez un niveau de transparence pour vos widgets afin qu\\'ils s\\'intègrent mieux à votre écran accueil.</string>\n  <string name=\"textRemoveAdsTitle\">Supprimer les publicités</string>\n  <string name=\"textRemoveAdsDesc\">Supprime toutes les publicités premium visibles dans l\\'application.</string>\n  <string name=\"textNewsTitle\">Section Actualités</string>\n  <string name=\"textNewsDesc\">Activer la section Actualités pour être au fait des derniers films, séries, nouvelles et bande-annonces. Propulsée par Reddit.</string>\n  <string name=\"textViewsTitle\">Types d’affichage de listes</string>\n  <string name=\"textViewsDesc\">Activer les types d\\'affichage de collection supplémentaires comme la liste compacte ou la grille.</string>\n  <string name=\"textPurchaseThanks\">Vous êtes maintenant un supporteur de Showly !\\nMerci et profitez de l\\'application !</string>\n</resources>\n"
  },
  {
    "path": "ui-premium/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPremium\">Showly Premium</string>\n  <string name=\"textPremiumAdFull\">Diventa un sostenitore di Showly, aiuta il suo sviluppo e ottieni l\\'accesso a fantastiche funzioni bonus!</string>\n  <string name=\"textLightThemeTitle\">Tema chiaro</string>\n  <string name=\"textLightThemeDesc\">Unisciti al lato chiaro della forza con un tema chiaro aggiuntivo applicabile per l\\'app principale e i widget.</string>\n  <string name=\"textCustomImagesTitle\">Immagini personalizzate</string>\n  <string name=\"textCustomImagesDesc\">Imposta i tuoi poster e fanart preferiti per gli show e i film. Seleziona un\\'immagine dalla galleria o carica il tuo URL.</string>\n  <string name=\"textQuickRatingTitle\">Valutazioni rapide</string>\n  <string name=\"textQuickRatingDesc\">Non dimenticare mai di valutare un film o l\\'episodio di uno show che hai appena visto. Con la funzione di valutazione rapida sarai in grado di valutare automaticamente un elemento ogni volta che lo contrassegni come visto.</string>\n  <string name=\"textWidgetsTitle\">Widget trasparenti</string>\n  <string name=\"textWidgetsDesc\">Imposta la trasparenza dello sfondo dei widget per abbinarli meglio alla tua schermata iniziale.</string>\n  <string name=\"textRemoveAdsTitle\">Rimuovere le pubblicità</string>\n  <string name=\"textRemoveAdsDesc\">Rimuovi tutti gli annunci premium visibili nell\\'app.</string>\n  <string name=\"textNewsTitle\">Sezione notizie</string>\n  <string name=\"textNewsDesc\">Attiva la sezione notizie con gli ultimi articoli di show e film, notizie e trailer. Offerto da Reddit.</string>\n  <string name=\"textViewsTitle\">Tipi di visualizzazione elenchi</string>\n  <string name=\"textViewsDesc\">Abilita ulteriori tipi di visualizzazione della collezione come elenco compatto o visualizzazione a griglia.</string>\n  <string name=\"textPurchaseThanks\">Ora sei un sostenitore di Showly!\\nGrazie e goditi l\\'app!</string>\n</resources>\n"
  },
  {
    "path": "ui-premium/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPremium\">Showly Premium</string>\n  <string name=\"textPremiumAdFull\">Zostań subskrybentem, pomóż w rozwoju aplikacji i uzyskaj dostęp do funkcji bonusowych!</string>\n  <string name=\"textLightThemeTitle\">Jasny motyw</string>\n  <string name=\"textLightThemeDesc\">Dołącz do jasnej strony mocy i odblokuj dodatkowy motyw głównej aplikacji i widgetów.</string>\n  <string name=\"textCustomImagesTitle\">Własne grafiki</string>\n  <string name=\"textCustomImagesDesc\">Ustaw swoje własne ulubione plakaty i fanarty dla seriali i filmów. Wybierz obraz z galerii lub pobierz swój własny.</string>\n  <string name=\"textQuickRatingTitle\">Szybkie oceny</string>\n  <string name=\"textQuickRatingDesc\">Dzięki funkcji szybkich ocen będziesz mógł automatycznie oceniać filmy i odcinki seriali zawsze gdy oznaczasz je jako obejrzane.</string>\n  <string name=\"textWidgetsTitle\">Przezroczyste widgety</string>\n  <string name=\"textWidgetsDesc\">Ustaw przezroczystość widgetów, aby lepiej dopasować je do swojego ekranu głównego.</string>\n  <string name=\"textRemoveAdsTitle\">Brak reklam</string>\n  <string name=\"textRemoveAdsDesc\">Schowaj wszystkie reklamy premium widoczne w aplikacji.</string>\n  <string name=\"textNewsTitle\">Newsy</string>\n  <string name=\"textNewsDesc\">Odblokuj sekcję newsów z najnowszymi artykułami oraz wiadomościami ze świata seriali i filmów.</string>\n  <string name=\"textViewsTitle\">Widok list</string>\n  <string name=\"textViewsDesc\">Dodatkowe typy widoków kolekcji, takie jak kompaktowa lista oraz siatka.</string>\n  <string name=\"textPurchaseThanks\">Od teraz jesteś subskrybentem i wspierasz Showly!\\nDziękuję i miłego użytkowania!</string>\n</resources>\n"
  },
  {
    "path": "ui-premium/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPremium\">Showly Premium</string>\n  <string name=\"textPremiumAdFull\">Torne-se um apoiador Showly, ajude no seu desenvolvimento e tenha acesso a recursos premium!</string>\n  <string name=\"textLightThemeTitle\">Tema claro</string>\n  <string name=\"textLightThemeDesc\">Junte-se ao lado da luz com um tema claro adicional aplicável ao aplicativo principal e aos widgets.</string>\n  <string name=\"textCustomImagesTitle\">Imagens personalizadas</string>\n  <string name=\"textCustomImagesDesc\">Defina seus próprios pôsteres e fanarts favoritos para séries e filmes. Selecione uma imagem da galeria ou carregue sua própria URL.</string>\n  <string name=\"textQuickRatingTitle\">Avaliações rápidas</string>\n  <string name=\"textQuickRatingDesc\">Nunca esqueça de avaliar um filme ou um episódio que você acabou de assistir. Com o recurso de avaliação rápida, você será capaz de avaliar automaticamente um item sempre que você marcar como assistido.</string>\n  <string name=\"textWidgetsTitle\">Widgets transparentes</string>\n  <string name=\"textWidgetsDesc\">Defina transparência de fundo dos widgets para melhor coincidir com a sua tela inicial.</string>\n  <string name=\"textRemoveAdsTitle\">Remover anúncios</string>\n  <string name=\"textRemoveAdsDesc\">Remover todos os anúncios premium visíveis no app.</string>\n  <string name=\"textNewsTitle\">Seção de Notícias</string>\n  <string name=\"textNewsDesc\">Ativa a seção de notícias com as últimas séries, filmes, novidades e trailers. Fornecidas pelo Reddit.</string>\n  <string name=\"textViewsTitle\">Tipos de exibição de lista</string>\n  <string name=\"textViewsDesc\">Habilite tipos de exibição de coleção adicionais, como lista compacta ou exibição de grade.</string>\n  <string name=\"textPurchaseThanks\">Você agora é um apoiador Showly!\\nObrigado e aproveite o aplicativo!</string>\n</resources>\n"
  },
  {
    "path": "ui-premium/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPremium\">Showly Premium</string>\n  <string name=\"textPremiumAdFull\">Станьте спонсором Showly, помогите его развитию и получите доступ к крутым бонусным функциям!</string>\n  <string name=\"textLightThemeTitle\">Светлая тема</string>\n  <string name=\"textLightThemeDesc\">Присоединитесь к светлой стороне силы с цветом темы, применимой к главному приложению и виджетам.</string>\n  <string name=\"textCustomImagesTitle\">Пользовательские изображения</string>\n  <string name=\"textCustomImagesDesc\">Установите свои любимые постеры и фан-арты для сериалов и фильмов. Выберите изображение из галереи или загрузите по URL.</string>\n  <string name=\"textQuickRatingTitle\">Быстрый рейтинг</string>\n  <string name=\"textQuickRatingDesc\">Никогда не забывайте оценить фильм или сериал, который вы только что посмотрели. С помощью функции быстрого рейтинга вы сможете автоматически оценивать эпизод всякий раз, когда вы отметите его как просмотренный.</string>\n  <string name=\"textWidgetsTitle\">Прозрачный виджет</string>\n  <string name=\"textWidgetsDesc\">Установите прозрачность фона виджетов, чтобы они лучше соответствовали вашему главному экрану.</string>\n  <string name=\"textRemoveAdsTitle\">Убрать рекламу</string>\n  <string name=\"textRemoveAdsDesc\">Убрать всю видимую рекламу премиума в приложении.</string>\n  <string name=\"textNewsTitle\">Раздел новостей</string>\n  <string name=\"textNewsDesc\">Включить раздел новостей с последними статьями сериалов и фильмов, новостями и трейлерами. Работает на Reddit.</string>\n  <string name=\"textViewsTitle\">Типы списков</string>\n  <string name=\"textViewsDesc\">Включайте дополнительные типы коллекций, такие как компактный список или представление сетки.</string>\n  <string name=\"textPurchaseThanks\">Теперь вы спонсор Showly!\\nБлагодарим вас, наслаждайтесь приложением!</string>\n</resources>\n"
  },
  {
    "path": "ui-premium/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPremium\">Showly Premium</string>\n  <string name=\"textPremiumAdFull\">Showly destekçisi olun, gelişimine yardımcı olun ve harika bonus özelliklere erişin!</string>\n  <string name=\"textLightThemeTitle\">Aydınlık tema</string>\n  <string name=\"textLightThemeDesc\">Ana uygulama ve widget\\'lar için geçerli olan aydınlık temasıyla gücün aydınlık tarafına katılın.</string>\n  <string name=\"textCustomImagesTitle\">Özel fotoğraflar</string>\n  <string name=\"textCustomImagesDesc\">Diziler ve filmler için kendi favori afişinizi ve hayranların çalışmalarını ayarlayın. Galeriden bir fotoğraf seçin veya kendi URL\\'nizi yükleyin.</string>\n  <string name=\"textQuickRatingTitle\">Hızlı değerlendirme</string>\n  <string name=\"textQuickRatingDesc\">İzlediğiniz bir filme veya dizinin bölümüne oy vermeyi asla unutmayın. Hızlı değerlendirme özelliği ile, bir ögeyi izlendi olarak işaretlediğinizde otomatik olarak oy verebileceksiniz.</string>\n  <string name=\"textWidgetsTitle\">Şeffaf Widget\\'lar</string>\n  <string name=\"textWidgetsDesc\">Widget\\'ların arka plan şeffaflığını ana ekranınıza daha iyi uyacak şekilde ayarlayın.</string>\n  <string name=\"textRemoveAdsTitle\">Reklamları Kaldır</string>\n  <string name=\"textRemoveAdsDesc\">Uygulamada gösterilen tüm premium reklamları kaldırır.</string>\n  <string name=\"textNewsTitle\">Haberler Bölümü</string>\n  <string name=\"textNewsDesc\">En son diziler ve filmler makaleleri, haberler ve fragmanlarla haberler bölümünü etkinleştirin. Reddit tarafından desteklenmektedir.</string>\n  <string name=\"textViewsTitle\">Liste görünüm türleri</string>\n  <string name=\"textViewsDesc\">Sıkıştırılmış liste veya ızgara görünümü gibi ek koleksiyon görünüm türlerini etkinleştirir.</string>\n  <string name=\"textPurchaseThanks\">Artık Showly destekçisisiniz!\\nTeşekkürler ve uygulamanın keyfini çıkarın!</string>\n</resources>\n"
  },
  {
    "path": "ui-premium/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPremium\">Showly Premium</string>\n  <string name=\"textPremiumAdFull\">Станьте підписником додатку Showly, допоможіть його розробці та отримайте доступ до крутих бонусних функцій!</string>\n  <string name=\"textLightThemeTitle\">Світла тема</string>\n  <string name=\"textLightThemeDesc\">Приєднуйтесь до сил світла з додатковою світлою темою для головного додатку і віджетів.</string>\n  <string name=\"textCustomImagesTitle\">Користувацькі зображення</string>\n  <string name=\"textCustomImagesDesc\">Встановіть свої улюблені постери та фанари для серіалів та фільмів. Виберіть зображення з галереї або завантажте своє власне за допомогою посилання.</string>\n  <string name=\"textQuickRatingTitle\">Швидке оцінювання</string>\n  <string name=\"textQuickRatingDesc\">Завдяки функції швидкого оцінювання ви зможете автоматично оцінювати фільми та серії серіалів щоразу, коли ви позначаєте їх як переглянуті.</string>\n  <string name=\"textWidgetsTitle\">Прозорі віджети</string>\n  <string name=\"textWidgetsDesc\">Встановіть прозорість фону віджетів, щоб краще відповідати вашому головному екрану.</string>\n  <string name=\"textRemoveAdsTitle\">Прибрати рекламу</string>\n  <string name=\"textRemoveAdsDesc\">Прибрати всю рекламу преміуму, що є в додатку.</string>\n  <string name=\"textNewsTitle\">Розділ новин</string>\n  <string name=\"textNewsDesc\">Увімкнути розділ новин з останніми статтями про серіали та фільми, новинами та трейлерами. Використовується Reddit.</string>\n  <string name=\"textViewsTitle\">Типи вигляду списків</string>\n  <string name=\"textViewsDesc\">Увімкнути додаткові типи перегляду колекцій, такі як компактний список або сітка.</string>\n  <string name=\"textPurchaseThanks\">Тепер ви Підписник Showly!\\nДякуємо вам та насолоджуйтесь додатком!</string>\n</resources>\n"
  },
  {
    "path": "ui-premium/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPremium\">Showly Premium</string>\n  <string name=\"textPremiumAdFull\">Trở thành người ủng hộ Showly, giúp phát triển nó và có quyền truy cập vào các tính năng thưởng thú vị!</string>\n  <string name=\"textLightThemeTitle\">Chủ đề sáng</string>\n  <string name=\"textLightThemeDesc\">Tham gia vào phe sáng bằng một chủ đề sáng bổ sung áp dụng cho ứng dụng chính và các tiện ích.</string>\n  <string name=\"textCustomImagesTitle\">Hình ảnh tùy chỉnh</string>\n  <string name=\"textCustomImagesDesc\">Đặt áp phích và fanart yêu thích của riêng bạn cho các chương trình và phim. Chọn một hình ảnh từ thư viện hoặc tải lên URL của riêng bạn.</string>\n  <string name=\"textQuickRatingTitle\">Xếp hạng nhanh</string>\n  <string name=\"textQuickRatingDesc\">Đừng bao giờ quên xếp hạng một bộ phim hoặc tập chương trình mà bạn vừa xem. Với tính năng xếp hạng nhanh, bạn sẽ có thể tự động xếp hạng một mục bất cứ khi nào bạn đánh dấu mục đó là đã xem.</string>\n  <string name=\"textWidgetsTitle\">Tiện ích trong suốt</string>\n  <string name=\"textWidgetsDesc\">Đặt độ trong suốt của nền tiện ích để phù hợp hơn với màn hình chính của bạn.</string>\n  <string name=\"textRemoveAdsTitle\">Loại bỏ quảng cáo</string>\n  <string name=\"textRemoveAdsDesc\">Loại bỏ tất cả quảng cáo cao cấp hiển thị trong ứng dụng.</string>\n  <string name=\"textNewsTitle\">Mục tin tức</string>\n  <string name=\"textNewsDesc\">Bật mục tin tức với các bài viết, tin tức và đoạn giới thiệu phim và chương trình mới nhất. Được hỗ trợ bởi Reddit.</string>\n  <string name=\"textViewsTitle\">Kiểu xem danh sách</string>\n  <string name=\"textViewsDesc\">Bật các loại chế độ xem bộ sưu tập bổ sung như danh sách thu gọn hoặc chế độ xem lưới.</string>\n  <string name=\"textPurchaseThanks\">Bây giờ bạn đã là Người ủng hộ Showly!\\nCảm ơn bạn và hãy tận hưởng ứng dụng này!</string>\n  <string name=\"textPurchasePending\" translatable=\"false\">Giao dịch mua hàng của bạn đang được xử lý bởi Cửa hàng Google Play...</string>\n  <string name=\"textPurchaseNotAvailable\">Thanh toán không có sẵn trong phiên bản OSS.</string>\n</resources>\n"
  },
  {
    "path": "ui-premium/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textPremium\">Showly 会员</string>\n  <string name=\"textPremiumAdFull\">成为 Showly 支持者，帮助其开发并获得很酷的额外功能！</string>\n  <string name=\"textLightThemeTitle\">亮色主题</string>\n  <string name=\"textLightThemeDesc\">加入原力的光明一派，解锁适用于主应用程序和桌面小部件的亮色主题。</string>\n  <string name=\"textCustomImagesTitle\">自定义图片</string>\n  <string name=\"textCustomImagesDesc\">为节目和电影设置您自己最喜欢的海报和粉丝作品。支持从手机图库中选择图片或上传您自己的 URL。</string>\n  <string name=\"textQuickRatingTitle\">快速评分</string>\n  <string name=\"textQuickRatingDesc\">不要忘记给您刚刚看过的电影或剧集评分。 使用快速评分功能，每当您标记项目为已看时，可自动对其进行评分。</string>\n  <string name=\"textWidgetsTitle\">透明桌面小部件</string>\n  <string name=\"textWidgetsDesc\">设置桌面小部件背景透明度，以更好地匹配您的手机主屏背景。</string>\n  <string name=\"textRemoveAdsTitle\">移除广告</string>\n  <string name=\"textRemoveAdsDesc\">移除应用中可见的所有广告。</string>\n  <string name=\"textNewsTitle\">新闻模块</string>\n  <string name=\"textNewsDesc\">解锁新闻模块以获取最新关于剧集和电影的文章、新闻和预告。由 Reddit 提供支持。</string>\n  <string name=\"textViewsTitle\">更多列表视图</string>\n  <string name=\"textViewsDesc\">解锁额外的列表视图，比如紧凑列表或网格视图。</string>\n  <string name=\"textPurchaseThanks\">您现已成为 Showly 支持者！\\n谢谢您喜欢这款应用！</string>\n</resources>\n"
  },
  {
    "path": "ui-progress/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-progress/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_progress'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-local')\n  implementation project(':ui-base')\n  implementation project(':repository')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n  implementation project(':ui-episodes')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-progress/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/CalendarFragment.kt",
    "content": "package com.michaldrabik.ui_progress.calendar\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateMargins\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.OnScrollResetListener\nimport com.michaldrabik.ui_base.common.OnSearchClickListener\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withSpanSizeLookup\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.CalendarMode.PRESENT_FUTURE\nimport com.michaldrabik.ui_model.CalendarMode.RECENTS\nimport com.michaldrabik.ui_progress.R\nimport com.michaldrabik.ui_progress.calendar.recycler.CalendarAdapter\nimport com.michaldrabik.ui_progress.calendar.recycler.CalendarListItem\nimport com.michaldrabik.ui_progress.databinding.FragmentCalendarBinding\nimport com.michaldrabik.ui_progress.helpers.ProgressLayoutManagerProvider\nimport com.michaldrabik.ui_progress.helpers.TopOverscrollAdapter\nimport com.michaldrabik.ui_progress.main.EpisodeCheckActionUiEvent\nimport com.michaldrabik.ui_progress.main.ProgressMainFragment\nimport com.michaldrabik.ui_progress.main.ProgressMainViewModel\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport me.everything.android.ui.overscroll.IOverScrollDecor\nimport me.everything.android.ui.overscroll.IOverScrollState\nimport me.everything.android.ui.overscroll.OverScrollBounceEffectDecoratorBase.DEFAULT_DECELERATE_FACTOR\nimport me.everything.android.ui.overscroll.OverScrollBounceEffectDecoratorBase.DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK\nimport me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass CalendarFragment :\n  BaseFragment<CalendarViewModel>(R.layout.fragment_calendar),\n  OnSearchClickListener,\n  OnScrollResetListener {\n\n  private companion object {\n    const val OVERSCROLL_OFFSET = 100F\n    const val OVERSCROLL_OFFSET_TRANSLATION = 5F\n  }\n\n  @Inject lateinit var settings: SettingsViewModeRepository\n\n  override val viewModel by viewModels<CalendarViewModel>()\n  private val parentViewModel by viewModels<ProgressMainViewModel>({ requireParentFragment() })\n  private val binding by viewBinding(FragmentCalendarBinding::bind)\n\n  private var adapter: CalendarAdapter? = null\n  private var layoutManager: LayoutManager? = null\n  private var overscroll: IOverScrollDecor? = null\n  private var statusBarHeight = 0\n  private var overscrollEnabled = true\n  private var isSearching = false\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupRecycler()\n    setupStatusBar()\n\n    viewLifecycleOwner.lifecycleScope.launch {\n      viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {\n        with(parentViewModel) {\n          launch { uiState.collect { viewModel.handleParentAction(it) } }\n        }\n        with(viewModel) {\n          launch { uiState.collect { render(it) } }\n          launch { messageFlow.collect { showSnack(it) } }\n          launch { eventFlow.collect { handleEvent(it) } }\n        }\n      }\n    }\n  }\n\n  private fun setupRecycler() {\n    val gridSpanSize = settings.tabletGridSpanSize\n    layoutManager = ProgressLayoutManagerProvider.provideLayoutManger(requireContext(), gridSpanSize)\n    (layoutManager as? GridLayoutManager)?.run {\n      withSpanSizeLookup { position ->\n        when (adapter?.getItems()?.get(position)) {\n          is CalendarListItem.Header -> gridSpanSize\n          is CalendarListItem.Episode -> 1\n          else -> throw IllegalStateException()\n        }\n      }\n    }\n    adapter = CalendarAdapter(\n      itemClickListener = { requireMainFragment().openShowDetails(it.show) },\n      missingImageListener = { item, force -> viewModel.findMissingImage(item, force) },\n      checkClickListener = { viewModel.onEpisodeChecked(it) },\n      detailsClickListener = {\n        requireMainFragment().openEpisodeDetails(\n          show = it.show,\n          episode = it.episode,\n          season = it.season\n        )\n      },\n      missingTranslationListener = { viewModel.findMissingTranslation(it) }\n    )\n    binding.progressCalendarRecycler.apply {\n      adapter = this@CalendarFragment.adapter\n      layoutManager = this@CalendarFragment.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      setHasFixedSize(true)\n    }\n    setupOverscroll()\n  }\n\n  private fun setupOverscroll() {\n    if (overscroll != null || view == null) {\n      return\n    }\n    val adapt = TopOverscrollAdapter(binding.progressCalendarRecycler)\n    overscroll = VerticalOverScrollBounceEffectDecorator(\n      adapt,\n      1.75F,\n      DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK,\n      DEFAULT_DECELERATE_FACTOR\n    ).apply {\n      setOverScrollUpdateListener { _, state, offset ->\n        binding.progressCalendarOverscrollIcon.run {\n          if (offset > 0) {\n            val value = (offset / OVERSCROLL_OFFSET).coerceAtMost(1F)\n            val valueTranslation = offset / OVERSCROLL_OFFSET_TRANSLATION\n            when (state) {\n              IOverScrollState.STATE_DRAG_START_SIDE -> {\n                alpha = value\n                scaleX = value\n                scaleY = value\n                translationY = valueTranslation\n                overscrollEnabled = true\n              }\n              IOverScrollState.STATE_BOUNCE_BACK -> {\n                alpha = value\n                scaleX = value\n                scaleY = value\n                translationY = valueTranslation\n                if (offset >= OVERSCROLL_OFFSET && overscrollEnabled) {\n                  overscrollEnabled = false\n                  requireMainFragment().toggleCalendarMode()\n                }\n              }\n            }\n          } else {\n            alpha = 0F\n            scaleX = 0F\n            scaleY = 0F\n            translationY = 0F\n          }\n        }\n      }\n    }\n  }\n\n  private fun setupStatusBar() {\n    val recyclerPadding = if (moviesEnabled) R.dimen.progressCalendarTabsViewPadding else R.dimen.progressCalendarTabsViewPaddingNoModes\n    val overscrollPadding = if (moviesEnabled) R.dimen.progressOverscrollPadding else R.dimen.progressOverscrollPaddingNoModes\n\n    if (statusBarHeight != 0) {\n      binding.progressCalendarRecycler.updatePadding(top = statusBarHeight + dimenToPx(recyclerPadding))\n      (binding.progressCalendarOverscrollIcon.layoutParams as ViewGroup.MarginLayoutParams)\n        .updateMargins(top = statusBarHeight + dimenToPx(overscrollPadding))\n      return\n    }\n\n    binding.progressCalendarRecycler.doOnApplyWindowInsets { view, insets, _, _ ->\n      val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n      statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n      view.updatePadding(top = statusBarHeight + dimenToPx(recyclerPadding))\n      (binding.progressCalendarOverscrollIcon.layoutParams as ViewGroup.MarginLayoutParams)\n        .updateMargins(top = statusBarHeight + dimenToPx(overscrollPadding))\n    }\n  }\n\n  override fun onEnterSearch() {\n    isSearching = true\n\n    with(binding) {\n      progressCalendarRecycler.translationY = dimenToPx(R.dimen.progressSearchLocalOffset).toFloat()\n      progressCalendarRecycler.smoothScrollToPosition(0)\n    }\n\n    overscroll?.detach()\n    overscroll = null\n  }\n\n  override fun onExitSearch() {\n    isSearching = false\n\n    with(binding) {\n      progressCalendarRecycler.translationY = 0F\n      progressCalendarRecycler.smoothScrollToPosition(0)\n    }\n\n    setupOverscroll()\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is EpisodeCheckActionUiEvent -> {\n        if (event.isQuickRate) requireMainFragment().openRateDialog(event.episode)\n        else parentViewModel.setWatchedEpisode(event.episode)\n      }\n    }\n  }\n\n  private fun render(uiState: CalendarUiState) {\n    uiState.run {\n      with(binding) {\n        items?.let {\n          adapter?.setItems(it)\n          progressCalendarRecycler.fadeIn(150, withHardware = true)\n          progressCalendarEmptyFutureView.root.visibleIf(items.isEmpty() && mode == PRESENT_FUTURE && !isSearching)\n          progressCalendarEmptyRecentsView.root.visibleIf(items.isEmpty() && mode == RECENTS && !isSearching)\n        }\n        mode.let {\n          viewLifecycleOwner.lifecycleScope.launch {\n            delay(300)\n            when (it) {\n              PRESENT_FUTURE -> progressCalendarOverscrollIcon.setImageResource(R.drawable.ic_history)\n              RECENTS -> progressCalendarOverscrollIcon.setImageResource(R.drawable.ic_calendar)\n            }\n          }\n        }\n      }\n    }\n  }\n\n  override fun onScrollReset() = binding.progressCalendarRecycler.smoothScrollToPosition(0)\n\n  private fun requireMainFragment() = requireParentFragment() as ProgressMainFragment\n\n  override fun onDestroyView() {\n    overscroll?.detach()\n    overscroll = null\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n\n  override fun setupBackPressed() = Unit\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/CalendarUiState.kt",
    "content": "package com.michaldrabik.ui_progress.calendar\n\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_progress.calendar.recycler.CalendarListItem\n\ndata class CalendarUiState(\n  val items: List<CalendarListItem>? = null,\n  val mode: CalendarMode = CalendarMode.PRESENT_FUTURE\n)\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/CalendarViewModel.kt",
    "content": "package com.michaldrabik.ui_progress.calendar\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_model.EpisodeBundle\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_progress.calendar.cases.CalendarRatingsCase\nimport com.michaldrabik.ui_progress.calendar.cases.items.CalendarFutureCase\nimport com.michaldrabik.ui_progress.calendar.cases.items.CalendarRecentsCase\nimport com.michaldrabik.ui_progress.calendar.recycler.CalendarListItem\nimport com.michaldrabik.ui_progress.main.EpisodeCheckActionUiEvent\nimport com.michaldrabik.ui_progress.main.ProgressMainUiState\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@HiltViewModel\nclass CalendarViewModel @Inject constructor(\n  private val recentsCase: CalendarRecentsCase,\n  private val futureCase: CalendarFutureCase,\n  private val ratingsCase: CalendarRatingsCase,\n  private val imagesProvider: ShowImagesProvider,\n  private val translationsRepository: TranslationsRepository,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private var loadItemsJob: Job? = null\n  private var loadTranslationJobs: MutableSet<IdTrakt> = mutableSetOf()\n\n  private val itemsState = MutableStateFlow<List<CalendarListItem>?>(null)\n  private val modeState = MutableStateFlow(CalendarMode.PRESENT_FUTURE)\n\n  private var mode = CalendarMode.PRESENT_FUTURE\n  private var searchQuery: String? = null\n  private var timestamp = 0L\n\n  fun handleParentAction(state: ProgressMainUiState) {\n    when {\n      this.timestamp != state.timestamp && state.timestamp != 0L -> {\n        this.timestamp = state.timestamp ?: 0L\n        loadItems()\n      }\n      this.mode != state.calendarMode -> {\n        this.mode = state.calendarMode ?: CalendarMode.PRESENT_FUTURE\n        loadItems()\n      }\n      this.searchQuery != state.searchQuery -> {\n        this.searchQuery = state.searchQuery\n        loadItems()\n      }\n    }\n  }\n\n  private fun loadItems() {\n    loadItemsJob?.cancel()\n    loadItemsJob = viewModelScope.launch {\n      val items = when (mode) {\n        CalendarMode.PRESENT_FUTURE -> futureCase.loadItems(searchQuery)\n        CalendarMode.RECENTS -> recentsCase.loadItems(searchQuery)\n      }\n      itemsState.value = items\n      modeState.value = mode\n    }\n  }\n\n  fun onEpisodeChecked(episode: CalendarListItem.Episode) {\n    viewModelScope.launch {\n      val bundle = EpisodeBundle(episode.episode, episode.season, episode.show)\n      val isQuickRate = ratingsCase.isQuickRateEnabled()\n      eventChannel.send(EpisodeCheckActionUiEvent(bundle, isQuickRate))\n    }\n  }\n\n  fun findMissingImage(item: CalendarListItem, force: Boolean) {\n    check(item is CalendarListItem.Episode)\n    viewModelScope.launch {\n      updateItem(item.copy(isLoading = true))\n      try {\n        val image = imagesProvider.loadRemoteImage(item.show, item.image.type, force)\n        updateItem(item.copy(image = image, isLoading = false))\n      } catch (t: Throwable) {\n        val unavailable = Image.createUnavailable(item.image.type)\n        updateItem(item.copy(image = unavailable, isLoading = false))\n      }\n    }\n  }\n\n  fun findMissingTranslation(item: CalendarListItem) {\n    check(item is CalendarListItem.Episode)\n    val showId = item.show.ids.trakt\n    val language = translationsRepository.getLanguage()\n    if (item.translations?.show != null || language == Config.DEFAULT_LANGUAGE || loadTranslationJobs.contains(showId)) {\n      return\n    }\n    viewModelScope.launch {\n      try {\n        val translation = translationsRepository.loadTranslation(item.show, language)\n        val translations = item.translations?.copy(show = translation)\n        updateItem(item.copy(translations = translations))\n      } catch (error: Throwable) {\n        Timber.e(error)\n      } finally {\n        loadTranslationJobs.remove(showId)\n      }\n    }\n    loadTranslationJobs.add(showId)\n  }\n\n  private fun updateItem(new: CalendarListItem.Episode) {\n    val currentItems = itemsState.value?.toMutableList() ?: mutableListOf()\n    currentItems.findReplace(new) { it.isSameAs(new) }\n    itemsState.value = currentItems\n    modeState.value = mode\n  }\n\n  val uiState = combine(\n    itemsState,\n    modeState\n  ) { s1, s2 ->\n    CalendarUiState(\n      items = s1,\n      mode = s2\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = CalendarUiState()\n  )\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/cases/CalendarRatingsCase.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.cases\n\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass CalendarRatingsCase @Inject constructor(\n  private val userTraktManager: UserTraktManager,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun isQuickRateEnabled(): Boolean {\n    val isSignedIn = userTraktManager.isAuthorized()\n    val isPremium = settingsRepository.isPremium\n    val isQuickRate = settingsRepository.load().traktQuickRateEnabled\n    return isPremium && isSignedIn && isQuickRate\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/cases/items/CalendarFutureCase.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.cases.items\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.Episode\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_progress.calendar.helpers.WatchlistAppender\nimport com.michaldrabik.ui_progress.calendar.helpers.filters.CalendarFutureFilter\nimport com.michaldrabik.ui_progress.calendar.helpers.groupers.CalendarFutureGrouper\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CalendarFutureCase @Inject constructor(\n  dispatchers: CoroutineDispatchers,\n  localSource: LocalDataSource,\n  mappers: Mappers,\n  showsRepository: ShowsRepository,\n  translationsRepository: TranslationsRepository,\n  spoilersRepository: SettingsSpoilersRepository,\n  imagesProvider: ShowImagesProvider,\n  dateFormatProvider: DateFormatProvider,\n  watchlistAppender: WatchlistAppender,\n  override val filter: CalendarFutureFilter,\n  override val grouper: CalendarFutureGrouper,\n) : CalendarItemsCase(\n  dispatchers,\n  localSource,\n  mappers,\n  showsRepository,\n  translationsRepository,\n  spoilersRepository,\n  imagesProvider,\n  dateFormatProvider,\n  watchlistAppender\n) {\n\n  override fun sortEpisodes() =\n    compareBy<Episode> { it.firstAired }\n      .thenByDescending { it.idShowTrakt }\n      .thenBy { it.episodeNumber }\n\n  override fun isWatched(episode: Episode) = true\n\n  override fun isSpoilerHidden(episode: Episode) = !episode.isWatched\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/cases/items/CalendarItemsCase.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.cases.items\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.Episode\nimport com.michaldrabik.data_local.database.model.Season\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_progress.calendar.helpers.WatchlistAppender\nimport com.michaldrabik.ui_progress.calendar.helpers.filters.CalendarFilter\nimport com.michaldrabik.ui_progress.calendar.helpers.groupers.CalendarGrouper\nimport com.michaldrabik.ui_progress.calendar.recycler.CalendarListItem\nimport com.michaldrabik.ui_progress.helpers.TranslationsBundle\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.withContext\n\n@Suppress(\"UNCHECKED_CAST\")\nabstract class CalendarItemsCase constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n  private val showsRepository: ShowsRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val spoilersRepository: SettingsSpoilersRepository,\n  private val imagesProvider: ShowImagesProvider,\n  private val dateFormatProvider: DateFormatProvider,\n  private val watchlistAppender: WatchlistAppender,\n) {\n\n  abstract val filter: CalendarFilter\n  abstract val grouper: CalendarGrouper\n\n  abstract fun sortEpisodes(): Comparator<Episode>\n  abstract fun isWatched(episode: Episode): Boolean\n  abstract fun isSpoilerHidden(episode: Episode): Boolean\n\n  suspend fun loadItems(searchQuery: String? = \"\") =\n    withContext(dispatchers.IO) {\n      val now = nowUtc().toLocalZone()\n\n      val language = translationsRepository.getLanguage()\n      val dateFormat = dateFormatProvider.loadFullHourFormat()\n      val spoilers = spoilersRepository.getAll()\n\n      val (myShows, watchlistShows) = coroutineScope {\n        val async1 = async { showsRepository.myShows.loadAll() }\n        val async2 = async { showsRepository.watchlistShows.loadAll() }\n        awaitAll(async1, async2)\n      }\n\n      val shows = myShows + watchlistShows\n\n      val showsIds = shows.map { it.traktId }.chunked(250)\n      val watchlistShowsIds = watchlistShows.map { it.traktId }\n\n      val (episodes, seasons) = awaitAll(\n        async {\n          showsIds.fold(mutableListOf<Episode>()) { acc, list ->\n            acc += localSource.episodes.getAllByShowsIds(list)\n            acc\n          }\n        },\n        async {\n          showsIds.fold(mutableListOf<Season>()) { acc, list ->\n            acc += localSource.seasons.getAllByShowsIds(list)\n            acc\n          }\n        }\n      )\n\n      val filteredSeasons = (seasons as List<Season>).filter { it.seasonNumber != 0 }.toMutableList()\n      val filteredEpisodes = (episodes as List<Episode>).filter { it.seasonNumber != 0 }.toMutableList()\n\n      watchlistAppender.appendWatchlistShows(\n        watchlistShows,\n        filteredSeasons,\n        filteredEpisodes\n      )\n\n      val elements = filteredEpisodes\n        .filter { filter.filter(now, it) }\n        .sortedWith(sortEpisodes())\n        .map { episode ->\n          async {\n            val show = shows.firstOrNull { it.traktId == episode.idShowTrakt }\n            val season = filteredSeasons.firstOrNull { it.idShowTrakt == episode.idShowTrakt && it.seasonNumber == episode.seasonNumber }\n\n            if (show == null || season == null) {\n              return@async null\n            }\n\n            val seasonEpisodes = episodes.filter { it.idShowTrakt == season.idShowTrakt && it.seasonNumber == season.seasonNumber }\n\n            val episodeUi = mappers.episode.fromDatabase(episode)\n            val seasonUi = mappers.season.fromDatabase(season, seasonEpisodes)\n\n            var translations: TranslationsBundle? = null\n            if (language != Config.DEFAULT_LANGUAGE) {\n              translations = TranslationsBundle(\n                episode = translationsRepository.loadTranslation(episodeUi, show.ids.trakt, language, onlyLocal = true),\n                show = translationsRepository.loadTranslation(show, language, onlyLocal = true)\n              )\n            }\n            CalendarListItem.Episode(\n              show = show,\n              image = imagesProvider.findCachedImage(show, ImageType.POSTER),\n              episode = episodeUi,\n              season = seasonUi,\n              isWatched = isWatched(episode),\n              isWatchlist = show.traktId in watchlistShowsIds,\n              isSpoilerHidden = isSpoilerHidden(episode),\n              dateFormat = dateFormat,\n              translations = translations,\n              spoilers = spoilers\n            )\n          }\n        }\n        .awaitAll()\n        .filterNotNull()\n\n      val queryElements = filterByQuery(searchQuery ?: \"\", elements)\n      grouper.groupByTime(queryElements)\n    }\n\n  private fun filterByQuery(query: String, items: List<CalendarListItem.Episode>) =\n    items.filter {\n      it.show.title.contains(query, true) ||\n        it.episode.title.contains(query, true) ||\n        it.translations?.show?.title?.contains(query, true) == true ||\n        it.translations?.episode?.title?.contains(query, true) == true\n    }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/cases/items/CalendarRecentsCase.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.cases.items\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.Episode\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_progress.calendar.helpers.WatchlistAppender\nimport com.michaldrabik.ui_progress.calendar.helpers.filters.CalendarRecentsFilter\nimport com.michaldrabik.ui_progress.calendar.helpers.groupers.CalendarRecentsGrouper\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CalendarRecentsCase @Inject constructor(\n  dispatchers: CoroutineDispatchers,\n  localSource: LocalDataSource,\n  mappers: Mappers,\n  showsRepository: ShowsRepository,\n  translationsRepository: TranslationsRepository,\n  spoilersRepository: SettingsSpoilersRepository,\n  imagesProvider: ShowImagesProvider,\n  dateFormatProvider: DateFormatProvider,\n  watchlistAppender: WatchlistAppender,\n  override val filter: CalendarRecentsFilter,\n  override val grouper: CalendarRecentsGrouper,\n) : CalendarItemsCase(\n  dispatchers,\n  localSource,\n  mappers,\n  showsRepository,\n  translationsRepository,\n  spoilersRepository,\n  imagesProvider,\n  dateFormatProvider,\n  watchlistAppender\n) {\n\n  override fun sortEpisodes() =\n    compareByDescending<Episode> { it.firstAired }\n      .thenByDescending { it.idShowTrakt }\n      .thenByDescending { it.episodeNumber }\n\n  override fun isWatched(episode: Episode) = episode.isWatched\n\n  override fun isSpoilerHidden(episode: Episode) = !episode.isWatched\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/helpers/WatchlistAppender.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.helpers\n\nimport com.michaldrabik.common.extensions.toZonedDateTime\nimport com.michaldrabik.data_local.database.model.Episode\nimport com.michaldrabik.data_local.database.model.Season\nimport com.michaldrabik.ui_model.Show\nimport javax.inject.Inject\n\nclass WatchlistAppender @Inject constructor() {\n\n  fun appendWatchlistShows(\n    shows: List<Show>,\n    seasons: MutableList<Season>,\n    episodes: MutableList<Episode>,\n  ) {\n    if (shows.isEmpty() || seasons.isEmpty() || episodes.isEmpty()) {\n      return\n    }\n\n    val seasonId = seasons.maxOf { it.idTrakt }\n    val episodeId = episodes.maxOf { it.idTrakt }\n\n    shows\n      .filter { it.firstAired.isNotBlank() }\n      .forEachIndexed { index, show ->\n        val season = createWatchlistSeason(\n          show = show,\n          seasonId = seasonId + index + 1\n        )\n\n        val episode = createWatchlistEpisode(\n          show = show,\n          season = season,\n          episodeId = episodeId + index + 1\n        )\n\n        seasons.add(season)\n        episodes.add(episode)\n      }\n  }\n\n  private fun createWatchlistSeason(\n    show: Show,\n    seasonId: Long,\n  ) = Season(\n    idTrakt = seasonId,\n    idShowTrakt = show.traktId,\n    seasonNumber = 1,\n    seasonTitle = \"\",\n    seasonOverview = \"\",\n    seasonFirstAired = show.firstAired.toZonedDateTime(),\n    episodesCount = 1,\n    episodesAiredCount = 0,\n    rating = null,\n    isWatched = false\n  )\n\n  private fun createWatchlistEpisode(\n    show: Show,\n    season: Season,\n    episodeId: Long,\n  ) = Episode(\n    idTrakt = episodeId,\n    idSeason = season.idTrakt,\n    idShowTrakt = show.traktId,\n    idShowTvdb = show.ids.tvdb.id,\n    idShowImdb = show.ids.imdb.id,\n    idShowTmdb = show.ids.tmdb.id,\n    seasonNumber = 1,\n    episodeNumber = 1,\n    episodeNumberAbs = null,\n    episodeOverview = \"\",\n    title = \"\",\n    firstAired = show.firstAired.toZonedDateTime(),\n    commentsCount = 0,\n    rating = 0.0f,\n    runtime = 0,\n    votesCount = 0,\n    isWatched = false,\n    lastWatchedAt = null\n  )\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/helpers/filters/CalendarFilter.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.helpers.filters\n\nimport com.michaldrabik.data_local.database.model.Episode\nimport java.time.ZonedDateTime\n\ninterface CalendarFilter {\n  fun filter(now: ZonedDateTime, episode: Episode): Boolean\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/helpers/filters/CalendarFutureFilter.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.helpers.filters\n\nimport com.michaldrabik.common.extensions.isSameDayOrAfter\nimport com.michaldrabik.common.extensions.toLocalZone\nimport java.time.ZonedDateTime\nimport java.time.temporal.ChronoUnit.DAYS\nimport javax.inject.Inject\nimport javax.inject.Singleton\nimport com.michaldrabik.data_local.database.model.Episode as EpisodeDb\n\n@Singleton\nclass CalendarFutureFilter @Inject constructor() : CalendarFilter {\n\n  override fun filter(now: ZonedDateTime, episode: EpisodeDb): Boolean {\n    val dateDays = episode.firstAired?.toLocalZone()?.truncatedTo(DAYS)\n    return episode.seasonNumber != 0 && dateDays?.isSameDayOrAfter(now) == true\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/helpers/filters/CalendarRecentsFilter.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.helpers.filters\n\nimport com.michaldrabik.common.extensions.toLocalZone\nimport java.time.ZonedDateTime\nimport java.time.temporal.ChronoUnit.DAYS\nimport javax.inject.Inject\nimport javax.inject.Singleton\nimport com.michaldrabik.data_local.database.model.Episode as EpisodeDb\n\n@Singleton\nclass CalendarRecentsFilter @Inject constructor() : CalendarFilter {\n\n  override fun filter(now: ZonedDateTime, episode: EpisodeDb): Boolean {\n    val dateDays = episode.firstAired?.toLocalZone()?.truncatedTo(DAYS)\n    val isHistory = dateDays?.isBefore(now.truncatedTo(DAYS)) == true\n    val isLast3Months = dateDays?.isAfter(now.truncatedTo(DAYS).minusMonths(3)) == true\n    return episode.seasonNumber != 0 && isHistory && isLast3Months\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/helpers/groupers/CalendarFutureGrouper.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.helpers.groupers\n\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_progress.R\nimport com.michaldrabik.ui_progress.calendar.recycler.CalendarListItem\nimport java.time.DayOfWeek\nimport java.time.Month\nimport java.time.temporal.ChronoUnit.DAYS\nimport java.time.temporal.TemporalAdjusters.next\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CalendarFutureGrouper @Inject constructor() : CalendarGrouper {\n\n  override fun groupByTime(items: List<CalendarListItem.Episode>): List<CalendarListItem> {\n    val nowDays = nowUtc().toLocalZone().truncatedTo(DAYS)\n\n    val itemsMap = mutableMapOf<Int, MutableList<CalendarListItem>>()\n      .apply {\n        put(R.string.textToday, mutableListOf())\n        put(R.string.textTomorrow, mutableListOf())\n        put(R.string.textThisWeek, mutableListOf())\n        put(R.string.textNextWeek, mutableListOf())\n        put(R.string.textThisMonth, mutableListOf())\n        put(R.string.textNextMonth, mutableListOf())\n        put(R.string.textThisYear, mutableListOf())\n        put(R.string.textLater, mutableListOf())\n      }\n\n    items.forEach { item ->\n      val itemDays = item.episode.firstAired?.toLocalZone()?.truncatedTo(DAYS)\n      when {\n        itemDays?.isEqual(nowDays) == true -> {\n          itemsMap[R.string.textToday]?.add(item)\n        }\n        itemDays?.isEqual(nowDays.plusDays(1)) == true -> {\n          itemsMap[R.string.textTomorrow]?.add(item)\n        }\n        itemDays?.isBefore(nowDays.with(next(DayOfWeek.MONDAY))) == true -> {\n          itemsMap[R.string.textThisWeek]?.add(item)\n        }\n        itemDays?.isBefore(nowDays.plusWeeks(1).with(next(DayOfWeek.MONDAY))) == true -> {\n          itemsMap[R.string.textNextWeek]?.add(item)\n        }\n        itemDays?.month == nowDays.month && itemDays?.year == nowDays.year -> {\n          itemsMap[R.string.textThisMonth]?.add(item)\n        }\n        (itemDays?.monthValue == (nowDays.monthValue + 1) && itemDays.year == nowDays.year) ||\n          (itemDays?.month == Month.JANUARY && nowDays.month == Month.DECEMBER) -> {\n          itemsMap[R.string.textNextMonth]?.add(item)\n        }\n        itemDays?.year == nowDays.year -> {\n          itemsMap[R.string.textThisYear]?.add(item)\n        }\n        else -> itemsMap[R.string.textLater]?.add(item)\n      }\n    }\n\n    return itemsMap.entries.fold(mutableListOf()) { acc, entry ->\n      acc.apply {\n        if (entry.value.isNotEmpty()) {\n          add(CalendarListItem.Header.create(entry.key, CalendarMode.PRESENT_FUTURE))\n          addAll(entry.value)\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/helpers/groupers/CalendarGrouper.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.helpers.groupers\n\nimport com.michaldrabik.ui_progress.calendar.recycler.CalendarListItem\n\ninterface CalendarGrouper {\n  fun groupByTime(items: List<CalendarListItem.Episode>): List<CalendarListItem>\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/helpers/groupers/CalendarRecentsGrouper.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.helpers.groupers\n\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_progress.R\nimport com.michaldrabik.ui_progress.calendar.recycler.CalendarListItem\nimport java.time.temporal.ChronoUnit.DAYS\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CalendarRecentsGrouper @Inject constructor() : CalendarGrouper {\n\n  override fun groupByTime(items: List<CalendarListItem.Episode>): List<CalendarListItem> {\n    val now = nowUtc().toLocalZone().truncatedTo(DAYS)\n\n    val yesterdayItems = items.filter {\n      val dateDays = it.episode.firstAired?.toLocalZone()?.truncatedTo(DAYS)\n      dateDays?.isEqual(now.minusDays(1)) == true\n    }\n    val last7DaysItems = (items - yesterdayItems).filter {\n      val dateDays = it.episode.firstAired?.toLocalZone()?.truncatedTo(DAYS)\n      dateDays?.isAfter(now.minusDays(8)) == true\n    }\n    val last30DaysItems = (items - yesterdayItems - last7DaysItems).filter {\n      val dateDays = it.episode.firstAired?.toLocalZone()?.truncatedTo(DAYS)\n      dateDays?.isAfter(now.minusDays(31)) == true\n    }\n    val last90Days = (items - yesterdayItems - last7DaysItems - last30DaysItems).filter {\n      val dateDays = it.episode.firstAired?.toLocalZone()?.truncatedTo(DAYS)\n      dateDays?.isAfter(now.minusDays(91)) == true\n    }\n\n    val itemsMap = mutableMapOf<Int, List<CalendarListItem>>()\n      .apply {\n        put(R.string.textYesterday, yesterdayItems)\n        put(R.string.textLast7Days, last7DaysItems)\n        put(R.string.textLast30Days, last30DaysItems)\n        put(R.string.textLast90Days, last90Days)\n      }\n\n    return itemsMap.entries.fold(mutableListOf()) { acc, entry ->\n      acc.apply {\n        if (entry.value.isNotEmpty()) {\n          add(CalendarListItem.Header.create(entry.key, CalendarMode.RECENTS))\n          addAll(entry.value)\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/recycler/CalendarAdapter.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.recycler\n\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseAdapter\nimport com.michaldrabik.ui_progress.calendar.views.CalendarHeaderView\nimport com.michaldrabik.ui_progress.calendar.views.CalendarItemView\n\nclass CalendarAdapter(\n  private val itemClickListener: (CalendarListItem) -> Unit,\n  private val missingImageListener: (CalendarListItem, Boolean) -> Unit,\n  private val missingTranslationListener: (CalendarListItem) -> Unit,\n  var detailsClickListener: ((CalendarListItem.Episode) -> Unit),\n  var checkClickListener: ((CalendarListItem.Episode) -> Unit)\n) : BaseAdapter<CalendarListItem>() {\n\n  companion object {\n    private const val VIEW_TYPE_ITEM = 1\n    private const val VIEW_TYPE_HEADER = 2\n  }\n\n  override val asyncDiffer = AsyncListDiffer(this, CalendarItemDiffCallback())\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    when (viewType) {\n      VIEW_TYPE_ITEM -> BaseViewHolder(\n        CalendarItemView(parent.context).apply {\n          itemClickListener = this@CalendarAdapter.itemClickListener\n          missingImageListener = this@CalendarAdapter.missingImageListener\n          missingTranslationListener = this@CalendarAdapter.missingTranslationListener\n          detailsClickListener = this@CalendarAdapter.detailsClickListener\n          checkClickListener = this@CalendarAdapter.checkClickListener\n        }\n      )\n      VIEW_TYPE_HEADER -> BaseViewHolder(CalendarHeaderView(parent.context))\n      else -> throw IllegalStateException()\n    }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    when (val item = asyncDiffer.currentList[position]) {\n      is CalendarListItem.Episode -> (holder.itemView as CalendarItemView).bind(item)\n      is CalendarListItem.Header -> (holder.itemView as CalendarHeaderView).bind(item)\n    }\n  }\n\n  override fun getItemViewType(position: Int) =\n    when (asyncDiffer.currentList[position]) {\n      is CalendarListItem.Header -> VIEW_TYPE_HEADER\n      is CalendarListItem.Episode -> VIEW_TYPE_ITEM\n      else -> throw IllegalStateException()\n    }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/recycler/CalendarItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass CalendarItemDiffCallback : DiffUtil.ItemCallback<CalendarListItem>() {\n\n  override fun areItemsTheSame(oldItem: CalendarListItem, newItem: CalendarListItem): Boolean {\n    val areEpisodes = oldItem is CalendarListItem.Episode && newItem is CalendarListItem.Episode\n    val areHeaders = oldItem is CalendarListItem.Header && newItem is CalendarListItem.Header\n    return when {\n      areEpisodes -> areItemsTheSame(\n        (oldItem as CalendarListItem.Episode),\n        (newItem as CalendarListItem.Episode)\n      )\n      areHeaders -> areItemsTheSame(\n        (oldItem as CalendarListItem.Header),\n        (newItem as CalendarListItem.Header)\n      )\n      else -> false\n    }\n  }\n\n  private fun areItemsTheSame(oldItem: CalendarListItem.Episode, newItem: CalendarListItem.Episode) =\n    oldItem.episode.ids.trakt == newItem.episode.ids.trakt\n\n  private fun areItemsTheSame(oldItem: CalendarListItem.Header, newItem: CalendarListItem.Header) =\n    oldItem.textResId == newItem.textResId\n\n  override fun areContentsTheSame(oldItem: CalendarListItem, newItem: CalendarListItem) =\n    when (oldItem) {\n      is CalendarListItem.Episode -> areContentsTheSame(oldItem, (newItem as CalendarListItem.Episode))\n      is CalendarListItem.Header -> areContentsTheSame(oldItem, (newItem as CalendarListItem.Header))\n    }\n\n  private fun areContentsTheSame(oldItem: CalendarListItem.Episode, newItem: CalendarListItem.Episode) =\n    oldItem.episode == newItem.episode &&\n      oldItem.season == newItem.season &&\n      oldItem.show == newItem.show &&\n      oldItem.image == newItem.image &&\n      oldItem.isLoading == newItem.isLoading &&\n      oldItem.isSpoilerHidden == newItem.isSpoilerHidden &&\n      oldItem.isWatchlist == newItem.isWatchlist &&\n      oldItem.translations == newItem.translations &&\n      oldItem.spoilers == newItem.spoilers &&\n      oldItem.isWatched == newItem.isWatched\n\n  private fun areContentsTheSame(oldItem: CalendarListItem.Header, newItem: CalendarListItem.Header) =\n    oldItem == newItem\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/recycler/CalendarListItem.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.recycler\n\nimport androidx.annotation.StringRes\nimport com.michaldrabik.ui_base.common.ListItem\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_progress.helpers.TranslationsBundle\nimport java.time.format.DateTimeFormatter\nimport com.michaldrabik.ui_model.Episode as EpisodeModel\n\nsealed class CalendarListItem(\n  override val show: Show,\n  override val image: Image,\n  override val isLoading: Boolean = false,\n) : ListItem {\n\n  data class Episode(\n    override val show: Show,\n    override val image: Image,\n    override val isLoading: Boolean = false,\n    val episode: EpisodeModel,\n    val season: Season,\n    val isWatched: Boolean,\n    val isWatchlist: Boolean,\n    val isSpoilerHidden: Boolean,\n    val translations: TranslationsBundle? = null,\n    val dateFormat: DateTimeFormatter? = null,\n    val spoilers: SpoilersSettings? = null,\n  ) : CalendarListItem(show, image, isLoading) {\n\n    override fun isSameAs(other: ListItem) =\n      episode.ids.trakt == (other as? Episode)?.episode?.ids?.trakt\n  }\n\n  data class Header(\n    override val show: Show,\n    override val image: Image,\n    override val isLoading: Boolean = false,\n    @StringRes val textResId: Int,\n    val calendarMode: CalendarMode,\n  ) : CalendarListItem(show, image, isLoading) {\n\n    companion object {\n      fun create(@StringRes textResId: Int, mode: CalendarMode) =\n        Header(\n          show = Show.EMPTY,\n          image = Image.createUnavailable(ImageType.POSTER),\n          textResId = textResId,\n          calendarMode = mode\n        )\n    }\n\n    override fun isSameAs(other: ListItem) =\n      textResId == (other as? Header)?.textResId\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/views/CalendarHeaderView.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.LinearLayout\nimport androidx.core.view.updatePadding\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_progress.R\nimport com.michaldrabik.ui_progress.calendar.recycler.CalendarListItem\nimport com.michaldrabik.ui_progress.databinding.ViewCalendarHeaderBinding\n\nclass CalendarHeaderView : LinearLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCalendarHeaderBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    orientation = HORIZONTAL\n    updatePadding(\n      top = context.dimenToPx(R.dimen.spaceNormal),\n      bottom = context.dimenToPx(R.dimen.spaceTiny),\n      left = context.dimenToPx(R.dimen.spaceMedium),\n      right = context.dimenToPx(R.dimen.spaceMedium)\n    )\n  }\n\n  fun bind(item: CalendarListItem.Header) {\n    with(binding) {\n      calendarHeaderText.setText(item.textResId)\n      calendarHeaderIcon.visibleIf(item.calendarMode == CalendarMode.RECENTS)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/calendar/views/CalendarItemView.kt",
    "content": "package com.michaldrabik.ui_progress.calendar.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.addRipple\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.expandTouch\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_progress.R\nimport com.michaldrabik.ui_progress.calendar.recycler.CalendarListItem\nimport com.michaldrabik.ui_progress.databinding.ViewCalendarItemBinding\nimport java.util.Locale.ENGLISH\n\n@SuppressLint(\"SetTextI18n\")\nclass CalendarItemView : ShowView<CalendarListItem.Episode> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCalendarItemBinding.inflate(LayoutInflater.from(context), this)\n\n  var detailsClickListener: ((CalendarListItem.Episode) -> Unit)? = null\n  var checkClickListener: ((CalendarListItem.Episode) -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    addRipple()\n\n    onClick { itemClickListener?.invoke(item) }\n    with(binding) {\n      calendarItemInfoButton.expandTouch(100)\n      calendarItemInfoButton.onClick { detailsClickListener?.invoke(item) }\n      calendarItemCheckButton.onClick { checkClickListener?.invoke(item) }\n    }\n\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  private lateinit var item: CalendarListItem.Episode\n\n  override val imageView: ImageView = binding.calendarItemImage\n  override val placeholderView: ImageView = binding.calendarItemPlaceholder\n\n  override fun bind(item: CalendarListItem.Episode) {\n    this.item = item\n    clear()\n\n    with(binding) {\n      calendarItemTitle.text =\n        if (item.translations?.show?.title.isNullOrBlank()) item.show.title\n        else item.translations?.show?.title\n\n      calendarItemDateText.text =\n        item.episode.firstAired?.toLocalZone()?.let { item.dateFormat?.format(it)?.capitalizeWords() }\n\n      val episodeTitle = when {\n        item.episode.title.isBlank() -> context.getString(R.string.textTba)\n        item.translations?.episode?.title?.isBlank() == false -> item.translations.episode.title\n        item.episode.title == \"Episode ${item.episode.number}\" -> String.format(ENGLISH, context.getString(R.string.textEpisode), item.episode.number)\n        else -> item.episode.title\n      }\n\n      val isNewSeason = item.episode.number == 1\n      if (isNewSeason) {\n        val title = String.format(ENGLISH, context.getString(R.string.textSeason), item.episode.season)\n        calendarItemSubtitle.text = context.getString(R.string.textNewSeason)\n        calendarItemSubtitle2.text =\n          if (item.isSpoilerHidden && item.spoilers?.isEpisodeTitleHidden == true) {\n            calendarItemSubtitle2.tag = title\n            SPOILERS_REGEX.replace(title, SPOILERS_HIDE_SYMBOL)\n          } else {\n            title\n          }\n      } else {\n        calendarItemSubtitle.text = String.format(\n          ENGLISH,\n          context.getString(R.string.textSeasonEpisode),\n          item.episode.season,\n          item.episode.number\n        ).plus(\n          item.episode.numberAbs?.let { if (it > 0 && item.show.isAnime) \" ($it)\" else \"\" } ?: \"\"\n        )\n        calendarItemSubtitle2.text =\n          if (item.isSpoilerHidden && item.spoilers?.isEpisodeTitleHidden == true) {\n            calendarItemSubtitle2.tag = episodeTitle\n            SPOILERS_REGEX.replace(episodeTitle, SPOILERS_HIDE_SYMBOL)\n          } else {\n            episodeTitle\n          }\n      }\n\n      if (item.isSpoilerHidden && item.spoilers?.isTapToReveal == true) {\n        calendarItemSubtitle2.onClick { view ->\n          view.tag?.let { calendarItemSubtitle2.text = it.toString() }\n          view.isClickable = false\n        }\n      }\n\n      calendarItemCheckButton.visibleIf(!item.isWatched && !item.isWatchlist)\n      calendarItemInfoButton.visibleIf(!item.isWatchlist)\n      calendarItemBadge.visibleIf(item.isWatchlist)\n    }\n\n    loadImage(item)\n  }\n\n  private fun loadTranslation() {\n    if (item.translations?.show == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      calendarItemPlaceholder.gone()\n      Glide.with(this@CalendarItemView).clear(calendarItemImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/helpers/ProgressItemsSorter.kt",
    "content": "package com.michaldrabik.ui_progress.helpers\n\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.EPISODES_LEFT\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.RECENTLY_WATCHED\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SortType.ASCENDING\nimport com.michaldrabik.ui_model.SortType.DESCENDING\nimport com.michaldrabik.ui_progress.progress.recycler.ProgressListItem\nimport java.util.Locale\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass ProgressItemsSorter @Inject constructor() {\n  fun sort(sortOrder: SortOrder, sortType: SortType) = when (sortType) {\n    ASCENDING -> sortAscending(sortOrder)\n    DESCENDING -> sortDescending(sortOrder)\n  }\n\n  private fun sortAscending(sortOrder: SortOrder) = when (sortOrder) {\n    NAME -> compareBy { getTitle(it) }\n    RECENTLY_WATCHED -> compareBy { it.show.updatedAt }\n    NEWEST -> compareBy { it.episode?.firstAired?.toMillis() }\n    RATING -> compareBy { it.show.rating }\n    USER_RATING ->\n      compareByDescending<ProgressListItem.Episode> { it.userRating != null }\n        .thenBy { it.userRating }\n        .thenBy { getTitle(it) }\n    EPISODES_LEFT -> compareBy<ProgressListItem.Episode> { it.totalCount - it.watchedCount }.thenBy { getTitle(it) }\n    else -> throw IllegalStateException(\"Invalid sort order\")\n  }\n\n  private fun sortDescending(sortOrder: SortOrder) = when (sortOrder) {\n    NAME -> compareByDescending { getTitle(it) }\n    RECENTLY_WATCHED -> compareByDescending { it.show.updatedAt }\n    NEWEST -> compareByDescending { it.episode?.firstAired?.toMillis() }\n    RATING -> compareByDescending { it.show.rating }\n    USER_RATING ->\n      compareByDescending<ProgressListItem.Episode> { it.userRating != null }\n        .thenByDescending { it.userRating }\n        .thenBy { getTitle(it) }\n    EPISODES_LEFT -> compareByDescending<ProgressListItem.Episode> { it.totalCount - it.watchedCount }.thenBy { getTitle(it) }\n    else -> throw IllegalStateException(\"Invalid sort order\")\n  }\n\n  private fun getTitle(item: ProgressListItem.Episode): String {\n    val translatedTitle =\n      if (item.translations?.show?.hasTitle == false) null\n      else item.translations?.show?.title\n    return (translatedTitle ?: item.show.titleNoThe).uppercase(Locale.ROOT)\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/helpers/ProgressLayoutManagerProvider.kt",
    "content": "package com.michaldrabik.ui_progress.helpers\n\nimport android.content.Context\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\n\ninternal object ProgressLayoutManagerProvider {\n\n  fun provideLayoutManger(\n    context: Context,\n    gridSpanSize: Int,\n  ): LayoutManager {\n    return if (context.isTablet()) {\n      GridLayoutManager(context, gridSpanSize)\n    } else {\n      LinearLayoutManager(context, VERTICAL, false)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/helpers/TopOverscrollAdapter.kt",
    "content": "package com.michaldrabik.ui_progress.helpers\n\nimport androidx.recyclerview.widget.RecyclerView\nimport me.everything.android.ui.overscroll.adapters.RecyclerViewOverScrollDecorAdapter\n\nclass TopOverscrollAdapter(recycler: RecyclerView) : RecyclerViewOverScrollDecorAdapter(recycler) {\n  override fun isInAbsoluteEnd() = false\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/helpers/TranslationsBundle.kt",
    "content": "package com.michaldrabik.ui_progress.helpers\n\nimport com.michaldrabik.ui_model.Translation\n\ndata class TranslationsBundle(\n  val show: Translation? = null,\n  val episode: Translation? = null,\n)\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/main/ProgressMainFragment.kt",
    "content": "package com.michaldrabik.ui_progress.main\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.activity.addCallback\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateMargins\nimport androidx.core.widget.doAfterTextChanged\nimport androidx.fragment.app.clearFragmentResultListener\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.viewpager.widget.ViewPager\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.OnScrollResetListener\nimport com.michaldrabik.ui_base.common.OnSearchClickListener\nimport com.michaldrabik.ui_base.common.OnShowsMoviesSyncedListener\nimport com.michaldrabik.ui_base.common.OnTabReselectedListener\nimport com.michaldrabik.ui_base.common.sheets.context_menu.ContextMenuBottomSheet\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Operation\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Type\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.add\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.disableUi\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.enableUi\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.hideKeyboard\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.nextPage\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.requireParcelable\nimport com.michaldrabik.ui_base.utilities.extensions.showKeyboard\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_episodes.details.EpisodeDetailsBottomSheet\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.EpisodeBundle\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ACTION_EPISODE_TAB_SELECTED\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SHOW_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_EPISODE_DETAILS\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_ITEM_MENU\nimport com.michaldrabik.ui_progress.R\nimport com.michaldrabik.ui_progress.databinding.FragmentProgressMainBinding\nimport com.michaldrabik.ui_progress.main.adapters.ProgressMainAdapter\nimport dagger.hilt.android.AndroidEntryPoint\nimport timber.log.Timber\n\n@AndroidEntryPoint\nclass ProgressMainFragment :\n  BaseFragment<ProgressMainViewModel>(R.layout.fragment_progress_main),\n  OnShowsMoviesSyncedListener,\n  OnTabReselectedListener {\n\n  companion object {\n    private const val TRANSLATION_DURATION = 225L\n  }\n\n  override val navigationId = R.id.progressMainFragment\n\n  override val viewModel by viewModels<ProgressMainViewModel>()\n  private val binding by viewBinding(FragmentProgressMainBinding::bind)\n\n  private var adapter: ProgressMainAdapter? = null\n\n  private var searchViewTranslation = 0F\n  private var tabsTranslation = 0F\n  private var sideIconTranslation = 0F\n  private var currentPage = 0\n  private var isSearching = false\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    savedInstanceState?.let {\n      searchViewTranslation = it.getFloat(\"ARG_SEARCH_POSITION\")\n      tabsTranslation = it.getFloat(\"ARG_TABS_POSITION\")\n      sideIconTranslation = it.getFloat(\"ARG_SIDE_ICON_POSITION\")\n      currentPage = it.getInt(\"ARG_PAGE\")\n    }\n  }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupPager()\n    setupStatusBar()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.messageFlow.collect { showSnack(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = { viewModel.loadProgress() }\n    )\n  }\n\n  override fun onSaveInstanceState(outState: Bundle) {\n    super.onSaveInstanceState(outState)\n    outState.putFloat(\"ARG_SEARCH_POSITION\", searchViewTranslation)\n    outState.putFloat(\"ARG_TABS_POSITION\", tabsTranslation)\n    outState.putFloat(\"ARG_SIDE_ICON_POSITION\", sideIconTranslation)\n    outState.putInt(\"ARG_PAGE\", currentPage)\n  }\n\n  override fun onResume() {\n    super.onResume()\n    showNavigation()\n  }\n\n  override fun onPause() {\n    enableUi()\n    with(binding) {\n      tabsTranslation = progressMainTabs.translationY\n      searchViewTranslation = progressMainSearchView.translationY\n      sideIconTranslation = progressMainSideIcons.translationY\n    }\n    super.onPause()\n  }\n\n  override fun onDestroyView() {\n    with(binding) {\n      progressMainPager.removeOnPageChangeListener(pageChangeListener)\n      progressMainPager.adapter = null\n    }\n    adapter = null\n    super.onDestroyView()\n  }\n\n  private fun setupView() {\n    with(binding) {\n      with(progressMainCalendarIcon) {\n        visibleIf(currentPage == 1)\n        onClick { toggleCalendarMode() }\n      }\n      with(progressMainSearchIcon) {\n        onClick { if (!isSearching) enterSearch() else exitSearch() }\n      }\n\n      with(progressMainSearchView) {\n        hint = getString(R.string.textSearchFor)\n        settingsIconVisible = true\n        traktIconVisible = true\n        isClickable = false\n        onClick { openMainSearch() }\n        onSettingsClickListener = { openSettings() }\n        onTraktClickListener = { openTraktSync() }\n      }\n\n      with(progressMainSearchLocalView) {\n        onCloseClickListener = { exitSearch() }\n      }\n\n      with(progressMainPagerModeTabs) {\n        visibleIf(moviesEnabled)\n        onModeSelected = { mode = it }\n        selectShows()\n      }\n\n      progressMainTabs.translationY = tabsTranslation\n      progressMainPagerModeTabs.translationY = tabsTranslation\n      progressMainSearchView.translationY = searchViewTranslation\n      progressMainSideIcons.translationY = sideIconTranslation\n    }\n  }\n\n  private fun setupPager() {\n    adapter = ProgressMainAdapter(childFragmentManager, requireContext())\n    with(binding) {\n      progressMainPager.run {\n        adapter = this@ProgressMainFragment.adapter\n        offscreenPageLimit = ProgressMainAdapter.PAGES_COUNT\n        addOnPageChangeListener(pageChangeListener)\n      }\n      progressMainTabs.setupWithViewPager(progressMainPager)\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      progressMainRoot.doOnApplyWindowInsets { _, insets, _, _ ->\n        val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n        val statusBarSize = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n        val progressTabsMargin = if (moviesEnabled) R.dimen.progressSearchViewPadding else R.dimen.progressSearchViewPaddingNoModes\n        val progressMainSearchLocalMargin =\n          if (moviesEnabled) R.dimen.progressSearchLocalViewPadding else R.dimen.progressSearchLocalViewPaddingNoModes\n        (progressMainSearchView.layoutParams as ViewGroup.MarginLayoutParams)\n          .updateMargins(top = statusBarSize + dimenToPx(R.dimen.spaceMedium))\n        (progressMainSearchLocalView.layoutParams as ViewGroup.MarginLayoutParams)\n          .updateMargins(top = statusBarSize + dimenToPx(progressMainSearchLocalMargin))\n        (progressMainPagerModeTabs.layoutParams as ViewGroup.MarginLayoutParams)\n          .updateMargins(top = statusBarSize + dimenToPx(R.dimen.collectionTabsMargin))\n        arrayOf(progressMainTabs, progressMainSideIcons).forEach {\n          val margin = statusBarSize + dimenToPx(progressTabsMargin)\n          (it.layoutParams as ViewGroup.MarginLayoutParams).updateMargins(top = margin)\n        }\n      }\n    }\n  }\n\n  override fun setupBackPressed() {\n    val dispatcher = requireActivity().onBackPressedDispatcher\n    dispatcher.addCallback(viewLifecycleOwner) {\n      if (isSearching) {\n        exitSearch()\n      } else {\n        isEnabled = false\n        activity?.onBackPressed()\n      }\n    }\n  }\n\n  private fun openMainSearch() {\n    with(binding) {\n      disableUi()\n      hideNavigation()\n      progressMainPagerModeTabs.fadeOut(duration = 200).add(animations)\n      progressMainTabs.fadeOut(duration = 200).add(animations)\n      progressMainSideIcons.fadeOut(duration = 200).add(animations)\n      progressMainPager.fadeOut(duration = 200) {\n        navigateToSafe(R.id.actionProgressFragmentToSearch)\n      }.add(animations)\n    }\n  }\n\n  fun openTraktSync() {\n    hideNavigation()\n    exitSearch()\n    navigateToSafe(R.id.actionProgressFragmentToTraktSyncFragment)\n  }\n\n  fun openShowDetails(show: Show) {\n    with(binding) {\n      hideNavigation()\n      progressMainRoot.fadeOut(150) {\n        if (findNavControl()?.currentDestination?.id == R.id.progressMainFragment) {\n          val bundle = Bundle().apply { putLong(ARG_SHOW_ID, show.traktId) }\n          navigateToSafe(R.id.actionProgressFragmentToShowDetailsFragment, bundle)\n          exitSearch()\n        } else {\n          showNavigation()\n          progressMainRoot.fadeIn(50).add(animations)\n        }\n      }.add(animations)\n    }\n  }\n\n  fun openShowMenu(show: Show) {\n    setFragmentResultListener(REQUEST_ITEM_MENU) { requestKey, _ ->\n      if (requestKey == REQUEST_ITEM_MENU) {\n        viewModel.loadProgress()\n      }\n      clearFragmentResultListener(REQUEST_ITEM_MENU)\n    }\n    val bundle = ContextMenuBottomSheet.createBundle(show.ids.trakt, showPinButtons = true)\n    navigateToSafe(R.id.actionProgressFragmentToItemMenu, bundle)\n  }\n\n  fun openEpisodeDetails(\n    show: Show,\n    episode: Episode,\n    season: Season,\n  ) {\n    setFragmentResultListener(REQUEST_EPISODE_DETAILS) { _, bundle ->\n      when {\n        bundle.containsKey(ACTION_EPISODE_TAB_SELECTED) -> {\n          val selectedEpisode = bundle.requireParcelable<Episode>(ACTION_EPISODE_TAB_SELECTED)\n          openEpisodeDetails(show, selectedEpisode, season)\n        }\n      }\n    }\n    viewModel.onEpisodeDetails(show, episode)\n  }\n\n  fun openRateDialog(episodeBundle: EpisodeBundle) {\n    setFragmentResultListener(NavigationArgs.REQUEST_RATING) { _, bundle ->\n      when (bundle.getParcelable<Operation>(NavigationArgs.RESULT)) {\n        Operation.SAVE -> showSnack(MessageEvent.Info(R.string.textRateSaved))\n        Operation.REMOVE -> showSnack(MessageEvent.Info(R.string.textRateRemoved))\n        else -> Timber.w(\"Unknown result\")\n      }\n      viewModel.setWatchedEpisode(episodeBundle)\n    }\n    val bundle = RatingsBottomSheet.createBundle(episodeBundle.episode.ids.trakt, Type.EPISODE)\n    navigateToSafe(R.id.actionProgressFragmentToRating, bundle)\n  }\n\n  private fun openSettings() {\n    hideNavigation()\n    exitSearch()\n    navigateToSafe(R.id.actionProgressFragmentToSettingsFragment)\n  }\n\n  private fun enterSearch() {\n    resetTranslations()\n    with(binding) {\n      progressMainSearchLocalView.fadeIn(150)\n      with(progressMainSearchLocalView.binding.searchViewLocalInput) {\n        setText(\"\")\n        doAfterTextChanged { viewModel.onSearchQuery(it?.toString()) }\n        visible()\n        showKeyboard()\n        requestFocus()\n      }\n    }\n    isSearching = true\n    childFragmentManager.fragments.forEach { (it as? OnSearchClickListener)?.onEnterSearch() }\n  }\n\n  private fun exitSearch() {\n    isSearching = false\n    childFragmentManager.fragments.forEach { (it as? OnSearchClickListener)?.onExitSearch() }\n    resetTranslations()\n    with(binding) {\n      progressMainSearchLocalView.gone()\n      with(progressMainSearchLocalView.binding.searchViewLocalInput) {\n        setText(\"\")\n        gone()\n        hideKeyboard()\n        clearFocus()\n      }\n    }\n  }\n\n  fun toggleCalendarMode() {\n    exitSearch()\n    onScrollReset()\n    resetTranslations()\n    viewModel.toggleCalendarMode()\n  }\n\n  override fun onShowsMoviesSyncFinished() = viewModel.loadProgress()\n\n  override fun onTabReselected() {\n    if (view == null) return\n    resetTranslations(duration = 0)\n    binding.progressMainPager.nextPage()\n    onScrollReset()\n  }\n\n  fun resetTranslations(duration: Long = TRANSLATION_DURATION) {\n    if (view == null) return\n    with(binding) {\n      arrayOf(\n        progressMainSearchView,\n        progressMainTabs,\n        progressMainPagerModeTabs,\n        progressMainSideIcons,\n        progressMainSearchLocalView\n      ).forEach {\n        it.animate().translationY(0F).setDuration(duration).add(animations)?.start()\n      }\n    }\n  }\n\n  private fun onScrollReset() =\n    childFragmentManager.fragments.forEach { (it as? OnScrollResetListener)?.onScrollReset() }\n\n  private fun render(uiState: ProgressMainUiState) {\n    with(binding) {\n      progressMainSearchView.setTraktProgress(uiState.isSyncing, withIcon = true)\n      progressMainSearchView.isEnabled = !uiState.isSyncing\n      when (uiState.calendarMode) {\n        CalendarMode.PRESENT_FUTURE -> progressMainCalendarIcon.setImageResource(R.drawable.ic_history)\n        CalendarMode.RECENTS -> progressMainCalendarIcon.setImageResource(R.drawable.ic_calendar)\n        else -> Unit\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is OpenEpisodeDetails -> {\n        val bundle = EpisodeDetailsBottomSheet.createBundle(\n          ids = event.show.ids,\n          episode = event.episode,\n          seasonEpisodesIds = null,\n          isWatched = event.isWatched,\n          showButton = false,\n          showTabs = true\n        )\n        navigateToSafe(R.id.actionProgressFragmentToEpisodeDetails, bundle)\n      }\n    }\n  }\n\n  private val pageChangeListener = object : ViewPager.OnPageChangeListener {\n    override fun onPageSelected(position: Int) {\n      if (currentPage == position) return\n\n      binding.progressMainCalendarIcon.fadeIf(position == 1, duration = 150)\n      if (binding.progressMainTabs.translationY != 0F) {\n        resetTranslations()\n        requireView().postDelayed({ onScrollReset() }, TRANSLATION_DURATION)\n      }\n\n      currentPage = position\n    }\n\n    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) = Unit\n    override fun onPageScrollStateChanged(state: Int) = Unit\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/main/ProgressMainUiEvents.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_progress.main\n\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.EpisodeBundle\nimport com.michaldrabik.ui_model.Show\n\ndata class EpisodeCheckActionUiEvent(\n  val episode: EpisodeBundle,\n  val isQuickRate: Boolean,\n) : Event<EpisodeBundle>(episode)\n\ndata class OpenEpisodeDetails(\n  val show: Show,\n  val episode: Episode,\n  val isWatched: Boolean\n) : Event<Episode>(episode)\n\nobject RequestWidgetsUpdate : Event<Unit>(Unit)\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/main/ProgressMainUiState.kt",
    "content": "package com.michaldrabik.ui_progress.main\n\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.CalendarMode\n\ndata class ProgressMainUiState(\n  val timestamp: Long? = null,\n  val searchQuery: String? = null,\n  val calendarMode: CalendarMode? = null,\n  val resetScroll: Event<Boolean>? = null,\n  val isSyncing: Boolean = false,\n)\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/main/ProgressMainViewModel.kt",
    "content": "package com.michaldrabik.ui_progress.main\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkInfo\nimport androidx.work.WorkManager\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.TraktSyncAuthError\nimport com.michaldrabik.ui_base.events.TraktSyncError\nimport com.michaldrabik.ui_base.events.TraktSyncSuccess\nimport com.michaldrabik.ui_base.trakt.TraktSyncWorker\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.EpisodeBundle\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_progress.R\nimport com.michaldrabik.ui_progress.main.cases.ProgressMainEpisodesCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\nimport com.michaldrabik.ui_base.events.Event as EventSync\n\n@HiltViewModel\nclass ProgressMainViewModel @Inject constructor(\n  private val episodesCase: ProgressMainEpisodesCase,\n  private val eventsManager: EventsManager,\n  workManager: WorkManager,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val timestampState = MutableStateFlow<Long?>(null)\n  private val searchQueryState = MutableStateFlow<String?>(null)\n  private val calendarModeState = MutableStateFlow<CalendarMode?>(null)\n  private val scrollState = MutableStateFlow<Event<Boolean>?>(null)\n  private val syncingState = MutableStateFlow(false)\n\n  private var calendarMode = CalendarMode.PRESENT_FUTURE\n\n  init {\n    viewModelScope.launch {\n      eventsManager.events.collect { onEvent(it) }\n    }\n    workManager.getWorkInfosByTagLiveData(TraktSyncWorker.TAG_ID).observeForever { work ->\n      syncingState.value = work.any { it.state == WorkInfo.State.RUNNING }\n    }\n  }\n\n  fun loadProgress() {\n    viewModelScope.launch {\n      timestampState.value = System.currentTimeMillis()\n      calendarModeState.value = calendarMode\n    }\n  }\n\n  fun onSearchQuery(searchQuery: String?) {\n    searchQueryState.value = searchQuery ?: \"\"\n  }\n\n  fun onEpisodeDetails(show: Show, episode: Episode) {\n    viewModelScope.launch {\n      val isWatched = episodesCase.isWatched(show, episode)\n      eventChannel.send(\n        OpenEpisodeDetails(\n          show = show,\n          episode = episode,\n          isWatched = isWatched\n        )\n      )\n    }\n  }\n\n  fun toggleCalendarMode() {\n    calendarMode = when (calendarMode) {\n      CalendarMode.PRESENT_FUTURE -> CalendarMode.RECENTS\n      CalendarMode.RECENTS -> CalendarMode.PRESENT_FUTURE\n    }\n    calendarModeState.value = calendarMode\n  }\n\n  fun setWatchedEpisode(bundle: EpisodeBundle) {\n    viewModelScope.launch {\n      if (!bundle.episode.hasAired(bundle.season)) {\n        messageChannel.send(MessageEvent.Info(R.string.errorEpisodeNotAired))\n        return@launch\n      }\n      episodesCase.setEpisodeWatched(bundle)\n      timestampState.value = System.currentTimeMillis()\n      scrollState.value = Event(false)\n    }\n  }\n\n  private fun onEvent(event: EventSync) {\n    if (event in arrayOf(TraktSyncError, TraktSyncAuthError, TraktSyncSuccess)) {\n      loadProgress()\n    }\n  }\n\n  val uiState = combine(\n    timestampState,\n    searchQueryState,\n    calendarModeState,\n    scrollState,\n    syncingState\n  ) { s1, s2, s3, s4, s5 ->\n    ProgressMainUiState(\n      timestamp = s1,\n      searchQuery = s2,\n      calendarMode = s3,\n      resetScroll = s4,\n      isSyncing = s5\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ProgressMainUiState()\n  )\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/main/adapters/ProgressMainAdapter.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.ui_progress.main.adapters\n\nimport android.content.Context\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentManager\nimport androidx.fragment.app.FragmentPagerAdapter\nimport com.michaldrabik.ui_progress.R\nimport com.michaldrabik.ui_progress.calendar.CalendarFragment\nimport com.michaldrabik.ui_progress.progress.ProgressFragment\n\nclass ProgressMainAdapter(\n  fragManager: FragmentManager,\n  private val context: Context,\n) : FragmentPagerAdapter(fragManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {\n\n  companion object {\n    const val PAGES_COUNT = 2\n  }\n\n  override fun getItem(position: Int): Fragment = when (position) {\n    0 -> ProgressFragment()\n    1 -> CalendarFragment()\n    else -> throw IllegalStateException(\"Unknown position\")\n  }\n\n  override fun getCount() = PAGES_COUNT\n\n  override fun getPageTitle(position: Int) =\n    when (position) {\n      0 -> context.getString(R.string.tabProgress)\n      1 -> context.getString(R.string.tabCalendar)\n      else -> throw IllegalStateException()\n    }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/main/cases/ProgressMainEpisodesCase.kt",
    "content": "package com.michaldrabik.ui_progress.main.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.sources.EpisodesLocalDataSource\nimport com.michaldrabik.repository.EpisodesManager\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.EpisodeBundle\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ProgressMainEpisodesCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val episodesManager: EpisodesManager,\n  private val quickSyncManager: QuickSyncManager,\n  private val spoilersSettings: SettingsSpoilersRepository,\n  private val localDataSource: EpisodesLocalDataSource\n) {\n\n  suspend fun setEpisodeWatched(bundle: EpisodeBundle) {\n    episodesManager.setEpisodeWatched(bundle)\n    quickSyncManager.scheduleEpisodes(\n      showId = bundle.show.traktId,\n      episodesIds = listOf(bundle.episode.ids.trakt.id)\n    )\n  }\n\n  suspend fun isWatched(\n    show: Show,\n    episode: Episode\n  ): Boolean {\n    return withContext(dispatchers.IO) {\n      // No need to query DB if spoilers settings are all off in that case.\n      if (!(\n        spoilersSettings.isEpisodesTitleHidden ||\n          spoilersSettings.isEpisodesDescriptionHidden ||\n          spoilersSettings.isEpisodesImageHidden ||\n          spoilersSettings.isEpisodesRatingHidden\n        )\n      ) {\n        return@withContext false\n      }\n      return@withContext localDataSource.isEpisodeWatched(show.traktId, episode.ids.trakt.id)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/progress/ProgressFragment.kt",
    "content": "package com.michaldrabik.ui_progress.progress\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateMargins\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.OnScrollResetListener\nimport com.michaldrabik.ui_base.common.OnSearchClickListener\nimport com.michaldrabik.ui_base.common.WidgetsProvider\nimport com.michaldrabik.ui_base.common.sheets.sort_order.SortOrderBottomSheet\nimport com.michaldrabik.ui_base.utilities.NavigationHost\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.add\nimport com.michaldrabik.ui_base.utilities.extensions.bump\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withSpanSizeLookup\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.EPISODES_LEFT\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.RECENTLY_WATCHED\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.Tip\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_NEW_AT_TOP\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_SORT_ORDER\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_SORT_TYPE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_SORT_ORDER\nimport com.michaldrabik.ui_progress.R\nimport com.michaldrabik.ui_progress.databinding.FragmentProgressBinding\nimport com.michaldrabik.ui_progress.helpers.ProgressLayoutManagerProvider\nimport com.michaldrabik.ui_progress.helpers.TopOverscrollAdapter\nimport com.michaldrabik.ui_progress.main.EpisodeCheckActionUiEvent\nimport com.michaldrabik.ui_progress.main.ProgressMainFragment\nimport com.michaldrabik.ui_progress.main.ProgressMainViewModel\nimport com.michaldrabik.ui_progress.main.RequestWidgetsUpdate\nimport com.michaldrabik.ui_progress.progress.recycler.ProgressAdapter\nimport com.michaldrabik.ui_progress.progress.recycler.ProgressListItem\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport me.everything.android.ui.overscroll.IOverScrollDecor\nimport me.everything.android.ui.overscroll.IOverScrollState.STATE_BOUNCE_BACK\nimport me.everything.android.ui.overscroll.IOverScrollState.STATE_DRAG_START_SIDE\nimport me.everything.android.ui.overscroll.OverScrollBounceEffectDecoratorBase\nimport me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ProgressFragment :\n  BaseFragment<ProgressViewModel>(R.layout.fragment_progress),\n  OnSearchClickListener,\n  OnScrollResetListener {\n\n  private companion object {\n    const val OVERSCROLL_OFFSET = 225F\n    const val OVERSCROLL_OFFSET_TRANSLATION = 4.5F\n  }\n\n  @Inject lateinit var settings: SettingsViewModeRepository\n\n  override val navigationId = R.id.progressMainFragment\n  private val binding by viewBinding(FragmentProgressBinding::bind)\n\n  private val parentViewModel by viewModels<ProgressMainViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<ProgressViewModel>()\n\n  private var adapter: ProgressAdapter? = null\n  private var layoutManager: LayoutManager? = null\n  private var overscroll: IOverScrollDecor? = null\n  private var overscrollJob: Job? = null\n  private var statusBarHeight = 0\n  private var overscrollEnabled = true\n  private var isSearching = false\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupRecycler()\n    setupStatusBar()\n\n    viewLifecycleOwner.lifecycleScope.launch {\n      viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {\n        with(parentViewModel) {\n          launch { uiState.collect { viewModel.onParentState(it) } }\n        }\n        with(viewModel) {\n          launch { uiState.collect { render(it) } }\n          launch { messageFlow.collect { showSnack(it) } }\n          launch { eventFlow.collect { handleEvent(it) } }\n        }\n      }\n    }\n  }\n\n  private fun setupView() {\n    with(binding) {\n      progressEmptyView.progressEmptyTraktButton.onClick { requireMainFragment().openTraktSync() }\n      progressEmptyView.progressEmptyDiscoverButton.onClick {\n        (requireActivity() as NavigationHost).navigateToDiscover()\n      }\n      progressTipItem.onClick {\n        it.gone()\n        showTip(Tip.WATCHLIST_ITEM_PIN)\n      }\n    }\n  }\n\n  private fun setupRecycler() {\n    val gridSpanSize = settings.tabletGridSpanSize\n    layoutManager = ProgressLayoutManagerProvider.provideLayoutManger(requireContext(), gridSpanSize)\n    (layoutManager as? GridLayoutManager)?.run {\n      withSpanSizeLookup { position ->\n        when (adapter?.getItems()?.get(position)) {\n          is ProgressListItem.Header -> gridSpanSize\n          is ProgressListItem.Filters -> gridSpanSize\n          is ProgressListItem.Episode -> 1\n          else -> throw IllegalStateException()\n        }\n      }\n    }\n    adapter = ProgressAdapter(\n      itemClickListener = { requireMainFragment().openShowDetails(it.show) },\n      itemLongClickListener = { requireMainFragment().openShowMenu(it.show) },\n      headerClickListener = { viewModel.toggleHeaderCollapsed(it.type) },\n      detailsClickListener = {\n        requireMainFragment().openEpisodeDetails(\n          show = it.show,\n          episode = it.requireEpisode(),\n          season = it.requireSeason()\n        )\n      },\n      checkClickListener = viewModel::onEpisodeChecked,\n      sortChipClickListener = viewModel::loadSortOrder,\n      upcomingChipClickListener = viewModel::setUpcomingFilter,\n      onHoldChipClickListener = viewModel::setOnHoldFilter,\n      missingTranslationListener = viewModel::findMissingTranslation,\n      missingImageListener = { item: ProgressListItem, force -> viewModel.findMissingImage(item, force) },\n      listChangeListener = {\n        requireMainFragment().resetTranslations()\n        layoutManager?.scrollToPosition(0)\n      }\n    )\n    binding.progressRecycler.apply {\n      adapter = this@ProgressFragment.adapter\n      layoutManager = this@ProgressFragment.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      setHasFixedSize(true)\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      val recyclerPadding = if (moviesEnabled) R.dimen.progressTabsViewPadding else R.dimen.progressTabsViewPaddingNoModes\n      val overscrollPadding = if (moviesEnabled) R.dimen.progressOverscrollPadding else R.dimen.progressOverscrollPaddingNoModes\n      if (statusBarHeight != 0) {\n        progressRecycler.updatePadding(top = statusBarHeight + dimenToPx(recyclerPadding))\n        (progressOverscroll.layoutParams as ViewGroup.MarginLayoutParams)\n          .updateMargins(top = statusBarHeight + dimenToPx(overscrollPadding))\n        return\n      }\n      progressRecycler.doOnApplyWindowInsets { view, insets, _, _ ->\n        val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n        statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n        view.updatePadding(top = statusBarHeight + dimenToPx(recyclerPadding))\n        (progressEmptyView.root.layoutParams as ViewGroup.MarginLayoutParams)\n          .updateMargins(top = statusBarHeight + dimenToPx(R.dimen.spaceBig))\n        (progressOverscroll.layoutParams as ViewGroup.MarginLayoutParams)\n          .updateMargins(top = statusBarHeight + dimenToPx(overscrollPadding))\n      }\n    }\n  }\n\n  private fun setupOverscroll() {\n    if (overscroll != null || view == null) {\n      return\n    }\n    val adapt = TopOverscrollAdapter(binding.progressRecycler)\n    overscroll = VerticalOverScrollBounceEffectDecorator(\n      adapt,\n      1F,\n      OverScrollBounceEffectDecoratorBase.DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK,\n      OverScrollBounceEffectDecoratorBase.DEFAULT_DECELERATE_FACTOR\n    ).apply {\n      setOverScrollUpdateListener { _, state, offset ->\n        binding.progressOverscroll.run {\n          if (offset > 0) {\n            val value = (offset / OVERSCROLL_OFFSET).coerceAtMost(1F)\n            val valueTranslation = offset / OVERSCROLL_OFFSET_TRANSLATION\n            if (value >= 1F) {\n              onOverscrollReach()\n            } else {\n              onOverscrollCancel()\n            }\n            when (state) {\n              STATE_DRAG_START_SIDE -> {\n                alpha = value\n                scaleX = value\n                scaleY = value\n                translationY = valueTranslation\n                overscrollEnabled = true\n              }\n              STATE_BOUNCE_BACK -> {\n                alpha = value\n                scaleX = value\n                scaleY = value\n                translationY = valueTranslation\n                if (offset >= OVERSCROLL_OFFSET &&\n                  overscrollEnabled &&\n                  binding.progressOverscrollProgress.progress >= 100\n                ) {\n                  overscrollEnabled = false\n                  viewModel.startTraktSync()\n                }\n              }\n            }\n          } else {\n            alpha = 0F\n            scaleX = 0F\n            scaleY = 0F\n            translationY = 0F\n            onOverscrollCancel()\n          }\n        }\n      }\n    }\n  }\n\n  private fun onOverscrollReach() {\n    if (overscrollJob != null) return\n    overscrollJob = viewLifecycleOwner.lifecycleScope.launch {\n      repeat(100) {\n        val progress = it + 1\n        binding.progressOverscrollProgress.progress = progress\n        if (progress >= 100) {\n          binding.progressOverscroll.bump(200)\n        }\n        delay(5)\n      }\n    }\n  }\n\n  private fun onOverscrollCancel() {\n    overscrollJob?.cancel()\n    overscrollJob = null\n    binding.progressOverscrollProgress.progress = 0\n  }\n\n  private fun openSortOrderDialog(order: SortOrder, type: SortType, newAtTop: Boolean) {\n    val options = listOf(NAME, RATING, USER_RATING, NEWEST, RECENTLY_WATCHED, EPISODES_LEFT)\n    val args = SortOrderBottomSheet.createBundle(options, order, type, newAtTop = Pair(true, newAtTop))\n\n    requireParentFragment().setFragmentResultListener(REQUEST_SORT_ORDER) { _, bundle ->\n      val sortOrder = bundle.getSerializable(ARG_SELECTED_SORT_ORDER) as SortOrder\n      val sortType = bundle.getSerializable(ARG_SELECTED_SORT_TYPE) as SortType\n      val newTop = bundle.getBoolean(ARG_SELECTED_NEW_AT_TOP)\n      viewModel.setSortOrder(sortOrder, sortType, newTop)\n    }\n\n    navigateToSafe(R.id.actionProgressFragmentToSortOrder, args)\n  }\n\n  override fun onEnterSearch() {\n    isSearching = true\n\n    with(binding) {\n      progressRecycler.translationY = dimenToPx(R.dimen.progressSearchLocalOffset).toFloat()\n      progressRecycler.smoothScrollToPosition(0)\n    }\n\n    overscroll?.detach()\n    overscroll = null\n  }\n\n  override fun onExitSearch() {\n    isSearching = false\n\n    with(binding) {\n      progressRecycler.translationY = 0F\n      progressRecycler.smoothScrollToPosition(0)\n    }\n\n    setupOverscroll()\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is EpisodeCheckActionUiEvent -> {\n        if (event.isQuickRate) requireMainFragment().openRateDialog(event.episode)\n        else parentViewModel.setWatchedEpisode(event.episode)\n      }\n      is RequestWidgetsUpdate -> {\n        (requireAppContext() as WidgetsProvider).requestShowsWidgetsUpdate()\n      }\n    }\n  }\n\n  private fun render(uiState: ProgressUiState) {\n    uiState.run {\n      with(binding) {\n        items?.let {\n          val resetScroll = scrollReset?.consume() == true\n          adapter?.setItems(it, resetScroll)\n          progressEmptyView.root.visibleIf(it.isEmpty() && !isLoading && !isSearching)\n          progressTipItem.visibleIf(it.count() >= 3 && !isTipShown(Tip.WATCHLIST_ITEM_PIN))\n          progressRecycler.fadeIn(\n            duration = 200,\n            withHardware = true\n          ).add(animations)\n        }\n      }\n      isOverScrollEnabled.let {\n        if (it) {\n          setupOverscroll()\n        } else {\n          overscroll?.detach()\n          overscroll = null\n        }\n      }\n      sortOrder?.let { event ->\n        event.consume()?.let {\n          openSortOrderDialog(it.first, it.second, it.third)\n        }\n      }\n    }\n  }\n\n  override fun onScrollReset() {\n    binding.progressRecycler.smoothScrollToPosition(0)\n  }\n\n  private fun requireMainFragment() = requireParentFragment() as ProgressMainFragment\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    overscrollJob?.cancel()\n    overscrollJob = null\n    overscroll = null\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/progress/ProgressUiState.kt",
    "content": "package com.michaldrabik.ui_progress.progress\n\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_progress.progress.recycler.ProgressListItem\n\ndata class ProgressUiState(\n  val items: List<ProgressListItem>? = null,\n  val isLoading: Boolean = false,\n  val isOverScrollEnabled: Boolean = false,\n  val scrollReset: Event<Boolean>? = null,\n  val sortOrder: Event<Triple<SortOrder, SortType, Boolean>>? = null,\n)\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/progress/ProgressViewModel.kt",
    "content": "package com.michaldrabik.ui_progress.progress\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkManager\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.ui_base.trakt.TraktSyncWorker\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.EpisodeBundle\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_progress.main.EpisodeCheckActionUiEvent\nimport com.michaldrabik.ui_progress.main.ProgressMainUiState\nimport com.michaldrabik.ui_progress.main.RequestWidgetsUpdate\nimport com.michaldrabik.ui_progress.progress.cases.ProgressFiltersCase\nimport com.michaldrabik.ui_progress.progress.cases.ProgressHeadersCase\nimport com.michaldrabik.ui_progress.progress.cases.ProgressItemsCase\nimport com.michaldrabik.ui_progress.progress.cases.ProgressRatingsCase\nimport com.michaldrabik.ui_progress.progress.cases.ProgressSortOrderCase\nimport com.michaldrabik.ui_progress.progress.recycler.ProgressListItem\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ProgressViewModel @Inject constructor(\n  private val itemsCase: ProgressItemsCase,\n  private val headersCase: ProgressHeadersCase,\n  private val sortOrderCase: ProgressSortOrderCase,\n  private val filtersCase: ProgressFiltersCase,\n  private val ratingsCase: ProgressRatingsCase,\n  private val imagesProvider: ShowImagesProvider,\n  private val userTraktManager: UserTraktManager,\n  private val workManager: WorkManager,\n  private val translationsRepository: TranslationsRepository,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private var loadItemsJob: Job? = null\n\n  private val itemsState = MutableStateFlow<List<ProgressListItem>?>(null)\n  private val loadingState = MutableStateFlow(false)\n  private val overscrollState = MutableStateFlow(false)\n  private val scrollState = MutableStateFlow(Event(false))\n  private val sortOrderState = MutableStateFlow<Event<Triple<SortOrder, SortType, Boolean>>?>(null)\n\n  private var searchQuery: String? = null\n  private var timestamp = 0L\n\n  fun onParentState(state: ProgressMainUiState) {\n    when {\n      this.timestamp != state.timestamp && state.timestamp != 0L -> {\n        this.timestamp = state.timestamp ?: 0L\n        loadItems(resetScroll = state.resetScroll?.consume() == true)\n      }\n      this.searchQuery != state.searchQuery -> {\n        this.searchQuery = state.searchQuery\n        loadItems(resetScroll = state.searchQuery.isNullOrBlank())\n      }\n    }\n  }\n\n  private fun loadItems(resetScroll: Boolean = false) {\n    loadItemsJob?.cancel()\n    loadItemsJob = viewModelScope.launch {\n      loadingState.value = true\n\n      val items = itemsCase.loadItems(searchQuery ?: \"\")\n      itemsState.value = items\n      loadingState.value = false\n      scrollState.value = Event(resetScroll)\n      overscrollState.value = userTraktManager.isAuthorized() && items.isNotEmpty()\n\n      eventChannel.send(RequestWidgetsUpdate)\n    }\n  }\n\n  fun loadSortOrder() {\n    if (itemsState.value?.isEmpty() == true) return\n    viewModelScope.launch {\n      val sortOrder = sortOrderCase.loadSortOrder()\n      sortOrderState.value = Event(sortOrder)\n    }\n  }\n\n  fun onEpisodeChecked(episode: ProgressListItem.Episode) {\n    viewModelScope.launch {\n      val bundle = EpisodeBundle(episode.requireEpisode(), episode.requireSeason(), episode.show)\n      val isQuickRate = ratingsCase.isQuickRateEnabled()\n      eventChannel.send(EpisodeCheckActionUiEvent(bundle, isQuickRate))\n    }\n  }\n\n  fun findMissingImage(item: ProgressListItem, force: Boolean) {\n    check(item is ProgressListItem.Episode)\n    viewModelScope.launch {\n      updateItem(item.copy(isLoading = true))\n      try {\n        val image = imagesProvider.loadRemoteImage(item.show, item.image.type, force)\n        updateItem(item.copy(image = image, isLoading = false))\n      } catch (t: Throwable) {\n        val unavailable = Image.createUnavailable(item.image.type)\n        updateItem(item.copy(image = unavailable, isLoading = false))\n      }\n    }\n  }\n\n  fun findMissingTranslation(item: ProgressListItem) {\n    check(item is ProgressListItem.Episode)\n    val language = translationsRepository.getLanguage()\n    if (item.translations?.show != null || language == Config.DEFAULT_LANGUAGE) return\n    viewModelScope.launch {\n      try {\n        val translation = translationsRepository.loadTranslation(item.show, language)\n        val translations = item.translations?.copy(show = translation)\n        updateItem(item.copy(translations = translations))\n      } catch (error: Throwable) {\n        Timber.e(error)\n      }\n    }\n  }\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType, newAtTop: Boolean) {\n    sortOrderCase.setSortOrder(sortOrder, sortType, newAtTop)\n    loadItems(resetScroll = true)\n  }\n\n  fun setUpcomingFilter(isEnabled: Boolean) {\n    filtersCase.setUpcomingFilter(isEnabled)\n    loadItems(resetScroll = true)\n  }\n\n  fun setOnHoldFilter(isEnabled: Boolean) {\n    filtersCase.setOnHoldFilter(isEnabled)\n    loadItems(resetScroll = true)\n  }\n\n  fun toggleHeaderCollapsed(headerType: ProgressListItem.Header.Type) {\n    headersCase.toggleHeaderCollapsed(headerType)\n    loadItems()\n  }\n\n  fun startTraktSync() {\n    TraktSyncWorker.scheduleOneOff(\n      workManager,\n      isImport = true,\n      isExport = true,\n      isSilent = false\n    )\n  }\n\n  private fun updateItem(new: ProgressListItem) {\n    val currentItems = itemsState.value?.toMutableList() ?: mutableListOf()\n    currentItems.findReplace(new) { it.isSameAs(new) }\n    itemsState.value = currentItems\n    scrollState.value = Event(false)\n  }\n\n  val uiState = combine(\n    itemsState,\n    scrollState,\n    sortOrderState,\n    loadingState,\n    overscrollState\n  ) { s1, s2, s3, s4, s5 ->\n    ProgressUiState(\n      items = s1,\n      scrollReset = s2,\n      sortOrder = s3,\n      isLoading = s4,\n      isOverScrollEnabled = s5\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ProgressUiState()\n  )\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/progress/cases/ProgressFiltersCase.kt",
    "content": "package com.michaldrabik.ui_progress.progress.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ProgressFiltersCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setUpcomingFilter(isEnabled: Boolean) {\n    settingsRepository.filters.progressShowsUpcoming = isEnabled\n    if (isEnabled) {\n      settingsRepository.filters.progressShowsOnHold = false\n    }\n  }\n\n  fun setOnHoldFilter(isEnabled: Boolean) {\n    settingsRepository.filters.progressShowsOnHold = isEnabled\n    if (isEnabled) {\n      settingsRepository.filters.progressShowsUpcoming = false\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/progress/cases/ProgressHeadersCase.kt",
    "content": "package com.michaldrabik.ui_progress.progress.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_progress.progress.recycler.ProgressListItem.Header.Type\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ProgressHeadersCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun toggleHeaderCollapsed(type: Type) {\n    when (type) {\n      Type.UPCOMING -> {\n        val isCollapsed = settingsRepository.isProgressUpcomingCollapsed\n        settingsRepository.isProgressUpcomingCollapsed = !isCollapsed\n      }\n      Type.ON_HOLD -> {\n        val isCollapsed = settingsRepository.isProgressOnHoldCollapsed\n        settingsRepository.isProgressOnHoldCollapsed = !isCollapsed\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/progress/cases/ProgressItemsCase.kt",
    "content": "package com.michaldrabik.ui_progress.progress.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.Episode\nimport com.michaldrabik.repository.OnHoldItemsRepository\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.ProgressNextEpisodeType\nimport com.michaldrabik.ui_model.ProgressNextEpisodeType.LAST_WATCHED\nimport com.michaldrabik.ui_model.ProgressNextEpisodeType.OLDEST\nimport com.michaldrabik.ui_model.ProgressType\nimport com.michaldrabik.ui_progress.R\nimport com.michaldrabik.ui_progress.helpers.ProgressItemsSorter\nimport com.michaldrabik.ui_progress.helpers.TranslationsBundle\nimport com.michaldrabik.ui_progress.progress.recycler.ProgressListItem\nimport com.michaldrabik.ui_progress.progress.recycler.ProgressListItem.Header.Type\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\nimport javax.inject.Singleton\nimport com.michaldrabik.ui_model.Episode.Companion as EpisodeUi\n\n@Singleton\nclass ProgressItemsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n  private val showsRepository: ShowsRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val settingsRepository: SettingsRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val onHoldItemsRepository: OnHoldItemsRepository,\n  private val ratingsRepository: RatingsRepository,\n  private val imagesProvider: ShowImagesProvider,\n  private val dateFormatProvider: DateFormatProvider,\n  private val sorter: ProgressItemsSorter,\n) {\n\n  suspend fun loadItems(searchQuery: String): List<ProgressListItem> =\n    withContext(dispatchers.IO) {\n      val nowUtc = nowUtc()\n\n      val dateFormat = dateFormatProvider.loadFullHourFormat()\n      val language = translationsRepository.getLanguage()\n      val nextEpisodeType = settingsRepository.progressNextEpisodeType\n      val progressUpcomingDays = settingsRepository.progressUpcomingDays\n      val isUpcomingEnabled = progressUpcomingDays > 0\n      val upcomingLimit = nowUtc.plusDays(progressUpcomingDays).toMillis()\n      val filtersItem = loadFiltersItem(isUpcomingEnabled)\n      val spoilers = settingsRepository.spoilers.getAll()\n\n      val items = showsRepository.myShows.loadAll()\n        .map { show ->\n          async {\n            val nextEpisode = findNextEpisode(show.traktId, nextEpisodeType, upcomingLimit)\n\n            val episodeUi = nextEpisode?.let { mappers.episode.fromDatabase(it) }\n            val seasonUi = nextEpisode?.let { ep ->\n              localSource.seasons.getById(ep.idSeason)?.let {\n                mappers.season.fromDatabase(it)\n              }\n            }\n            val isUpcoming = nextEpisode?.firstAired?.isAfter(nowUtc) == true\n\n            ProgressListItem.Episode(\n              show = show,\n              image = Image.createUnavailable(ImageType.POSTER),\n              episode = episodeUi,\n              season = seasonUi,\n              totalCount = 0,\n              watchedCount = 0,\n              isWatched = nextEpisode?.isWatched == true,\n              isUpcoming = isUpcoming,\n              isPinned = false,\n              isOnHold = false,\n              spoilers = spoilers,\n              dateFormat = dateFormat,\n              sortOrder = filtersItem.sortOrder\n            )\n          }\n        }.awaitAll()\n\n      val validItems = items\n        .filter { if (isUpcomingEnabled) true else !it.isUpcoming }\n        .filter { it.episode?.firstAired != null }\n\n      val filledItems = validItems\n        .map {\n          async {\n            val image = imagesProvider.findCachedImage(it.show, ImageType.POSTER)\n            val rating = ratingsRepository.shows.loadRatings(listOf(it.show))\n            val isPinned = pinnedItemsRepository.isItemPinned(it.show)\n            val isOnHold = onHoldItemsRepository.isOnHold(it.show)\n\n            var translations: TranslationsBundle? = null\n            if (language != Config.DEFAULT_LANGUAGE) {\n              translations = TranslationsBundle(\n                show = translationsRepository.loadTranslation(it.show, language, onlyLocal = true),\n                episode = translationsRepository.loadTranslation(it.episode ?: EpisodeUi.EMPTY, it.show.ids.trakt, language, onlyLocal = true)\n              )\n            }\n\n            val (total, watched) = when (settingsRepository.progressPercentType) {\n              ProgressType.AIRED -> {\n                awaitAll(\n                  async { localSource.episodes.getTotalCount(it.show.traktId, nowUtc.toMillis()) },\n                  async { localSource.episodes.getWatchedCount(it.show.traktId, nowUtc.toMillis()) }\n                )\n              }\n\n              ProgressType.ALL -> {\n                awaitAll(\n                  async { localSource.episodes.getTotalCount(it.show.traktId) },\n                  async { localSource.episodes.getWatchedCount(it.show.traktId) }\n                )\n              }\n            }\n\n            it.copy(\n              image = image,\n              isPinned = isPinned,\n              isOnHold = isOnHold,\n              translations = translations,\n              userRating = rating.firstOrNull()?.rating,\n              watchedCount = watched,\n              totalCount = total\n            )\n          }\n        }.awaitAll()\n\n      val filteredItems = filterByQuery(searchQuery, filledItems)\n      val groupedItems = groupItems(\n        filteredItems,\n        filtersItem\n      )\n\n      if (groupedItems.isNotEmpty() || filtersItem.hasActiveFilters()) {\n        listOf(filtersItem) + groupedItems\n      } else {\n        groupedItems\n      }\n    }\n\n  private suspend fun findNextEpisode(\n    showId: Long,\n    nextEpisodeType: ProgressNextEpisodeType,\n    upcomingLimit: Long,\n  ): Episode? = when (nextEpisodeType) {\n    LAST_WATCHED -> {\n      when (val lastWatchedEpisode = localSource.episodes.getLastWatched(showId)) {\n        null -> localSource.episodes.getFirstUnwatched(showId, upcomingLimit)\n        else -> localSource.episodes.getFirstUnwatchedAfterEpisode(\n          showId,\n          lastWatchedEpisode.seasonNumber,\n          lastWatchedEpisode.episodeNumber,\n          upcomingLimit\n        )\n      }\n    }\n    OLDEST -> {\n      localSource.episodes.getFirstUnwatched(showId, upcomingLimit)\n    }\n  }\n\n  private fun filterByQuery(query: String, items: List<ProgressListItem.Episode>) =\n    items.filter {\n      it.show.title.contains(query, true) ||\n        it.episode?.title?.contains(query, true) == true ||\n        it.translations?.show?.title?.contains(query, true) == true ||\n        it.translations?.episode?.title?.contains(query, true) == true\n    }\n\n  private suspend fun groupItems(\n    input: List<ProgressListItem.Episode>,\n    filters: ProgressListItem.Filters\n  ): List<ProgressListItem> = coroutineScope {\n    val (newItems, pinnedItems, onHoldItems) = awaitAll(\n      async {\n        if (filters.newAtTop) {\n          input\n            .filter { it.isNew() && !it.isOnHold && !it.isPinned }\n            .sortedWith(sorter.sort(filters.sortOrder, filters.sortType))\n        } else {\n          emptyList()\n        }\n      },\n      async {\n        input\n          .filter { it.isPinned }\n          .sortedWith(\n            compareByDescending<ProgressListItem.Episode> { it.isNew() }\n              then sorter.sort(filters.sortOrder, filters.sortType)\n          )\n      },\n      async {\n        input\n          .filter { it.isOnHold }\n          .sortedWith(\n            compareByDescending<ProgressListItem.Episode> { it.isNew() }\n              then sorter.sort(filters.sortOrder, filters.sortType)\n          )\n      }\n    )\n\n    val groupedItems = (input - newItems.toSet() - pinnedItems.toSet() - onHoldItems.toSet())\n      .groupBy { !it.isUpcoming }\n\n    val (airedItems, upcomingItems) = awaitAll(\n      async {\n        ((groupedItems[true] ?: emptyList()))\n          .sortedWith(sorter.sort(filters.sortOrder, filters.sortType))\n      },\n      async {\n        ((groupedItems[false] ?: emptyList()))\n          .sortedBy { it.episode?.firstAired?.toMillis() }\n      }\n    )\n\n    mutableListOf<ProgressListItem>().apply {\n      if (pinnedItems.isNotEmpty() && !filters.hasActiveFilters()) {\n        addAll(pinnedItems)\n      }\n      if (newItems.isNotEmpty() && !filters.hasActiveFilters()) {\n        addAll(newItems)\n      }\n      if (airedItems.isNotEmpty() && !filters.hasActiveFilters()) {\n        addAll(airedItems)\n      }\n      if (upcomingItems.isNotEmpty() && (filters.isUpcoming || !filters.hasActiveFilters())) {\n        val isCollapsed = settingsRepository.isProgressUpcomingCollapsed\n        val upcomingHeader = ProgressListItem.Header.create(Type.UPCOMING, R.string.textWatchlistIncoming, isCollapsed)\n        addAll(listOf(upcomingHeader))\n        if (!isCollapsed) addAll(upcomingItems)\n      }\n      if (onHoldItems.isNotEmpty() && (filters.isOnHold || !filters.hasActiveFilters())) {\n        val isCollapsed = settingsRepository.isProgressOnHoldCollapsed\n        val onHoldHeader = ProgressListItem.Header.create(Type.ON_HOLD, R.string.textOnHold, isCollapsed)\n        addAll(listOf(onHoldHeader))\n        if (!isCollapsed) addAll(onHoldItems)\n      }\n    }\n  }\n\n  private fun loadFiltersItem(isUpcomingEnabled: Boolean): ProgressListItem.Filters {\n    return ProgressListItem.Filters(\n      newAtTop = settingsRepository.sorting.progressShowsNewAtTop,\n      sortOrder = settingsRepository.sorting.progressShowsSortOrder,\n      sortType = settingsRepository.sorting.progressShowsSortType,\n      isUpcoming = settingsRepository.filters.progressShowsUpcoming,\n      isUpcomingEnabled = isUpcomingEnabled,\n      isOnHold = settingsRepository.filters.progressShowsOnHold\n    )\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/progress/cases/ProgressRatingsCase.kt",
    "content": "package com.michaldrabik.ui_progress.progress.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ProgressRatingsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val userTraktManager: UserTraktManager,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun isQuickRateEnabled(): Boolean = withContext(dispatchers.IO) {\n    val isSignedIn = userTraktManager.isAuthorized()\n    val isPremium = settingsRepository.isPremium\n    val isQuickRate = settingsRepository.load().traktQuickRateEnabled\n    isPremium && isSignedIn && isQuickRate\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/progress/cases/ProgressSortOrderCase.kt",
    "content": "package com.michaldrabik.ui_progress.progress.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ProgressSortOrderCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType, newAtTop: Boolean) {\n    settingsRepository.sorting.progressShowsSortOrder = sortOrder\n    settingsRepository.sorting.progressShowsSortType = sortType\n    settingsRepository.sorting.progressShowsNewAtTop = newAtTop\n  }\n\n  fun loadSortOrder() = Triple(\n    settingsRepository.sorting.progressShowsSortOrder,\n    settingsRepository.sorting.progressShowsSortType,\n    settingsRepository.sorting.progressShowsNewAtTop\n  )\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/progress/recycler/ProgressAdapter.kt",
    "content": "package com.michaldrabik.ui_progress.progress.recycler\n\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseAdapter\nimport com.michaldrabik.ui_progress.progress.views.ProgressFiltersView\nimport com.michaldrabik.ui_progress.progress.views.ProgressHeaderView\nimport com.michaldrabik.ui_progress.progress.views.ProgressItemView\n\nclass ProgressAdapter(\n  private val itemClickListener: (ProgressListItem) -> Unit,\n  private val itemLongClickListener: (ProgressListItem) -> Unit,\n  private val sortChipClickListener: () -> Unit,\n  private val upcomingChipClickListener: (Boolean) -> Unit,\n  private val onHoldChipClickListener: (Boolean) -> Unit,\n  private val detailsClickListener: ((ProgressListItem.Episode) -> Unit)?,\n  private val checkClickListener: ((ProgressListItem.Episode) -> Unit)?,\n  private val headerClickListener: ((ProgressListItem.Header) -> Unit)?,\n  private val missingImageListener: (ProgressListItem, Boolean) -> Unit,\n  private val missingTranslationListener: (ProgressListItem) -> Unit,\n  listChangeListener: () -> Unit,\n) : BaseAdapter<ProgressListItem>(\n  listChangeListener = listChangeListener\n) {\n\n  companion object {\n    private const val VIEW_TYPE_ITEM = 1\n    private const val VIEW_TYPE_HEADER = 2\n    private const val VIEW_TYPE_FILTERS = 3\n  }\n\n  override val asyncDiffer = AsyncListDiffer(this, ProgressItemDiffCallback())\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    when (viewType) {\n      VIEW_TYPE_ITEM -> BaseViewHolder(\n        ProgressItemView(parent.context).apply {\n          itemClickListener = this@ProgressAdapter.itemClickListener\n          itemLongClickListener = this@ProgressAdapter.itemLongClickListener\n          missingImageListener = this@ProgressAdapter.missingImageListener\n          missingTranslationListener = this@ProgressAdapter.missingTranslationListener\n          checkClickListener = this@ProgressAdapter.checkClickListener\n          detailsClickListener = this@ProgressAdapter.detailsClickListener\n        }\n      )\n      VIEW_TYPE_HEADER -> BaseViewHolder(\n        ProgressHeaderView(parent.context).apply {\n          headerClickListener = this@ProgressAdapter.headerClickListener\n        }\n      )\n      VIEW_TYPE_FILTERS -> BaseViewHolder(\n        ProgressFiltersView(parent.context).apply {\n          onSortChipClicked = this@ProgressAdapter.sortChipClickListener\n          upcomingChipClicked = this@ProgressAdapter.upcomingChipClickListener\n          onHoldChipClicked = this@ProgressAdapter.onHoldChipClickListener\n        }\n      )\n      else -> throw IllegalStateException()\n    }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    when (val item = asyncDiffer.currentList[position]) {\n      is ProgressListItem.Episode -> (holder.itemView as ProgressItemView).bind(item)\n      is ProgressListItem.Header -> (holder.itemView as ProgressHeaderView).bind(item)\n      is ProgressListItem.Filters -> (holder.itemView as ProgressFiltersView).bind(item)\n    }\n  }\n\n  override fun getItemViewType(position: Int) =\n    when (asyncDiffer.currentList[position]) {\n      is ProgressListItem.Header -> VIEW_TYPE_HEADER\n      is ProgressListItem.Episode -> VIEW_TYPE_ITEM\n      is ProgressListItem.Filters -> VIEW_TYPE_FILTERS\n      else -> throw IllegalStateException()\n    }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/progress/recycler/ProgressItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_progress.progress.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass ProgressItemDiffCallback : DiffUtil.ItemCallback<ProgressListItem>() {\n\n  override fun areItemsTheSame(oldItem: ProgressListItem, newItem: ProgressListItem): Boolean {\n    val areEpisodes = oldItem is ProgressListItem.Episode && newItem is ProgressListItem.Episode\n    val areHeaders = oldItem is ProgressListItem.Header && newItem is ProgressListItem.Header\n    val areFilters = oldItem is ProgressListItem.Filters && newItem is ProgressListItem.Filters\n    return when {\n      areEpisodes -> areItemsTheSame(\n        (oldItem as ProgressListItem.Episode),\n        (newItem as ProgressListItem.Episode)\n      )\n      areHeaders -> areItemsTheSame(\n        (oldItem as ProgressListItem.Header),\n        (newItem as ProgressListItem.Header)\n      )\n      areFilters -> true\n      else -> false\n    }\n  }\n\n  override fun areContentsTheSame(oldItem: ProgressListItem, newItem: ProgressListItem) =\n    when (oldItem) {\n      is ProgressListItem.Episode -> areContentsTheSame(oldItem, (newItem as ProgressListItem.Episode))\n      is ProgressListItem.Header -> areContentsTheSame(oldItem, (newItem as ProgressListItem.Header))\n      is ProgressListItem.Filters -> areContentsTheSame(oldItem, (newItem as ProgressListItem.Filters))\n    }\n\n  private fun areItemsTheSame(oldItem: ProgressListItem.Episode, newItem: ProgressListItem.Episode) =\n    oldItem.show.traktId == newItem.show.traktId\n\n  private fun areItemsTheSame(oldItem: ProgressListItem.Header, newItem: ProgressListItem.Header) =\n    oldItem.textResId == newItem.textResId &&\n      oldItem.type == newItem.type\n\n  private fun areContentsTheSame(oldItem: ProgressListItem.Episode, newItem: ProgressListItem.Episode) =\n    oldItem.show.traktId == newItem.show.traktId &&\n      oldItem.image == newItem.image &&\n      oldItem.isLoading == newItem.isLoading &&\n      oldItem.watchedCount == newItem.watchedCount &&\n      oldItem.totalCount == newItem.totalCount &&\n      oldItem.episode == newItem.episode &&\n      oldItem.translations == newItem.translations &&\n      oldItem.isWatched == newItem.isWatched &&\n      oldItem.isUpcoming == newItem.isUpcoming &&\n      oldItem.sortOrder == newItem.sortOrder &&\n      oldItem.spoilers == newItem.spoilers &&\n      oldItem.isOnHold == newItem.isOnHold &&\n      oldItem.userRating == newItem.userRating &&\n      oldItem.isPinned == newItem.isPinned\n\n  private fun areContentsTheSame(oldItem: ProgressListItem.Header, newItem: ProgressListItem.Header) =\n    oldItem.textResId == newItem.textResId &&\n      oldItem.isCollapsed == newItem.isCollapsed\n\n  private fun areContentsTheSame(\n    oldItem: ProgressListItem.Filters,\n    newItem: ProgressListItem.Filters,\n  ): Boolean {\n    return oldItem.sortOrder == newItem.sortOrder &&\n      oldItem.sortType == newItem.sortType &&\n      oldItem.newAtTop == newItem.newAtTop &&\n      oldItem.isUpcoming == newItem.isUpcoming &&\n      oldItem.isUpcomingEnabled == newItem.isUpcomingEnabled &&\n      oldItem.isOnHold == newItem.isOnHold\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/progress/recycler/ProgressListItem.kt",
    "content": "package com.michaldrabik.ui_progress.progress.recycler\n\nimport androidx.annotation.StringRes\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.ui_base.common.ListItem\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_progress.helpers.TranslationsBundle\nimport java.time.format.DateTimeFormatter\nimport com.michaldrabik.ui_model.Episode as EpisodeModel\n\nsealed class ProgressListItem(\n  override val show: Show,\n  override val image: Image,\n  override val isLoading: Boolean = false,\n) : ListItem {\n\n  data class Episode(\n    override val show: Show,\n    override val image: Image,\n    override val isLoading: Boolean = false,\n    val episode: EpisodeModel?,\n    val season: Season?,\n    val totalCount: Int,\n    val watchedCount: Int,\n    val isWatched: Boolean,\n    val isUpcoming: Boolean,\n    val isPinned: Boolean,\n    val isOnHold: Boolean,\n    val translations: TranslationsBundle? = null,\n    val dateFormat: DateTimeFormatter? = null,\n    val sortOrder: SortOrder? = null,\n    val userRating: Int? = null,\n    val spoilers: SpoilersSettings? = null\n  ) : ProgressListItem(show, image, isLoading) {\n\n    fun isNew() = episode?.firstAired?.isBefore(nowUtc()) ?: false &&\n      nowUtcMillis() - (episode?.firstAired?.toMillis() ?: 0) < Config.NEW_BADGE_DURATION\n\n    fun requireEpisode() = episode!!\n    fun requireSeason() = season!!\n  }\n\n  data class Header(\n    override val show: Show,\n    override val image: Image,\n    override val isLoading: Boolean = false,\n    val type: Type,\n    @StringRes val textResId: Int,\n    val isCollapsed: Boolean,\n  ) : ProgressListItem(show, image, isLoading) {\n\n    companion object {\n      fun create(\n        type: Type,\n        @StringRes textResId: Int,\n        isCollapsed: Boolean,\n      ) = Header(\n        type = type,\n        show = Show.EMPTY,\n        image = Image.createUnavailable(ImageType.POSTER),\n        textResId = textResId,\n        isCollapsed = isCollapsed\n      )\n    }\n\n    override fun isSameAs(other: ListItem) =\n      textResId == (other as? Header)?.textResId\n\n    enum class Type {\n      UPCOMING,\n      ON_HOLD\n    }\n  }\n\n  data class Filters(\n    val sortOrder: SortOrder,\n    val sortType: SortType,\n    val isUpcoming: Boolean,\n    val isUpcomingEnabled: Boolean,\n    val isOnHold: Boolean,\n    val newAtTop: Boolean\n  ) : ProgressListItem(\n    show = Show.EMPTY,\n    image = Image.createUnknown(ImageType.POSTER),\n    isLoading = false\n  ) {\n\n    fun hasActiveFilters() = isUpcoming || isOnHold\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/progress/views/ProgressFiltersView.kt",
    "content": "package com.michaldrabik.ui_progress.progress.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport androidx.core.content.ContextCompat\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.SortType.ASCENDING\nimport com.michaldrabik.ui_model.SortType.DESCENDING\nimport com.michaldrabik.ui_progress.R\nimport com.michaldrabik.ui_progress.databinding.ViewProgressFiltersBinding\nimport com.michaldrabik.ui_progress.progress.recycler.ProgressListItem\n\nclass ProgressFiltersView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewProgressFiltersBinding.inflate(LayoutInflater.from(context), this)\n\n  var onSortChipClicked: (() -> Unit)? = null\n  var upcomingChipClicked: ((Boolean) -> Unit)? = null\n  var onHoldChipClicked: ((Boolean) -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    with(binding) {\n      progressFiltersSortingChip.onClick {\n        onSortChipClicked?.invoke()\n      }\n      progressFiltersUpcomingChip.onClick(safe = false) {\n        if (!::filters.isInitialized) return@onClick\n        upcomingChipClicked?.invoke(!filters.isUpcoming)\n      }\n      progressFiltersOnHoldChip.onClick(safe = false) {\n        if (!::filters.isInitialized) return@onClick\n        onHoldChipClicked?.invoke(!filters.isOnHold)\n      }\n    }\n  }\n\n  private lateinit var filters: ProgressListItem.Filters\n\n  fun bind(filters: ProgressListItem.Filters) {\n    this.filters = filters\n    with(binding) {\n      val sortIcon = when (filters.sortType) {\n        ASCENDING -> R.drawable.ic_arrow_alt_up\n        DESCENDING -> R.drawable.ic_arrow_alt_down\n      }\n      progressFiltersSortingChip.closeIcon = ContextCompat.getDrawable(context, sortIcon)\n      progressFiltersSortingChip.text = context.getText(filters.sortOrder.displayString)\n      progressFiltersUpcomingChip.visibleIf(filters.isUpcomingEnabled)\n      progressFiltersUpcomingChip.isSelected = filters.isUpcoming\n      progressFiltersOnHoldChip.isSelected = filters.isOnHold\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/progress/views/ProgressHeaderView.kt",
    "content": "package com.michaldrabik.ui_progress.progress.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.text.TextUtils\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.LinearLayout\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_progress.databinding.ViewProgressHeaderBinding\nimport com.michaldrabik.ui_progress.progress.recycler.ProgressListItem\nimport java.util.Locale\n\n@SuppressLint(\"SetTextI18n\")\nclass ProgressHeaderView : LinearLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewProgressHeaderBinding.inflate(LayoutInflater.from(context), this)\n\n  var headerClickListener: ((ProgressListItem.Header) -> Unit)? = null\n\n  private lateinit var item: ProgressListItem.Header\n  private val isRtl by lazy { TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL }\n\n  init {\n    layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)\n    orientation = HORIZONTAL\n    onClick { headerClickListener?.invoke(item) }\n  }\n\n  fun bind(item: ProgressListItem.Header) {\n    this.item = item\n\n    val rotation = if (isRtl) -90F else 90F\n    binding.progressHeaderArrow.rotation = if (item.isCollapsed) 0F else rotation\n    binding.progressHeaderText.setText(item.textResId)\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/java/com/michaldrabik/ui_progress/progress/views/ProgressItemView.kt",
    "content": "package com.michaldrabik.ui_progress.progress.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport android.widget.LinearLayout\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_RATINGS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.DurationPrinter\nimport com.michaldrabik.ui_base.utilities.extensions.addRipple\nimport com.michaldrabik.ui_base.utilities.extensions.bump\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.colorStateListFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.expandTouch\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_progress.R\nimport com.michaldrabik.ui_progress.databinding.ViewProgressItemBinding\nimport com.michaldrabik.ui_progress.progress.recycler.ProgressListItem\nimport java.util.Locale.ENGLISH\n\n@SuppressLint(\"SetTextI18n\")\nclass ProgressItemView : ShowView<ProgressListItem.Episode> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewProgressItemBinding.inflate(LayoutInflater.from(context), this)\n\n  var detailsClickListener: ((ProgressListItem.Episode) -> Unit)? = null\n  var checkClickListener: ((ProgressListItem.Episode) -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    addRipple()\n    binding.progressItemCheckButton.expandTouch(100)\n\n    onClick { itemClickListener?.invoke(item) }\n    onLongClick { itemLongClickListener?.invoke(item) }\n    binding.progressItemInfoButton.onClick { detailsClickListener?.invoke(item) }\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  private lateinit var item: ProgressListItem.Episode\n\n  override val imageView: ImageView = binding.progressItemImage\n  override val placeholderView: ImageView = binding.progressItemPlaceholder\n\n  private val durationPrinter by lazy { DurationPrinter(context.applicationContext) }\n  private val checkButtonWidth by lazy { context.dimenToPx(R.dimen.progressItemCheckButtonWidth) }\n  private val checkButtonHeight by lazy { context.dimenToPx(R.dimen.progressItemButtonHeight) }\n\n  override fun bind(item: ProgressListItem.Episode) {\n    this.item = item\n    clear()\n\n    with(binding) {\n      val translationTitle = item.translations?.show?.title\n      progressItemTitle.text =\n        if (translationTitle.isNullOrBlank()) item.show.title\n        else translationTitle\n\n      progressItemSubtitle.text = String.format(\n        ENGLISH,\n        context.getString(R.string.textSeasonEpisode),\n        item.episode?.season,\n        item.episode?.number\n      ).plus(\n        item.episode?.numberAbs?.let { if (it > 0 && item.show.isAnime) \" ($it)\" else \"\" } ?: \"\"\n      )\n\n      bindEpisodeTitle(item)\n      progressItemNewBadge.visibleIf(item.isNew())\n      progressItemPin.visibleIf(item.isPinned)\n      progressItemPause.visibleIf(item.isOnHold)\n    }\n\n    bindProgress(item)\n    bindRating(item)\n    bindCheckButton(item, checkClickListener, detailsClickListener)\n\n    loadImage(item)\n  }\n\n  private fun bindEpisodeTitle(item: ProgressListItem.Episode) {\n    var episodeTitle = when {\n      item.episode?.title?.isBlank() == true -> {\n        context.getString(R.string.textTba)\n      }\n      item.translations?.episode?.title?.isBlank() == false -> {\n        item.translations.episode.title\n      }\n      item.episode?.title == \"Episode ${item.episode?.number}\" -> {\n        String.format(ENGLISH, context.getString(R.string.textEpisode), item.episode.number)\n      }\n      else -> item.episode?.title\n    }\n\n    if (item.spoilers?.isEpisodeTitleHidden == true) {\n      with(binding) {\n        progressItemSubtitle2.tag = episodeTitle\n        episodeTitle = SPOILERS_REGEX.replace(episodeTitle.toString(), SPOILERS_HIDE_SYMBOL)\n\n        if (item.spoilers.isTapToReveal) {\n          progressItemSubtitle2.onClick { view ->\n            view.tag?.let { progressItemSubtitle2.text = it.toString() }\n            view.isClickable = false\n          }\n        }\n      }\n    }\n\n    binding.progressItemSubtitle2.text = episodeTitle\n  }\n\n  private fun bindRating(episodeItem: ProgressListItem.Episode) {\n    val isNew = episodeItem.isNew()\n    val isUpcoming = episodeItem.isUpcoming\n    with(binding) {\n      when (episodeItem.sortOrder) {\n        RATING -> {\n          progressItemRating.visibleIf(!isNew && !isUpcoming)\n          progressItemRatingStar.visibleIf(!isNew && !isUpcoming)\n          progressItemRatingStar.imageTintList = context.colorStateListFromAttr(android.R.attr.colorAccent)\n          val rating = String.format(ENGLISH, \"%.1f\", episodeItem.show.rating)\n          if (item.spoilers?.isMyShowsRatingsHidden == true) {\n            progressItemRating.tag = rating\n            progressItemRating.text = SPOILERS_RATINGS_HIDE_SYMBOL\n            if (item.spoilers?.isTapToReveal == true) {\n              progressItemRating.onClick { view ->\n                view.tag?.let {\n                  progressItemRating.text = it.toString()\n                }\n                view.isClickable = false\n              }\n            }\n          } else {\n            progressItemRating.text = rating\n          }\n        }\n        USER_RATING -> {\n          val hasRating = episodeItem.userRating != null\n          progressItemRating.visibleIf(!isNew && !isUpcoming && hasRating)\n          progressItemRatingStar.visibleIf(!isNew && !isUpcoming && hasRating)\n          progressItemRatingStar.imageTintList = context.colorStateListFromAttr(android.R.attr.textColorPrimary)\n          progressItemRating.text = String.format(ENGLISH, \"%d\", episodeItem.userRating)\n        }\n        else -> {\n          progressItemRating.gone()\n          progressItemRatingStar.gone()\n        }\n      }\n    }\n  }\n\n  private fun bindProgress(item: ProgressListItem.Episode) {\n    binding.progressItemProgress.max = item.totalCount\n    binding.progressItemProgress.progress = item.watchedCount\n    renderEpisodesLeft()\n  }\n\n  private fun bindCheckButton(\n    item: ProgressListItem.Episode,\n    checkClickListener: ((ProgressListItem.Episode) -> Unit)?,\n    detailsClickListener: ((ProgressListItem.Episode) -> Unit)?,\n  ) {\n    val hasAired = item.episode?.hasAired(item.season ?: Season.EMPTY) == true\n    val color = if (hasAired) android.R.attr.textColorPrimary else android.R.attr.textColorSecondary\n\n    with(binding) {\n      if (hasAired) {\n        progressItemInfoButton.visible()\n        progressItemCheckButton.run {\n          layoutParams = LinearLayout.LayoutParams(checkButtonWidth, checkButtonHeight)\n          text = \"\"\n          setIconResource(R.drawable.ic_check)\n          onClick { it.bump { checkClickListener?.invoke(item) } }\n        }\n      } else {\n        progressItemInfoButton.gone()\n        progressItemCheckButton.run {\n          layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, checkButtonHeight)\n          text = durationPrinter.print(item.episode?.firstAired)\n          icon = null\n          onClick { detailsClickListener?.invoke(item) }\n        }\n      }\n      progressItemCheckButton.setTextColor(context.colorFromAttr(color))\n      progressItemCheckButton.strokeColor = context.colorStateListFromAttr(color)\n      progressItemCheckButton.iconTint = context.colorStateListFromAttr(color)\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translations?.show == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun renderEpisodesLeft() {\n    with(binding) {\n      val episodesLeft = item.totalCount - item.watchedCount\n      if (episodesLeft <= 0) {\n        progressItemProgressText.text = String.format(ENGLISH, \"%d/%d\", item.watchedCount, item.totalCount)\n      } else {\n        val episodesLeftString = resources.getQuantityString(R.plurals.textEpisodesLeft, episodesLeft, episodesLeft)\n        progressItemProgressText.text = String.format(ENGLISH, \"%d/%d ($episodesLeftString)\", item.watchedCount, item.totalCount)\n      }\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      progressItemTitle.text = \"\"\n      progressItemSubtitle.text = \"\"\n      progressItemSubtitle2.text = \"\"\n      progressItemProgressText.text = \"\"\n      progressItemPlaceholder.gone()\n      Glide.with(this@ProgressItemView).clear(progressItemImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress/src/main/res/layout/fragment_calendar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  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/progressCalendarRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/progressCalendarRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/transparent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/progressRecyclerHorizontalSpace\"\n    android:paddingTop=\"@dimen/progressCalendarTabsViewPadding\"\n    android:paddingEnd=\"@dimen/progressRecyclerHorizontalSpace\"\n    android:paddingBottom=\"@dimen/bottomNavigationHeightPadded\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <include\n    android:id=\"@+id/progressCalendarEmptyRecentsView\"\n    layout=\"@layout/layout_recents_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <include\n    android:id=\"@+id/progressCalendarEmptyFutureView\"\n    layout=\"@layout/layout_calendar_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    />\n\n  <ImageView\n    android:id=\"@+id/progressCalendarOverscrollIcon\"\n    android:layout_width=\"@dimen/progressOverscrollIcon\"\n    android:layout_height=\"@dimen/progressOverscrollIcon\"\n    android:layout_gravity=\"center_horizontal\"\n    android:layout_marginTop=\"@dimen/progressOverscrollPadding\"\n    android:alpha=\"0\"\n    android:scaleX=\"0\"\n    android:scaleY=\"0\"\n    app:srcCompat=\"@drawable/ic_history\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "ui-progress/src/main/res/layout/fragment_progress.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  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/progressRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/progressRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/transparent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/progressRecyclerHorizontalSpace\"\n    android:paddingTop=\"@dimen/progressTabsViewPadding\"\n    android:paddingEnd=\"@dimen/progressRecyclerHorizontalSpace\"\n    android:paddingBottom=\"@dimen/bottomNavigationHeightPadded\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.tips.TipView\n    android:id=\"@+id/progressTipItem\"\n    android:layout_width=\"@dimen/tutorialTipSize\"\n    android:layout_height=\"@dimen/tutorialTipSize\"\n    android:layout_marginStart=\"110dp\"\n    android:layout_marginTop=\"254dp\"\n    android:visibility=\"gone\"\n    />\n\n  <include\n    android:id=\"@+id/progressEmptyView\"\n    layout=\"@layout/layout_progress_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginTop=\"@dimen/spaceHuge\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:layout_marginBottom=\"@dimen/bottomNavigationHeight\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <FrameLayout\n    android:id=\"@+id/progressOverscroll\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center_horizontal\"\n    android:layout_marginTop=\"@dimen/progressOverscrollPadding\"\n    android:alpha=\"0\"\n    android:scaleX=\"0\"\n    android:scaleY=\"0\"\n    tools:alpha=\"1\"\n    tools:scaleX=\"1\"\n    tools:scaleY=\"1\"\n    >\n\n    <ImageView\n      android:id=\"@+id/progressOverscrollIcon\"\n      android:layout_width=\"@dimen/progressOverscrollIcon\"\n      android:layout_height=\"@dimen/progressOverscrollIcon\"\n      android:layout_gravity=\"center\"\n      app:srcCompat=\"@drawable/ic_trakt\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <com.google.android.material.progressindicator.CircularProgressIndicator\n      android:id=\"@+id/progressOverscrollProgress\"\n      android:layout_width=\"@dimen/progressOverscrollProgress\"\n      android:layout_height=\"@dimen/progressOverscrollProgress\"\n      android:layout_gravity=\"center\"\n      android:indeterminate=\"false\"\n      app:indicatorColor=\"?android:attr/textColorPrimary\"\n      app:indicatorSize=\"@dimen/progressOverscrollProgress\"\n      app:trackThickness=\"4dp\"\n      tools:progress=\"75\"\n      />\n\n  </FrameLayout>\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "ui-progress/src/main/res/layout/fragment_progress_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  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/progressMainRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.viewpager.widget.ViewPager\n    android:id=\"@+id/progressMainPager\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:overScrollMode=\"never\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.ModeTabsView\n    android:id=\"@+id/progressMainPagerModeTabs\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/collectionTabsMargin\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.ScrollableTabLayout\n    android:id=\"@+id/progressMainTabs\"\n    style=\"@style/ScrollableTabsStyle\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"36dp\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/progressSearchViewPadding\"\n    android:translationX=\"@dimen/progressTabsViewTranslation\"\n    app:tabTextAppearance=\"@style/ScrollableTabTextStyle\"\n    tools:layout_width=\"300dp\"\n    />\n\n  <FrameLayout\n    android:id=\"@+id/progressMainSideIcons\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"end\"\n    android:layout_marginTop=\"@dimen/progressSearchViewPadding\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    >\n\n    <com.michaldrabik.ui_base.common.views.ScrollableImageView\n      android:id=\"@+id/progressMainCalendarIcon\"\n      android:layout_width=\"36dp\"\n      android:layout_height=\"36dp\"\n      android:layout_marginEnd=\"38dp\"\n      android:paddingStart=\"12dp\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_history\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.michaldrabik.ui_base.common.views.ScrollableImageView\n      android:id=\"@+id/progressMainSearchIcon\"\n      android:layout_width=\"36dp\"\n      android:layout_height=\"36dp\"\n      android:layout_gravity=\"end\"\n      android:paddingStart=\"14dp\"\n      android:visibility=\"visible\"\n      app:srcCompat=\"@drawable/ic_search\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:visibility=\"visible\"\n      />\n\n  </FrameLayout>\n\n  <com.michaldrabik.ui_base.common.views.SearchLocalView\n    android:id=\"@+id/progressMainSearchLocalView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/searchLocalViewHeight\"\n    android:layout_marginLeft=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/progressSearchLocalViewPadding\"\n    android:layout_marginRight=\"@dimen/screenMarginHorizontal\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.SearchView\n    android:id=\"@+id/progressMainSearchView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/searchViewHeight\"\n    android:layout_marginLeft=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginRight=\"@dimen/screenMarginHorizontal\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "ui-progress/src/main/res/layout/layout_calendar_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/tabCalendar\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textCalendarEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-progress/src/main/res/layout/layout_progress_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  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=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/tabProgress\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textProgressEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/progressEmptyDiscoverButton\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:text=\"@string/textDiscoverShows\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:backgroundTint=\"?attr/colorAccent\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    app:strokeColor=\"?android:attr/textColorPrimary\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/progressEmptyTraktButton\"\n    style=\"@style/RoundTextButton\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:text=\"@string/textTraktSynchronize\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-progress/src/main/res/layout/layout_recents_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/tabCalendar\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textRecentsEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-progress/src/main/res/layout/view_calendar_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:orientation=\"horizontal\"\n  android:paddingStart=\"@dimen/screenMarginHorizontal\"\n  android:paddingTop=\"@dimen/spaceNormal\"\n  android:paddingEnd=\"@dimen/screenMarginHorizontal\"\n  android:paddingBottom=\"@dimen/spaceTiny\"\n  tools:parentTag=\"android.widget.LinearLayout\"\n  >\n\n  <ImageView\n    android:id=\"@+id/calendarHeaderIcon\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center_vertical\"\n    android:layout_marginTop=\"@dimen/spaceMicro\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    app:srcCompat=\"@drawable/ic_history\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n  <TextView\n    android:id=\"@+id/calendarHeaderText\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center_vertical\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"@dimen/progressHeaderTextSize\"\n    android:textStyle=\"bold\"\n    tools:text=\"@string/textLast7Days\"\n    />\n\n</merge>\n"
  },
  {
    "path": "ui-progress/src/main/res/layout/view_calendar_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    >\n\n    <ImageView\n      android:id=\"@+id/calendarItemImage\"\n      android:layout_width=\"@dimen/progressImageWidth\"\n      android:layout_height=\"@dimen/progressImageHeight\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:layout_marginTop=\"8dp\"\n      android:layout_marginBottom=\"8dp\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/calendarItemPlaceholder\"\n      android:layout_width=\"@dimen/progressImageWidth\"\n      android:layout_height=\"@dimen/progressImageHeight\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"@dimen/spaceBig\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/calendarItemBadge\"\n      style=\"@style/Badge.Watchlist\"\n      android:layout_width=\"22dp\"\n      android:layout_height=\"22dp\"\n      android:layout_marginEnd=\"2dp\"\n      android:translationY=\"-3dp\"\n      app:layout_constraintEnd_toEndOf=\"@id/calendarItemImage\"\n      app:layout_constraintTop_toTopOf=\"@id/calendarItemImage\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/calendarItemTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/calendarItemSubtitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/calendarItemImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      app:layout_goneMarginEnd=\"@dimen/spaceMedium\"\n      tools:ignore=\"RtlSymmetry\"\n      tools:text=\"Breaking Bad\"\n      />\n\n    <TextView\n      android:id=\"@+id/calendarItemSubtitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginTop=\"@dimen/spaceTiny\"\n      android:layout_marginEnd=\"6dp\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_badge\"\n      android:elevation=\"@dimen/elevationTiny\"\n      android:ellipsize=\"end\"\n      android:includeFontPadding=\"false\"\n      android:maxLines=\"1\"\n      android:paddingStart=\"6dp\"\n      android:paddingTop=\"2dp\"\n      android:paddingEnd=\"6dp\"\n      android:paddingBottom=\"2dp\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/calendarItemDateText\"\n      app:layout_constraintEnd_toStartOf=\"@id/calendarItemSubtitle2\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintHorizontal_chainStyle=\"packed\"\n      app:layout_constraintStart_toEndOf=\"@id/calendarItemImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/calendarItemTitle\"\n      tools:text=\"S.01 E.01\"\n      />\n\n    <TextView\n      android:id=\"@+id/calendarItemSubtitle2\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"0dp\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      android:ellipsize=\"end\"\n      android:gravity=\"center_vertical|start\"\n      android:includeFontPadding=\"false\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/calendarItemDateText\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toEndOf=\"@id/calendarItemSubtitle\"\n      app:layout_constraintTop_toBottomOf=\"@id/calendarItemTitle\"\n      tools:text=\"Some Title of an Episode\"\n      />\n\n    <TextView\n      android:id=\"@+id/calendarItemDateText\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:gravity=\"start|center_vertical\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/calendarItemButtons\"\n      app:layout_constraintStart_toEndOf=\"@id/calendarItemImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/calendarItemSubtitle\"\n      tools:text=\"Wednesday, 27 June 2019\"\n      />\n\n    <LinearLayout\n      android:id=\"@+id/calendarItemButtons\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginTop=\"@dimen/spaceMicro\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      android:clipChildren=\"false\"\n      android:gravity=\"end|center_vertical\"\n      android:orientation=\"horizontal\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"1\"\n      app:layout_constraintTop_toBottomOf=\"@id/calendarItemDateText\"\n      >\n\n      <com.google.android.material.button.MaterialButton\n        android:id=\"@+id/calendarItemInfoButton\"\n        style=\"@style/RoundOutlinedButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"@dimen/progressItemButtonHeight\"\n        android:gravity=\"center\"\n        android:text=\"@string/textEpisodeInfo\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"12sp\"\n        app:rippleColor=\"?android:attr/textColorSecondary\"\n        app:strokeWidth=\"0dp\"\n        />\n\n      <com.google.android.material.button.MaterialButton\n        android:id=\"@+id/calendarItemCheckButton\"\n        style=\"@style/RoundOutlinedButton\"\n        android:layout_width=\"@dimen/progressItemCheckButtonWidth\"\n        android:layout_height=\"@dimen/progressItemButtonHeight\"\n        android:layout_marginStart=\"@dimen/spaceTiny\"\n        android:gravity=\"center\"\n        android:includeFontPadding=\"false\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"12sp\"\n        android:visibility=\"gone\"\n        app:icon=\"@drawable/ic_check\"\n        app:iconGravity=\"textStart\"\n        app:iconPadding=\"0dp\"\n        app:iconSize=\"21dp\"\n        app:iconTint=\"?android:attr/textColorPrimary\"\n        app:rippleColor=\"?android:attr/textColorPrimary\"\n        app:strokeColor=\"?android:attr/textColorPrimary\"\n        />\n\n    </LinearLayout>\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>\n"
  },
  {
    "path": "ui-progress/src/main/res/layout/view_progress_filters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <HorizontalScrollView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/spaceMedium\"\n    android:paddingEnd=\"@dimen/spaceMedium\"\n    android:scrollbars=\"none\"\n    >\n\n    <com.google.android.material.chip.ChipGroup\n      android:id=\"@+id/progressFiltersChips\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:paddingTop=\"@dimen/spaceSmall\"\n      android:paddingBottom=\"@dimen/spaceSmall\"\n      app:singleLine=\"true\"\n      app:singleSelection=\"true\"\n      >\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/progressFiltersSortingChip\"\n        style=\"@style/ShowlyChip.Sort\"\n        android:checkable=\"false\"\n        android:text=\"@string/textSortName\"\n        />\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/progressFiltersUpcomingChip\"\n        style=\"@style/ShowlyChip.Filter\"\n        android:checkable=\"false\"\n        android:text=\"@string/textWatchlistIncoming\"\n        />\n\n      <com.google.android.material.chip.Chip\n        android:id=\"@+id/progressFiltersOnHoldChip\"\n        style=\"@style/ShowlyChip.Filter\"\n        android:checkable=\"false\"\n        android:text=\"@string/textOnHold\"\n        />\n\n    </com.google.android.material.chip.ChipGroup>\n\n  </HorizontalScrollView>\n\n</merge>"
  },
  {
    "path": "ui-progress/src/main/res/layout/view_progress_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"wrap_content\"\n  android:layout_height=\"wrap_content\"\n  android:orientation=\"horizontal\"\n  tools:parentTag=\"android.widget.LinearLayout\"\n  >\n\n  <TextView\n    android:id=\"@+id/progressHeaderText\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceMedium\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginBottom=\"@dimen/spaceNormal\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"@dimen/progressHeaderTextSize\"\n    android:textStyle=\"bold\"\n    tools:text=\"Coming Soon\"\n    />\n\n  <ImageView\n    android:id=\"@+id/progressHeaderArrow\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"6dp\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:layout_marginBottom=\"@dimen/spaceNormal\"\n    android:rotation=\"90\"\n    app:srcCompat=\"@drawable/ic_arrow_right\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n</merge>\n"
  },
  {
    "path": "ui-progress/src/main/res/layout/view_progress_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    >\n\n    <ImageView\n      android:id=\"@+id/progressItemImage\"\n      android:layout_width=\"@dimen/progressImageWidth\"\n      android:layout_height=\"@dimen/progressImageHeight\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:layout_marginTop=\"8dp\"\n      android:layout_marginBottom=\"8dp\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/progressItemPlaceholder\"\n      android:layout_width=\"@dimen/progressImageWidth\"\n      android:layout_height=\"@dimen/progressImageHeight\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"@dimen/spaceBig\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <androidx.constraintlayout.widget.Barrier\n      android:id=\"@+id/progressItemBarrier\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"horizontal\"\n      app:barrierDirection=\"start\"\n      app:constraint_referenced_ids=\"progressItemNewBadge, progressItemRatingStar, progressItemRating, progressItemBarrierSpace\"\n      />\n\n    <Space\n      android:id=\"@+id/progressItemBarrierSpace\"\n      android:layout_width=\"@dimen/spaceMedium\"\n      android:layout_height=\"wrap_content\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressItemNewBadge\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"0dp\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      android:gravity=\"center\"\n      android:paddingStart=\"@dimen/spaceTiny\"\n      android:paddingEnd=\"@dimen/spaceTiny\"\n      android:text=\"@string/textNew\"\n      android:textAllCaps=\"true\"\n      android:textColor=\"?attr/colorAccent\"\n      android:textSize=\"14sp\"\n      android:textStyle=\"bold\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@id/progressItemTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/progressItemTitle\"\n      app:layout_constraintTop_toTopOf=\"@id/progressItemTitle\"\n      tools:visibility=\"gone\"\n      />\n\n    <ImageView\n      android:id=\"@+id/progressItemRatingStar\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@+id/progressItemRating\"\n      app:layout_constraintEnd_toStartOf=\"@id/progressItemRating\"\n      app:layout_constraintTop_toTopOf=\"@id/progressItemRating\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressItemRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@+id/progressItemTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/progressItemTitle\"\n      tools:text=\"7.6\"\n      />\n\n    <ImageView\n      android:id=\"@+id/progressItemPin\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"wrap_content\"\n      android:rotation=\"45\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@id/progressItemButtons\"\n      app:layout_constraintStart_toStartOf=\"@id/progressItemProgressText\"\n      app:layout_constraintTop_toTopOf=\"@id/progressItemButtons\"\n      app:srcCompat=\"@drawable/ic_pin\"\n      app:tint=\"?android:attr/textColorSecondary\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/progressItemPause\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"wrap_content\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@id/progressItemButtons\"\n      app:layout_constraintStart_toStartOf=\"@id/progressItemProgressText\"\n      app:layout_constraintTop_toTopOf=\"@id/progressItemButtons\"\n      app:srcCompat=\"@drawable/ic_pause\"\n      app:tint=\"?android:attr/textColorSecondary\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressItemTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      app:layout_constraintBottom_toTopOf=\"@id/progressItemSubtitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/progressItemBarrier\"\n      app:layout_constraintStart_toEndOf=\"@id/progressItemImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"RtlSymmetry\"\n      tools:text=\"@tools:sample/lorem\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressItemSubtitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginTop=\"@dimen/spaceTiny\"\n      android:layout_marginEnd=\"6dp\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_badge\"\n      android:elevation=\"@dimen/elevationTiny\"\n      android:ellipsize=\"end\"\n      android:includeFontPadding=\"false\"\n      android:maxLines=\"1\"\n      android:paddingStart=\"6dp\"\n      android:paddingTop=\"2dp\"\n      android:paddingEnd=\"6dp\"\n      android:paddingBottom=\"2dp\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/progressItemProgress\"\n      app:layout_constraintEnd_toStartOf=\"@id/progressItemSubtitle2\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintHorizontal_chainStyle=\"packed\"\n      app:layout_constraintStart_toEndOf=\"@id/progressItemImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/progressItemTitle\"\n      tools:text=\"S.01 E.01\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressItemSubtitle2\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start\"\n      android:includeFontPadding=\"false\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/progressItemSubtitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toEndOf=\"@id/progressItemSubtitle\"\n      app:layout_constraintTop_toTopOf=\"@id/progressItemSubtitle\"\n      tools:text=\"Some Title of an Episode\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressItemProgressText\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"0dp\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginTop=\"@dimen/spaceTiny\"\n      android:gravity=\"start|center_vertical\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintStart_toEndOf=\"@id/progressItemImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/progressItemSubtitle\"\n      tools:text=\"999/999\"\n      />\n\n    <com.google.android.material.progressindicator.LinearProgressIndicator\n      android:id=\"@+id/progressItemProgress\"\n      style=\"@style/Widget.MaterialComponents.LinearProgressIndicator\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:layout_marginTop=\"1dp\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      app:indicatorColor=\"?attr/colorAccent\"\n      app:layout_constraintBottom_toBottomOf=\"@id/progressItemProgressText\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/progressItemProgressText\"\n      app:layout_constraintTop_toTopOf=\"@id/progressItemProgressText\"\n      app:trackColor=\"?attr/colorProgressTrack\"\n      app:trackCornerRadius=\"10dp\"\n      app:trackThickness=\"4dp\"\n      />\n\n    <LinearLayout\n      android:id=\"@+id/progressItemButtons\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginTop=\"@dimen/spaceMicro\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      android:clipChildren=\"false\"\n      android:gravity=\"end|center_vertical\"\n      android:orientation=\"horizontal\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"1\"\n      app:layout_constraintStart_toEndOf=\"@id/progressItemImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/progressItemProgress\"\n      >\n\n      <com.google.android.material.button.MaterialButton\n        android:id=\"@+id/progressItemInfoButton\"\n        style=\"@style/RoundTextButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"@dimen/progressItemButtonHeight\"\n        android:layout_marginStart=\"@dimen/spaceTiny\"\n        android:layout_marginEnd=\"@dimen/spaceTiny\"\n        android:gravity=\"center\"\n        android:text=\"@string/textEpisodeInfo\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"12sp\"\n        app:rippleColor=\"?android:attr/textColorSecondary\"\n        app:strokeWidth=\"0dp\"\n        />\n\n      <com.google.android.material.button.MaterialButton\n        android:id=\"@+id/progressItemCheckButton\"\n        style=\"@style/RoundOutlinedButton\"\n        android:layout_width=\"@dimen/progressItemCheckButtonWidth\"\n        android:layout_height=\"@dimen/progressItemButtonHeight\"\n        android:gravity=\"center\"\n        android:includeFontPadding=\"false\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"12sp\"\n        app:icon=\"@drawable/ic_check\"\n        app:iconGravity=\"textStart\"\n        app:iconPadding=\"0dp\"\n        app:iconSize=\"21dp\"\n        app:iconTint=\"?android:attr/textColorPrimary\"\n        app:rippleColor=\"?android:attr/textColorPrimary\"\n        app:strokeColor=\"?android:attr/textColorPrimary\"\n        />\n\n    </LinearLayout>\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>\n"
  },
  {
    "path": "ui-progress/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"progressTabsViewTranslation\">-7dp</dimen>\n  <dimen name=\"progressHeaderTextSize\">22sp</dimen>\n\n  <dimen name=\"progressRecyclerHorizontalSpace\">0dp</dimen>\n\n  <dimen name=\"progressSearchViewPaddingNoModes\">68dp</dimen>\n  <dimen name=\"progressSearchViewPadding\">104dp</dimen>\n\n  <dimen name=\"progressSearchLocalViewPaddingNoModes\">114dp</dimen>\n  <dimen name=\"progressSearchLocalViewPadding\">150dp</dimen>\n  <dimen name=\"progressSearchLocalOffset\">40dp</dimen>\n\n  <dimen name=\"progressTabsViewPaddingNoModes\">112dp</dimen>\n  <dimen name=\"progressTabsViewPadding\">148dp</dimen>\n\n  <dimen name=\"progressCalendarTabsViewPaddingNoModes\">96dp</dimen>\n  <dimen name=\"progressCalendarTabsViewPadding\">132dp</dimen>\n\n  <dimen name=\"progressOverscrollPadding\">132dp</dimen>\n  <dimen name=\"progressOverscrollPaddingNoModes\">96dp</dimen>\n  <dimen name=\"progressOverscrollIcon\">36dp</dimen>\n  <dimen name=\"progressOverscrollProgress\">48dp</dimen>\n\n  <dimen name=\"progressImageHeight\">120dp</dimen>\n  <dimen name=\"progressImageWidth\">80dp</dimen>\n\n  <dimen name=\"progressItemCheckButtonWidth\">72dp</dimen>\n  <dimen name=\"progressItemButtonHeight\">40dp</dimen>\n</resources>\n"
  },
  {
    "path": "ui-progress/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabCalendar\">Calendar</string>\n  <string name=\"tabProgress\">Progress</string>\n  <string name=\"textCalendarEmpty\">There are no new episodes on the way.</string>\n  <string name=\"textRecentsEmpty\">There are no recently aired episodes.</string>\n\n  <string name=\"textOnHold\" comment=\"This is a header for Progress subsection. It will contain pending shows marked by user.\">On Hold</string>\n  <string name=\"textDiscoverShows\">Discover Shows</string>\n  <string name=\"textEpisodeInfo\">Details</string>\n  <string name=\"textTraktSynchronize\">Import from Trakt.tv</string>\n  <string name=\"textProgressEmpty\">Your <b>Progress</b> is currently empty.\\n\\nVisit <b>Discover</b> tab to add shows or import your Trakt.tv collection.</string>\n  <string name=\"textNewSeason\">Season Premiere</string>\n</resources>"
  },
  {
    "path": "ui-progress/src/main/res/values-ar/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"progressTabsViewTranslation\">6dp</dimen>\n</resources>"
  },
  {
    "path": "ui-progress/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabCalendar\">تقويم المسلسلات</string>\n  <string name=\"tabProgress\">مستوى التقدم</string>\n  <string name=\"textCalendarEmpty\">لا توجد أي حلقات جديدة قريباً.</string>\n  <string name=\"textRecentsEmpty\">لا تُوجد أي حلقات جديدة.</string>\n  <string name=\"textOnHold\" comment=\"This is a header for Progress subsection. It will contain pending shows marked by user.\">المعلقة</string>\n  <string name=\"textDiscoverShows\">إكتشف مسلسلات</string>\n  <string name=\"textEpisodeInfo\">تفاصيل الحلقة</string>\n  <string name=\"textTraktSynchronize\">إستيراد البيانات من Trakt.tv</string>\n  <string name=\"textProgressEmpty\">قائمة <b>مستوى التقدم</b> فارغة حالياً.\\n\\nإذهب إلى صفحة <b>إكتشف</b> لإضافة مسلسلات، أو اِستورد البيانات من Trakt.tv.</string>\n  <string name=\"textNewSeason\">إفتتاحية الموسم</string>\n</resources>\n"
  },
  {
    "path": "ui-progress/src/main/res/values-de/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"progressTabsViewTranslation\">-7dp</dimen>\n</resources>"
  },
  {
    "path": "ui-progress/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabCalendar\">Kalender</string>\n  <string name=\"tabProgress\">Fortschritt</string>\n  <string name=\"textCalendarEmpty\">Es gibt keine neuen Folgen.</string>\n  <string name=\"textRecentsEmpty\">Es gibt keine kürzlich ausgestrahlten Folgen.</string>\n  <string name=\"textOnHold\" comment=\"This is a header for Progress subsection. It will contain pending shows marked by user.\">Angehalten</string>\n  <string name=\"textDiscoverShows\">Shows entdecken</string>\n  <string name=\"textEpisodeInfo\">Details</string>\n  <string name=\"textTraktSynchronize\">Von Trakt.tv importieren</string>\n  <string name=\"textProgressEmpty\">Dein <b>Fortschritt</b>ist aktuell leer.\\n\\nTippe auf den <b>Entdecken</b> Tab, um Serien hinzuzufügen oder deine Trakt.tv-Sammlung zu importieren.</string>\n  <string name=\"textNewSeason\">Staffel Premiere</string>\n</resources>\n"
  },
  {
    "path": "ui-progress/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabCalendar\">Calendario</string>\n  <string name=\"tabProgress\">Progreso</string>\n  <string name=\"textCalendarEmpty\">No hay nuevos episodios en camino.</string>\n  <string name=\"textRecentsEmpty\">No hay episodios emitidos recientemente.</string>\n  <string name=\"textOnHold\" comment=\"This is a header for Progress subsection. It will contain pending shows marked by user.\">En Espera</string>\n  <string name=\"textDiscoverShows\">Descubrir Series</string>\n  <string name=\"textEpisodeInfo\">Detalles</string>\n  <string name=\"textTraktSynchronize\">Importar desde Trakt.tv</string>\n  <string name=\"textProgressEmpty\">Tu <b>Progreso</b> actualmente está vacío.\\n\\nVisita la pestaña <b>Descubrir</b> para añadir series o importar tu colección de Trakt.tv.</string>\n  <string name=\"textNewSeason\">Estreno</string>\n</resources>\n"
  },
  {
    "path": "ui-progress/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabCalendar\">Kalenteri</string>\n  <string name=\"tabProgress\">Katselutila</string>\n  <string name=\"textCalendarEmpty\">Uusia jaksoja ei ole tulossa.</string>\n  <string name=\"textRecentsEmpty\">Hiljattain esitettyjä jaksoja ei ole.</string>\n  <string name=\"textOnHold\" comment=\"This is a header for Progress subsection. It will contain pending shows marked by user.\">Pidossa</string>\n  <string name=\"textDiscoverShows\">Etsi sarjoja</string>\n  <string name=\"textEpisodeInfo\">Tiedot</string>\n  <string name=\"textTraktSynchronize\">Tuo Trakt.tv:sta</string>\n  <string name=\"textProgressEmpty\"><b>Katselutila</b> on tyhjä.\\n\\nLisää sarjoja <b>Etsi</b>-osiosta tai tuo Trakt.tv-kokoelmasi.</string>\n  <string name=\"textNewSeason\">Kauden aloitus</string>\n</resources>\n"
  },
  {
    "path": "ui-progress/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabCalendar\">Calendrier</string>\n  <string name=\"tabProgress\">Progression</string>\n  <string name=\"textCalendarEmpty\">Aucun épisode à venir.</string>\n  <string name=\"textRecentsEmpty\">Il n\\'y a pas d\\'épisodes récemment diffusés.</string>\n  <string name=\"textOnHold\" comment=\"This is a header for Progress subsection. It will contain pending shows marked by user.\">En attente</string>\n  <string name=\"textDiscoverShows\">Découvrir des séries</string>\n  <string name=\"textEpisodeInfo\">Détails</string>\n  <string name=\"textTraktSynchronize\">Importer de Trakt.tv</string>\n  <string name=\"textProgressEmpty\">Votre <b>Progression</b> est présentement vide.\\n\\nVisitez l\\'onglet <b>Découvrir</b> pour ajouter des séries ou importer votre collection Trakt.tv.</string>\n  <string name=\"textNewSeason\">Première</string>\n</resources>\n"
  },
  {
    "path": "ui-progress/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabCalendar\">Calendario</string>\n  <string name=\"tabProgress\">Progressi</string>\n  <string name=\"textCalendarEmpty\">Non sono previsti nuovi episodi a breve.</string>\n  <string name=\"textRecentsEmpty\">Non sono stati trasmessi episodi di recente.</string>\n  <string name=\"textOnHold\" comment=\"This is a header for Progress subsection. It will contain pending shows marked by user.\">In pausa</string>\n  <string name=\"textDiscoverShows\">Scopri show</string>\n  <string name=\"textEpisodeInfo\">Dettagli</string>\n  <string name=\"textTraktSynchronize\">Importa da Trakt.tv</string>\n  <string name=\"textProgressEmpty\">La tua pagina <b>Progressi</b> è vuota.\\n\\nVisita la pagina <b>Scopri</b> per aggiungere uno show o importa la tua raccolta Trakt.tv.</string>\n  <string name=\"textNewSeason\">Primo episodio</string>\n</resources>\n"
  },
  {
    "path": "ui-progress/src/main/res/values-pl/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"progressTabsViewTranslation\">-12dp</dimen>\n</resources>"
  },
  {
    "path": "ui-progress/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabCalendar\">Kalendarz</string>\n  <string name=\"tabProgress\">Postęp</string>\n  <string name=\"textCalendarEmpty\">Brak nowych nadchodzących odcinków.</string>\n  <string name=\"textRecentsEmpty\">Brak ostatnio emitowanych odcinków.</string>\n  <string name=\"textOnHold\" comment=\"This is a header for Progress subsection. It will contain pending shows marked by user.\">Wstrzymane</string>\n  <string name=\"textDiscoverShows\">Odkrywaj Seriale</string>\n  <string name=\"textEpisodeInfo\">Szczegóły</string>\n  <string name=\"textTraktSynchronize\">Import z Trakt.tv</string>\n  <string name=\"textProgressEmpty\">Twój <b>Postęp</b> jest obecnie pusty.\\n\\nDodaj nowe seriale w zakładce <b>Odkrywaj</b> lub zaimportuj swoją kolekcję z Trakt.tv.</string>\n  <string name=\"textNewSeason\">Premiera Sezonu</string>\n</resources>\n"
  },
  {
    "path": "ui-progress/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabCalendar\">Calendário</string>\n  <string name=\"tabProgress\">Progresso</string>\n  <string name=\"textCalendarEmpty\">Não há novos episódios a caminho.</string>\n  <string name=\"textRecentsEmpty\">Não há episódios recentes.</string>\n  <string name=\"textOnHold\" comment=\"This is a header for Progress subsection. It will contain pending shows marked by user.\">Em Espera</string>\n  <string name=\"textDiscoverShows\">Descubra séries</string>\n  <string name=\"textEpisodeInfo\">Detalhes</string>\n  <string name=\"textTraktSynchronize\">Importar de Trakt.tv</string>\n  <string name=\"textProgressEmpty\">Seu <b>Progresso</b> está vazio atualmente.\\n\\nVisite a guia <b>Explorar</b> para adicionar séries ou importar seu acervo de Trakt.tv.</string>\n  <string name=\"textNewSeason\">Pré estreia</string>\n</resources>\n"
  },
  {
    "path": "ui-progress/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabCalendar\">Календарь</string>\n  <string name=\"tabProgress\">Прогресс</string>\n  <string name=\"textCalendarEmpty\">Нет новых эпизодов.</string>\n  <string name=\"textRecentsEmpty\">Недавно вышедших эпизодов нет.</string>\n  <string name=\"textOnHold\" comment=\"This is a header for Progress subsection. It will contain pending shows marked by user.\">На паузе</string>\n  <string name=\"textDiscoverShows\">Найти сериалы</string>\n  <string name=\"textEpisodeInfo\">Подробнее</string>\n  <string name=\"textTraktSynchronize\">Импортировать из Trakt.tv</string>\n  <string name=\"textProgressEmpty\">Ваш <b>Прогресс</b> в настоящее время пуст.\\n\\nПосетите вкладку <b>Открытия</b>, чтобы добавить сериалы или импортировать вашу коллекцию Trakt.tv.</string>\n  <string name=\"textNewSeason\">Премьера сезона</string>\n</resources>\n"
  },
  {
    "path": "ui-progress/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"progressRecyclerHorizontalSpace\">12dp</dimen>\n</resources>\n"
  },
  {
    "path": "ui-progress/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabCalendar\">Takvim</string>\n  <string name=\"tabProgress\">İlerleme</string>\n  <string name=\"textCalendarEmpty\">Yaklaşan yeni hiçbir bölüm yok.</string>\n  <string name=\"textRecentsEmpty\">Yakın zamanda yayınlanan bölüm yok.</string>\n  <string name=\"textOnHold\" comment=\"This is a header for Progress subsection. It will contain pending shows marked by user.\">Beklemede</string>\n  <string name=\"textDiscoverShows\">Dizileri Keşfet</string>\n  <string name=\"textEpisodeInfo\">Ayrıntılar</string>\n  <string name=\"textTraktSynchronize\">Trakt.tv hizmetinden aktar</string>\n  <string name=\"textProgressEmpty\"><b>İlerlemeniz</b> şu anda boş.\\n\\nDiziler eklemek için <b>Keşfet</b> sekmesini ziyaret edin veya Trakt.tv koleksiyonunuzdan aktarın.</string>\n  <string name=\"textNewSeason\">Sezon Prömiyeri</string>\n</resources>\n"
  },
  {
    "path": "ui-progress/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabCalendar\">Календар</string>\n  <string name=\"tabProgress\">Прогрес</string>\n  <string name=\"textCalendarEmpty\">Немає нових серій.</string>\n  <string name=\"textRecentsEmpty\">Немає нещодавно випущених серій.</string>\n  <string name=\"textOnHold\" comment=\"This is a header for Progress subsection. It will contain pending shows marked by user.\">Призупинено</string>\n  <string name=\"textDiscoverShows\">Знайти серіали</string>\n  <string name=\"textEpisodeInfo\">Подробиці</string>\n  <string name=\"textTraktSynchronize\">Імпорт з Trakt.tv</string>\n  <string name=\"textProgressEmpty\">Ваш <b>Прогрес</b> зараз порожній.\\n\\nВідвідайте вкладку <b>Огляд</b>, щоб додати серіали або імпортуйте колекцію з Trakt.tv.</string>\n  <string name=\"textNewSeason\">Прем\\'єра сезону</string>\n</resources>\n"
  },
  {
    "path": "ui-progress/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabCalendar\">Lịch</string>\n  <string name=\"tabProgress\">Tiến độ</string>\n  <string name=\"textCalendarEmpty\">Không có tập phim mới nào sắp ra mắt.</string>\n  <string name=\"textRecentsEmpty\">Không có tập nào được phát sóng gần đây.</string>\n\n  <string name=\"textOnHold\" comment=\"This is a header for Progress subsection. It will contain pending shows marked by user.\">Đang chờ</string>\n  <string name=\"textDiscoverShows\">Khám phá các chương trình</string>\n  <string name=\"textEpisodeInfo\">Chi tiết</string>\n  <string name=\"textTraktSynchronize\">Nhập từ Trakt.tv</string>\n  <string name=\"textProgressEmpty\"><b>Tiến độ</b> của bạn hiện trống.\\n\\nTruy cập tab <b>Khám phá</b> để thêm chương trình hoặc nhập bộ sưu tập Trakt.tv của bạn.</string>\n  <string name=\"textNewSeason\">Buổi ra mắt mùa phim</string>\n</resources>\n"
  },
  {
    "path": "ui-progress/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabCalendar\">时间表</string>\n  <string name=\"tabProgress\">进度</string>\n  <string name=\"textCalendarEmpty\">目前暂无即将播出的剧集。</string>\n  <string name=\"textRecentsEmpty\">最近没有已播剧集。</string>\n  <string name=\"textOnHold\" comment=\"This is a header for Progress subsection. It will contain pending shows marked by user.\">暂缓观看</string>\n  <string name=\"textDiscoverShows\">发现剧集</string>\n  <string name=\"textEpisodeInfo\">详情</string>\n  <string name=\"textTraktSynchronize\">从 Trakt.tv 导入数据</string>\n  <string name=\"textProgressEmpty\">您的 <b>进度</b> 目前为空。\\n\\n访问 <b>发现</b> 选项卡去添加剧集或从 Trakt.tv 导入数据。</string>\n  <string name=\"textNewSeason\">新季首播</string>\n</resources>\n"
  },
  {
    "path": "ui-progress-movies/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-progress-movies/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_progress_movies'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-local')\n  implementation project(':ui-base')\n  implementation project(':repository')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  testImplementation project(':common-test')\n  testImplementation libs.bundles.testing\n  androidTestImplementation libs.android.test.runner\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/CalendarMoviesFragment.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateMargins\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.OnScrollResetListener\nimport com.michaldrabik.ui_base.common.OnSearchClickListener\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withSpanSizeLookup\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.CalendarMode.PRESENT_FUTURE\nimport com.michaldrabik.ui_model.CalendarMode.RECENTS\nimport com.michaldrabik.ui_progress_movies.R\nimport com.michaldrabik.ui_progress_movies.calendar.recycler.CalendarMovieListItem\nimport com.michaldrabik.ui_progress_movies.calendar.recycler.CalendarMoviesAdapter\nimport com.michaldrabik.ui_progress_movies.databinding.FragmentCalendarMoviesBinding\nimport com.michaldrabik.ui_progress_movies.helpers.ProgressMoviesLayoutManagerProvider\nimport com.michaldrabik.ui_progress_movies.helpers.TopOverscrollAdapter\nimport com.michaldrabik.ui_progress_movies.main.ProgressMoviesMainFragment\nimport com.michaldrabik.ui_progress_movies.main.ProgressMoviesMainViewModel\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport me.everything.android.ui.overscroll.IOverScrollDecor\nimport me.everything.android.ui.overscroll.IOverScrollState\nimport me.everything.android.ui.overscroll.OverScrollBounceEffectDecoratorBase.DEFAULT_DECELERATE_FACTOR\nimport me.everything.android.ui.overscroll.OverScrollBounceEffectDecoratorBase.DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK\nimport me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass CalendarMoviesFragment :\n  BaseFragment<CalendarMoviesViewModel>(R.layout.fragment_calendar_movies),\n  OnSearchClickListener,\n  OnScrollResetListener {\n\n  private companion object {\n    const val OVERSCROLL_OFFSET = 100F\n    const val OVERSCROLL_OFFSET_TRANSLATION = 5F\n  }\n\n  @Inject lateinit var settings: SettingsViewModeRepository\n\n  private val parentViewModel by viewModels<ProgressMoviesMainViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<CalendarMoviesViewModel>()\n\n  private val binding by viewBinding(FragmentCalendarMoviesBinding::bind)\n\n  private var adapter: CalendarMoviesAdapter? = null\n  private var layoutManager: LayoutManager? = null\n  private var overscroll: IOverScrollDecor? = null\n  private var overscrollEnabled = true\n  private var statusBarHeight = 0\n  private var isSearching = false\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupRecycler()\n    setupStatusBar()\n\n    viewLifecycleOwner.lifecycleScope.launch {\n      viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {\n        with(parentViewModel) {\n          launch { uiState.collect { viewModel.onParentState(it) } }\n        }\n        with(viewModel) {\n          launch { uiState.collect { render(it) } }\n          checkQuickRateEnabled()\n        }\n      }\n    }\n  }\n\n  private fun setupRecycler() {\n    val gridSpanSize = settings.tabletGridSpanSize\n    layoutManager = ProgressMoviesLayoutManagerProvider.provideLayoutManger(requireContext(), gridSpanSize)\n    (layoutManager as? GridLayoutManager)?.run {\n      withSpanSizeLookup { position ->\n        when (adapter?.getItems()?.get(position)) {\n          is CalendarMovieListItem.Header -> gridSpanSize\n          is CalendarMovieListItem.MovieItem -> 1\n          else -> throw IllegalStateException()\n        }\n      }\n    }\n    adapter = CalendarMoviesAdapter(\n      itemClickListener = { requireMainFragment().openMovieDetails(it.movie) },\n      itemLongClickListener = { requireMainFragment().openMovieMenu(it.movie, showPinButtons = false) },\n      missingImageListener = { item, force -> viewModel.findMissingImage(item, force) },\n      missingTranslationListener = { item -> viewModel.findMissingTranslation(item) }\n    )\n    binding.progressMoviesCalendarRecycler.apply {\n      adapter = this@CalendarMoviesFragment.adapter\n      layoutManager = this@CalendarMoviesFragment.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      setHasFixedSize(true)\n    }\n    setupOverscroll()\n  }\n\n  private fun setupOverscroll() {\n    if (overscroll != null || view == null) {\n      return\n    }\n    val adapt = TopOverscrollAdapter(binding.progressMoviesCalendarRecycler)\n    overscroll = VerticalOverScrollBounceEffectDecorator(\n      adapt,\n      1.75F,\n      DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK,\n      DEFAULT_DECELERATE_FACTOR\n    ).apply {\n      setOverScrollUpdateListener { _, state, offset ->\n        binding.progressMoviesCalendarOverscrollIcon?.run {\n          if (offset > 0) {\n            val value = (offset / OVERSCROLL_OFFSET).coerceAtMost(1F)\n            val valueTranslation = offset / OVERSCROLL_OFFSET_TRANSLATION\n            when (state) {\n              IOverScrollState.STATE_DRAG_START_SIDE -> {\n                alpha = value\n                scaleX = value\n                scaleY = value\n                translationY = valueTranslation\n                overscrollEnabled = true\n              }\n              IOverScrollState.STATE_BOUNCE_BACK -> {\n                alpha = value\n                scaleX = value\n                scaleY = value\n                translationY = valueTranslation\n                if (offset >= OVERSCROLL_OFFSET && overscrollEnabled) {\n                  overscrollEnabled = false\n                  requireMainFragment().toggleCalendarMode()\n                }\n              }\n            }\n          } else {\n            alpha = 0F\n            scaleX = 0F\n            scaleY = 0F\n            translationY = 0F\n          }\n        }\n      }\n    }\n  }\n\n  private fun setupStatusBar() {\n    if (statusBarHeight != 0) {\n      binding.progressMoviesCalendarRecycler.updatePadding(top = statusBarHeight + dimenToPx(R.dimen.progressMoviesCalendarTabsViewPadding))\n      return\n    }\n    binding.progressMoviesCalendarRecycler.doOnApplyWindowInsets { view, insets, _, _ ->\n      val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n      statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n      view.updatePadding(top = statusBarHeight + dimenToPx(R.dimen.progressMoviesCalendarTabsViewPadding))\n      (binding.progressMoviesCalendarOverscrollIcon.layoutParams as ViewGroup.MarginLayoutParams)\n        .updateMargins(top = statusBarHeight + dimenToPx(R.dimen.progressMoviesOverscrollPadding))\n    }\n  }\n\n  override fun onScrollReset() = binding.progressMoviesCalendarRecycler.smoothScrollToPosition(0)\n\n  override fun onEnterSearch() {\n    isSearching = true\n\n    binding.progressMoviesCalendarRecycler.translationY = dimenToPx(R.dimen.progressMoviesSearchLocalOffset).toFloat()\n    binding.progressMoviesCalendarRecycler.smoothScrollToPosition(0)\n\n    overscroll?.detach()\n    overscroll = null\n  }\n\n  override fun onExitSearch() {\n    isSearching = false\n\n    binding.progressMoviesCalendarRecycler.translationY = 0F\n    binding.progressMoviesCalendarRecycler.smoothScrollToPosition(0)\n\n    setupOverscroll()\n  }\n\n  private fun render(uiState: CalendarMoviesUiState) {\n    with(binding) {\n      uiState.run {\n        items?.let {\n          adapter?.setItems(it)\n          progressMoviesCalendarRecycler.fadeIn(150, withHardware = true)\n          progressMoviesCalendarEmptyFutureView.rootLayout.visibleIf(items.isEmpty() && mode == PRESENT_FUTURE && !isSearching)\n          progressMoviesCalendarEmptyRecentsView.rootLayout.visibleIf(items.isEmpty() && mode == RECENTS && !isSearching)\n        }\n        mode.let {\n          viewLifecycleOwner.lifecycleScope.launch {\n            delay(300)\n            when (it) {\n              PRESENT_FUTURE -> progressMoviesCalendarOverscrollIcon.setImageResource(R.drawable.ic_history)\n              RECENTS -> progressMoviesCalendarOverscrollIcon.setImageResource(R.drawable.ic_calendar)\n            }\n          }\n        }\n      }\n    }\n  }\n\n  private fun requireMainFragment() = (requireParentFragment() as ProgressMoviesMainFragment)\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    overscroll = null\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/CalendarMoviesUiState.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar\n\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_progress_movies.calendar.recycler.CalendarMovieListItem\n\ndata class CalendarMoviesUiState(\n  val items: List<CalendarMovieListItem>? = null,\n  val mode: CalendarMode = CalendarMode.PRESENT_FUTURE,\n)\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/CalendarMoviesViewModel.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_progress_movies.calendar.cases.CalendarMoviesRatingsCase\nimport com.michaldrabik.ui_progress_movies.calendar.cases.items.CalendarMoviesFutureCase\nimport com.michaldrabik.ui_progress_movies.calendar.cases.items.CalendarMoviesRecentsCase\nimport com.michaldrabik.ui_progress_movies.calendar.recycler.CalendarMovieListItem\nimport com.michaldrabik.ui_progress_movies.main.ProgressMoviesMainUiState\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@HiltViewModel\nclass CalendarMoviesViewModel @Inject constructor(\n  private val recentsCase: CalendarMoviesRecentsCase,\n  private val futureCase: CalendarMoviesFutureCase,\n  private val ratingsCase: CalendarMoviesRatingsCase,\n  private val imagesProvider: MovieImagesProvider,\n  private val translationsRepository: TranslationsRepository,\n) : ViewModel() {\n\n  private var loadItemsJob: Job? = null\n\n  private val itemsState = MutableStateFlow<List<CalendarMovieListItem>?>(null)\n  private val modeState = MutableStateFlow(CalendarMode.PRESENT_FUTURE)\n\n  private var mode = CalendarMode.PRESENT_FUTURE\n  private var searchQuery: String? = null\n  private var timestamp = 0L\n  var isQuickRateEnabled = false\n\n  fun onParentState(state: ProgressMoviesMainUiState) {\n    when {\n      this.timestamp != state.timestamp && state.timestamp != 0L -> {\n        this.timestamp = state.timestamp ?: 0L\n        loadItems()\n      }\n      this.mode != state.calendarMode -> {\n        this.mode = state.calendarMode ?: CalendarMode.PRESENT_FUTURE\n        loadItems()\n      }\n      this.searchQuery != state.searchQuery -> {\n        this.searchQuery = state.searchQuery\n        loadItems()\n      }\n    }\n  }\n\n  private fun loadItems() {\n    loadItemsJob?.cancel()\n    loadItemsJob = viewModelScope.launch {\n      val items = when (mode) {\n        CalendarMode.PRESENT_FUTURE -> futureCase.loadItems(searchQuery)\n        CalendarMode.RECENTS -> recentsCase.loadItems(searchQuery)\n      }\n      itemsState.value = items\n      modeState.value = mode\n    }\n  }\n\n  fun findMissingImage(item: CalendarMovieListItem, force: Boolean) {\n    check(item is CalendarMovieListItem.MovieItem)\n    viewModelScope.launch {\n      updateItem(item.copy(isLoading = true))\n      try {\n        val image = imagesProvider.loadRemoteImage(item.movie, item.image.type, force)\n        updateItem(item.copy(image = image, isLoading = false))\n      } catch (t: Throwable) {\n        val unavailable = Image.createUnavailable(item.image.type)\n        updateItem(item.copy(image = unavailable, isLoading = false))\n      }\n    }\n  }\n\n  fun findMissingTranslation(item: CalendarMovieListItem) {\n    check(item is CalendarMovieListItem.MovieItem)\n    val language = translationsRepository.getLanguage()\n    if (item.translation != null || language == Config.DEFAULT_LANGUAGE) return\n    viewModelScope.launch {\n      try {\n        val translation = translationsRepository.loadTranslation(item.movie, language)\n        updateItem(item.copy(translation = translation))\n      } catch (error: Throwable) {\n        Timber.e(error)\n      }\n    }\n  }\n\n  private fun updateItem(new: CalendarMovieListItem.MovieItem) {\n    val currentItems = itemsState.value?.toMutableList() ?: mutableListOf()\n    currentItems.findReplace(new) { it isSameAs new }\n    itemsState.value = currentItems\n    modeState.value = mode\n  }\n\n  fun checkQuickRateEnabled() {\n    viewModelScope.launch {\n      isQuickRateEnabled = ratingsCase.isQuickRateEnabled()\n    }\n  }\n\n  val uiState = combine(\n    itemsState,\n    modeState\n  ) { s1, s2 ->\n    CalendarMoviesUiState(\n      items = s1,\n      mode = s2\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = CalendarMoviesUiState()\n  )\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/cases/CalendarMoviesRatingsCase.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass CalendarMoviesRatingsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val userTraktManager: UserTraktManager,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun isQuickRateEnabled(): Boolean =\n    withContext(dispatchers.IO) {\n      val isSignedIn = userTraktManager.isAuthorized()\n      val isPremium = settingsRepository.isPremium\n      val isQuickRate = settingsRepository.load().traktQuickRateEnabled\n      isPremium && isSignedIn && isQuickRate\n    }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/cases/items/CalendarMoviesFutureCase.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.cases.items\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_progress_movies.calendar.helpers.filters.CalendarFutureFilter\nimport com.michaldrabik.ui_progress_movies.calendar.helpers.groupers.CalendarFutureGrouper\nimport com.michaldrabik.ui_progress_movies.calendar.helpers.sorter.CalendarFutureSorter\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CalendarMoviesFutureCase @Inject constructor(\n  dispatchers: CoroutineDispatchers,\n  moviesRepository: MoviesRepository,\n  translationsRepository: TranslationsRepository,\n  settingsSpoilersRepository: SettingsSpoilersRepository,\n  imagesProvider: MovieImagesProvider,\n  dateFormatProvider: DateFormatProvider,\n  override val filter: CalendarFutureFilter,\n  override val grouper: CalendarFutureGrouper,\n  override val sorter: CalendarFutureSorter,\n) : CalendarMoviesItemsCase(\n  dispatchers,\n  moviesRepository,\n  translationsRepository,\n  settingsSpoilersRepository,\n  imagesProvider,\n  dateFormatProvider\n)\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/cases/items/CalendarMoviesItemsCase.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.cases.items\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_progress_movies.calendar.helpers.filters.CalendarFilter\nimport com.michaldrabik.ui_progress_movies.calendar.helpers.groupers.CalendarGrouper\nimport com.michaldrabik.ui_progress_movies.calendar.helpers.sorter.CalendarSorter\nimport com.michaldrabik.ui_progress_movies.calendar.recycler.CalendarMovieListItem\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\n\nabstract class CalendarMoviesItemsCase constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val moviesRepository: MoviesRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val settingsSpoilersRepository: SettingsSpoilersRepository,\n  private val imagesProvider: MovieImagesProvider,\n  private val dateFormatProvider: DateFormatProvider,\n) {\n\n  abstract val filter: CalendarFilter\n  abstract val grouper: CalendarGrouper\n  abstract val sorter: CalendarSorter\n\n  suspend fun loadItems(searchQuery: String? = \"\"): List<CalendarMovieListItem> =\n    withContext(dispatchers.IO) {\n      val now = nowUtc().toLocalZone()\n      val language = translationsRepository.getLanguage()\n      val dateFormat = dateFormatProvider.loadFullDayFormat()\n      val spoilers = settingsSpoilersRepository.getAll()\n\n      val (myMovies, watchlistMovies) = awaitAll(\n        async { moviesRepository.myMovies.loadAll() },\n        async { moviesRepository.watchlistMovies.loadAll() }\n      )\n\n      val elements = (myMovies + watchlistMovies)\n        .filter { filter.filter(now, it) }\n        .sortedWith(sorter.sort())\n        .map { movie ->\n          async {\n            var translation: Translation? = null\n            if (language != Config.DEFAULT_LANGUAGE) {\n              translation = translationsRepository.loadTranslation(movie, language, onlyLocal = true)\n            }\n            CalendarMovieListItem.MovieItem(\n              movie = movie,\n              image = imagesProvider.findCachedImage(movie, ImageType.POSTER),\n              isWatched = myMovies.any { it.traktId == movie.traktId },\n              isWatchlist = watchlistMovies.any { it.traktId == movie.traktId },\n              dateFormat = dateFormat,\n              translation = translation,\n              spoilers = spoilers\n            )\n          }\n        }.awaitAll()\n\n      val queryElements = filterByQuery(searchQuery ?: \"\", elements)\n      grouper.groupByTime(nowUtc(), queryElements)\n    }\n\n  private fun filterByQuery(query: String, items: List<CalendarMovieListItem.MovieItem>) =\n    items.filter {\n      it.movie.title.contains(query, true) ||\n        it.translation?.title?.contains(query, true) == true\n    }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/cases/items/CalendarMoviesRecentsCase.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.cases.items\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_progress_movies.calendar.helpers.filters.CalendarRecentsFilter\nimport com.michaldrabik.ui_progress_movies.calendar.helpers.groupers.CalendarRecentsGrouper\nimport com.michaldrabik.ui_progress_movies.calendar.helpers.sorter.CalendarRecentsSorter\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CalendarMoviesRecentsCase @Inject constructor(\n  dispatchers: CoroutineDispatchers,\n  moviesRepository: MoviesRepository,\n  translationsRepository: TranslationsRepository,\n  settingsSpoilersRepository: SettingsSpoilersRepository,\n  imagesProvider: MovieImagesProvider,\n  dateFormatProvider: DateFormatProvider,\n  override val filter: CalendarRecentsFilter,\n  override val grouper: CalendarRecentsGrouper,\n  override val sorter: CalendarRecentsSorter,\n) : CalendarMoviesItemsCase(\n  dispatchers,\n  moviesRepository,\n  translationsRepository,\n  settingsSpoilersRepository,\n  imagesProvider,\n  dateFormatProvider\n)\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/helpers/filters/CalendarFilter.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.helpers.filters\n\nimport com.michaldrabik.ui_model.Movie\nimport java.time.ZonedDateTime\n\ninterface CalendarFilter {\n  fun filter(now: ZonedDateTime, movie: Movie): Boolean\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/helpers/filters/CalendarFutureFilter.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.helpers.filters\n\nimport com.michaldrabik.ui_model.Movie\nimport java.time.ZonedDateTime\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CalendarFutureFilter @Inject constructor() : CalendarFilter {\n\n  override fun filter(now: ZonedDateTime, movie: Movie) =\n    movie.released?.isAfter(now.toLocalDate()) == true || movie.released?.isEqual(now.toLocalDate()) == true\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/helpers/filters/CalendarRecentsFilter.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.helpers.filters\n\nimport com.michaldrabik.ui_model.Movie\nimport java.time.ZonedDateTime\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CalendarRecentsFilter @Inject constructor() : CalendarFilter {\n\n  override fun filter(now: ZonedDateTime, movie: Movie) =\n    movie.released?.isBefore(now.toLocalDate()) == true\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/helpers/groupers/CalendarFutureGrouper.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.helpers.groupers\n\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_progress_movies.R\nimport com.michaldrabik.ui_progress_movies.calendar.recycler.CalendarMovieListItem\nimport java.time.DayOfWeek\nimport java.time.Month\nimport java.time.ZonedDateTime\nimport java.time.temporal.TemporalAdjusters.next\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CalendarFutureGrouper @Inject constructor() : CalendarGrouper {\n\n  override fun groupByTime(\n    nowUtc: ZonedDateTime,\n    items: List<CalendarMovieListItem.MovieItem>\n  ): List<CalendarMovieListItem> {\n    val nowDays = nowUtc.toLocalZone().toLocalDate()\n\n    val itemsMap = mutableMapOf<Int, MutableList<CalendarMovieListItem>>()\n      .apply {\n        put(R.string.textToday, mutableListOf())\n        put(R.string.textTomorrow, mutableListOf())\n        put(R.string.textThisWeek, mutableListOf())\n        put(R.string.textNextWeek, mutableListOf())\n        put(R.string.textThisMonth, mutableListOf())\n        put(R.string.textNextMonth, mutableListOf())\n        put(R.string.textThisYear, mutableListOf())\n        put(R.string.textLater, mutableListOf())\n      }\n\n    items.forEach { item ->\n      val itemDays = item.movie.released\n      when {\n        itemDays?.isEqual(nowDays) == true -> {\n          itemsMap[R.string.textToday]?.add(item)\n        }\n        itemDays?.isEqual(nowDays.plusDays(1)) == true -> {\n          itemsMap[R.string.textTomorrow]?.add(item)\n        }\n        itemDays?.isBefore(nowDays.with(next(DayOfWeek.MONDAY))) == true -> {\n          itemsMap[R.string.textThisWeek]?.add(item)\n        }\n        itemDays?.isBefore(nowDays.plusWeeks(1).with(next(DayOfWeek.MONDAY))) == true -> {\n          itemsMap[R.string.textNextWeek]?.add(item)\n        }\n        itemDays?.month == nowDays.month && itemDays?.year == nowDays.year -> {\n          itemsMap[R.string.textThisMonth]?.add(item)\n        }\n        (itemDays?.monthValue == (nowDays.monthValue + 1) && itemDays.year == nowDays.year) ||\n          (itemDays?.month == Month.JANUARY && nowDays.month == Month.DECEMBER) -> {\n          itemsMap[R.string.textNextMonth]?.add(item)\n        }\n        itemDays?.year == nowDays.year -> {\n          itemsMap[R.string.textThisYear]?.add(item)\n        }\n        else -> itemsMap[R.string.textLater]?.add(item)\n      }\n    }\n\n    return itemsMap.entries.fold(mutableListOf()) { acc, entry ->\n      acc.apply {\n        if (entry.value.isNotEmpty()) {\n          add(CalendarMovieListItem.Header.create(entry.key, CalendarMode.PRESENT_FUTURE))\n          addAll(entry.value)\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/helpers/groupers/CalendarGrouper.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.helpers.groupers\n\nimport com.michaldrabik.ui_progress_movies.calendar.recycler.CalendarMovieListItem\nimport java.time.ZonedDateTime\n\ninterface CalendarGrouper {\n  fun groupByTime(\n    nowUtc: ZonedDateTime,\n    items: List<CalendarMovieListItem.MovieItem>\n  ): List<CalendarMovieListItem>\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/helpers/groupers/CalendarRecentsGrouper.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.helpers.groupers\n\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_progress_movies.R\nimport com.michaldrabik.ui_progress_movies.calendar.recycler.CalendarMovieListItem\nimport java.time.ZonedDateTime\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CalendarRecentsGrouper @Inject constructor() : CalendarGrouper {\n\n  override fun groupByTime(\n    nowUtc: ZonedDateTime,\n    items: List<CalendarMovieListItem.MovieItem>\n  ): List<CalendarMovieListItem> {\n    val now = nowUtc.toLocalZone().toLocalDate()\n\n    val yesterdayItems = items.filter {\n      val dateDays = it.movie.released\n      dateDays?.isEqual(now.minusDays(1)) == true\n    }\n    val last7DaysItems = (items - yesterdayItems).filter {\n      val dateDays = it.movie.released\n      dateDays?.isAfter(now.minusDays(8)) == true\n    }\n    val last30DaysItems = (items - yesterdayItems - last7DaysItems).filter {\n      val dateDays = it.movie.released\n      dateDays?.isAfter(now.minusDays(31)) == true\n    }\n    val last90Days = (items - yesterdayItems - last7DaysItems - last30DaysItems).filter {\n      val dateDays = it.movie.released\n      dateDays?.isAfter(now.minusDays(91)) == true\n    }\n\n    val itemsMap = mutableMapOf<Int, List<CalendarMovieListItem>>()\n      .apply {\n        put(R.string.textYesterday, yesterdayItems)\n        put(R.string.textLast7Days, last7DaysItems)\n        put(R.string.textLast30Days, last30DaysItems)\n        put(R.string.textLast90Days, last90Days)\n      }\n\n    return itemsMap.entries.fold(mutableListOf()) { acc, entry ->\n      acc.apply {\n        if (entry.value.isNotEmpty()) {\n          add(CalendarMovieListItem.Header.create(entry.key, CalendarMode.RECENTS))\n          addAll(entry.value)\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/helpers/sorter/CalendarFutureSorter.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.helpers.sorter\n\nimport com.michaldrabik.ui_model.Movie\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CalendarFutureSorter @Inject constructor() : CalendarSorter {\n  override fun sort() = compareBy<Movie> { it.released }.thenBy { it.year }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/helpers/sorter/CalendarRecentsSorter.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.helpers.sorter\n\nimport com.michaldrabik.ui_model.Movie\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CalendarRecentsSorter @Inject constructor() : CalendarSorter {\n  override fun sort() = compareByDescending<Movie> { it.released }.thenByDescending { it.year }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/helpers/sorter/CalendarSorter.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.helpers.sorter\n\nimport com.michaldrabik.ui_model.Movie\nimport java.util.Comparator\n\ninterface CalendarSorter {\n  fun sort(): Comparator<Movie>\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/recycler/CalendarMovieItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass CalendarMovieItemDiffCallback : DiffUtil.ItemCallback<CalendarMovieListItem>() {\n\n  override fun areItemsTheSame(oldItem: CalendarMovieListItem, newItem: CalendarMovieListItem): Boolean {\n    if (oldItem is CalendarMovieListItem.Header && newItem is CalendarMovieListItem.Header) {\n      return oldItem.textResId == newItem.textResId\n    }\n    if (oldItem is CalendarMovieListItem.MovieItem && newItem is CalendarMovieListItem.MovieItem) {\n      return oldItem.movie.traktId == newItem.movie.traktId\n    }\n    return false\n  }\n\n  override fun areContentsTheSame(oldItem: CalendarMovieListItem, newItem: CalendarMovieListItem): Boolean {\n    if (oldItem is CalendarMovieListItem.Header && newItem is CalendarMovieListItem.Header) {\n      return oldItem.textResId == newItem.textResId\n    }\n    if (oldItem is CalendarMovieListItem.MovieItem && newItem is CalendarMovieListItem.MovieItem) {\n      return oldItem.image == newItem.image &&\n        oldItem.isWatched == newItem.isWatched &&\n        oldItem.isWatchlist == newItem.isWatchlist &&\n        oldItem.isLoading == newItem.isLoading &&\n        oldItem.translation == newItem.translation &&\n        oldItem.spoilers == newItem.spoilers &&\n        oldItem.movie == newItem.movie\n    }\n    return false\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/recycler/CalendarMovieListItem.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.recycler\n\nimport androidx.annotation.StringRes\nimport com.michaldrabik.ui_base.common.MovieListItem\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.Translation\nimport java.time.format.DateTimeFormatter\n\nsealed class CalendarMovieListItem(\n  override val movie: Movie,\n  override val image: Image,\n  override val isLoading: Boolean = false,\n) : MovieListItem {\n\n  data class MovieItem(\n    override val movie: Movie,\n    override val image: Image,\n    override val isLoading: Boolean = false,\n    val isWatched: Boolean,\n    val isWatchlist: Boolean,\n    val translation: Translation? = null,\n    val dateFormat: DateTimeFormatter? = null,\n    val spoilers: SpoilersSettings\n  ) : CalendarMovieListItem(movie, image, isLoading)\n\n  data class Header(\n    override val movie: Movie,\n    override val image: Image,\n    override val isLoading: Boolean = false,\n    @StringRes val textResId: Int,\n    val calendarMode: CalendarMode,\n  ) : CalendarMovieListItem(movie, image, isLoading) {\n\n    companion object {\n      fun create(@StringRes textResId: Int, mode: CalendarMode) =\n        Header(\n          movie = Movie.EMPTY,\n          image = Image.createUnavailable(ImageType.POSTER),\n          textResId = textResId,\n          calendarMode = mode\n        )\n    }\n\n    override fun isSameAs(other: MovieListItem) =\n      textResId == (other as? Header)?.textResId\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/recycler/CalendarMoviesAdapter.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.recycler\n\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseMovieAdapter\nimport com.michaldrabik.ui_progress_movies.calendar.views.CalendarMoviesHeaderView\nimport com.michaldrabik.ui_progress_movies.calendar.views.CalendarMoviesItemView\n\nclass CalendarMoviesAdapter(\n  private val itemClickListener: (CalendarMovieListItem) -> Unit,\n  private val itemLongClickListener: (CalendarMovieListItem) -> Unit,\n  private val missingImageListener: (CalendarMovieListItem, Boolean) -> Unit,\n  private val missingTranslationListener: (CalendarMovieListItem) -> Unit,\n) : BaseMovieAdapter<CalendarMovieListItem>() {\n\n  companion object {\n    private const val VIEW_TYPE_ITEM = 1\n    private const val VIEW_TYPE_HEADER = 2\n  }\n\n  override val asyncDiffer = AsyncListDiffer(this, CalendarMovieItemDiffCallback())\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    when (viewType) {\n      VIEW_TYPE_ITEM -> BaseViewHolder(\n        CalendarMoviesItemView(parent.context).apply {\n          itemClickListener = this@CalendarMoviesAdapter.itemClickListener\n          itemLongClickListener = this@CalendarMoviesAdapter.itemLongClickListener\n          missingImageListener = this@CalendarMoviesAdapter.missingImageListener\n          missingTranslationListener = this@CalendarMoviesAdapter.missingTranslationListener\n        }\n      )\n      VIEW_TYPE_HEADER -> BaseViewHolder(CalendarMoviesHeaderView(parent.context))\n      else -> throw IllegalStateException()\n    }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    when (val item = asyncDiffer.currentList[position]) {\n      is CalendarMovieListItem.MovieItem -> (holder.itemView as CalendarMoviesItemView).bind(item)\n      is CalendarMovieListItem.Header -> (holder.itemView as CalendarMoviesHeaderView).bind(item)\n    }\n  }\n\n  override fun getItemViewType(position: Int) =\n    when (asyncDiffer.currentList[position]) {\n      is CalendarMovieListItem.MovieItem -> VIEW_TYPE_ITEM\n      is CalendarMovieListItem.Header -> VIEW_TYPE_HEADER\n      else -> throw IllegalStateException()\n    }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/views/CalendarMoviesHeaderView.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.LinearLayout\nimport androidx.core.view.updatePadding\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_progress_movies.R\nimport com.michaldrabik.ui_progress_movies.calendar.recycler.CalendarMovieListItem\nimport com.michaldrabik.ui_progress_movies.databinding.ViewCalendarMoviesHeaderBinding\n\n@SuppressLint(\"SetTextI18n\")\nclass CalendarMoviesHeaderView : LinearLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewCalendarMoviesHeaderBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    orientation = HORIZONTAL\n    updatePadding(\n      top = context.dimenToPx(R.dimen.spaceNormal),\n      bottom = context.dimenToPx(R.dimen.spaceTiny),\n      left = context.dimenToPx(R.dimen.spaceMedium),\n      right = context.dimenToPx(R.dimen.spaceMedium)\n    )\n  }\n\n  fun bind(item: CalendarMovieListItem.Header) {\n    with(binding) {\n      calendarMoviesHeaderText.setText(item.textResId)\n      calendarMoviesHeaderIcon.visibleIf(item.calendarMode == CalendarMode.RECENTS)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/calendar/views/CalendarMoviesItemView.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport androidx.core.content.ContextCompat\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.ui_base.common.views.MovieView\nimport com.michaldrabik.ui_base.utilities.extensions.addRipple\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_progress_movies.R\nimport com.michaldrabik.ui_progress_movies.calendar.recycler.CalendarMovieListItem\nimport com.michaldrabik.ui_progress_movies.databinding.ViewProgressMoviesCalendarItemBinding\n\n@SuppressLint(\"SetTextI18n\")\nclass CalendarMoviesItemView : MovieView<CalendarMovieListItem.MovieItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewProgressMoviesCalendarItemBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    addRipple()\n    onClick { itemClickListener?.invoke(item) }\n    onLongClick { itemLongClickListener?.invoke(item) }\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  private lateinit var item: CalendarMovieListItem.MovieItem\n\n  override val imageView: ImageView = binding.progressMovieCalendarItemImage\n  override val placeholderView: ImageView = binding.progressMovieCalendarItemPlaceholder\n\n  override fun bind(item: CalendarMovieListItem.MovieItem) {\n    this.item = item\n    clear()\n\n    with(binding) {\n      progressMovieCalendarItemTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.movie.title\n        else item.translation?.title\n\n      bindDescription(item)\n      bindBadge(item)\n\n      if (item.movie.released != null) {\n        progressMovieCalendarItemDate.text = item.dateFormat?.format(item.movie.released)?.capitalizeWords()\n      } else {\n        progressMovieCalendarItemDate.text = context.getString(R.string.textTba)\n      }\n\n      loadImage(item)\n    }\n  }\n\n  private fun bindBadge(item: CalendarMovieListItem.MovieItem) {\n    with(binding.progressMovieCalendarItemBadge) {\n      val inCollection = item.isWatched || item.isWatchlist\n      visibleIf(inCollection)\n      if (inCollection) {\n        val color = if (item.isWatched) R.color.colorAccent else R.color.colorGrayLight\n        imageTintList = ColorStateList.valueOf(ContextCompat.getColor(context, color))\n      }\n    }\n  }\n\n  private fun bindDescription(item: CalendarMovieListItem.MovieItem) {\n    var description = if (item.translation?.overview.isNullOrBlank()) {\n      item.movie.overview.ifBlank { context.getString(R.string.textNoDescription) }\n    } else {\n      item.translation?.overview\n    }\n\n    with(binding) {\n      val isMyHidden = item.spoilers.isMyMoviesHidden && item.isWatched\n      val isWatchlistHidden = item.spoilers.isWatchlistMoviesHidden && item.isWatchlist\n      val isNotCollectedHidden = item.spoilers.isNotCollectedMoviesHidden && (!item.isWatched && !item.isWatchlist)\n      if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n        progressMovieCalendarItemSubtitle.tag = description\n        description = SPOILERS_REGEX.replace(description.toString(), SPOILERS_HIDE_SYMBOL)\n\n        if (item.spoilers.isTapToReveal) {\n          progressMovieCalendarItemSubtitle.onClick { view ->\n            view.tag?.let { progressMovieCalendarItemSubtitle.text = it.toString() }\n            view.isClickable = false\n          }\n        }\n      }\n\n      progressMovieCalendarItemSubtitle.text = description\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      progressMovieCalendarItemTitle.text = \"\"\n      progressMovieCalendarItemSubtitle.text = \"\"\n      progressMovieCalendarItemPlaceholder.gone()\n      Glide.with(this@CalendarMoviesItemView).clear(progressMovieCalendarItemImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/helpers/ProgressMoviesItemsSorter.kt",
    "content": "package com.michaldrabik.ui_progress_movies.helpers\n\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_ADDED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SortType.ASCENDING\nimport com.michaldrabik.ui_model.SortType.DESCENDING\nimport com.michaldrabik.ui_progress_movies.progress.recycler.ProgressMovieListItem\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass ProgressMoviesItemsSorter @Inject constructor() {\n\n  fun sort(sortOrder: SortOrder, sortType: SortType) = when (sortType) {\n    ASCENDING -> sortAscending(sortOrder)\n    DESCENDING -> sortDescending(sortOrder)\n  }\n\n  private fun sortAscending(sortOrder: SortOrder) = when (sortOrder) {\n    NAME -> compareBy { getTitle(it) }\n    RATING -> compareBy { it.movie.rating }\n    USER_RATING ->\n      compareByDescending<ProgressMovieListItem.MovieItem> { it.userRating != null }\n        .thenBy { it.userRating }\n        .thenBy { getTitle(it) }\n    DATE_ADDED -> compareBy { it.movie.updatedAt }\n    NEWEST ->\n      compareBy<ProgressMovieListItem.MovieItem> { it.movie.released }\n        .thenBy { it.movie.year }\n    else -> throw IllegalStateException(\"Invalid sort order\")\n  }\n\n  private fun sortDescending(sortOrder: SortOrder) = when (sortOrder) {\n    NAME -> compareByDescending { getTitle(it) }\n    RATING -> compareByDescending { it.movie.rating }\n    USER_RATING ->\n      compareByDescending<ProgressMovieListItem.MovieItem> { it.userRating != null }\n        .thenByDescending { it.userRating }\n        .thenBy { getTitle(it) }\n    DATE_ADDED -> compareByDescending { it.movie.updatedAt }\n    NEWEST ->\n      compareByDescending<ProgressMovieListItem.MovieItem> { it.movie.released }\n        .thenByDescending { it.movie.year }\n    else -> throw IllegalStateException(\"Invalid sort order\")\n  }\n\n  private fun getTitle(item: ProgressMovieListItem.MovieItem): String {\n    val translatedTitle =\n      if (item.translation?.hasTitle == true) item.translation.title\n      else item.movie.titleNoThe\n    return translatedTitle.uppercase()\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/helpers/ProgressMoviesLayoutManagerProvider.kt",
    "content": "package com.michaldrabik.ui_progress_movies.helpers\n\nimport android.content.Context\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\n\ninternal object ProgressMoviesLayoutManagerProvider {\n\n  fun provideLayoutManger(\n    context: Context,\n    gridSpanSize: Int,\n  ): LayoutManager {\n    return if (context.isTablet()) {\n      GridLayoutManager(context, gridSpanSize)\n    } else {\n      LinearLayoutManager(context, VERTICAL, false)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/helpers/TopOverscrollAdapter.kt",
    "content": "package com.michaldrabik.ui_progress_movies.helpers\n\nimport androidx.recyclerview.widget.RecyclerView\nimport me.everything.android.ui.overscroll.adapters.RecyclerViewOverScrollDecorAdapter\n\nclass TopOverscrollAdapter(recycler: RecyclerView) : RecyclerViewOverScrollDecorAdapter(recycler) {\n  override fun isInAbsoluteEnd() = false\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/main/ProgressMoviesMainAdapter.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.michaldrabik.ui_progress_movies.main\n\nimport android.content.Context\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentManager\nimport androidx.fragment.app.FragmentStatePagerAdapter\nimport com.michaldrabik.ui_progress_movies.R\nimport com.michaldrabik.ui_progress_movies.calendar.CalendarMoviesFragment\nimport com.michaldrabik.ui_progress_movies.progress.ProgressMoviesFragment\n\nclass ProgressMoviesMainAdapter(\n  fragManager: FragmentManager,\n  private val context: Context,\n) : FragmentStatePagerAdapter(fragManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {\n\n  companion object {\n    const val PAGES_COUNT = 2\n  }\n\n  override fun getCount() = PAGES_COUNT\n\n  override fun getItem(position: Int): Fragment = when (position) {\n    0 -> ProgressMoviesFragment()\n    1 -> CalendarMoviesFragment()\n    else -> throw IllegalStateException(\"Unknown position\")\n  }\n\n  override fun getPageTitle(position: Int) =\n    when (position) {\n      0 -> context.getString(R.string.tabMoviesProgress)\n      1 -> context.getString(R.string.tabMoviesCalendar)\n      else -> throw IllegalStateException()\n    }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/main/ProgressMoviesMainFragment.kt",
    "content": "package com.michaldrabik.ui_progress_movies.main\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.activity.addCallback\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateMargins\nimport androidx.core.widget.doAfterTextChanged\nimport androidx.fragment.app.clearFragmentResultListener\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.viewpager.widget.ViewPager\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.OnScrollResetListener\nimport com.michaldrabik.ui_base.common.OnSearchClickListener\nimport com.michaldrabik.ui_base.common.OnShowsMoviesSyncedListener\nimport com.michaldrabik.ui_base.common.OnTabReselectedListener\nimport com.michaldrabik.ui_base.common.sheets.context_menu.ContextMenuBottomSheet\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Operation\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Type\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.add\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.disableUi\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.enableUi\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.hideKeyboard\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.nextPage\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.showKeyboard\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_MOVIE_ID\nimport com.michaldrabik.ui_progress_movies.R\nimport com.michaldrabik.ui_progress_movies.databinding.FragmentProgressMainMoviesBinding\nimport dagger.hilt.android.AndroidEntryPoint\nimport timber.log.Timber\n\n@AndroidEntryPoint\nclass ProgressMoviesMainFragment :\n  BaseFragment<ProgressMoviesMainViewModel>(R.layout.fragment_progress_main_movies),\n  OnShowsMoviesSyncedListener,\n  OnTabReselectedListener {\n\n  companion object {\n    private const val TRANSLATION_DURATION = 225L\n  }\n\n  override val navigationId = R.id.progressMoviesMainFragment\n\n  override val viewModel by viewModels<ProgressMoviesMainViewModel>()\n  private val binding by viewBinding(FragmentProgressMainMoviesBinding::bind)\n\n  private var searchViewTranslation = 0F\n  private var tabsTranslation = 0F\n  private var sideIconTranslation = 0F\n  private var currentPage = 0\n  private var isSearching = false\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    savedInstanceState?.let {\n      searchViewTranslation = it.getFloat(\"ARG_SEARCH_POSITION\")\n      tabsTranslation = it.getFloat(\"ARG_TABS_POSITION\")\n      sideIconTranslation = it.getFloat(\"ARG_SIDE_ICON_POSITION\")\n      currentPage = it.getInt(\"ARG_PAGE\")\n    }\n  }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupPager()\n    setupStatusBar()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      doAfterLaunch = { viewModel.loadProgress() }\n    )\n  }\n\n  override fun onSaveInstanceState(outState: Bundle) {\n    super.onSaveInstanceState(outState)\n    outState.putFloat(\"ARG_SEARCH_POSITION\", searchViewTranslation)\n    outState.putFloat(\"ARG_TABS_POSITION\", tabsTranslation)\n    outState.putFloat(\"ARG_SIDE_ICON_POSITION\", sideIconTranslation)\n    outState.putInt(\"ARG_PAGE\", currentPage)\n  }\n\n  override fun onResume() {\n    super.onResume()\n    showNavigation()\n  }\n\n  override fun onPause() {\n    enableUi()\n    with(binding) {\n      searchViewTranslation = progressMoviesSearchView.translationY\n      tabsTranslation = progressMoviesTabs.translationY\n      sideIconTranslation = progressMoviesSideIcons.translationY\n    }\n    super.onPause()\n  }\n\n  private fun setupView() {\n    with(binding) {\n      with(progressMoviesCalendarIcon) {\n        visibleIf(currentPage == 1)\n        onClick { toggleCalendarMode() }\n      }\n\n      with(progressMoviesSearchIcon) {\n        onClick { if (!isSearching) enterSearch() else exitSearch() }\n      }\n\n      with(progressMoviesSearchView) {\n        hint = getString(R.string.textSearchFor)\n        settingsIconVisible = true\n        traktIconVisible = true\n        isClickable = false\n        onClick { openMainSearch() }\n        onSettingsClickListener = { openSettings() }\n        onTraktClickListener = { navigateTo(R.id.actionProgressMoviesFragmentToTraktSyncFragment) }\n      }\n\n      with(progressMoviesModeTabs) {\n        visibleIf(moviesEnabled)\n        onModeSelected = { mode = it }\n        selectMovies()\n      }\n\n      with(progressMoviesSearchLocalView) {\n        onCloseClickListener = { exitSearch() }\n      }\n\n      progressMoviesTabs.translationY = tabsTranslation\n      progressMoviesModeTabs.translationY = tabsTranslation\n      progressMoviesSearchView.translationY = searchViewTranslation\n      progressMoviesSideIcons.translationY = sideIconTranslation\n    }\n  }\n\n  private fun setupPager() {\n    with(binding) {\n      progressMoviesPager.run {\n        offscreenPageLimit = ProgressMoviesMainAdapter.PAGES_COUNT\n        adapter = ProgressMoviesMainAdapter(childFragmentManager, requireContext())\n        addOnPageChangeListener(pageChangeListener)\n      }\n      progressMoviesTabs.setupWithViewPager(progressMoviesPager)\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      progressMoviesRoot.doOnApplyWindowInsets { _, insets, _, _ ->\n        val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n        val statusBarSize = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n        (progressMoviesSearchView.layoutParams as ViewGroup.MarginLayoutParams)\n          .updateMargins(top = statusBarSize + dimenToPx(R.dimen.spaceMedium))\n        (progressMoviesModeTabs.layoutParams as ViewGroup.MarginLayoutParams)\n          .updateMargins(top = statusBarSize + dimenToPx(R.dimen.collectionTabsMargin))\n        (progressMoviesSearchLocalView.layoutParams as ViewGroup.MarginLayoutParams)\n          .updateMargins(top = statusBarSize + dimenToPx(R.dimen.progressMoviesSearchLocalViewPadding))\n        arrayOf(progressMoviesSideIcons, progressMoviesTabs).forEach {\n          val margin = statusBarSize + dimenToPx(R.dimen.progressMoviesSearchViewPadding)\n          (it.layoutParams as ViewGroup.MarginLayoutParams).updateMargins(top = margin)\n        }\n      }\n    }\n  }\n\n  override fun setupBackPressed() {\n    val dispatcher = requireActivity().onBackPressedDispatcher\n    dispatcher.addCallback(viewLifecycleOwner) {\n      if (isSearching) {\n        exitSearch()\n      } else {\n        isEnabled = false\n        activity?.onBackPressed()\n      }\n    }\n  }\n\n  fun openMovieDetails(movie: Movie) {\n    hideNavigation()\n    binding.progressMoviesRoot.fadeOut(150) {\n      val bundle = Bundle().apply { putLong(ARG_MOVIE_ID, movie.ids.trakt.id) }\n      navigateTo(R.id.actionProgressMoviesFragmentToMovieDetailsFragment, bundle)\n      exitSearch()\n    }.add(animations)\n  }\n\n  fun openMovieMenu(movie: Movie, showPinButtons: Boolean = true) {\n    setFragmentResultListener(NavigationArgs.REQUEST_ITEM_MENU) { requestKey, _ ->\n      if (requestKey == NavigationArgs.REQUEST_ITEM_MENU) {\n        viewModel.loadProgress()\n      }\n      clearFragmentResultListener(NavigationArgs.REQUEST_ITEM_MENU)\n    }\n    val bundle = ContextMenuBottomSheet.createBundle(movie.ids.trakt, showPinButtons)\n    navigateToSafe(R.id.actionProgressMoviesFragmentToItemMenu, bundle)\n  }\n\n  fun openRateDialog(movie: Movie) {\n    setFragmentResultListener(NavigationArgs.REQUEST_RATING) { _, bundle ->\n      when (bundle.getParcelable<Operation>(NavigationArgs.RESULT)) {\n        Operation.SAVE -> showSnack(MessageEvent.Info(R.string.textRateSaved))\n        Operation.REMOVE -> showSnack(MessageEvent.Info(R.string.textRateRemoved))\n        else -> Timber.w(\"Unknown result.\")\n      }\n      viewModel.setWatchedMovie(movie)\n    }\n    val bundle = RatingsBottomSheet.createBundle(movie.ids.trakt, Type.MOVIE)\n    navigateTo(R.id.actionProgressMoviesFragmentToRating, bundle)\n  }\n\n  private fun openSettings() {\n    hideNavigation()\n    exitSearch()\n    navigateTo(R.id.actionProgressMoviesFragmentToSettingsFragment)\n  }\n\n  fun openTraktSync() {\n    hideNavigation()\n    exitSearch()\n    navigateTo(R.id.actionProgressMoviesFragmentToTraktSyncFragment)\n  }\n\n  private fun openMainSearch() {\n    disableUi()\n    hideNavigation()\n    with(binding) {\n      progressMoviesModeTabs.fadeOut(duration = 200).add(animations)\n      progressMoviesTabs.fadeOut(duration = 200).add(animations)\n      progressMoviesSideIcons.fadeOut(duration = 200).add(animations)\n      progressMoviesPager.fadeOut(duration = 200) {\n        navigateToSafe(R.id.actionProgressMoviesFragmentToSearch)\n      }.add(animations)\n    }\n  }\n\n  private fun enterSearch() {\n    binding.progressMoviesSearchLocalView.fadeIn(150)\n    resetTranslations()\n    with(binding.progressMoviesSearchLocalView.binding.searchViewLocalInput) {\n      setText(\"\")\n      doAfterTextChanged { viewModel.onSearchQuery(it?.toString() ?: \"\") }\n      visible()\n      showKeyboard()\n      requestFocus()\n    }\n    isSearching = true\n    childFragmentManager.fragments.forEach { (it as? OnSearchClickListener)?.onEnterSearch() }\n  }\n\n  private fun exitSearch() {\n    isSearching = false\n    childFragmentManager.fragments.forEach { (it as? OnSearchClickListener)?.onExitSearch() }\n    binding.progressMoviesSearchLocalView.gone()\n    resetTranslations()\n    with(binding.progressMoviesSearchLocalView.binding.searchViewLocalInput) {\n      setText(\"\")\n      gone()\n      hideKeyboard()\n      clearFocus()\n    }\n  }\n\n  fun toggleCalendarMode() {\n    exitSearch()\n    onScrollReset()\n    resetTranslations()\n    viewModel.toggleCalendarMode()\n  }\n\n  override fun onShowsMoviesSyncFinished() = viewModel.loadProgress()\n\n  override fun onTabReselected() {\n    if (view == null) return\n    resetTranslations(duration = 0)\n    binding.progressMoviesPager.nextPage()\n    onScrollReset()\n  }\n\n  fun resetTranslations(duration: Long = TRANSLATION_DURATION) {\n    if (view == null) return\n    with(binding) {\n      arrayOf(\n        progressMoviesSearchView,\n        progressMoviesTabs,\n        progressMoviesModeTabs,\n        progressMoviesSideIcons,\n        progressMoviesSearchLocalView\n      ).forEach {\n        it.animate().translationY(0F).setDuration(duration).add(animations)?.start()\n      }\n    }\n  }\n\n  private fun onScrollReset() =\n    childFragmentManager.fragments.forEach { (it as? OnScrollResetListener)?.onScrollReset() }\n\n  private fun render(uiState: ProgressMoviesMainUiState) {\n    with(binding) {\n      progressMoviesSearchView.setTraktProgress(uiState.isSyncing, withIcon = true)\n      progressMoviesSearchView.isEnabled = !uiState.isSyncing\n      when (uiState.calendarMode) {\n        CalendarMode.PRESENT_FUTURE -> progressMoviesCalendarIcon.setImageResource(R.drawable.ic_history)\n        CalendarMode.RECENTS -> progressMoviesCalendarIcon.setImageResource(R.drawable.ic_calendar)\n        else -> Unit\n      }\n    }\n  }\n\n  private val pageChangeListener = object : ViewPager.OnPageChangeListener {\n    override fun onPageSelected(position: Int) {\n      if (currentPage == position) return\n\n      binding.progressMoviesCalendarIcon.fadeIf(position == 1, duration = 150)\n      if (binding.progressMoviesTabs.translationY != 0F) {\n        resetTranslations()\n        requireView().postDelayed({ onScrollReset() }, TRANSLATION_DURATION)\n      }\n\n      currentPage = position\n    }\n\n    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) = Unit\n    override fun onPageScrollStateChanged(state: Int) = Unit\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/main/ProgressMoviesMainUiEvents.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_progress_movies.main\n\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.Movie\n\ndata class MovieCheckActionUiEvent(\n  val movie: Movie,\n  val isQuickRate: Boolean,\n) : Event<Movie>(movie)\n\nobject RequestWidgetsUpdate : Event<Unit>(Unit)\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/main/ProgressMoviesMainUiState.kt",
    "content": "package com.michaldrabik.ui_progress_movies.main\n\nimport com.michaldrabik.ui_model.CalendarMode\n\ndata class ProgressMoviesMainUiState(\n  val timestamp: Long? = null,\n  val searchQuery: String? = null,\n  val calendarMode: CalendarMode? = null,\n  val isSyncing: Boolean = false,\n)\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/main/ProgressMoviesMainViewModel.kt",
    "content": "package com.michaldrabik.ui_progress_movies.main\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkInfo\nimport androidx.work.WorkManager\nimport com.michaldrabik.ui_base.events.Event\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.TraktSyncAuthError\nimport com.michaldrabik.ui_base.events.TraktSyncError\nimport com.michaldrabik.ui_base.events.TraktSyncSuccess\nimport com.michaldrabik.ui_base.trakt.TraktSyncWorker\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_progress_movies.main.cases.ProgressMoviesMainCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ProgressMoviesMainViewModel @Inject constructor(\n  private val moviesCase: ProgressMoviesMainCase,\n  private val eventsManager: EventsManager,\n  workManager: WorkManager,\n) : ViewModel() {\n\n  private val timestampState = MutableStateFlow<Long?>(null)\n  private val searchQueryState = MutableStateFlow<String?>(null)\n  private val calendarModeState = MutableStateFlow<CalendarMode?>(null)\n  private val syncingState = MutableStateFlow(false)\n\n  private var calendarMode = CalendarMode.PRESENT_FUTURE\n\n  init {\n    viewModelScope.launch {\n      eventsManager.events.collect { onEvent(it) }\n    }\n    workManager.getWorkInfosByTagLiveData(TraktSyncWorker.TAG_ID).observeForever { work ->\n      syncingState.value = work.any { it.state == WorkInfo.State.RUNNING }\n    }\n  }\n\n  fun loadProgress() {\n    viewModelScope.launch {\n      timestampState.value = System.currentTimeMillis()\n      calendarModeState.value = calendarMode\n    }\n  }\n\n  fun onSearchQuery(searchQuery: String) {\n    searchQueryState.value = searchQuery\n  }\n\n  fun toggleCalendarMode() {\n    calendarMode = when (calendarMode) {\n      CalendarMode.PRESENT_FUTURE -> CalendarMode.RECENTS\n      CalendarMode.RECENTS -> CalendarMode.PRESENT_FUTURE\n    }\n    calendarModeState.value = calendarMode\n  }\n\n  fun setWatchedMovie(movie: Movie) {\n    viewModelScope.launch {\n      moviesCase.addToMyMovies(movie)\n      timestampState.value = System.currentTimeMillis()\n    }\n  }\n\n  private fun onEvent(event: Event) {\n    if (event in arrayOf(TraktSyncError, TraktSyncAuthError, TraktSyncSuccess)) {\n      loadProgress()\n    }\n  }\n\n  val uiState = combine(\n    timestampState,\n    searchQueryState,\n    calendarModeState,\n    syncingState\n  ) { s1, s2, s3, s4 ->\n    ProgressMoviesMainUiState(\n      timestamp = s1,\n      searchQuery = s2,\n      calendarMode = s3,\n      isSyncing = s4\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ProgressMoviesMainUiState()\n  )\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/main/cases/ProgressMoviesMainCase.kt",
    "content": "package com.michaldrabik.ui_progress_movies.main.cases\n\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Movie\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass ProgressMoviesMainCase @Inject constructor(\n  private val moviesRepository: MoviesRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val quickSyncManager: QuickSyncManager\n) {\n\n  suspend fun addToMyMovies(movie: Movie) {\n    moviesRepository.myMovies.insert(movie.ids.trakt)\n    pinnedItemsRepository.removePinnedItem(movie)\n    quickSyncManager.scheduleMovies(listOf(movie.ids.trakt.id))\n  }\n\n  suspend fun addToMyMovies(movieId: IdTrakt) {\n    val movie = Movie.EMPTY.copy(Ids.EMPTY.copy(trakt = movieId))\n    addToMyMovies(movie)\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/progress/ProgressMoviesFragment.kt",
    "content": "package com.michaldrabik.ui_progress_movies.progress\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateMargins\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.OnScrollResetListener\nimport com.michaldrabik.ui_base.common.OnSearchClickListener\nimport com.michaldrabik.ui_base.common.WidgetsProvider\nimport com.michaldrabik.ui_base.common.sheets.sort_order.SortOrderBottomSheet\nimport com.michaldrabik.ui_base.utilities.NavigationHost\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.add\nimport com.michaldrabik.ui_base.utilities.extensions.bump\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.withSpanSizeLookup\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortOrder.DATE_ADDED\nimport com.michaldrabik.ui_model.SortOrder.NAME\nimport com.michaldrabik.ui_model.SortOrder.NEWEST\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_SORT_ORDER\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_SORT_TYPE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_SORT_ORDER\nimport com.michaldrabik.ui_progress_movies.R\nimport com.michaldrabik.ui_progress_movies.databinding.FragmentProgressMoviesBinding\nimport com.michaldrabik.ui_progress_movies.helpers.ProgressMoviesLayoutManagerProvider\nimport com.michaldrabik.ui_progress_movies.helpers.TopOverscrollAdapter\nimport com.michaldrabik.ui_progress_movies.main.MovieCheckActionUiEvent\nimport com.michaldrabik.ui_progress_movies.main.ProgressMoviesMainFragment\nimport com.michaldrabik.ui_progress_movies.main.ProgressMoviesMainViewModel\nimport com.michaldrabik.ui_progress_movies.main.RequestWidgetsUpdate\nimport com.michaldrabik.ui_progress_movies.progress.recycler.ProgressMovieListItem.FiltersItem\nimport com.michaldrabik.ui_progress_movies.progress.recycler.ProgressMovieListItem.HeaderItem\nimport com.michaldrabik.ui_progress_movies.progress.recycler.ProgressMovieListItem.MovieItem\nimport com.michaldrabik.ui_progress_movies.progress.recycler.ProgressMoviesAdapter\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport me.everything.android.ui.overscroll.IOverScrollDecor\nimport me.everything.android.ui.overscroll.IOverScrollState.STATE_BOUNCE_BACK\nimport me.everything.android.ui.overscroll.IOverScrollState.STATE_DRAG_START_SIDE\nimport me.everything.android.ui.overscroll.OverScrollBounceEffectDecoratorBase\nimport me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ProgressMoviesFragment :\n  BaseFragment<ProgressMoviesViewModel>(R.layout.fragment_progress_movies),\n  OnSearchClickListener,\n  OnScrollResetListener {\n\n  private companion object {\n    const val OVERSCROLL_OFFSET = 225F\n    const val OVERSCROLL_OFFSET_TRANSLATION = 4.5F\n  }\n\n  @Inject lateinit var settings: SettingsViewModeRepository\n\n  private val parentViewModel by viewModels<ProgressMoviesMainViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<ProgressMoviesViewModel>()\n\n  private val binding by viewBinding(FragmentProgressMoviesBinding::bind)\n\n  private var adapter: ProgressMoviesAdapter? = null\n  private var layoutManager: LayoutManager? = null\n  private var statusBarHeight = 0\n  private var overscroll: IOverScrollDecor? = null\n  private var overscrollJob: Job? = null\n  private var overscrollEnabled = true\n  private var isSearching = false\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupRecycler()\n    setupStatusBar()\n\n    viewLifecycleOwner.lifecycleScope.launch {\n      viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {\n        with(parentViewModel) {\n          launch { uiState.collect { viewModel.onParentState(it) } }\n        }\n        with(viewModel) {\n          launch { uiState.collect { render(it) } }\n          launch { messageFlow.collect { showSnack(it) } }\n          launch { eventFlow.collect { handleEvent(it) } }\n        }\n      }\n    }\n  }\n\n  private fun setupView() {\n    with(binding) {\n      progressMoviesEmptyView.progressMoviesEmptyTraktButton.onClick { requireMainFragment().openTraktSync() }\n      progressMoviesEmptyView.progressMoviesEmptyDiscoverButton.onClick {\n        (requireActivity() as NavigationHost).navigateToDiscover()\n      }\n    }\n  }\n\n  private fun setupRecycler() {\n    val gridSpanSize = settings.tabletGridSpanSize\n    layoutManager = ProgressMoviesLayoutManagerProvider.provideLayoutManger(requireContext(), gridSpanSize)\n    (layoutManager as? GridLayoutManager)?.run {\n      withSpanSizeLookup { position ->\n        when (adapter?.getItems()?.get(position)) {\n          is HeaderItem -> gridSpanSize\n          is FiltersItem -> gridSpanSize\n          is MovieItem -> 1\n          else -> throw IllegalStateException()\n        }\n      }\n    }\n    adapter = ProgressMoviesAdapter(\n      itemClickListener = { requireMainFragment().openMovieDetails(it.movie) },\n      itemLongClickListener = { requireMainFragment().openMovieMenu(it.movie) },\n      sortChipClickListener = ::openSortOrderDialog,\n      missingImageListener = viewModel::findMissingImage,\n      missingTranslationListener = viewModel::findMissingTranslation,\n      checkClickListener = { viewModel.onMovieChecked(it.movie) },\n      listChangeListener = {\n        requireMainFragment().resetTranslations()\n        layoutManager?.scrollToPosition(0)\n      }\n    )\n    binding.progressMoviesMainRecycler.apply {\n      adapter = this@ProgressMoviesFragment.adapter\n      layoutManager = this@ProgressMoviesFragment.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      setHasFixedSize(true)\n    }\n  }\n\n  private fun setupOverscroll() {\n    if (overscroll != null || view == null) {\n      return\n    }\n    overscroll = VerticalOverScrollBounceEffectDecorator(\n      TopOverscrollAdapter(binding.progressMoviesMainRecycler),\n      1F,\n      OverScrollBounceEffectDecoratorBase.DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK,\n      OverScrollBounceEffectDecoratorBase.DEFAULT_DECELERATE_FACTOR\n    ).apply {\n      setOverScrollUpdateListener { _, state, offset ->\n        binding.progressMoviesOverscroll?.run {\n          if (offset > 0) {\n            val value = (offset / OVERSCROLL_OFFSET).coerceAtMost(1F)\n            val valueTranslation = offset / OVERSCROLL_OFFSET_TRANSLATION\n            if (value >= 1F) {\n              onOverscrollReach()\n            } else {\n              onOverscrollCancel()\n            }\n            when (state) {\n              STATE_DRAG_START_SIDE -> {\n                alpha = value\n                scaleX = value\n                scaleY = value\n                translationY = valueTranslation\n                overscrollEnabled = true\n              }\n              STATE_BOUNCE_BACK -> {\n                alpha = value\n                scaleX = value\n                scaleY = value\n                translationY = valueTranslation\n                if (offset >= OVERSCROLL_OFFSET &&\n                  overscrollEnabled &&\n                  binding.progressMoviesOverscrollProgress.progress >= 100\n                ) {\n                  overscrollEnabled = false\n                  viewModel.startTraktSync()\n                }\n              }\n            }\n          } else {\n            alpha = 0F\n            scaleX = 0F\n            scaleY = 0F\n            translationY = 0F\n            onOverscrollCancel()\n          }\n        }\n      }\n    }\n  }\n\n  private fun onOverscrollReach() {\n    if (overscrollJob != null) return\n    overscrollJob = viewLifecycleOwner.lifecycleScope.launch {\n      repeat(100) {\n        val progress = it + 1\n        binding.progressMoviesOverscrollProgress.progress = progress\n        if (progress >= 100) {\n          binding.progressMoviesOverscroll.bump(200)\n        }\n        delay(5)\n      }\n    }\n  }\n\n  private fun onOverscrollCancel() {\n    overscrollJob?.cancel()\n    overscrollJob = null\n    binding.progressMoviesOverscrollProgress.progress = 0\n  }\n\n  private fun setupStatusBar() {\n    if (statusBarHeight != 0) {\n      binding.progressMoviesMainRecycler.updatePadding(top = statusBarHeight + dimenToPx(R.dimen.progressMoviesTabsViewPadding))\n      return\n    }\n    binding.progressMoviesMainRecycler.doOnApplyWindowInsets { view, insets, _, _ ->\n      val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n      statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n      view.updatePadding(top = statusBarHeight + dimenToPx(R.dimen.progressMoviesTabsViewPadding))\n      (binding.progressMoviesEmptyView.rootLayout.layoutParams as ViewGroup.MarginLayoutParams)\n        .updateMargins(top = statusBarHeight + dimenToPx(R.dimen.spaceBig))\n      (binding.progressMoviesOverscroll.layoutParams as ViewGroup.MarginLayoutParams)\n        .updateMargins(top = statusBarHeight + dimenToPx(R.dimen.progressMoviesOverscrollPadding))\n    }\n  }\n\n  private fun openSortOrderDialog(order: SortOrder, type: SortType) {\n    val options = listOf(NAME, RATING, USER_RATING, NEWEST, DATE_ADDED)\n    val args = SortOrderBottomSheet.createBundle(options, order, type)\n\n    requireParentFragment().setFragmentResultListener(REQUEST_SORT_ORDER) { _, bundle ->\n      val sortOrder = bundle.getSerializable(ARG_SELECTED_SORT_ORDER) as SortOrder\n      val sortType = bundle.getSerializable(ARG_SELECTED_SORT_TYPE) as SortType\n      viewModel.setSortOrder(sortOrder, sortType)\n    }\n\n    navigateTo(R.id.actionProgressMoviesFragmentToSortOrder, args)\n  }\n\n  override fun onEnterSearch() {\n    isSearching = true\n\n    binding.progressMoviesMainRecycler.translationY = dimenToPx(R.dimen.progressMoviesSearchLocalOffset).toFloat()\n    binding.progressMoviesMainRecycler.smoothScrollToPosition(0)\n\n    overscroll?.detach()\n    overscroll = null\n  }\n\n  override fun onExitSearch() {\n    isSearching = false\n\n    binding.progressMoviesMainRecycler.translationY = 0F\n    binding.progressMoviesMainRecycler.smoothScrollToPosition(0)\n\n    setupOverscroll()\n  }\n\n  override fun onScrollReset() = binding.progressMoviesMainRecycler.smoothScrollToPosition(0)\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is MovieCheckActionUiEvent -> {\n        if (event.isQuickRate) requireMainFragment().openRateDialog(event.movie)\n        else parentViewModel.setWatchedMovie(event.movie)\n      }\n      is RequestWidgetsUpdate -> {\n        (requireAppContext() as WidgetsProvider).requestMoviesWidgetsUpdate()\n      }\n    }\n  }\n\n  private fun render(uiState: ProgressMoviesUiState) {\n    uiState.run {\n      items?.let {\n        val resetScroll = scrollReset?.consume() == true\n        adapter?.setItems(it, resetScroll)\n        binding.progressMoviesEmptyView.rootLayout.fadeIf(items.isEmpty() && !isSearching)\n        binding.progressMoviesMainRecycler.fadeIn(\n          duration = 200,\n          withHardware = true\n        ).add(animations)\n      }\n      isOverScrollEnabled.let {\n        if (it) {\n          setupOverscroll()\n        } else {\n          overscroll?.detach()\n          overscroll = null\n        }\n      }\n      sortOrder?.let { event -> event.consume()?.let { openSortOrderDialog(it.first, it.second) } }\n    }\n  }\n\n  private fun requireMainFragment() = (requireParentFragment() as ProgressMoviesMainFragment)\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    overscrollJob?.cancel()\n    overscrollJob = null\n    overscroll = null\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/progress/ProgressMoviesUiState.kt",
    "content": "package com.michaldrabik.ui_progress_movies.progress\n\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_progress_movies.progress.recycler.ProgressMovieListItem\n\ndata class ProgressMoviesUiState(\n  val items: List<ProgressMovieListItem>? = null,\n  val scrollReset: Event<Boolean>? = null,\n  val sortOrder: Event<Pair<SortOrder, SortType>>? = null,\n  val isOverScrollEnabled: Boolean = false\n)\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/progress/ProgressMoviesViewModel.kt",
    "content": "package com.michaldrabik.ui_progress_movies.progress\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkManager\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.trakt.TraktSyncWorker\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_progress_movies.main.MovieCheckActionUiEvent\nimport com.michaldrabik.ui_progress_movies.main.ProgressMoviesMainUiState\nimport com.michaldrabik.ui_progress_movies.main.RequestWidgetsUpdate\nimport com.michaldrabik.ui_progress_movies.progress.cases.ProgressMoviesItemsCase\nimport com.michaldrabik.ui_progress_movies.progress.cases.ProgressMoviesPinnedCase\nimport com.michaldrabik.ui_progress_movies.progress.cases.ProgressMoviesSortCase\nimport com.michaldrabik.ui_progress_movies.progress.recycler.ProgressMovieListItem\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ProgressMoviesViewModel @Inject constructor(\n  private val itemsCase: ProgressMoviesItemsCase,\n  private val sortCase: ProgressMoviesSortCase,\n  private val pinnedCase: ProgressMoviesPinnedCase,\n  private val imagesProvider: MovieImagesProvider,\n  private val userTraktManager: UserTraktManager,\n  private val workManager: WorkManager,\n  private val settingsRepository: SettingsRepository,\n  private val translationsRepository: TranslationsRepository,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private var loadItemsJob: Job? = null\n\n  private val itemsState = MutableStateFlow<List<ProgressMovieListItem>?>(null)\n  private val scrollState = MutableStateFlow(Event(false))\n  private val sortOrderState = MutableStateFlow<Event<Pair<SortOrder, SortType>>?>(null)\n  private val overscrollState = MutableStateFlow(false)\n\n  private var searchQuery: String? = null\n  private var timestamp = 0L\n\n  fun onParentState(state: ProgressMoviesMainUiState) {\n    when {\n      this.timestamp != state.timestamp && state.timestamp != 0L -> {\n        this.timestamp = state.timestamp ?: 0L\n        loadItems()\n      }\n      this.searchQuery != state.searchQuery -> {\n        this.searchQuery = state.searchQuery\n        loadItems(resetScroll = state.searchQuery.isNullOrBlank())\n      }\n    }\n  }\n\n  fun onMovieChecked(movie: Movie) {\n    viewModelScope.launch {\n      val isQuickRate = isQuickRateEnabled()\n      eventChannel.send(MovieCheckActionUiEvent(movie, isQuickRate))\n    }\n  }\n\n  private fun loadItems(resetScroll: Boolean = false) {\n    loadItemsJob?.cancel()\n    loadItemsJob = viewModelScope.launch {\n      val items = itemsCase.loadItems(searchQuery ?: \"\")\n      itemsState.value = items\n      scrollState.value = Event(resetScroll)\n      overscrollState.value = userTraktManager.isAuthorized() && items.isNotEmpty()\n      eventChannel.send(RequestWidgetsUpdate)\n    }\n  }\n\n  fun findMissingImage(item: ProgressMovieListItem.MovieItem, force: Boolean) {\n    viewModelScope.launch {\n      updateItem(item.copy(isLoading = true))\n      try {\n        val image = imagesProvider.loadRemoteImage(item.movie, item.image.type, force)\n        updateItem(item.copy(image = image, isLoading = false))\n      } catch (t: Throwable) {\n        val unavailable = Image.createUnavailable(item.image.type)\n        updateItem(item.copy(image = unavailable, isLoading = false))\n      }\n    }\n  }\n\n  fun findMissingTranslation(item: ProgressMovieListItem.MovieItem) {\n    val language = translationsRepository.getLanguage()\n    if (item.translation != null || language == Config.DEFAULT_LANGUAGE) return\n    viewModelScope.launch {\n      try {\n        val translation = translationsRepository.loadTranslation(item.movie, language)\n        updateItem(item.copy(translation = translation))\n      } catch (error: Throwable) {\n        Timber.e(error)\n      }\n    }\n  }\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType) {\n    viewModelScope.launch {\n      sortCase.setSortOrder(sortOrder, sortType)\n      loadItems(resetScroll = true)\n    }\n  }\n\n  fun togglePinItem(item: ProgressMovieListItem.MovieItem) {\n    if (item.isPinned) {\n      pinnedCase.removePinnedItem(item.movie)\n    } else {\n      pinnedCase.addPinnedItem(item.movie)\n    }\n    loadItems(resetScroll = item.isPinned)\n  }\n\n  fun startTraktSync() {\n    TraktSyncWorker.scheduleOneOff(\n      workManager,\n      isImport = true,\n      isExport = true,\n      isSilent = false\n    )\n  }\n\n  private suspend fun isQuickRateEnabled(): Boolean {\n    val isSignedIn = userTraktManager.isAuthorized()\n    val isPremium = settingsRepository.isPremium\n    val isQuickRate = settingsRepository.load().traktQuickRateEnabled\n    return isPremium && isSignedIn && isQuickRate\n  }\n\n  private fun updateItem(new: ProgressMovieListItem.MovieItem) {\n    val currentItems = itemsState.value?.toMutableList() ?: mutableListOf()\n    currentItems.findReplace(new) { it isSameAs new }\n    itemsState.value = currentItems\n    scrollState.value = Event(false)\n  }\n\n  val uiState = combine(\n    itemsState,\n    scrollState,\n    sortOrderState,\n    overscrollState\n  ) { s1, s2, s3, s4 ->\n    ProgressMoviesUiState(\n      items = s1,\n      scrollReset = s2,\n      sortOrder = s3,\n      isOverScrollEnabled = s4\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ProgressMoviesUiState()\n  )\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/progress/cases/ProgressMoviesItemsCase.kt",
    "content": "package com.michaldrabik.ui_progress_movies.progress.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_progress_movies.helpers.ProgressMoviesItemsSorter\nimport com.michaldrabik.ui_progress_movies.progress.recycler.ProgressMovieListItem\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass ProgressMoviesItemsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val moviesRepository: MoviesRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val ratingsRepository: RatingsRepository,\n  private val settingsRepository: SettingsRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val imagesProvider: MovieImagesProvider,\n  private val dateFormatProvider: DateFormatProvider,\n  private val sorter: ProgressMoviesItemsSorter,\n) {\n\n  suspend fun loadItems(searchQuery: String) =\n    withContext(dispatchers.IO) {\n      val language = translationsRepository.getLanguage()\n      val dateFormat = dateFormatProvider.loadFullDayFormat()\n      val spoilers = settingsRepository.spoilers.getAll()\n\n      val sortOrder = settingsRepository.sorting.progressMoviesSortOrder\n      val sortType = settingsRepository.sorting.progressMoviesSortType\n\n      val watchlistMovies = moviesRepository.watchlistMovies.loadAll()\n      val items = watchlistMovies.map { movie ->\n        async {\n          val rating = ratingsRepository.movies.loadRatings(listOf(movie))\n          var translation: Translation? = null\n          if (language != Config.DEFAULT_LANGUAGE) {\n            translation = translationsRepository.loadTranslation(movie, language, onlyLocal = true)\n          }\n\n          ProgressMovieListItem.MovieItem(\n            movie = movie,\n            image = imagesProvider.findCachedImage(movie, ImageType.POSTER),\n            isLoading = false,\n            isPinned = pinnedItemsRepository.isItemPinned(movie),\n            translation = translation,\n            dateFormat = dateFormat,\n            sortOrder = sortOrder,\n            userRating = rating.firstOrNull()?.rating,\n            spoilers = spoilers\n          )\n        }\n      }.awaitAll()\n\n      val filtered = filterItems(searchQuery, items)\n      val sorted = filtered.sortedWith(sorter.sort(sortOrder, sortType))\n      val preparedItems = prepareItems(sorted)\n\n      if (preparedItems.isNotEmpty()) {\n        val filtersItem = loadFiltersItem(sortOrder, sortType)\n        listOf(filtersItem) + preparedItems\n      } else {\n        preparedItems\n      }\n    }\n\n  private fun loadFiltersItem(\n    sortOrder: SortOrder,\n    sortType: SortType,\n  ): ProgressMovieListItem.FiltersItem {\n    return ProgressMovieListItem.FiltersItem(\n      sortOrder = sortOrder,\n      sortType = sortType\n    )\n  }\n\n  private fun filterItems(query: String, items: List<ProgressMovieListItem.MovieItem>) =\n    items.filter {\n      it.movie.title.contains(query, true) ||\n        it.translation?.title?.contains(query, true) == true\n    }\n\n  private fun prepareItems(items: List<ProgressMovieListItem.MovieItem>) =\n    items\n      .asSequence()\n      .filter { !it.movie.hasNoDate() }\n      .filter { it.movie.released == null || it.movie.hasAired() }\n      .sortedByDescending { it.isPinned }\n      .toList()\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/progress/cases/ProgressMoviesPinnedCase.kt",
    "content": "package com.michaldrabik.ui_progress_movies.progress.cases\n\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.ui_model.Movie\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ProgressMoviesPinnedCase @Inject constructor(\n  private val pinnedItemsRepository: PinnedItemsRepository\n) {\n\n  fun addPinnedItem(item: Movie) =\n    pinnedItemsRepository.addPinnedItem(item)\n\n  fun removePinnedItem(item: Movie) =\n    pinnedItemsRepository.removePinnedItem(item)\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/progress/cases/ProgressMoviesSortCase.kt",
    "content": "package com.michaldrabik.ui_progress_movies.progress.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ProgressMoviesSortCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType) {\n    settingsRepository.sorting.progressMoviesSortOrder = sortOrder\n    settingsRepository.sorting.progressMoviesSortType = sortType\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/progress/recycler/ProgressMovieItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_progress_movies.progress.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass ProgressMovieItemDiffCallback : DiffUtil.ItemCallback<ProgressMovieListItem>() {\n\n  override fun areItemsTheSame(oldItem: ProgressMovieListItem, newItem: ProgressMovieListItem): Boolean {\n    val areMovies = oldItem is ProgressMovieListItem.MovieItem && newItem is ProgressMovieListItem.MovieItem\n    val areFilters = oldItem is ProgressMovieListItem.FiltersItem && newItem is ProgressMovieListItem.FiltersItem\n\n    return when {\n      areMovies -> areItemsTheSame(\n        (oldItem as ProgressMovieListItem.MovieItem),\n        (newItem as ProgressMovieListItem.MovieItem)\n      )\n      areFilters -> true\n      else -> false\n    }\n  }\n\n  override fun areContentsTheSame(oldItem: ProgressMovieListItem, newItem: ProgressMovieListItem): Boolean {\n    return when (oldItem) {\n      is ProgressMovieListItem.MovieItem -> areContentsTheSame(oldItem, (newItem as ProgressMovieListItem.MovieItem))\n      is ProgressMovieListItem.FiltersItem -> areContentsTheSame(oldItem, (newItem as ProgressMovieListItem.FiltersItem))\n      is ProgressMovieListItem.HeaderItem -> true\n    }\n  }\n\n  private fun areItemsTheSame(\n    oldItem: ProgressMovieListItem.MovieItem,\n    newItem: ProgressMovieListItem.MovieItem,\n  ): Boolean {\n    return oldItem.movie.ids.trakt == newItem.movie.ids.trakt\n  }\n\n  private fun areContentsTheSame(\n    oldItem: ProgressMovieListItem.MovieItem,\n    newItem: ProgressMovieListItem.MovieItem,\n  ): Boolean {\n    return oldItem.isPinned == newItem.isPinned &&\n      oldItem.image == newItem.image &&\n      oldItem.movie == newItem.movie &&\n      oldItem.sortOrder == newItem.sortOrder &&\n      oldItem.userRating == newItem.userRating &&\n      oldItem.spoilers == newItem.spoilers &&\n      oldItem.translation == newItem.translation\n  }\n\n  private fun areContentsTheSame(\n    oldItem: ProgressMovieListItem.FiltersItem,\n    newItem: ProgressMovieListItem.FiltersItem,\n  ): Boolean {\n    return oldItem.sortOrder == newItem.sortOrder &&\n      oldItem.sortType == newItem.sortType\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/progress/recycler/ProgressMovieListItem.kt",
    "content": "package com.michaldrabik.ui_progress_movies.progress.recycler\n\nimport androidx.annotation.StringRes\nimport com.michaldrabik.ui_base.common.MovieListItem\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.Translation\nimport java.time.format.DateTimeFormatter\n\nsealed class ProgressMovieListItem(\n  override val movie: Movie,\n  override val image: Image,\n  override val isLoading: Boolean = false,\n) : MovieListItem {\n\n  data class MovieItem(\n    override val movie: Movie,\n    override val image: Image,\n    override val isLoading: Boolean = false,\n    val isPinned: Boolean,\n    val translation: Translation? = null,\n    val dateFormat: DateTimeFormatter? = null,\n    val sortOrder: SortOrder? = null,\n    val userRating: Int? = null,\n    val spoilers: SpoilersSettings\n  ) : ProgressMovieListItem(movie, image, isLoading)\n\n  data class HeaderItem(\n    override val movie: Movie,\n    override val image: Image,\n    override val isLoading: Boolean = false,\n    @StringRes val textResId: Int,\n  ) : ProgressMovieListItem(movie, image, isLoading) {\n\n    companion object {\n      fun create(@StringRes textResId: Int) =\n        HeaderItem(\n          movie = Movie.EMPTY,\n          image = Image.createUnavailable(ImageType.POSTER),\n          textResId = textResId\n        )\n    }\n\n    override fun isSameAs(other: MovieListItem) =\n      textResId == (other as? HeaderItem)?.textResId\n  }\n\n  data class FiltersItem(\n    val sortOrder: SortOrder,\n    val sortType: SortType,\n  ) : ProgressMovieListItem(\n    movie = Movie.EMPTY,\n    image = Image.createUnknown(ImageType.POSTER),\n    isLoading = false\n  )\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/progress/recycler/ProgressMoviesAdapter.kt",
    "content": "package com.michaldrabik.ui_progress_movies.progress.recycler\n\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseMovieAdapter\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_progress_movies.progress.views.ProgressMoviesFiltersView\nimport com.michaldrabik.ui_progress_movies.progress.views.ProgressMoviesItemView\n\nclass ProgressMoviesAdapter(\n  private val itemClickListener: (ProgressMovieListItem.MovieItem) -> Unit,\n  private val itemLongClickListener: (ProgressMovieListItem.MovieItem) -> Unit,\n  private val sortChipClickListener: (SortOrder, SortType) -> Unit,\n  private val missingImageListener: (ProgressMovieListItem.MovieItem, Boolean) -> Unit,\n  private val missingTranslationListener: (ProgressMovieListItem.MovieItem) -> Unit,\n  private val checkClickListener: (ProgressMovieListItem.MovieItem) -> Unit,\n  listChangeListener: () -> Unit,\n) : BaseMovieAdapter<ProgressMovieListItem>(\n  listChangeListener = listChangeListener\n) {\n\n  companion object {\n    private const val VIEW_TYPE_MOVIE = 1\n    private const val VIEW_TYPE_FILTERS = 2\n  }\n\n  override val asyncDiffer = AsyncListDiffer(this, ProgressMovieItemDiffCallback())\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {\n    return when (viewType) {\n      VIEW_TYPE_MOVIE -> BaseViewHolder(\n        ProgressMoviesItemView(parent.context).apply {\n          itemClickListener = this@ProgressMoviesAdapter.itemClickListener\n          itemLongClickListener = this@ProgressMoviesAdapter.itemLongClickListener\n          checkClickListener = this@ProgressMoviesAdapter.checkClickListener\n          missingImageListener = this@ProgressMoviesAdapter.missingImageListener\n          missingTranslationListener = this@ProgressMoviesAdapter.missingTranslationListener\n        }\n      )\n      VIEW_TYPE_FILTERS -> BaseViewHolder(\n        ProgressMoviesFiltersView(parent.context).apply {\n          onSortChipClicked = this@ProgressMoviesAdapter.sortChipClickListener\n        }\n      )\n      else -> throw IllegalStateException()\n    }\n  }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    when (val item = asyncDiffer.currentList[position]) {\n      is ProgressMovieListItem.FiltersItem ->\n        (holder.itemView as ProgressMoviesFiltersView).bind(item.sortOrder, item.sortType)\n      is ProgressMovieListItem.MovieItem ->\n        (holder.itemView as ProgressMoviesItemView).bind(item)\n      else -> throw IllegalStateException()\n    }\n  }\n\n  override fun getItemViewType(position: Int): Int {\n    return when (asyncDiffer.currentList[position]) {\n      is ProgressMovieListItem.MovieItem -> VIEW_TYPE_MOVIE\n      is ProgressMovieListItem.FiltersItem -> VIEW_TYPE_FILTERS\n      else -> throw IllegalStateException()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/progress/views/ProgressMoviesFiltersView.kt",
    "content": "package com.michaldrabik.ui_progress_movies.progress.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport androidx.core.content.ContextCompat\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SortType.ASCENDING\nimport com.michaldrabik.ui_model.SortType.DESCENDING\nimport com.michaldrabik.ui_progress_movies.R\nimport com.michaldrabik.ui_progress_movies.databinding.ViewProgressMoviesFiltersBinding\n\nclass ProgressMoviesFiltersView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewProgressMoviesFiltersBinding.inflate(LayoutInflater.from(context), this)\n\n  var onSortChipClicked: ((SortOrder, SortType) -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n  }\n\n  fun bind(\n    sortOrder: SortOrder,\n    sortType: SortType,\n  ) {\n    with(binding) {\n      val sortIcon = when (sortType) {\n        ASCENDING -> R.drawable.ic_arrow_alt_up\n        DESCENDING -> R.drawable.ic_arrow_alt_down\n      }\n      progressFiltersSortingChip.closeIcon = ContextCompat.getDrawable(context, sortIcon)\n      progressFiltersSortingChip.text = context.getText(sortOrder.displayString)\n      progressFiltersSortingChip.onClick {\n        onSortChipClicked?.invoke(sortOrder, sortType)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/java/com/michaldrabik/ui_progress_movies/progress/views/ProgressMoviesItemView.kt",
    "content": "package com.michaldrabik.ui_progress_movies.progress.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_RATINGS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.ui_base.common.views.MovieView\nimport com.michaldrabik.ui_base.utilities.extensions.addRipple\nimport com.michaldrabik.ui_base.utilities.extensions.bump\nimport com.michaldrabik.ui_base.utilities.extensions.colorStateListFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.expandTouch\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.SortOrder.RATING\nimport com.michaldrabik.ui_model.SortOrder.USER_RATING\nimport com.michaldrabik.ui_progress_movies.R\nimport com.michaldrabik.ui_progress_movies.databinding.ViewProgressMoviesMainItemBinding\nimport com.michaldrabik.ui_progress_movies.progress.recycler.ProgressMovieListItem\nimport java.util.Locale\n\n@SuppressLint(\"SetTextI18n\")\nclass ProgressMoviesItemView : MovieView<ProgressMovieListItem.MovieItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewProgressMoviesMainItemBinding.inflate(LayoutInflater.from(context), this)\n\n  var checkClickListener: ((ProgressMovieListItem.MovieItem) -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    addRipple()\n    binding.progressMovieItemCheckButton.expandTouch(100)\n    onClick { itemClickListener?.invoke(item) }\n    onLongClick { itemLongClickListener?.invoke(item) }\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  private lateinit var item: ProgressMovieListItem.MovieItem\n\n  override val imageView: ImageView = binding.progressMovieItemImage\n  override val placeholderView: ImageView = binding.progressMovieItemPlaceholder\n\n  override fun bind(item: ProgressMovieListItem.MovieItem) {\n    this.item = item\n    clear()\n\n    with(binding) {\n      val translationTitle = item.translation?.title\n      progressMovieItemTitle.text =\n        if (translationTitle.isNullOrBlank()) item.movie.title\n        else translationTitle\n\n      bindDescription(item)\n      bindRating(item)\n\n      progressMovieItemPin.visibleIf(item.isPinned)\n      progressMovieItemCheckButton.onClick {\n        it.bump { checkClickListener?.invoke(item) }\n      }\n\n      loadImage(item)\n    }\n  }\n\n  private fun bindDescription(item: ProgressMovieListItem.MovieItem) {\n    var description = if (item.translation?.overview.isNullOrBlank()) {\n      item.movie.overview.ifBlank { context.getString(R.string.textNoDescription) }\n    } else {\n      item.translation?.overview\n    }\n\n    with(binding) {\n      if (item.spoilers.isWatchlistMoviesHidden) {\n        progressMovieItemSubtitle.tag = description\n        description = SPOILERS_REGEX.replace(description.toString(), SPOILERS_HIDE_SYMBOL)\n\n        if (item.spoilers.isTapToReveal) {\n          progressMovieItemSubtitle.onClick { view ->\n            view.tag?.let { progressMovieItemSubtitle.text = it.toString() }\n            view.isClickable = false\n          }\n        }\n      }\n\n      progressMovieItemSubtitle.text = description\n    }\n  }\n\n  private fun bindRating(item: ProgressMovieListItem.MovieItem) {\n    with(binding) {\n      when (item.sortOrder) {\n        RATING -> {\n          progressMovieItemRating.visible()\n          progressMovieItemRatingStar.visible()\n          progressMovieItemRatingStar.imageTintList = context.colorStateListFromAttr(android.R.attr.colorAccent)\n          val rating = String.format(Locale.ENGLISH, \"%.1f\", item.movie.rating)\n          if (item.spoilers.isMyShowsRatingsHidden) {\n            progressMovieItemRating.tag = rating\n            progressMovieItemRating.text = SPOILERS_RATINGS_HIDE_SYMBOL\n            if (item.spoilers.isTapToReveal) {\n              progressMovieItemRating.onClick { view ->\n                view.tag?.let {\n                  progressMovieItemRating.text = it.toString()\n                }\n                view.isClickable = false\n              }\n            }\n          } else {\n            progressMovieItemRating.text = rating\n          }\n        }\n        USER_RATING -> {\n          val hasRating = item.userRating != null\n          progressMovieItemRating.visibleIf(hasRating)\n          progressMovieItemRatingStar.visibleIf(hasRating)\n          progressMovieItemRatingStar.imageTintList = context.colorStateListFromAttr(android.R.attr.textColorPrimary)\n          progressMovieItemRating.text = String.format(Locale.ENGLISH, \"%d\", item.userRating)\n        }\n        else -> {\n          progressMovieItemRating.gone()\n          progressMovieItemRatingStar.gone()\n        }\n      }\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      progressMovieItemTitle.text = \"\"\n      progressMovieItemSubtitle.text = \"\"\n      progressMovieItemPlaceholder.gone()\n      Glide.with(this@ProgressMoviesItemView).clear(progressMovieItemImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/layout/fragment_calendar_movies.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  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/progressMoviesCalendarRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/progressMoviesCalendarRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/transparent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/progressMoviesRecyclerHorizontalSpace\"\n    android:paddingTop=\"@dimen/progressMoviesCalendarTabsViewPadding\"\n    android:paddingEnd=\"@dimen/progressMoviesRecyclerHorizontalSpace\"\n    android:paddingBottom=\"@dimen/bottomNavigationHeightPadded\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <include\n    android:id=\"@+id/progressMoviesCalendarEmptyRecentsView\"\n    layout=\"@layout/layout_calendar_movies_recents_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <include\n    android:id=\"@+id/progressMoviesCalendarEmptyFutureView\"\n    layout=\"@layout/layout_calendar_movies_future_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:visibility=\"gone\"\n    />\n\n  <ImageView\n    android:id=\"@+id/progressMoviesCalendarOverscrollIcon\"\n    android:layout_width=\"@dimen/progressMoviesOverscrollIcon\"\n    android:layout_height=\"@dimen/progressMoviesOverscrollIcon\"\n    android:layout_gravity=\"center_horizontal\"\n    android:layout_marginTop=\"@dimen/progressMoviesOverscrollPadding\"\n    android:alpha=\"0\"\n    android:scaleX=\"0\"\n    android:scaleY=\"0\"\n    app:srcCompat=\"@drawable/ic_history\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/layout/fragment_progress_main_movies.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  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/progressMoviesRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.viewpager.widget.ViewPager\n    android:id=\"@+id/progressMoviesPager\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:overScrollMode=\"never\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.ModeTabsView\n    android:id=\"@+id/progressMoviesModeTabs\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/collectionTabsMargin\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.ScrollableTabLayout\n    android:id=\"@+id/progressMoviesTabs\"\n    style=\"@style/ScrollableTabsStyle\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"36dp\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/progressMoviesSearchViewPadding\"\n    android:translationX=\"@dimen/progressMoviesTabsViewTranslation\"\n    app:tabTextAppearance=\"@style/ScrollableTabTextStyle\"\n    tools:layout_width=\"300dp\"\n    />\n\n  <FrameLayout\n    android:id=\"@+id/progressMoviesSideIcons\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"end\"\n    android:layout_marginTop=\"@dimen/progressMoviesSearchViewPadding\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    >\n\n    <com.michaldrabik.ui_base.common.views.ScrollableImageView\n      android:id=\"@+id/progressMoviesCalendarIcon\"\n      android:layout_width=\"36dp\"\n      android:layout_height=\"36dp\"\n      android:layout_marginEnd=\"38dp\"\n      android:paddingStart=\"12dp\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_history\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:visibility=\"visible\"\n      />\n\n    <com.michaldrabik.ui_base.common.views.ScrollableImageView\n      android:id=\"@+id/progressMoviesSearchIcon\"\n      android:layout_width=\"36dp\"\n      android:layout_height=\"36dp\"\n      android:layout_gravity=\"end\"\n      android:paddingStart=\"14dp\"\n      app:srcCompat=\"@drawable/ic_search\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n  </FrameLayout>\n\n  <com.michaldrabik.ui_base.common.views.SearchLocalView\n    android:id=\"@+id/progressMoviesSearchLocalView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/searchLocalViewHeight\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/progressMoviesSearchLocalViewPadding\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.SearchView\n    android:id=\"@+id/progressMoviesSearchView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/searchViewHeight\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "ui-progress-movies/src/main/res/layout/fragment_progress_movies.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n  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/progressMoviesMainRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/progressMoviesMainRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/transparent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/progressMoviesRecyclerHorizontalSpace\"\n    android:paddingTop=\"@dimen/progressMoviesTabsViewPadding\"\n    android:paddingEnd=\"@dimen/progressMoviesRecyclerHorizontalSpace\"\n    android:paddingBottom=\"@dimen/bottomNavigationHeightPadded\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <include\n    android:id=\"@+id/progressMoviesEmptyView\"\n    layout=\"@layout/layout_progress_movies_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginTop=\"@dimen/spaceHuge\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:layout_marginBottom=\"@dimen/bottomNavigationHeight\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    />\n\n  <FrameLayout\n    android:id=\"@+id/progressMoviesOverscroll\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center_horizontal\"\n    android:layout_marginTop=\"@dimen/progressMoviesOverscrollPadding\"\n    android:alpha=\"0\"\n    android:scaleX=\"0\"\n    android:scaleY=\"0\"\n    tools:alpha=\"1\"\n    tools:scaleX=\"1\"\n    tools:scaleY=\"1\"\n    >\n\n    <ImageView\n      android:id=\"@+id/progressMoviesOverscrollIcon\"\n      android:layout_width=\"@dimen/progressMoviesOverscrollIcon\"\n      android:layout_height=\"@dimen/progressMoviesOverscrollIcon\"\n      android:layout_gravity=\"center\"\n      app:srcCompat=\"@drawable/ic_trakt\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <com.google.android.material.progressindicator.CircularProgressIndicator\n      android:id=\"@+id/progressMoviesOverscrollProgress\"\n      android:layout_width=\"@dimen/progressMoviesOverscrollProgress\"\n      android:layout_height=\"@dimen/progressMoviesOverscrollProgress\"\n      android:layout_gravity=\"center\"\n      android:indeterminate=\"false\"\n      app:indicatorColor=\"?android:attr/textColorPrimary\"\n      app:indicatorSize=\"@dimen/progressMoviesOverscrollProgress\"\n      app:trackThickness=\"4dp\"\n      tools:progress=\"75\"\n      />\n\n  </FrameLayout>\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "ui-progress-movies/src/main/res/layout/layout_calendar_movies_future_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/tabMoviesCalendar\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textMoviesCalendarEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-progress-movies/src/main/res/layout/layout_calendar_movies_recents_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/tabMoviesCalendar\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textMoviesCalendarRecentsEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-progress-movies/src/main/res/layout/layout_progress_movies_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n  android:id=\"@+id/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/tabMoviesProgress\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textMoviesProgressEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/progressMoviesEmptyDiscoverButton\"\n    style=\"@style/RoundMaterialButton\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:text=\"@string/textDiscoverMovies\"\n    android:textColor=\"?attr/textColorOnSurface\"\n    app:backgroundTint=\"?attr/colorAccent\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    app:strokeColor=\"?android:attr/textColorPrimary\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/progressMoviesEmptyTraktButton\"\n    style=\"@style/RoundTextButton\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:text=\"@string/textMoviesTraktSynchronize\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-progress-movies/src/main/res/layout/view_calendar_movies_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:orientation=\"horizontal\"\n  android:paddingStart=\"@dimen/spaceMedium\"\n  android:paddingTop=\"@dimen/spaceNormal\"\n  android:paddingEnd=\"@dimen/spaceMedium\"\n  android:paddingBottom=\"@dimen/spaceTiny\"\n  tools:parentTag=\"android.widget.LinearLayout\"\n  >\n\n  <ImageView\n    android:id=\"@+id/calendarMoviesHeaderIcon\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center_vertical\"\n    android:layout_marginTop=\"@dimen/spaceMicro\"\n    android:layout_marginEnd=\"@dimen/spaceSmall\"\n    app:srcCompat=\"@drawable/ic_history\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n  <TextView\n    android:id=\"@+id/calendarMoviesHeaderText\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center_vertical\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"@dimen/progressMoviesHeaderTextSize\"\n    android:textStyle=\"bold\"\n    tools:text=\"@string/textLast7Days\"\n    />\n\n</merge>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/layout/view_progress_movies_calendar_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    >\n\n    <ImageView\n      android:id=\"@+id/progressMovieCalendarItemImage\"\n      android:layout_width=\"@dimen/progressMoviesImageWidth\"\n      android:layout_height=\"@dimen/progressMoviesImageHeight\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:layout_marginTop=\"8dp\"\n      android:layout_marginBottom=\"8dp\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/progressMovieCalendarItemPlaceholder\"\n      android:layout_width=\"@dimen/progressMoviesImageWidth\"\n      android:layout_height=\"@dimen/progressMoviesImageHeight\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"@dimen/spaceBig\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_film\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/progressMovieCalendarItemBadge\"\n      style=\"@style/Badge\"\n      android:layout_width=\"22dp\"\n      android:layout_height=\"22dp\"\n      android:layout_marginEnd=\"2dp\"\n      android:translationY=\"-4dp\"\n      app:layout_constraintEnd_toEndOf=\"@id/progressMovieCalendarItemImage\"\n      app:layout_constraintTop_toTopOf=\"@id/progressMovieCalendarItemImage\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressMovieCalendarItemTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/progressMovieCalendarItemSubtitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/progressMovieCalendarItemImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      app:layout_goneMarginEnd=\"@dimen/spaceMedium\"\n      tools:ignore=\"RtlSymmetry\"\n      tools:text=\"Breaking Bad\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressMovieCalendarItemSubtitle\"\n      style=\"@style/CollectionItem.Description\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginTop=\"@dimen/spaceMicro\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/progressMovieCalendarItemDate\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/progressMovieCalendarItemImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/progressMovieCalendarItemTitle\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressMovieCalendarItemDate\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_badge\"\n      android:elevation=\"@dimen/elevationTiny\"\n      android:gravity=\"start|center_vertical\"\n      android:paddingStart=\"6dp\"\n      android:paddingTop=\"2dp\"\n      android:paddingEnd=\"6dp\"\n      android:paddingBottom=\"2dp\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"13sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/progressMovieCalendarItemImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/progressMovieCalendarItemSubtitle\"\n      tools:text=\"Wednesday, 27 June 2019\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/layout/view_progress_movies_filters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <com.google.android.material.chip.ChipGroup\n    android:id=\"@+id/progressFiltersChips\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceMedium\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:paddingTop=\"@dimen/spaceSmall\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    app:singleLine=\"true\"\n    >\n\n    <com.google.android.material.chip.Chip\n      android:id=\"@+id/progressFiltersSortingChip\"\n      style=\"@style/ShowlyChip.Sort\"\n      android:text=\"@string/textSortName\"\n      />\n\n  </com.google.android.material.chip.ChipGroup>\n\n</merge>"
  },
  {
    "path": "ui-progress-movies/src/main/res/layout/view_progress_movies_main_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    >\n\n    <ImageView\n      android:id=\"@+id/progressMovieItemImage\"\n      android:layout_width=\"@dimen/progressMoviesImageWidth\"\n      android:layout_height=\"@dimen/progressMoviesImageHeight\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:layout_marginTop=\"8dp\"\n      android:layout_marginBottom=\"8dp\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/progressMovieItemPlaceholder\"\n      android:layout_width=\"@dimen/progressMoviesImageWidth\"\n      android:layout_height=\"@dimen/progressMoviesImageHeight\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"@dimen/spaceBig\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_film\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/progressMovieItemPin\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"wrap_content\"\n      android:rotation=\"45\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@id/progressMovieItemButtons\"\n      app:layout_constraintStart_toStartOf=\"@id/progressMovieItemTitle\"\n      app:layout_constraintTop_toTopOf=\"@id/progressMovieItemButtons\"\n      app:srcCompat=\"@drawable/ic_pin\"\n      app:tint=\"?android:attr/textColorSecondary\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressMovieItemTitle\"\n      style=\"@style/CollectionItem.Title\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/progressMovieItemSubtitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/progressMovieItemRatingStar\"\n      app:layout_constraintStart_toEndOf=\"@id/progressMovieItemImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      app:layout_goneMarginEnd=\"@dimen/spaceMedium\"\n      tools:ignore=\"RtlSymmetry\"\n      tools:text=\"Breaking Bad\"\n      />\n\n    <ImageView\n      android:id=\"@+id/progressMovieItemRatingStar\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"0dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@+id/progressMovieItemRating\"\n      app:layout_constraintEnd_toStartOf=\"@id/progressMovieItemRating\"\n      app:layout_constraintTop_toTopOf=\"@id/progressMovieItemRating\"\n      app:srcCompat=\"@drawable/ic_star\"\n      app:tint=\"?attr/colorAccent\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressMovieItemRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      android:gravity=\"end\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@+id/progressMovieItemTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/progressMovieItemTitle\"\n      tools:text=\"7.6\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressMovieItemSubtitle\"\n      style=\"@style/CollectionItem.Description\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginTop=\"@dimen/spaceMicro\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      app:layout_constraintBottom_toTopOf=\"@id/progressMovieItemButtons\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toEndOf=\"@id/progressMovieItemImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/progressMovieItemTitle\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <LinearLayout\n      android:id=\"@+id/progressMovieItemButtons\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      android:clipChildren=\"false\"\n      android:gravity=\"end|center_vertical\"\n      android:orientation=\"horizontal\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"1\"\n      app:layout_constraintStart_toEndOf=\"@id/progressMovieItemImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/progressMovieItemSubtitle\"\n      >\n\n      <com.google.android.material.button.MaterialButton\n        android:id=\"@+id/progressMovieItemCheckButton\"\n        style=\"@style/RoundOutlinedButton\"\n        android:layout_width=\"@dimen/progressMoviesItemCheckButtonWidth\"\n        android:layout_height=\"@dimen/progressMoviesItemButtonHeight\"\n        android:gravity=\"center\"\n        android:includeFontPadding=\"false\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"12sp\"\n        app:icon=\"@drawable/ic_check\"\n        app:iconGravity=\"textStart\"\n        app:iconPadding=\"0dp\"\n        app:iconSize=\"21dp\"\n        app:iconTint=\"?android:attr/textColorPrimary\"\n        app:rippleColor=\"?android:attr/textColorPrimary\"\n        app:strokeColor=\"?android:attr/textColorPrimary\"\n        />\n\n    </LinearLayout>\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"progressMoviesTabsViewTranslation\">-7dp</dimen>\n\n  <dimen name=\"progressMoviesRecyclerHorizontalSpace\">0dp</dimen>\n\n  <dimen name=\"progressMoviesHeaderTextSize\">22sp</dimen>\n  <dimen name=\"progressMoviesSearchViewPadding\">104dp</dimen>\n  <dimen name=\"progressMoviesTabsViewPadding\">148dp</dimen>\n  <dimen name=\"progressMoviesCalendarTabsViewPadding\">132dp</dimen>\n\n  <dimen name=\"progressMoviesImageHeight\">120dp</dimen>\n  <dimen name=\"progressMoviesImageWidth\">80dp</dimen>\n\n  <dimen name=\"progressMoviesSearchLocalViewPaddingNoModes\">114dp</dimen>\n  <dimen name=\"progressMoviesSearchLocalViewPadding\">150dp</dimen>\n  <dimen name=\"progressMoviesSearchLocalOffset\">40dp</dimen>\n\n  <dimen name=\"progressMoviesOverscrollPadding\">132dp</dimen>\n  <dimen name=\"progressMoviesOverscrollIcon\">36dp</dimen>\n  <dimen name=\"progressMoviesOverscrollProgress\">48dp</dimen>\n\n  <dimen name=\"progressMoviesItemCheckButtonWidth\">72dp</dimen>\n  <dimen name=\"progressMoviesItemButtonHeight\">40dp</dimen>\n</resources>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabMoviesCalendar\">Calendar</string>\n  <string name=\"tabMoviesProgress\">Progress</string>\n  <string name=\"textMoviesCalendarEmpty\">There are no new movies on the way.</string>\n  <string name=\"textMoviesCalendarRecentsEmpty\">There are no recently aired movies.</string>\n\n  <string name=\"textDiscoverMovies\">Discover Movies</string>\n  <string name=\"textMoviesTraktSynchronize\">Import from Trakt.tv</string>\n  <string name=\"textMoviesProgressEmpty\">Your <b>Progress</b> is currently empty.\\n\\nVisit <b>Discover</b> tab to add movies or import your Trakt.tv collection.</string>\n\n</resources>"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-ar/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"progressMoviesTabsViewTranslation\">6dp</dimen>\n</resources>"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabMoviesCalendar\">تقويم الأفلام</string>\n  <string name=\"tabMoviesProgress\">مستوى التقدم</string>\n  <string name=\"textMoviesCalendarEmpty\">لا توجد أي أفلام جديدة قريياً.</string>\n  <string name=\"textMoviesCalendarRecentsEmpty\">لا يُوجد أي أفلام جديدة.</string>\n  <string name=\"textDiscoverMovies\">إكتشف أفلام</string>\n  <string name=\"textMoviesTraktSynchronize\">إستيراد البيانات من Trakt.tv</string>\n  <string name=\"textMoviesProgressEmpty\">قائمة <b>مستوى التقدم</b> فارغة حالياً.\\n\\nإذهب إلى صفحة <b>إكتشف</b> لإضافة أفلام، أو اِستورد البيانات من Trakt.tv.</string>\n</resources>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-de/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"progressMoviesTabsViewTranslation\">-7dp</dimen>\n</resources>"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabMoviesCalendar\">Kalender</string>\n  <string name=\"tabMoviesProgress\">Fortschritt</string>\n  <string name=\"textMoviesCalendarEmpty\">Es sind keine neuen Filme vorhanden.</string>\n  <string name=\"textMoviesCalendarRecentsEmpty\">Es gibt keine kürzlich ausgestrahlten Filme.</string>\n  <string name=\"textDiscoverMovies\">Filme entdecken</string>\n  <string name=\"textMoviesTraktSynchronize\">Von Trakt.tv importieren</string>\n  <string name=\"textMoviesProgressEmpty\">Dein <b>Fortschritt</b>ist aktuell leer.\\n\\nTippe auf den <b>Entdecken</b> Tab, um Filme hinzuzufügen oder deine Trakt.tv-Sammlung zu importieren.</string>\n</resources>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabMoviesCalendar\">Calendario</string>\n  <string name=\"tabMoviesProgress\">Progreso</string>\n  <string name=\"textMoviesCalendarEmpty\">No hay nuevas películas en camino.</string>\n  <string name=\"textMoviesCalendarRecentsEmpty\">No hay películas emitidas recientemente.</string>\n  <string name=\"textDiscoverMovies\">Descubrir Películas</string>\n  <string name=\"textMoviesTraktSynchronize\">Importar desde Trakt.tv</string>\n  <string name=\"textMoviesProgressEmpty\">Tu <b>Progreso</b> actualmente está vacío.\\n\\nVisita la pestaña <b>Descubrir</b> para añadir películas o importar tu colección de Trakt.tv.</string>\n</resources>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabMoviesCalendar\">Kalenteri</string>\n  <string name=\"tabMoviesProgress\">Katselutila</string>\n  <string name=\"textMoviesCalendarEmpty\">Uusia elokuvia ei ole tulossa.</string>\n  <string name=\"textMoviesCalendarRecentsEmpty\">Hiljattain esitettyjä elokuvia ei ole.</string>\n  <string name=\"textDiscoverMovies\">Etsi elokuvia</string>\n  <string name=\"textMoviesTraktSynchronize\">Tuo Trakt.tv:sta</string>\n  <string name=\"textMoviesProgressEmpty\"><b>Katselutila</b> on tyhjä.\\n\\nLisää elokuvia <b>Etsi</b>-osiosta tai tuo Trakt.tv-kokoelmasi.</string>\n</resources>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabMoviesCalendar\">Calendrier</string>\n  <string name=\"tabMoviesProgress\">Progression</string>\n  <string name=\"textMoviesCalendarEmpty\">Aucun film à venir.</string>\n  <string name=\"textMoviesCalendarRecentsEmpty\">Il n\\'y a aucun film récemment diffusé.</string>\n  <string name=\"textDiscoverMovies\">Découvrir des films</string>\n  <string name=\"textMoviesTraktSynchronize\">Importer de Trakt.tv</string>\n  <string name=\"textMoviesProgressEmpty\">Votre <b>Progression</b> est présentement vide.\\n\\nVisitez l\\'onglet <b>Découvrir</b> pour ajouter des films ou importer votre collection Trakt.tv.</string>\n</resources>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabMoviesCalendar\">Calendario</string>\n  <string name=\"tabMoviesProgress\">Progressi</string>\n  <string name=\"textMoviesCalendarEmpty\">Non ci sono nuovi film in arrivo a breve.</string>\n  <string name=\"textMoviesCalendarRecentsEmpty\">Non sono stati trasmessi film di recente.</string>\n  <string name=\"textDiscoverMovies\">Scopri film</string>\n  <string name=\"textMoviesTraktSynchronize\">Importa da Trakt.tv</string>\n  <string name=\"textMoviesProgressEmpty\">La tua pagina <b>Progressi</b> è vuota.\\n\\nVisita la pagina <b>Scopri</b> per aggiungere film o importa la tua raccolta Trak.tv.</string>\n</resources>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-pl/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"progressMoviesTabsViewTranslation\">-12dp</dimen>\n</resources>"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabMoviesCalendar\">Kalendarz</string>\n  <string name=\"tabMoviesProgress\">Postęp</string>\n  <string name=\"textMoviesCalendarEmpty\">Brak nowych nadchodzących filmów.</string>\n  <string name=\"textMoviesCalendarRecentsEmpty\">Brak ostatnio emitowanych filmów.</string>\n  <string name=\"textDiscoverMovies\">Odkrywaj Filmy</string>\n  <string name=\"textMoviesTraktSynchronize\">Import z Trakt.tv</string>\n  <string name=\"textMoviesProgressEmpty\">Twój <b>Postęp</b> jest obecnie pusty.\\n\\nDodaj nowe filmy w zakładce <b>Odkrywaj</b> lub zaimportuj swoją kolekcję z Trakt.tv.</string>\n</resources>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabMoviesCalendar\">Calendário</string>\n  <string name=\"tabMoviesProgress\">Progresso</string>\n  <string name=\"textMoviesCalendarEmpty\">Não há novos filmes a caminho.</string>\n  <string name=\"textMoviesCalendarRecentsEmpty\">Não há filmes recentes.</string>\n  <string name=\"textDiscoverMovies\">Descubra filmes</string>\n  <string name=\"textMoviesTraktSynchronize\">Importar de Trakt.tv</string>\n  <string name=\"textMoviesProgressEmpty\">Seu <b>Progresso</b> está vazio atualmente.\\n\\nVisite a guia <b>Explorar</b> para adicionar filmes ou importar sua coleção de Trakt.tv.</string>\n</resources>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabMoviesCalendar\">Календарь</string>\n  <string name=\"tabMoviesProgress\">Прогресс</string>\n  <string name=\"textMoviesCalendarEmpty\">Нет новых фильмов.</string>\n  <string name=\"textMoviesCalendarRecentsEmpty\">Недавно вышедших фильмов нет.</string>\n  <string name=\"textDiscoverMovies\">Найти фильмы</string>\n  <string name=\"textMoviesTraktSynchronize\">Импортировать из Trakt.tv</string>\n  <string name=\"textMoviesProgressEmpty\">Ваш <b>Прогресс</b> в настоящее время пуст.\\n\\nПосетите вкладку <b>Открытия</b>, чтобы добавить сериалы или импортировать вашу коллекцию Trakt.tv.</string>\n</resources>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"progressMoviesRecyclerHorizontalSpace\">12dp</dimen>\n</resources>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabMoviesCalendar\">Takvim</string>\n  <string name=\"tabMoviesProgress\">İlerleme</string>\n  <string name=\"textMoviesCalendarEmpty\">Yaklaşan yeni hiçbir film yok.</string>\n  <string name=\"textMoviesCalendarRecentsEmpty\">Yakın zamanda yayınlanan film yok.</string>\n  <string name=\"textDiscoverMovies\">Filmleri Keşfet</string>\n  <string name=\"textMoviesTraktSynchronize\">Trakt.tv hizmetinden aktar</string>\n  <string name=\"textMoviesProgressEmpty\"><b>İlerlemeniz</b> şu anda boştur.\\n\\nFilmler eklemek için <b>Keşfet</b> sekmesini ziyaret edin veya Trakt.tv koleksiyonunuzdan aktarın.</string>\n</resources>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabMoviesCalendar\">Календар</string>\n  <string name=\"tabMoviesProgress\">Прогрес</string>\n  <string name=\"textMoviesCalendarEmpty\">Немає нових фільмів.</string>\n  <string name=\"textMoviesCalendarRecentsEmpty\">Немає нещодавно випущених фільмів.</string>\n  <string name=\"textDiscoverMovies\">Знайти фільми</string>\n  <string name=\"textMoviesTraktSynchronize\">Імпорт з Trakt.tv</string>\n  <string name=\"textMoviesProgressEmpty\">Ваш <b>Прогрес</b> зараз порожній.\\n\\nВідвідайте вкладку <b>Огляд</b>, щоб додати фільми або імпортуйте колекцію з Trakt.tv.</string>\n</resources>\n"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabMoviesCalendar\">Lịch</string>\n  <string name=\"tabMoviesProgress\">Tiến độ</string>\n  <string name=\"textMoviesCalendarEmpty\">Không có bộ phim mới nào sắp ra mắt.</string>\n  <string name=\"textMoviesCalendarRecentsEmpty\">Không có phim nào được phát sóng gần đây.</string>\n\n  <string name=\"textDiscoverMovies\">Khám phá phim</string>\n  <string name=\"textMoviesTraktSynchronize\">Nhập từ Trakt.tv</string>\n  <string name=\"textMoviesProgressEmpty\"><b>Tiến độ</b> của bạn hiện đang trống.\\n\\nTruy cập tab <b>Khám phá</b> để thêm phim hoặc nhập bộ sưu tập Trakt.tv của bạn.</string>\n\n</resources>"
  },
  {
    "path": "ui-progress-movies/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"tabMoviesCalendar\">时间表</string>\n  <string name=\"tabMoviesProgress\">进度</string>\n  <string name=\"textMoviesCalendarEmpty\">目前暂无将要上映的新电影。</string>\n  <string name=\"textMoviesCalendarRecentsEmpty\">目前暂无即将上映的新电影。</string>\n  <string name=\"textDiscoverMovies\">发现电影</string>\n  <string name=\"textMoviesTraktSynchronize\">从 Trakt.tv 导入数据</string>\n  <string name=\"textMoviesProgressEmpty\">您的 <b>进度</b> 目前为空。\\n\\n访问 <b>发现</b> 选项卡去添加电影或从 Trakt.tv 导入数据。</string>\n</resources>\n"
  },
  {
    "path": "ui-progress-movies/src/test/java/com/michaldrabik/ui_progress_movies/BaseMockTest.kt",
    "content": "package com.michaldrabik.ui_progress_movies\n\nimport com.michaldrabik.common_test.MainDispatcherRule\nimport com.michaldrabik.common_test.UnconfinedCoroutineDispatchers\nimport io.mockk.MockKAnnotations\nimport io.mockk.mockkStatic\nimport org.junit.Before\nimport org.junit.Rule\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nabstract class BaseMockTest {\n\n  @get:Rule\n  val mainDispatcherRule = MainDispatcherRule()\n  protected val testDispatchers = UnconfinedCoroutineDispatchers()\n\n  @Before\n  open fun setUp() {\n    MockKAnnotations.init(this)\n    mockkStatic(\"androidx.room.RoomDatabaseKt\")\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/test/java/com/michaldrabik/ui_progress_movies/calendar/CalendarMoviesViewModelTest.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar\n\nimport androidx.lifecycle.viewModelScope\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_progress_movies.BaseMockTest\nimport com.michaldrabik.ui_progress_movies.calendar.cases.CalendarMoviesRatingsCase\nimport com.michaldrabik.ui_progress_movies.calendar.cases.items.CalendarMoviesFutureCase\nimport com.michaldrabik.ui_progress_movies.calendar.cases.items.CalendarMoviesRecentsCase\nimport com.michaldrabik.ui_progress_movies.calendar.recycler.CalendarMovieListItem\nimport com.michaldrabik.ui_progress_movies.main.ProgressMoviesMainUiState\nimport io.mockk.Called\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.mockk\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.flow.toList\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.runTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass CalendarMoviesViewModelTest : BaseMockTest() {\n\n  @MockK lateinit var recentsCase: CalendarMoviesRecentsCase\n  @MockK lateinit var futureCase: CalendarMoviesFutureCase\n  @MockK lateinit var ratingsCase: CalendarMoviesRatingsCase\n  @MockK lateinit var imagesProvider: MovieImagesProvider\n  @MockK lateinit var translationsRepository: TranslationsRepository\n\n  private lateinit var SUT: CalendarMoviesViewModel\n  private val parentState = ProgressMoviesMainUiState(calendarMode = CalendarMode.PRESENT_FUTURE)\n\n  private val stateResult = mutableListOf<CalendarMoviesUiState>()\n  private val messagesResult = mutableListOf<MessageEvent>()\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n\n    coEvery { translationsRepository.getLanguage() } returns \"en\"\n\n    SUT = CalendarMoviesViewModel(\n      recentsCase,\n      futureCase,\n      ratingsCase,\n      imagesProvider,\n      translationsRepository\n    )\n  }\n\n  @After\n  fun tearDown() {\n    stateResult.clear()\n    messagesResult.clear()\n    SUT.viewModelScope.cancel()\n  }\n\n  @Test\n  fun `Should load items if parent timestamp changed`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val item = mockk<CalendarMovieListItem.MovieItem>()\n    coEvery { futureCase.loadItems(any()) } returns listOf(item)\n\n    SUT.onParentState(parentState.copy(timestamp = 123))\n\n    assertThat(stateResult.last().items).containsExactly(item)\n    coVerify(exactly = 1) { futureCase.loadItems(any()) }\n    coVerify { recentsCase wasNot Called }\n    job.cancel()\n  }\n\n  @Test\n  fun `Should not reload items if parent timestamp is the same`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val item = mockk<CalendarMovieListItem.MovieItem>()\n    coEvery { futureCase.loadItems(any()) } returns listOf(item)\n\n    SUT.onParentState(parentState.copy(timestamp = 0))\n\n    assertThat(stateResult.lastOrNull()?.items).isNull()\n    coVerify { futureCase wasNot Called }\n    job.cancel()\n  }\n\n  @Test\n  fun `Should load items if calendar mode changed`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val item = mockk<CalendarMovieListItem.MovieItem>()\n    coEvery { recentsCase.loadItems(any()) } returns listOf(item)\n\n    SUT.onParentState(parentState.copy(timestamp = 0, calendarMode = CalendarMode.RECENTS))\n\n    assertThat(stateResult.last().items).containsExactly(item)\n    coVerify(exactly = 1) { recentsCase.loadItems(any()) }\n    coVerify { futureCase wasNot Called }\n    job.cancel()\n  }\n\n  @Test\n  fun `Should load items if search query changed`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val item = mockk<CalendarMovieListItem.MovieItem>()\n    coEvery { futureCase.loadItems(any()) } returns listOf(item)\n\n    SUT.onParentState(parentState.copy(timestamp = 0, searchQuery = \"test\"))\n\n    assertThat(stateResult.last().items).containsExactly(item)\n    coVerify(exactly = 1) { futureCase.loadItems(any()) }\n    coVerify { recentsCase wasNot Called }\n    job.cancel()\n  }\n\n  @Test\n  fun `Should not reload items if parent search query is the same`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val item = mockk<CalendarMovieListItem.MovieItem>()\n    coEvery { futureCase.loadItems(any()) } returns listOf(item)\n\n    SUT.onParentState(parentState.copy(timestamp = 0, searchQuery = \"test\"))\n    SUT.onParentState(parentState.copy(timestamp = 0, searchQuery = \"test\"))\n\n    assertThat(stateResult.last().items).containsExactly(item)\n    coVerify(exactly = 1) { futureCase.loadItems(any()) }\n    coVerify { recentsCase wasNot Called }\n    job.cancel()\n  }\n\n  @Test\n  fun `Should check quick rate option enabled`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    coEvery { ratingsCase.isQuickRateEnabled() } returns true\n    assertThat(SUT.isQuickRateEnabled).isFalse()\n\n    SUT.checkQuickRateEnabled()\n\n    assertThat(SUT.isQuickRateEnabled).isTrue()\n    coVerify(exactly = 1) { ratingsCase.isQuickRateEnabled() }\n    job.cancel()\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/test/java/com/michaldrabik/ui_progress_movies/calendar/cases/CalendarMoviesRatingsCaseTest.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.cases\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_progress_movies.BaseMockTest\nimport io.mockk.clearAllMocks\nimport io.mockk.coEvery\nimport io.mockk.impl.annotations.RelaxedMockK\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.test.runTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass CalendarMoviesRatingsCaseTest : BaseMockTest() {\n\n  @RelaxedMockK lateinit var userTraktManager: UserTraktManager\n  @RelaxedMockK lateinit var settingsRepository: SettingsRepository\n\n  private lateinit var SUT: CalendarMoviesRatingsCase\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n\n    coEvery { userTraktManager.isAuthorized() } returns true\n    coEvery { settingsRepository.isPremium } returns true\n    coEvery { settingsRepository.load().traktQuickRateEnabled } returns true\n\n    SUT = CalendarMoviesRatingsCase(\n      testDispatchers,\n      userTraktManager,\n      settingsRepository\n    )\n  }\n\n  @After\n  fun tearDown() {\n    clearAllMocks()\n  }\n\n  @Test\n  fun `Should check quick rate enabled`() = runTest {\n    val result = SUT.isQuickRateEnabled()\n    assertThat(result).isTrue()\n  }\n\n  @Test\n  fun `Should be false if user is not signed in`() = runTest {\n    coEvery { userTraktManager.isAuthorized() } returns false\n\n    val result = SUT.isQuickRateEnabled()\n    assertThat(result).isFalse()\n  }\n\n  @Test\n  fun `Should be false if user is not premium`() = runTest {\n    coEvery { settingsRepository.isPremium } returns false\n\n    val result = SUT.isQuickRateEnabled()\n    assertThat(result).isFalse()\n  }\n\n  @Test\n  fun `Should be false if user is feature is disabled`() = runTest {\n    coEvery { settingsRepository.load().traktQuickRateEnabled } returns false\n\n    val result = SUT.isQuickRateEnabled()\n    assertThat(result).isFalse()\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/test/java/com/michaldrabik/ui_progress_movies/calendar/helpers/filters/CalendarFutureFilterTest.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.helpers.filters\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_progress_movies.BaseMockTest\nimport kotlinx.coroutines.test.runBlockingTest\nimport org.junit.Before\nimport org.junit.Test\nimport java.time.LocalDate\nimport java.time.ZonedDateTime\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass CalendarFutureFilterTest : BaseMockTest() {\n\n  private lateinit var SUT: CalendarFutureFilter\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    SUT = CalendarFutureFilter()\n  }\n\n  @Test\n  fun `Should return true if release date is after now`() = runBlockingTest {\n    val movie = Movie.EMPTY.copy(released = LocalDate.now().plusDays(3))\n    val result = SUT.filter(ZonedDateTime.now(), movie)\n    assertThat(result).isTrue()\n  }\n\n  @Test\n  fun `Should return true if release date is today`() = runBlockingTest {\n    val movie = Movie.EMPTY.copy(released = LocalDate.now())\n    val result = SUT.filter(ZonedDateTime.now(), movie)\n    assertThat(result).isTrue()\n  }\n\n  @Test\n  fun `Should return false if release date is before today`() = runBlockingTest {\n    val movie = Movie.EMPTY.copy(released = LocalDate.now().minusDays(1))\n    val result = SUT.filter(ZonedDateTime.now(), movie)\n    assertThat(result).isFalse()\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/test/java/com/michaldrabik/ui_progress_movies/calendar/helpers/filters/CalendarRecentsFilterTest.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.helpers.filters\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_progress_movies.BaseMockTest\nimport kotlinx.coroutines.test.runBlockingTest\nimport org.junit.Before\nimport org.junit.Test\nimport java.time.LocalDate\nimport java.time.ZonedDateTime\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass CalendarRecentsFilterTest : BaseMockTest() {\n\n  private lateinit var SUT: CalendarRecentsFilter\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    SUT = CalendarRecentsFilter()\n  }\n\n  @Test\n  fun `Should return true if release date is before now`() = runBlockingTest {\n    val movie = Movie.EMPTY.copy(released = LocalDate.now().minusDays(1))\n    val result = SUT.filter(ZonedDateTime.now(), movie)\n    assertThat(result).isTrue()\n  }\n\n  @Test\n  fun `Should return false if release date is null`() = runBlockingTest {\n    val movie = Movie.EMPTY.copy(released = null)\n    val result = SUT.filter(ZonedDateTime.now(), movie)\n    assertThat(result).isFalse()\n  }\n\n  @Test\n  fun `Should return false if release date is today`() = runBlockingTest {\n    val movie = Movie.EMPTY.copy(released = LocalDate.now())\n    val result = SUT.filter(ZonedDateTime.now(), movie)\n    assertThat(result).isFalse()\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/test/java/com/michaldrabik/ui_progress_movies/calendar/helpers/groupers/CalendarFutureGrouperTest.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.helpers.groupers\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.ui_progress_movies.BaseMockTest\nimport com.michaldrabik.ui_progress_movies.R\nimport com.michaldrabik.ui_progress_movies.calendar.recycler.CalendarMovieListItem\nimport io.mockk.every\nimport io.mockk.mockk\nimport kotlinx.coroutines.test.runBlockingTest\nimport org.junit.Before\nimport org.junit.Test\nimport java.time.LocalDate\nimport java.time.ZonedDateTime\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass CalendarFutureGrouperTest : BaseMockTest() {\n\n  private lateinit var SUT: CalendarFutureGrouper\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    SUT = CalendarFutureGrouper()\n  }\n\n  @Test\n  fun `Should group past items by time properly`() = runBlockingTest {\n    val zonedNow = ZonedDateTime.parse(\"2021-10-11T12:00:00Z\")\n    val now = LocalDate.parse(\"2021-10-11\") // Monday\n    val item1 = mockk<CalendarMovieListItem.MovieItem> {\n      every { movie } returns mockk {\n        every { released } returns now\n      }\n    }\n    val item2 = mockk<CalendarMovieListItem.MovieItem> {\n      every { movie } returns mockk {\n        every { released } returns now.plusDays(1)\n      }\n    }\n    val item3 = mockk<CalendarMovieListItem.MovieItem> {\n      every { movie } returns mockk {\n        every { released } returns now.plusDays(6)\n      }\n    }\n    val item4 = mockk<CalendarMovieListItem.MovieItem> {\n      every { movie } returns mockk {\n        every { released } returns now.plusDays(7)\n      }\n    }\n    val item5 = mockk<CalendarMovieListItem.MovieItem> {\n      every { movie } returns mockk {\n        every { released } returns now.plusDays(14)\n      }\n    }\n    val item6 = mockk<CalendarMovieListItem.MovieItem> {\n      every { movie } returns mockk {\n        every { released } returns now.plusDays(21)\n      }\n    }\n    val item7 = mockk<CalendarMovieListItem.MovieItem> {\n      every { movie } returns mockk {\n        every { released } returns now.plusDays(60)\n      }\n    }\n    val item8 = mockk<CalendarMovieListItem.MovieItem> {\n      every { movie } returns mockk {\n        every { released } returns now.plusDays(100)\n      }\n    }\n\n    val results = SUT.groupByTime(\n      zonedNow,\n      listOf(item1, item2, item3, item4, item5, item6, item7, item8)\n    )\n\n    assertThat(results).hasSize(16)\n    assertThat((results[0] as CalendarMovieListItem.Header).textResId).isEqualTo(R.string.textToday)\n    assertThat((results[1] as CalendarMovieListItem.MovieItem)).isEqualTo(item1)\n    assertThat((results[2] as CalendarMovieListItem.Header).textResId).isEqualTo(R.string.textTomorrow)\n    assertThat((results[3] as CalendarMovieListItem.MovieItem)).isEqualTo(item2)\n    assertThat((results[4] as CalendarMovieListItem.Header).textResId).isEqualTo(R.string.textThisWeek)\n    assertThat((results[5] as CalendarMovieListItem.MovieItem)).isEqualTo(item3)\n    assertThat((results[6] as CalendarMovieListItem.Header).textResId).isEqualTo(R.string.textNextWeek)\n    assertThat((results[7] as CalendarMovieListItem.MovieItem)).isEqualTo(item4)\n    assertThat((results[8] as CalendarMovieListItem.Header).textResId).isEqualTo(R.string.textThisMonth)\n    assertThat((results[9] as CalendarMovieListItem.MovieItem)).isEqualTo(item5)\n    assertThat((results[10] as CalendarMovieListItem.Header).textResId).isEqualTo(R.string.textNextMonth)\n    assertThat((results[11] as CalendarMovieListItem.MovieItem)).isEqualTo(item6)\n    assertThat((results[12] as CalendarMovieListItem.Header).textResId).isEqualTo(R.string.textThisYear)\n    assertThat((results[13] as CalendarMovieListItem.MovieItem)).isEqualTo(item7)\n    assertThat((results[14] as CalendarMovieListItem.Header).textResId).isEqualTo(R.string.textLater)\n    assertThat((results[15] as CalendarMovieListItem.MovieItem)).isEqualTo(item8)\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/test/java/com/michaldrabik/ui_progress_movies/calendar/helpers/groupers/CalendarRecentsGrouperTest.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.helpers.groupers\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_progress_movies.BaseMockTest\nimport com.michaldrabik.ui_progress_movies.R\nimport com.michaldrabik.ui_progress_movies.calendar.recycler.CalendarMovieListItem\nimport io.mockk.every\nimport io.mockk.mockk\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport org.junit.Before\nimport org.junit.Test\nimport java.time.LocalDate\nimport java.time.ZonedDateTime\n\n@OptIn(ExperimentalCoroutinesApi::class)\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass CalendarRecentsGrouperTest : BaseMockTest() {\n\n  private lateinit var SUT: CalendarRecentsGrouper\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    SUT = CalendarRecentsGrouper()\n  }\n\n  @Test\n  fun `Should group past items by time properly`() {\n    val zonedNow = ZonedDateTime.parse(\"2021-12-20T12:00:00Z\")\n    val now = LocalDate.parse(\"2021-12-20\") // Monday\n    val movie1 = Movie.EMPTY.copy(released = now.minusDays(1))\n    val movie2 = Movie.EMPTY.copy(released = now.minusDays(7))\n    val movie3 = Movie.EMPTY.copy(released = now.minusDays(30))\n    val movie4 = Movie.EMPTY.copy(released = now.minusDays(90))\n\n    val item1 = mockk<CalendarMovieListItem.MovieItem> {\n      every { movie } returns movie1\n    }\n    val item2 = mockk<CalendarMovieListItem.MovieItem> {\n      every { movie } returns movie2\n    }\n    val item3 = mockk<CalendarMovieListItem.MovieItem> {\n      every { movie } returns movie3\n    }\n    val item4 = mockk<CalendarMovieListItem.MovieItem> {\n      every { movie } returns movie4\n    }\n\n    val results = SUT.groupByTime(zonedNow, listOf(item1, item2, item3, item4))\n\n    assertThat(results).hasSize(8)\n    assertThat((results[0] as CalendarMovieListItem.Header).textResId).isEqualTo(R.string.textYesterday)\n    assertThat((results[1] as CalendarMovieListItem.MovieItem).movie).isEqualTo(movie1)\n    assertThat((results[2] as CalendarMovieListItem.Header).textResId).isEqualTo(R.string.textLast7Days)\n    assertThat((results[3] as CalendarMovieListItem.MovieItem).movie).isEqualTo(movie2)\n    assertThat((results[4] as CalendarMovieListItem.Header).textResId).isEqualTo(R.string.textLast30Days)\n    assertThat((results[5] as CalendarMovieListItem.MovieItem).movie).isEqualTo(movie3)\n    assertThat((results[6] as CalendarMovieListItem.Header).textResId).isEqualTo(R.string.textLast90Days)\n    assertThat((results[7] as CalendarMovieListItem.MovieItem).movie).isEqualTo(movie4)\n  }\n\n  @Test\n  fun `Should not include items older than 90 days`() {\n    val zonedNow = ZonedDateTime.parse(\"2021-12-20T18:00:00Z\")\n    val now = LocalDate.parse(\"2021-12-20\") // Monday\n    val movie1 = Movie.EMPTY.copy(released = now.minusDays(91))\n\n    val item1 = mockk<CalendarMovieListItem.MovieItem> {\n      every { movie } returns movie1\n    }\n\n    val results = SUT.groupByTime(zonedNow, listOf(item1))\n\n    assertThat(results).isEmpty()\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/test/java/com/michaldrabik/ui_progress_movies/calendar/helpers/sorter/CalendarFutureSorterTest.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.helpers.sorter\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_progress_movies.BaseMockTest\nimport kotlinx.coroutines.test.runBlockingTest\nimport org.junit.Before\nimport org.junit.Test\nimport java.time.LocalDate\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass CalendarFutureSorterTest : BaseMockTest() {\n\n  private lateinit var SUT: CalendarFutureSorter\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    SUT = CalendarFutureSorter()\n  }\n\n  @Test\n  fun `Should sort by release date`() = runBlockingTest {\n    val movie1 = Movie.EMPTY.copy(released = LocalDate.now().plusDays(333))\n    val movie2 = Movie.EMPTY.copy(released = LocalDate.now().plusDays(33))\n    val movie3 = Movie.EMPTY.copy(released = LocalDate.now().plusDays(3))\n\n    val result = listOf(movie1, movie2, movie3).sortedWith(SUT.sort())\n\n    assertThat(result).containsExactly(movie3, movie2, movie1)\n  }\n\n  @Test\n  fun `Should sort by year if release date is the same`() = runBlockingTest {\n    val movie1 = Movie.EMPTY.copy(released = LocalDate.now().plusDays(3), year = 333)\n    val movie2 = Movie.EMPTY.copy(released = LocalDate.now().plusDays(3), year = 33)\n    val movie3 = Movie.EMPTY.copy(released = LocalDate.now().plusDays(3), year = 3)\n\n    val result = listOf(movie1, movie2, movie3).sortedWith(SUT.sort())\n\n    assertThat(result).containsExactly(movie3, movie2, movie1)\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/test/java/com/michaldrabik/ui_progress_movies/calendar/helpers/sorter/CalendarRecentsSorterTest.kt",
    "content": "package com.michaldrabik.ui_progress_movies.calendar.helpers.sorter\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_progress_movies.BaseMockTest\nimport kotlinx.coroutines.test.runBlockingTest\nimport org.junit.Before\nimport org.junit.Test\nimport java.time.LocalDate\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass CalendarRecentsSorterTest : BaseMockTest() {\n\n  private lateinit var SUT: CalendarRecentsSorter\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    SUT = CalendarRecentsSorter()\n  }\n\n  @Test\n  fun `Should sort by release date`() = runBlockingTest {\n    val movie1 = Movie.EMPTY.copy(released = LocalDate.now().plusDays(333))\n    val movie2 = Movie.EMPTY.copy(released = LocalDate.now().plusDays(33))\n    val movie3 = Movie.EMPTY.copy(released = LocalDate.now().plusDays(3))\n\n    val result = listOf(movie2, movie1, movie3).sortedWith(SUT.sort())\n\n    assertThat(result).containsExactly(movie1, movie2, movie3)\n  }\n\n  @Test\n  fun `Should sort by year if release date is the same`() = runBlockingTest {\n    val movie1 = Movie.EMPTY.copy(released = LocalDate.now().plusDays(3), year = 333)\n    val movie2 = Movie.EMPTY.copy(released = LocalDate.now().plusDays(3), year = 33)\n    val movie3 = Movie.EMPTY.copy(released = LocalDate.now().plusDays(3), year = 3)\n\n    val result = listOf(movie2, movie1, movie3).sortedWith(SUT.sort())\n\n    assertThat(result).containsExactly(movie1, movie2, movie3)\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/test/java/com/michaldrabik/ui_progress_movies/main/ProgressMoviesMainViewModelTest.kt",
    "content": "package com.michaldrabik.ui_progress_movies.main\n\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkManager\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_progress_movies.BaseMockTest\nimport com.michaldrabik.ui_progress_movies.main.cases.ProgressMoviesMainCase\nimport io.mockk.Runs\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.impl.annotations.RelaxedMockK\nimport io.mockk.just\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.toList\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.runTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass ProgressMoviesMainViewModelTest : BaseMockTest() {\n\n  @MockK lateinit var mainCase: ProgressMoviesMainCase\n  @MockK lateinit var eventsManager: EventsManager\n  @RelaxedMockK lateinit var workManager: WorkManager\n\n  private lateinit var SUT: ProgressMoviesMainViewModel\n\n  private val stateResult = mutableListOf<ProgressMoviesMainUiState>()\n  private val messagesResult = mutableListOf<MessageEvent>()\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n\n    coEvery { eventsManager.events } returns MutableSharedFlow()\n\n    SUT = ProgressMoviesMainViewModel(mainCase, eventsManager, workManager)\n  }\n\n  @After\n  fun tearDown() {\n    stateResult.clear()\n    messagesResult.clear()\n    SUT.viewModelScope.cancel()\n  }\n\n  @Test\n  fun `Should emit current timestamp and calendar mode`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.loadProgress()\n\n    with(stateResult.last()) {\n      assertThat(timestamp).isGreaterThan(0L)\n      assertThat(calendarMode).isEqualTo(CalendarMode.PRESENT_FUTURE)\n    }\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should emit search query`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.onSearchQuery(\"test\")\n\n    with(stateResult.last()) {\n      assertThat(searchQuery).isEqualTo(\"test\")\n    }\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should toggle calendar mode properly`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.toggleCalendarMode()\n    SUT.toggleCalendarMode()\n\n    assertThat(stateResult[0].calendarMode).isEqualTo(null)\n    assertThat(stateResult[1].calendarMode).isEqualTo(CalendarMode.RECENTS)\n    assertThat(stateResult[2].calendarMode).isEqualTo(CalendarMode.PRESENT_FUTURE)\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should set watched movie properly and update timestamp`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    coEvery { mainCase.addToMyMovies(any<Movie>()) } just Runs\n\n    SUT.setWatchedMovie(Movie.EMPTY)\n\n    assertThat(stateResult[0].timestamp).isEqualTo(null)\n    assertThat(stateResult[1].timestamp).isGreaterThan(0L)\n\n    coVerify(exactly = 1) { mainCase.addToMyMovies(any<Movie>()) }\n\n    job.cancel()\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/test/java/com/michaldrabik/ui_progress_movies/main/cases/ProgressMoviesMainCaseTest.kt",
    "content": "package com.michaldrabik.ui_progress_movies.main.cases\n\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_progress_movies.BaseMockTest\nimport io.mockk.clearAllMocks\nimport io.mockk.coVerify\nimport io.mockk.impl.annotations.RelaxedMockK\nimport kotlinx.coroutines.test.runBlockingTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass ProgressMoviesMainCaseTest : BaseMockTest() {\n\n  @RelaxedMockK lateinit var moviesRepository: MoviesRepository\n  @RelaxedMockK lateinit var pinnedItemsRepository: PinnedItemsRepository\n  @RelaxedMockK lateinit var quickSyncManager: QuickSyncManager\n\n  private lateinit var SUT: ProgressMoviesMainCase\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    SUT = ProgressMoviesMainCase(\n      moviesRepository,\n      pinnedItemsRepository,\n      quickSyncManager\n    )\n  }\n\n  @After\n  fun tearDown() {\n    clearAllMocks()\n  }\n\n  @Test\n  fun `Should add movie to My Movies properly`() = runBlockingTest {\n    val movie = Movie.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = IdTrakt(123)))\n\n    SUT.addToMyMovies(movie)\n\n    coVerify { moviesRepository.myMovies.insert(IdTrakt(123)) }\n    coVerify { pinnedItemsRepository.removePinnedItem(movie) }\n    coVerify { quickSyncManager.scheduleMovies(listOf(123)) }\n  }\n\n  @Test\n  fun `Should add movie to My Movies properly using only ID`() = runBlockingTest {\n    SUT.addToMyMovies(IdTrakt(123))\n\n    coVerify { moviesRepository.myMovies.insert(IdTrakt(123)) }\n    coVerify { pinnedItemsRepository.removePinnedItem(any<Movie>()) }\n    coVerify { quickSyncManager.scheduleMovies(listOf(123)) }\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/test/java/com/michaldrabik/ui_progress_movies/progress/ProgressMoviesViewModelTest.kt",
    "content": "package com.michaldrabik.ui_progress_movies.progress\n\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkManager\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Settings\nimport com.michaldrabik.ui_progress_movies.BaseMockTest\nimport com.michaldrabik.ui_progress_movies.main.MovieCheckActionUiEvent\nimport com.michaldrabik.ui_progress_movies.main.ProgressMoviesMainUiState\nimport com.michaldrabik.ui_progress_movies.progress.cases.ProgressMoviesItemsCase\nimport com.michaldrabik.ui_progress_movies.progress.cases.ProgressMoviesPinnedCase\nimport com.michaldrabik.ui_progress_movies.progress.cases.ProgressMoviesSortCase\nimport com.michaldrabik.ui_progress_movies.progress.recycler.ProgressMovieListItem\nimport io.mockk.Called\nimport io.mockk.Runs\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.just\nimport io.mockk.mockk\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.flow.toList\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.runTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass ProgressMoviesViewModelTest : BaseMockTest() {\n\n  @MockK lateinit var itemsCase: ProgressMoviesItemsCase\n  @MockK lateinit var sortCase: ProgressMoviesSortCase\n  @MockK lateinit var pinnedCase: ProgressMoviesPinnedCase\n  @MockK lateinit var imagesProvider: MovieImagesProvider\n  @MockK lateinit var userTraktManager: UserTraktManager\n  @MockK lateinit var workManager: WorkManager\n  @MockK lateinit var settingsRepository: SettingsRepository\n  @MockK lateinit var translationsRepository: TranslationsRepository\n\n  private lateinit var SUT: ProgressMoviesViewModel\n  private val parentState = ProgressMoviesMainUiState()\n\n  private val stateResult = mutableListOf<ProgressMoviesUiState>()\n  private val eventsResult = mutableListOf<Event<*>>()\n  private val messagesResult = mutableListOf<MessageEvent>()\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n\n    coEvery { translationsRepository.getLanguage() } returns \"en\"\n    coEvery { userTraktManager.isAuthorized() } returns false\n\n    SUT = ProgressMoviesViewModel(\n      itemsCase,\n      sortCase,\n      pinnedCase,\n      imagesProvider,\n      userTraktManager,\n      workManager,\n      settingsRepository,\n      translationsRepository\n    )\n  }\n\n  @After\n  fun tearDown() {\n    stateResult.clear()\n    messagesResult.clear()\n    SUT.viewModelScope.cancel()\n  }\n\n  @Test\n  fun `Should load items if parent timestamp changed`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val item = mockk<ProgressMovieListItem.MovieItem>()\n    coEvery { itemsCase.loadItems(any()) } returns listOf(item)\n\n    SUT.onParentState(parentState.copy(timestamp = 123))\n\n    assertThat(stateResult.last().items).containsExactly(item)\n    coVerify(exactly = 1) { itemsCase.loadItems(any()) }\n    job.cancel()\n  }\n\n  @Test\n  fun `Should not reload items if parent timestamp is the same`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val item = mockk<ProgressMovieListItem.MovieItem>()\n    coEvery { itemsCase.loadItems(any()) } returns listOf(item)\n\n    SUT.onParentState(parentState.copy(timestamp = 0))\n\n    assertThat(stateResult.lastOrNull()?.items).isNull()\n    coVerify { itemsCase wasNot Called }\n    job.cancel()\n  }\n\n  @Test\n  fun `Should load items if search query changed`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val item = mockk<ProgressMovieListItem.MovieItem>()\n    coEvery { itemsCase.loadItems(any()) } returns listOf(item)\n\n    SUT.onParentState(parentState.copy(timestamp = 0, searchQuery = \"test\"))\n\n    assertThat(stateResult.last().items).containsExactly(item)\n    coVerify(exactly = 1) { itemsCase.loadItems(any()) }\n    job.cancel()\n  }\n\n  @Test\n  fun `Should not reload items if parent search query is the same`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val item = mockk<ProgressMovieListItem.MovieItem>()\n    coEvery { itemsCase.loadItems(any()) } returns listOf(item)\n\n    SUT.onParentState(parentState.copy(timestamp = 0, searchQuery = \"test\"))\n    SUT.onParentState(parentState.copy(timestamp = 0, searchQuery = \"test\"))\n\n    assertThat(stateResult.last().items).containsExactly(item)\n    coVerify(exactly = 1) { itemsCase.loadItems(any()) }\n    job.cancel()\n  }\n\n  @Test\n  fun `Should toggle pinned item if pinned`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    coEvery { pinnedCase.addPinnedItem(any()) } just Runs\n    coEvery { pinnedCase.removePinnedItem(any()) } just Runs\n    coEvery { itemsCase.loadItems(any()) } returns listOf(mockk())\n\n    val item = mockk<ProgressMovieListItem.MovieItem> {\n      coEvery { isPinned } returns true\n      coEvery { movie } returns Movie.EMPTY\n    }\n\n    SUT.togglePinItem(item)\n\n    coVerify(exactly = 1) { pinnedCase.removePinnedItem(any()) }\n    coVerify(exactly = 0) { pinnedCase.addPinnedItem(any()) }\n    coVerify(exactly = 1) { itemsCase.loadItems(any()) }\n    job.cancel()\n  }\n\n  @Test\n  fun `Should toggle pinned item if not pinned`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    coEvery { pinnedCase.addPinnedItem(any()) } just Runs\n    coEvery { pinnedCase.removePinnedItem(any()) } just Runs\n    coEvery { itemsCase.loadItems(any()) } returns listOf(mockk())\n\n    val item = mockk<ProgressMovieListItem.MovieItem> {\n      coEvery { isPinned } returns false\n      coEvery { movie } returns Movie.EMPTY\n    }\n\n    SUT.togglePinItem(item)\n\n    coVerify(exactly = 0) { pinnedCase.removePinnedItem(any()) }\n    coVerify(exactly = 1) { pinnedCase.addPinnedItem(any()) }\n    coVerify(exactly = 1) { itemsCase.loadItems(any()) }\n    job.cancel()\n  }\n\n  @Test\n  fun `Should check quick rate option enabled`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.eventFlow.toList(eventsResult) }\n    coEvery { userTraktManager.isAuthorized() } returns true\n    coEvery { settingsRepository.isPremium } returns true\n    coEvery { settingsRepository.load() } returns Settings.createInitial().copy(traktQuickRateEnabled = true)\n\n    SUT.onMovieChecked(Movie.EMPTY)\n\n    assertThat(eventsResult.last()).isInstanceOf(MovieCheckActionUiEvent::class.java)\n    assertThat((eventsResult.last() as MovieCheckActionUiEvent).isQuickRate).isTrue()\n    coVerify(exactly = 1) { userTraktManager.isAuthorized() }\n    coVerify(exactly = 1) { settingsRepository.load() }\n    job.cancel()\n  }\n\n  @Test\n  fun `Should check quick rate option not enabled`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.eventFlow.toList(eventsResult) }\n    coEvery { userTraktManager.isAuthorized() } returns true\n    coEvery { settingsRepository.isPremium } returns true\n    coEvery { settingsRepository.load() } returns Settings.createInitial().copy(traktQuickRateEnabled = false)\n\n    SUT.onMovieChecked(Movie.EMPTY)\n\n    assertThat(eventsResult.last()).isInstanceOf(MovieCheckActionUiEvent::class.java)\n    assertThat((eventsResult.last() as MovieCheckActionUiEvent).isQuickRate).isFalse()\n    coVerify(exactly = 1) { userTraktManager.isAuthorized() }\n    coVerify(exactly = 1) { settingsRepository.load() }\n    job.cancel()\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/test/java/com/michaldrabik/ui_progress_movies/progress/cases/ProgressMoviesItemsCaseTest.kt",
    "content": "package com.michaldrabik.ui_progress_movies.progress.cases\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_progress_movies.BaseMockTest\nimport com.michaldrabik.ui_progress_movies.helpers.ProgressMoviesItemsSorter\nimport com.michaldrabik.ui_progress_movies.progress.recycler.ProgressMovieListItem\nimport io.mockk.clearAllMocks\nimport io.mockk.coEvery\nimport io.mockk.impl.annotations.RelaxedMockK\nimport kotlinx.coroutines.runBlocking\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\nimport java.time.LocalDate\nimport java.time.format.DateTimeFormatter\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass ProgressMoviesItemsCaseTest : BaseMockTest() {\n\n  @RelaxedMockK lateinit var sorter: ProgressMoviesItemsSorter\n  @RelaxedMockK lateinit var moviesRepository: MoviesRepository\n  @RelaxedMockK lateinit var translationsRepository: TranslationsRepository\n  @RelaxedMockK lateinit var ratingsRepository: RatingsRepository\n  @RelaxedMockK lateinit var settingsRepository: SettingsRepository\n  @RelaxedMockK lateinit var imagesProvider: MovieImagesProvider\n  @RelaxedMockK lateinit var pinnedItemsRepository: PinnedItemsRepository\n  @RelaxedMockK lateinit var dateFormatProvider: DateFormatProvider\n\n  private lateinit var SUT: ProgressMoviesItemsCase\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n\n    coEvery { translationsRepository.getLanguage() } returns \"en\"\n    coEvery { dateFormatProvider.loadFullDayFormat() } returns DateTimeFormatter.ofPattern(\"dd MMM yyyy\")\n\n    coEvery { settingsRepository.sorting getProperty \"progressMoviesSortOrder\" } returns SortOrder.RANK\n    coEvery { settingsRepository.sorting getProperty \"progressMoviesSortType\" } returns SortType.DESCENDING\n\n    coEvery { imagesProvider.findCachedImage(any(), any()) } returns Image.createUnknown(ImageType.POSTER)\n    coEvery { pinnedItemsRepository.isItemPinned(any<Movie>()) } returns false\n\n    SUT = ProgressMoviesItemsCase(\n      testDispatchers,\n      moviesRepository,\n      translationsRepository,\n      ratingsRepository,\n      settingsRepository,\n      pinnedItemsRepository,\n      imagesProvider,\n      dateFormatProvider,\n      sorter\n    )\n  }\n\n  @After\n  fun tearDown() {\n    clearAllMocks()\n  }\n\n  @Test\n  fun `Should load items properly`() = runBlocking {\n    val movie = Movie.EMPTY.copy(\n      title = \"test1\",\n      released = LocalDate.now().minusYears(5)\n    )\n\n    coEvery { moviesRepository.watchlistMovies.loadAll() } returns listOf(movie)\n\n    val result = SUT.loadItems(searchQuery = \"\")\n\n    assertThat(result).isNotEmpty()\n    assertThat(result).hasSize(2)\n  }\n\n  @Test\n  fun `Should filter items by query if present`() {\n    runBlocking {\n      val movie1 = Movie.EMPTY.copy(\n        title = \"test1\",\n        released = LocalDate.now().minusYears(5)\n      )\n\n      val movie2 = Movie.EMPTY.copy(\n        title = \"xxx\",\n        released = LocalDate.now().minusYears(5)\n      )\n\n      coEvery { moviesRepository.watchlistMovies.loadAll() } returns listOf(movie1, movie2)\n\n      val result = SUT.loadItems(searchQuery = \"test\")\n\n      assertThat(result).hasSize(2)\n      assertThat(result[1].movie).isEqualTo(movie1)\n    }\n  }\n\n  @Test\n  fun `Should put pinned items at the top`() {\n    runBlocking {\n      val movie1 = Movie.EMPTY.copy(\n        title = \"test1\",\n        released = LocalDate.now().minusDays(10)\n      )\n\n      val movie2 = Movie.EMPTY.copy(\n        title = \"xxx\",\n        released = LocalDate.now().minusDays(10)\n      )\n\n      val movie3 = Movie.EMPTY.copy(\n        title = \"xxx2\",\n        released = LocalDate.now().minusDays(10)\n      )\n\n      coEvery { pinnedItemsRepository.isItemPinned(movie2) } returns true\n      coEvery { moviesRepository.watchlistMovies.loadAll() } returns listOf(movie1, movie2, movie3)\n\n      val result = SUT.loadItems(searchQuery = \"\")\n\n      assertThat(result).isNotEmpty()\n      assertThat((result[1] as ProgressMovieListItem.MovieItem).isPinned).isTrue()\n      assertThat(result[1].movie).isEqualTo(movie2)\n      assertThat((result[2] as ProgressMovieListItem.MovieItem).isPinned).isFalse()\n      assertThat((result[3] as ProgressMovieListItem.MovieItem).isPinned).isFalse()\n    }\n  }\n\n  @Test\n  fun `Should sort items properly with given comparator`() {\n    runBlocking {\n      val movie1 = Movie.EMPTY.copy(\n        title = \"test1\",\n        rating = 4F,\n        released = LocalDate.now().minusDays(10)\n      )\n\n      val movie2 = Movie.EMPTY.copy(\n        title = \"xxx\",\n        rating = 1F,\n        released = LocalDate.now().minusDays(10)\n      )\n\n      val movie3 = Movie.EMPTY.copy(\n        title = \"xxx2\",\n        rating = 11F,\n        released = LocalDate.now().minusDays(10)\n      )\n\n      coEvery { moviesRepository.watchlistMovies.loadAll() } returns listOf(movie1, movie2, movie3)\n      coEvery { sorter.sort(any(), any()) } returns compareByDescending { it.movie.rating }\n\n      val result1 = SUT.loadItems(searchQuery = \"\")\n      assertThat(result1[1].movie).isEqualTo(movie3)\n      assertThat(result1[2].movie).isEqualTo(movie1)\n      assertThat(result1[3].movie).isEqualTo(movie2)\n\n      coEvery { sorter.sort(any(), any()) } returns compareBy { it.movie.title }\n\n      val result2 = SUT.loadItems(searchQuery = \"\")\n      assertThat(result2[1].movie).isEqualTo(movie1)\n      assertThat(result2[2].movie).isEqualTo(movie2)\n      assertThat(result2[3].movie).isEqualTo(movie3)\n    }\n  }\n\n  @Test\n  fun `Should filter items with no release date and upcoming ones`() {\n    runBlocking {\n      val movie1 = Movie.EMPTY.copy(\n        title = \"test1\",\n        released = LocalDate.now().plusDays(10)\n      )\n\n      val movie2 = Movie.EMPTY.copy(\n        title = \"xxx\",\n        released = null\n      )\n\n      coEvery { moviesRepository.watchlistMovies.loadAll() } returns listOf(movie1, movie2)\n\n      val result = SUT.loadItems(searchQuery = \"test\")\n\n      assertThat(result).isEmpty()\n    }\n  }\n\n  @Test\n  fun `Should not add translation if default language`() {\n    runBlocking {\n      val movie1 = Movie.EMPTY.copy(\n        title = \"test1\",\n        released = LocalDate.now().minusYears(5)\n      )\n\n      coEvery { moviesRepository.watchlistMovies.loadAll() } returns listOf(movie1)\n\n      val result = SUT.loadItems(searchQuery = \"\")\n\n      assertThat(result).hasSize(2)\n      assertThat(result[1].movie).isEqualTo(movie1)\n      assertThat((result[1] as ProgressMovieListItem.MovieItem).translation).isNull()\n    }\n  }\n\n  @Test\n  fun `Should add translation if not default langiage`() {\n    runBlocking {\n      val movie1 = Movie.EMPTY.copy(\n        title = \"test1\",\n        released = LocalDate.now().minusYears(5)\n      )\n\n      coEvery { translationsRepository.getLanguage() } returns \"pl\"\n      coEvery { translationsRepository.loadTranslation(any<Movie>(), any(), any()) } returns Translation.EMPTY\n      coEvery { moviesRepository.watchlistMovies.loadAll() } returns listOf(movie1)\n\n      val result = SUT.loadItems(searchQuery = \"\")\n\n      assertThat(result).hasSize(2)\n      assertThat(result[1].movie).isEqualTo(movie1)\n      assertThat((result[1] as ProgressMovieListItem.MovieItem).translation).isEqualTo(Translation.EMPTY)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/test/java/com/michaldrabik/ui_progress_movies/progress/cases/ProgressMoviesPinnedCaseTest.kt",
    "content": "package com.michaldrabik.ui_progress_movies.progress.cases\n\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_progress_movies.BaseMockTest\nimport io.mockk.clearAllMocks\nimport io.mockk.coVerify\nimport io.mockk.impl.annotations.RelaxedMockK\nimport kotlinx.coroutines.test.runBlockingTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass ProgressMoviesPinnedCaseTest : BaseMockTest() {\n\n  @RelaxedMockK lateinit var pinnedItemsRepository: PinnedItemsRepository\n\n  private lateinit var SUT: ProgressMoviesPinnedCase\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    SUT = ProgressMoviesPinnedCase(pinnedItemsRepository)\n  }\n\n  @After\n  fun tearDown() {\n    clearAllMocks()\n  }\n\n  @Test\n  fun `Should set pinned item properly`() = runBlockingTest {\n    SUT.addPinnedItem(Movie.EMPTY)\n\n    coVerify(exactly = 1) { pinnedItemsRepository.addPinnedItem(Movie.EMPTY) }\n  }\n\n  @Test\n  fun `Should remove pinned item properly`() = runBlockingTest {\n    SUT.removePinnedItem(Movie.EMPTY)\n\n    coVerify(exactly = 1) { pinnedItemsRepository.removePinnedItem(Movie.EMPTY) }\n  }\n}\n"
  },
  {
    "path": "ui-progress-movies/src/test/java/com/michaldrabik/ui_progress_movies/progress/cases/ProgressMoviesSortCaseTest.kt",
    "content": "package com.michaldrabik.ui_progress_movies.progress.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_progress_movies.BaseMockTest\nimport io.mockk.clearAllMocks\nimport io.mockk.coVerify\nimport io.mockk.impl.annotations.RelaxedMockK\nimport kotlinx.coroutines.test.runBlockingTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass ProgressMoviesSortCaseTest : BaseMockTest() {\n\n  @RelaxedMockK lateinit var settingsRepository: SettingsRepository\n\n  private lateinit var SUT: ProgressMoviesSortCase\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    SUT = ProgressMoviesSortCase(settingsRepository)\n  }\n\n  @After\n  fun tearDown() {\n    clearAllMocks()\n  }\n\n  @Test\n  fun `Should set sorting order properly`() = runBlockingTest {\n    SUT.setSortOrder(SortOrder.RANK, SortType.DESCENDING)\n\n    coVerify { settingsRepository.sorting setProperty \"progressMoviesSortOrder\" value SortOrder.RANK }\n    coVerify { settingsRepository.sorting setProperty \"progressMoviesSortType\" value SortType.DESCENDING }\n  }\n}\n"
  },
  {
    "path": "ui-search/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-search/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_search'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-remote')\n  implementation project(':data-local')\n  implementation project(':ui-base')\n  implementation project(':repository')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  testImplementation project(':common-test')\n  testImplementation libs.bundles.testing\n  androidTestImplementation libs.android.test.runner\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-search/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/SearchFragment.kt",
    "content": "package com.michaldrabik.ui_search\n\nimport android.graphics.drawable.Animatable\nimport android.os.Bundle\nimport android.text.Editable\nimport android.view.KeyEvent\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.inputmethod.EditorInfo\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.clearFragmentResultListener\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.sheets.context_menu.ContextMenuBottomSheet\nimport com.michaldrabik.ui_base.common.sheets.sort_order.SortOrderBottomSheet\nimport com.michaldrabik.ui_base.utilities.extensions.add\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.disableUi\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.enableUi\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.hideKeyboard\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.shake\nimport com.michaldrabik.ui_base.utilities.extensions.showKeyboard\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.RecentSearch\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_MOVIE_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_SORT_ORDER\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SELECTED_SORT_TYPE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SHOW_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_SORT_ORDER\nimport com.michaldrabik.ui_search.databinding.FragmentSearchBinding\nimport com.michaldrabik.ui_search.recycler.SearchAdapter\nimport com.michaldrabik.ui_search.recycler.SearchListItem\nimport com.michaldrabik.ui_search.recycler.suggestions.SuggestionAdapter\nimport com.michaldrabik.ui_search.utilities.SearchLayoutManagerProvider\nimport com.michaldrabik.ui_search.utilities.TextWatcherAdapter\nimport com.michaldrabik.ui_search.views.RecentSearchView\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass SearchFragment : BaseFragment<SearchViewModel>(R.layout.fragment_search), TextWatcherAdapter {\n\n  companion object {\n    private const val ARG_HEADER_TRANSLATION = \"ARG_HEADER_TRANSLATION\"\n  }\n\n  @Inject lateinit var settings: SettingsViewModeRepository\n\n  override val navigationId = R.id.searchFragment\n\n  override val viewModel by viewModels<SearchViewModel>()\n  private val binding by viewBinding(FragmentSearchBinding::bind)\n\n  private var adapter: SearchAdapter? = null\n  private var suggestionsAdapter: SuggestionAdapter? = null\n  private var layoutManager: LayoutManager? = null\n  private var suggestionsLayoutManager: LayoutManager? = null\n\n  private val swipeRefreshEndOffset by lazy { requireContext().dimenToPx(R.dimen.swipeRefreshEndOffset) }\n  private val swipeRefreshStartOffset by lazy { requireContext().dimenToPx(R.dimen.swipeRefreshStartOffset) }\n\n  private var headerTranslation = 0F\n\n  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {\n    savedInstanceState?.let {\n      headerTranslation = it.getFloat(ARG_HEADER_TRANSLATION)\n    }\n    return super.onCreateView(inflater, container, savedInstanceState)\n  }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    enableUi()\n    setupView()\n    setupRecycler()\n    setupSuggestionsRecycler()\n    setupStatusBar()\n\n    if (savedInstanceState == null && !isInitialized) {\n      isInitialized = true\n    }\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.messageFlow.collect { showSnack(it) } }\n    )\n  }\n\n  override fun onPause() {\n    enableUi()\n    headerTranslation = binding.searchFiltersView.translationY\n    super.onPause()\n  }\n\n  override fun onStop() {\n    viewModel.clearSuggestions()\n    with(binding) {\n      searchViewLayout.binding.searchViewInput.removeTextChangedListener(this@SearchFragment)\n      searchViewLayout.binding.searchViewInput.setText(\"\")\n    }\n    super.onStop()\n  }\n\n  override fun onDestroyView() {\n    adapter = null\n    layoutManager = null\n    super.onDestroyView()\n  }\n\n  private fun setupView() {\n    with(binding) {\n      searchViewLayout.binding.searchViewInput.visible()\n      searchViewLayout.binding.searchViewText.gone()\n      (searchViewLayout.binding.searchViewIcon.drawable as Animatable).start()\n      searchViewLayout.settingsIconVisible = false\n\n      viewModel.preloadSuggestions()\n\n      if (!isInitialized) {\n        searchViewLayout.binding.searchViewInput.showKeyboard()\n        searchViewLayout.binding.searchViewInput.requestFocus()\n        viewModel.loadRecentSearches()\n      }\n\n      searchViewLayout.binding.searchViewInput.run {\n        addTextChangedListener(this@SearchFragment)\n        setOnEditorActionListener { textView, id, _ ->\n          if (id == EditorInfo.IME_ACTION_SEARCH) {\n            val query = textView.text.toString()\n            return@setOnEditorActionListener onSearchQuery(query)\n          }\n          true\n        }\n        setOnKeyListener { _, keyCode, keyEvent ->\n          if (keyEvent.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {\n            val query = text.toString()\n            return@setOnKeyListener onSearchQuery(query)\n          }\n          false\n        }\n      }\n\n      searchViewLayout.binding.searchViewIcon.onClick {\n        searchViewLayout.binding.searchViewInput.hideKeyboard()\n        requireActivity().onBackPressed()\n      }\n\n      with(searchFiltersView) {\n        onChipsChangeListener = viewModel::setFilters\n        onSortClickListener = ::openSortingDialog\n        translationY = headerTranslation\n      }\n    }\n  }\n\n  private fun setupRecycler() {\n    with(binding) {\n      layoutManager = SearchLayoutManagerProvider.provideLayoutManger(requireContext(), settings.tabletGridSpanSize)\n      adapter = SearchAdapter(\n        itemClickListener = { openShowDetails(it) },\n        itemLongClickListener = { openContextMenu(it) },\n        missingImageListener = { ids, force -> viewModel.loadMissingImage(ids, force) },\n        listChangeListener = { searchRecycler.scrollToPosition(0) }\n      )\n      searchRecycler.apply {\n        setHasFixedSize(true)\n        adapter = this@SearchFragment.adapter\n        layoutManager = this@SearchFragment.layoutManager\n        itemAnimator = null\n        clearOnScrollListeners()\n        addOnScrollListener(object : RecyclerView.OnScrollListener() {\n          override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {\n            val value = searchFiltersView.translationY - dy\n            searchFiltersView.translationY = value.coerceAtMost(0F)\n          }\n        })\n      }\n\n      searchSwipeRefresh.apply {\n        isEnabled = false\n        val color = requireContext().colorFromAttr(R.attr.colorAccent)\n        setProgressBackgroundColorSchemeColor(requireContext().colorFromAttr(R.attr.colorSearchViewBackground))\n        setColorSchemeColors(color, color, color)\n        setProgressViewOffset(false, swipeRefreshStartOffset, swipeRefreshEndOffset)\n      }\n    }\n  }\n\n  private fun setupSuggestionsRecycler() {\n    suggestionsLayoutManager = SearchLayoutManagerProvider.provideLayoutManger(requireContext(), settings.tabletGridSpanSize)\n    suggestionsAdapter = SuggestionAdapter(\n      itemClickListener = {\n        val query =\n          if (it.translation?.title?.isNotBlank() == true) it.translation.title\n          else it.title\n        viewModel.saveRecentSearch(query)\n        openDetails(it)\n      },\n      missingImageListener = { ids, force -> viewModel.loadMissingSuggestionImage(ids, force) },\n      missingTranslationListener = { viewModel.loadMissingSuggestionTranslation(it) }\n    )\n    binding.suggestionsRecycler.apply {\n      adapter = this@SearchFragment.suggestionsAdapter\n      layoutManager = this@SearchFragment.suggestionsLayoutManager\n      itemAnimator = null\n    }\n  }\n\n  private fun setupStatusBar() {\n    binding.searchRoot.doOnApplyWindowInsets { view, insets, _, _ ->\n      val tabletOffset = if (isTablet) dimenToPx(R.dimen.spaceMedium) else 0\n      val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + tabletOffset\n      view.updatePadding(top = inset)\n    }\n  }\n\n  private fun onSearchQuery(query: String): Boolean {\n    with(binding) {\n      if (query.trim().isBlank()) {\n        searchViewLayout.shake()\n        return true\n      }\n      viewModel.search(query)\n      searchViewLayout.binding.searchViewInput.hideKeyboard()\n      searchViewLayout.binding.searchViewInput.clearFocus()\n      return true\n    }\n  }\n\n  private fun openSortingDialog(order: SortOrder, type: SortType) {\n    val options = listOf(SortOrder.RANK, SortOrder.NAME, SortOrder.NEWEST)\n    val args = SortOrderBottomSheet.createBundle(options, order, type)\n\n    setFragmentResultListener(REQUEST_SORT_ORDER) { _, bundle ->\n      val sortOrder = bundle.getSerializable(ARG_SELECTED_SORT_ORDER) as SortOrder\n      val sortType = bundle.getSerializable(ARG_SELECTED_SORT_TYPE) as SortType\n      viewModel.setSortOrder(sortOrder, sortType)\n    }\n\n    navigateToSafe(R.id.actionSearchFragmentToSortOrder, args)\n  }\n\n  private fun openShowDetails(item: SearchListItem) {\n    disableUi()\n    binding.searchRoot.fadeOut(150) {\n      openDetails(item)\n    }.add(animations)\n  }\n\n  private fun openDetails(item: SearchListItem) {\n    if (item.isShow) {\n      val bundle = Bundle().apply { putLong(ARG_SHOW_ID, item.show.traktId) }\n      navigateToSafe(R.id.actionSearchFragmentToShowDetailsFragment, bundle)\n    } else if (item.isMovie) {\n      val bundle = Bundle().apply { putLong(ARG_MOVIE_ID, item.movie.traktId) }\n      navigateToSafe(R.id.actionSearchFragmentToMovieDetailsFragment, bundle)\n    }\n  }\n\n  private fun openContextMenu(item: SearchListItem) {\n    setFragmentResultListener(NavigationArgs.REQUEST_ITEM_MENU) { requestKey, _ ->\n      if (requestKey == NavigationArgs.REQUEST_ITEM_MENU) {\n        viewModel.refreshFollowState(item)\n      }\n      clearFragmentResultListener(NavigationArgs.REQUEST_ITEM_MENU)\n    }\n    if (item.isShow) {\n      val bundle = ContextMenuBottomSheet.createBundle(item.show.ids.trakt)\n      navigateToSafe(R.id.actionSearchFragmentToShowItemMenu, bundle)\n    } else if (item.isMovie) {\n      val bundle = ContextMenuBottomSheet.createBundle(item.movie.ids.trakt)\n      navigateToSafe(R.id.actionSearchFragmentToMovieItemMenu, bundle)\n    }\n  }\n\n  private fun render(uiState: SearchUiState) {\n    uiState.run {\n      with(binding) {\n        searchItems?.let {\n          val resetScroll = resetScroll?.consume() == true\n          adapter?.setItems(it, resetScroll)\n          if (searchItemsAnimate?.consume() == true) {\n            searchRecycler.scheduleLayoutAnimation()\n          }\n          if (resetScroll) {\n            searchFiltersView.translationY = 0F\n          }\n        }\n        recentSearchItems?.let { renderRecentSearches(it) }\n        suggestionsItems?.let {\n          suggestionsAdapter?.setItems(it)\n          suggestionsRecycler.visibleIf(it.isNotEmpty())\n        }\n        searchOptions?.let {\n          searchFiltersView.setTypes(it.filters)\n          searchFiltersView.setSorting(it.sortOrder, it.sortType)\n        }\n        isSearching.let {\n          searchSwipeRefresh.isRefreshing = it\n          searchViewLayout.isEnabled = !it\n        }\n        sortOrder?.let { event ->\n          event.consume()?.let { openSortingDialog(it.first, it.second) }\n        }\n        isMoviesEnabled.let { isEnabled ->\n          val types = mutableListOf(Mode.SHOWS).apply {\n            if (isEnabled) add(Mode.MOVIES)\n          }\n          searchFiltersView.setEnabledTypes(types)\n        }\n        searchEmptyView.fadeIf(isEmpty)\n        searchInitialView.fadeIf(isInitial)\n        searchFiltersView.visibleIf(isFiltersVisible)\n      }\n    }\n  }\n\n  private fun renderRecentSearches(it: List<RecentSearch>) {\n    with(binding) {\n      if (it.isEmpty()) {\n        searchRecentsClearButton.gone()\n        searchRecentsLayout.removeAllViews()\n        searchRecentsLayout.gone()\n        return\n      }\n\n      searchRecentsLayout.fadeIn()\n      searchRecentsClearButton.fadeIn()\n      searchRecentsClearButton.onClick { viewModel.clearRecentSearches() }\n\n      val paddingH = requireContext().dimenToPx(R.dimen.screenMarginHorizontal)\n      val paddingV = requireContext().dimenToPx(R.dimen.spaceMedium)\n\n      searchRecentsLayout.removeAllViews()\n      it.forEach { item ->\n        val view = RecentSearchView(requireContext()).apply {\n          setPadding(paddingH, paddingV, paddingH, paddingV)\n          bind(item)\n          onClick {\n            viewModel.search(item.text)\n            searchViewLayout.binding.searchViewInput.setText(item.text)\n          }\n        }\n        searchRecentsLayout.addView(view)\n      }\n    }\n  }\n\n  override fun afterTextChanged(text: Editable?) {\n    viewModel.loadSuggestions(text.toString())\n  }\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/SearchUiState.kt",
    "content": "package com.michaldrabik.ui_search\n\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.RecentSearch\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_search.recycler.SearchListItem\nimport com.michaldrabik.ui_search.utilities.SearchOptions\n\ndata class SearchUiState(\n  val searchItems: List<SearchListItem>? = null,\n  val searchItemsAnimate: Event<Boolean>? = null,\n  val recentSearchItems: List<RecentSearch>? = null,\n  val suggestionsItems: List<SearchListItem>? = null,\n  val searchOptions: SearchOptions? = null,\n  val sortOrder: Event<Pair<SortOrder, SortType>>? = null,\n  val isSearching: Boolean = false,\n  val isEmpty: Boolean = false,\n  val isInitial: Boolean = false,\n  val isFiltersVisible: Boolean = false,\n  val isMoviesEnabled: Boolean = false,\n  val resetScroll: Event<Boolean>? = null,\n)\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/SearchViewModel.kt",
    "content": "package com.michaldrabik.ui_search\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Config.SEARCH_RECENTS_AMOUNT\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.combine\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.RecentSearch\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_search.cases.SearchFiltersCase\nimport com.michaldrabik.ui_search.cases.SearchInvalidateItemCase\nimport com.michaldrabik.ui_search.cases.SearchQueryCase\nimport com.michaldrabik.ui_search.cases.SearchRecentsCase\nimport com.michaldrabik.ui_search.cases.SearchSortingCase\nimport com.michaldrabik.ui_search.cases.SearchSuggestionsCase\nimport com.michaldrabik.ui_search.cases.SearchTranslationsCase\nimport com.michaldrabik.ui_search.recycler.SearchListItem\nimport com.michaldrabik.ui_search.utilities.SearchOptions\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SearchViewModel @Inject constructor(\n  private val searchQueryCase: SearchQueryCase,\n  private val searchFiltersCase: SearchFiltersCase,\n  private val searchSortingCase: SearchSortingCase,\n  private val searchTranslationsCase: SearchTranslationsCase,\n  private val searchInvalidateCase: SearchInvalidateItemCase,\n  private val recentSearchesCase: SearchRecentsCase,\n  private val suggestionsCase: SearchSuggestionsCase,\n  private val showsImagesProvider: ShowImagesProvider,\n  private val moviesImagesProvider: MovieImagesProvider,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val searchItemsState = MutableStateFlow<List<SearchListItem>?>(null)\n  private val searchItemsAnimateEvent = MutableStateFlow<Event<Boolean>?>(null)\n  private val recentSearchItemsState = MutableStateFlow<List<RecentSearch>?>(null)\n  private val suggestionsItemsState = MutableStateFlow<List<SearchListItem>?>(null)\n  private val searchOptionsState = MutableStateFlow(SearchOptions())\n  private val searchingState = MutableStateFlow(false)\n  private val emptyState = MutableStateFlow(false)\n  private val initialState = MutableStateFlow(false)\n  private val filtersVisibleState = MutableStateFlow(false)\n  private val moviesEnabledState = MutableStateFlow(true)\n  private val resetScrollEvent = MutableStateFlow<Event<Boolean>?>(null)\n\n  private var isSearching = false\n  private var suggestionsJob: Job? = null\n  private var currentSearch: List<SearchListItem>? = null\n\n  fun preloadSuggestions() {\n    viewModelScope.launch {\n      suggestionsCase.preloadCache()\n    }\n  }\n\n  fun loadRecentSearches() {\n    viewModelScope.launch {\n      val searches = recentSearchesCase.getRecentSearches(SEARCH_RECENTS_AMOUNT)\n      recentSearchItemsState.value = searches\n      initialState.value = searches.isEmpty()\n    }\n  }\n\n  fun clearRecentSearches() {\n    viewModelScope.launch {\n      recentSearchesCase.clearRecentSearches()\n      recentSearchItemsState.value = emptyList()\n      initialState.value = true\n    }\n  }\n\n  fun search(query: String) {\n\n    fun setInitialState() {\n      searchItemsState.value = emptyList()\n      searchItemsAnimateEvent.value = Event(false)\n      searchingState.value = true\n      emptyState.value = false\n      initialState.value = false\n      filtersVisibleState.value = false\n      suggestionsItemsState.value = emptyList()\n      searchOptionsState.value = SearchOptions()\n      recentSearchItemsState.value = emptyList()\n      currentSearch = null\n    }\n\n    fun setResultsState(items: List<SearchListItem>, isMoviesEnabled: Boolean) {\n      searchItemsState.value = items\n      searchItemsAnimateEvent.value = Event(true)\n      searchingState.value = false\n      emptyState.value = items.isEmpty()\n      initialState.value = false\n      filtersVisibleState.value = items.isNotEmpty()\n      moviesEnabledState.value = isMoviesEnabled\n      suggestionsItemsState.value = emptyList()\n      resetScrollEvent.value = Event(true)\n    }\n\n    val trimmed = query.trim()\n    if (trimmed.isEmpty()) return\n\n    viewModelScope.launch {\n      try {\n        isSearching = true\n        setInitialState()\n\n        val results = searchQueryCase.searchByQuery(trimmed)\n        val isMoviesEnabled = searchFiltersCase.isMoviesEnabled\n        val items = results.also { currentSearch = it }\n        recentSearchesCase.saveRecentSearch(trimmed)\n\n        setResultsState(items, isMoviesEnabled)\n      } catch (t: Throwable) {\n        onError()\n      } finally {\n        isSearching = false\n      }\n    }\n  }\n\n  fun saveRecentSearch(query: String) {\n    if (query.trim().isBlank()) return\n    viewModelScope.launch {\n      recentSearchesCase.saveRecentSearch(query)\n    }\n  }\n\n  fun setFilters(filters: List<Mode>) {\n    val currentOptions = searchOptionsState.value\n    if (currentOptions.filters == filters) return\n    val newOptions = currentOptions.copy(filters = filters)\n\n    searchOptionsState.value = newOptions\n    searchItemsState.value = currentSearch\n      ?.filter { searchFiltersCase.filter(newOptions, it) }\n      ?.sortedWith(searchSortingCase.sort(newOptions))\n    resetScrollEvent.value = Event(true)\n  }\n\n  fun setSortOrder(sortOrder: SortOrder, sortType: SortType) {\n    val currentOptions = searchOptionsState.value\n    if (currentOptions.sortOrder == sortOrder && currentOptions.sortType == sortType) {\n      return\n    }\n    val newOptions = currentOptions.copy(sortOrder = sortOrder, sortType = sortType)\n\n    searchOptionsState.value = newOptions\n    searchItemsState.value = currentSearch\n      ?.filter { searchFiltersCase.filter(newOptions, it) }\n      ?.sortedWith(searchSortingCase.sort(newOptions))\n    resetScrollEvent.value = Event(true)\n  }\n\n  fun loadSuggestions(query: String) {\n    suggestionsJob?.cancel()\n\n    if (query.trim().length < 2 || isSearching) {\n      clearSuggestions()\n      return\n    }\n\n    suggestionsJob = viewModelScope.launch {\n      val results = suggestionsCase.loadSuggestions(query)\n      suggestionsItemsState.value = results\n    }\n  }\n\n  fun clearSuggestions() {\n    suggestionsItemsState.value = emptyList()\n  }\n\n  fun refreshFollowState(item: SearchListItem) {\n    val currentItems = searchItemsState.value?.toMutableList() ?: mutableListOf()\n    currentItems.find { it.id == item.id }?.let {\n      viewModelScope.launch {\n        val (isFollowed, isWatchlist) = searchInvalidateCase.checkFollowedState(it)\n        updateItem(it.copy(isFollowed = isFollowed, isWatchlist = isWatchlist))\n      }\n    }\n  }\n\n  fun loadMissingImage(item: SearchListItem, force: Boolean) {\n    viewModelScope.launch {\n      updateItem(item.copy(isLoading = true))\n      try {\n        val image =\n          if (item.isShow) showsImagesProvider.loadRemoteImage(item.show, item.image.type, force)\n          else moviesImagesProvider.loadRemoteImage(item.movie, item.image.type, force)\n        updateItem(item.copy(isLoading = false, image = image))\n      } catch (t: Throwable) {\n        updateItem(item.copy(isLoading = false, image = Image.createUnavailable(item.image.type)))\n      }\n    }\n  }\n\n  fun loadMissingSuggestionImage(item: SearchListItem, force: Boolean) {\n    viewModelScope.launch {\n      updateSuggestionsItem(item.copy(isLoading = true))\n      try {\n        val image =\n          if (item.isShow) showsImagesProvider.loadRemoteImage(item.show, item.image.type, force)\n          else moviesImagesProvider.loadRemoteImage(item.movie, item.image.type, force)\n        updateSuggestionsItem(item.copy(isLoading = false, image = image))\n      } catch (t: Throwable) {\n        updateSuggestionsItem(item.copy(isLoading = false, image = Image.createUnavailable(item.image.type)))\n      }\n    }\n  }\n\n  fun loadMissingSuggestionTranslation(item: SearchListItem) {\n    if (item.translation != null || searchTranslationsCase.getLanguage() == Config.DEFAULT_LANGUAGE) {\n      return\n    }\n    viewModelScope.launch {\n      try {\n        val translation =\n          if (item.isShow) searchTranslationsCase.loadTranslation(item.show)\n          else searchTranslationsCase.loadTranslation(item.movie)\n        updateSuggestionsItem(item.copy(translation = translation))\n      } catch (error: Throwable) {\n        Timber.e(error)\n      }\n    }\n  }\n\n  private fun updateItem(new: SearchListItem) {\n    val currentItems = uiState.value.searchItems?.toMutableList()\n    currentItems?.run {\n      findReplace(new) { it.isSameAs(new) }\n    }\n    searchItemsState.value = currentItems\n  }\n\n  private fun updateSuggestionsItem(new: SearchListItem) {\n    val currentItems = uiState.value.suggestionsItems?.toMutableList()\n    currentItems?.run { findReplace(new) { it.isSameAs(new) } }\n    suggestionsItemsState.value = currentItems\n  }\n\n  private suspend fun onError() {\n    searchingState.value = false\n    emptyState.value = false\n    messageChannel.send(MessageEvent.Error(R.string.errorCouldNotLoadSearchResults))\n  }\n\n  override fun onCleared() {\n    suggestionsCase.clearCache()\n    super.onCleared()\n  }\n\n  val uiState = combine(\n    searchItemsState,\n    searchItemsAnimateEvent,\n    recentSearchItemsState,\n    suggestionsItemsState,\n    searchingState,\n    emptyState,\n    initialState,\n    moviesEnabledState,\n    resetScrollEvent,\n    searchOptionsState,\n    filtersVisibleState\n  ) { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11 ->\n    SearchUiState(\n      searchItems = s1,\n      searchItemsAnimate = s2,\n      recentSearchItems = s3,\n      suggestionsItems = s4,\n      isSearching = s5,\n      isEmpty = s6,\n      isInitial = s7,\n      isMoviesEnabled = s8,\n      resetScroll = s9,\n      searchOptions = s10,\n      isFiltersVisible = s11\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = SearchUiState()\n  )\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/cases/SearchFiltersCase.kt",
    "content": "package com.michaldrabik.ui_search.cases\n\nimport com.michaldrabik.common.Mode.MOVIES\nimport com.michaldrabik.common.Mode.SHOWS\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_search.recycler.SearchListItem\nimport com.michaldrabik.ui_search.utilities.SearchOptions\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SearchFiltersCase @Inject constructor(\n  private val settingsRepository: SettingsRepository\n) {\n\n  val isMoviesEnabled by lazy { settingsRepository.isMoviesEnabled }\n\n  fun filter(\n    searchOptions: SearchOptions,\n    item: SearchListItem\n  ): Boolean {\n    val filterNone = searchOptions.filters.isEmpty() || searchOptions.filters.containsAll(listOf(SHOWS, MOVIES))\n    return when {\n      filterNone -> true\n      searchOptions.filters.contains(SHOWS) -> item.isShow\n      searchOptions.filters.contains(MOVIES) && isMoviesEnabled -> item.isMovie\n      searchOptions.filters.contains(MOVIES) && !isMoviesEnabled -> false\n      else -> true\n    }\n  }\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/cases/SearchInvalidateItemCase.kt",
    "content": "package com.michaldrabik.ui_search.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_search.recycler.SearchListItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SearchInvalidateItemCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val showsRepository: ShowsRepository,\n  private val moviesRepository: MoviesRepository,\n) {\n\n  suspend fun checkFollowedState(item: SearchListItem) = withContext(dispatchers.IO) {\n    when {\n      item.isShow -> {\n        val (isMy, isWatchlist) = awaitAll(\n          async { showsRepository.myShows.exists(item.show.ids.trakt) },\n          async { showsRepository.watchlistShows.exists(item.show.ids.trakt) },\n        )\n        Pair(isMy, isWatchlist)\n      }\n      item.isMovie -> {\n        val (isMy, isWatchlist) = awaitAll(\n          async { moviesRepository.myMovies.exists(item.movie.ids.trakt) },\n          async { moviesRepository.watchlistMovies.exists(item.movie.ids.trakt) },\n        )\n        Pair(isMy, isWatchlist)\n      }\n      else -> throw IllegalStateException()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/cases/SearchQueryCase.kt",
    "content": "package com.michaldrabik.ui_search.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SearchResult\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_search.recycler.SearchListItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport java.util.UUID\nimport javax.inject.Inject\nimport com.michaldrabik.data_remote.trakt.model.SearchResult as SearchResultNetwork\n\n@ViewModelScoped\nclass SearchQueryCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val remoteSource: RemoteDataSource,\n  private val mappers: Mappers,\n  private val settingsRepository: SettingsRepository,\n  private val showsRepository: ShowsRepository,\n  private val moviesRepository: MoviesRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val showsImagesProvider: ShowImagesProvider,\n  private val moviesImagesProvider: MovieImagesProvider,\n) {\n\n  suspend fun searchByQuery(query: String): List<SearchListItem> =\n    withContext(dispatchers.IO) {\n      val withMovies = settingsRepository.isMoviesEnabled\n      val myShowsIds = showsRepository.myShows.loadAllIds()\n      val watchlistShowsIds = showsRepository.watchlistShows.loadAllIds()\n      val myMoviesIds = moviesRepository.myMovies.loadAllIds()\n      val watchlistMoviesIds = moviesRepository.watchlistMovies.loadAllIds()\n      val spoilers = settingsRepository.spoilers.getAll()\n\n      val results = remoteSource.trakt.fetchSearch(query, withMovies)\n      results\n        .sortedWith(\n          compareByDescending<SearchResultNetwork> { it.score }\n            .thenByDescending { it.getVotes() }\n        )\n        .map {\n          async {\n            val result = SearchResult(\n              it.score ?: 0F,\n              it.show?.let { s -> mappers.show.fromNetwork(s) } ?: Show.EMPTY,\n              it.movie?.let { m -> mappers.movie.fromNetwork(m) } ?: Movie.EMPTY\n            )\n\n            val isFollowed =\n              if (result.isShow) result.traktId in myShowsIds\n              else result.traktId in myMoviesIds\n\n            val isWatchlist =\n              if (result.isShow) result.traktId in watchlistShowsIds\n              else result.traktId in watchlistMoviesIds\n\n            val image = loadImage(result)\n            val translation = loadTranslation(result)\n\n            SearchListItem(\n              id = UUID.randomUUID(),\n              show = result.show,\n              movie = result.movie,\n              image = image,\n              score = result.score,\n              isFollowed = isFollowed,\n              isWatchlist = isWatchlist,\n              translation = translation,\n              spoilers = spoilers\n            )\n          }\n        }.awaitAll()\n    }\n\n  private suspend fun loadImage(result: SearchResult) = when {\n    result.isShow -> showsImagesProvider.findCachedImage(result.show, ImageType.POSTER)\n    else -> moviesImagesProvider.findCachedImage(result.movie, ImageType.POSTER)\n  }\n\n  private suspend fun loadTranslation(result: SearchResult): Translation? {\n    val language = translationsRepository.getLanguage()\n    if (language == Config.DEFAULT_LANGUAGE) return Translation.EMPTY\n    return when {\n      result.isShow -> translationsRepository.loadTranslation(result.show, language, onlyLocal = true)\n      else -> translationsRepository.loadTranslation(result.movie, language, onlyLocal = true)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/cases/SearchRecentsCase.kt",
    "content": "package com.michaldrabik.ui_search.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.ui_model.RecentSearch\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\nimport com.michaldrabik.data_local.database.model.RecentSearch as RecentSearchDb\n\n@ViewModelScoped\nclass SearchRecentsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val localSource: LocalDataSource\n) {\n\n  suspend fun getRecentSearches(limit: Int): List<RecentSearch> =\n    withContext(dispatchers.IO) {\n      localSource.recentSearch.getAll(limit)\n        .map { RecentSearch(it.text) }\n    }\n\n  suspend fun clearRecentSearches() = withContext(dispatchers.IO) {\n    localSource.recentSearch.deleteAll()\n  }\n\n  suspend fun saveRecentSearch(query: String) = withContext(dispatchers.IO) {\n    val now = nowUtcMillis()\n    localSource.recentSearch.upsert(listOf(RecentSearchDb(0, query, now, now)))\n  }\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/cases/SearchSortingCase.kt",
    "content": "package com.michaldrabik.ui_search.cases\n\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_search.recycler.SearchListItem\nimport com.michaldrabik.ui_search.utilities.SearchOptions\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport java.util.Locale\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SearchSortingCase @Inject constructor() {\n\n  fun sort(searchOptions: SearchOptions) =\n    when (searchOptions.sortType) {\n      SortType.ASCENDING -> sortAscending(searchOptions.sortOrder)\n      SortType.DESCENDING -> sortDescending(searchOptions.sortOrder)\n    }\n\n  private fun sortAscending(sortOrder: SortOrder) =\n    when (sortOrder) {\n      SortOrder.RANK -> compareByDescending<SearchListItem> { it.score }.thenByDescending { it.votes }\n      SortOrder.NAME -> compareBy { getTitle(it) }\n      SortOrder.NEWEST -> compareBy { it.year }\n      else -> throw IllegalStateException(\"Invalid sort order\")\n    }\n\n  private fun sortDescending(sortOrder: SortOrder) =\n    when (sortOrder) {\n      SortOrder.RANK -> compareBy<SearchListItem> { it.score }.thenBy { it.votes }\n      SortOrder.NAME -> compareByDescending { getTitle(it) }\n      SortOrder.NEWEST -> compareByDescending { it.year }\n      else -> throw IllegalStateException(\"Invalid sort order\")\n    }\n\n  private fun getTitle(item: SearchListItem): String {\n    val translatedTitle =\n      if (item.translation?.hasTitle == true) item.translation.title\n      else item.title\n    return translatedTitle.uppercase(Locale.ROOT)\n  }\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/cases/SearchSuggestionsCase.kt",
    "content": "package com.michaldrabik.ui_search.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.SearchResult\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_search.recycler.SearchListItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport java.util.UUID\nimport javax.inject.Inject\nimport com.michaldrabik.data_local.database.model.Movie as MovieDb\nimport com.michaldrabik.data_local.database.model.Show as ShowDb\n\n@ViewModelScoped\nclass SearchSuggestionsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n  private val showsRepository: ShowsRepository,\n  private val moviesRepository: MoviesRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val settingsRepository: SettingsRepository,\n  private val showsImagesProvider: ShowImagesProvider,\n  private val moviesImagesProvider: MovieImagesProvider,\n) {\n\n  private var showsCache: List<ShowDb>? = null\n  private var moviesCache: List<MovieDb>? = null\n  private var showTranslationsCache: Map<Long, Translation>? = null\n  private var movieTranslationsCache: Map<Long, Translation>? = null\n\n  suspend fun preloadCache() = withContext(dispatchers.IO) {\n    val language = translationsRepository.getLanguage()\n    val moviesEnabled = settingsRepository.isMoviesEnabled\n\n    if (showsCache == null) showsCache = localSource.shows.getAll()\n    if (moviesEnabled && moviesCache == null) moviesCache = localSource.movies.getAll()\n\n    if (translationsRepository.getLanguage() != Config.DEFAULT_LANGUAGE) {\n      if (showTranslationsCache == null) {\n        showTranslationsCache = translationsRepository.loadAllShowsLocal(language)\n      }\n      if (moviesEnabled && movieTranslationsCache == null) {\n        movieTranslationsCache = translationsRepository.loadAllMoviesLocal(language)\n      }\n    }\n  }\n\n  suspend fun loadSuggestions(query: String) = withContext(dispatchers.IO) {\n    val spoilers = settingsRepository.spoilers.getAll()\n\n    val showsDef = async { loadShows(query.trim(), 5) }\n    val moviesDef = async { loadMovies(query.trim(), 5) }\n\n    val suggestions = (showsDef.await() + moviesDef.await()).map {\n      when (it) {\n        is Show -> SearchResult(0F, it, Movie.EMPTY)\n        is Movie -> SearchResult(0F, Show.EMPTY, it)\n        else -> throw IllegalStateException()\n      }\n    }\n\n    suggestions.map {\n      async {\n        val isFollowed =\n          if (it.isShow) showsRepository.myShows.exists(it.show.ids.trakt)\n          else moviesRepository.myMovies.exists(it.movie.ids.trakt)\n\n        val isWatchlist =\n          if (it.isShow) showsRepository.watchlistShows.exists(it.show.ids.trakt)\n          else moviesRepository.watchlistMovies.exists(it.movie.ids.trakt)\n\n        val image =\n          if (it.isShow) showsImagesProvider.findCachedImage(it.show, ImageType.POSTER)\n          else moviesImagesProvider.findCachedImage(it.movie, ImageType.POSTER)\n\n        SearchListItem(\n          id = UUID.randomUUID(),\n          show = it.show,\n          movie = it.movie,\n          image = image,\n          score = it.score,\n          isFollowed = isFollowed,\n          isWatchlist = isWatchlist,\n          translation = loadTranslation(it),\n          spoilers = spoilers\n        )\n      }\n    }\n      .awaitAll()\n      .sortedByDescending { it.votes }\n  }\n\n  private suspend fun loadShows(query: String, limit: Int): List<Show> {\n    if (query.trim().isBlank()) return emptyList()\n    preloadCache()\n    return showsCache\n      ?.filter {\n        it.title.contains(query, true) ||\n          showTranslationsCache?.get(it.idTrakt)?.title?.contains(query, true) == true\n      }\n      ?.take(limit)\n      ?.map { mappers.show.fromDatabase(it) }\n      ?: emptyList()\n  }\n\n  private suspend fun loadMovies(query: String, limit: Int): List<Movie> {\n    if (query.trim().isBlank()) return emptyList()\n    preloadCache()\n    return moviesCache\n      ?.filter {\n        it.title.contains(query, true) ||\n          movieTranslationsCache?.get(it.idTrakt)?.title?.contains(query, true) == true\n      }\n      ?.take(limit)\n      ?.map { mappers.movie.fromDatabase(it) }\n      ?: emptyList()\n  }\n\n  private suspend fun loadTranslation(result: SearchResult): Translation? {\n    val language = translationsRepository.getLanguage()\n    if (language == Config.DEFAULT_LANGUAGE) return Translation.EMPTY\n    return when {\n      result.isShow -> translationsRepository.loadTranslation(result.show, language, onlyLocal = true)\n      else -> translationsRepository.loadTranslation(result.movie, language, onlyLocal = true)\n    }\n  }\n\n  fun clearCache() {\n    showsCache = null\n    moviesCache = null\n    showTranslationsCache = null\n    movieTranslationsCache = null\n  }\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/cases/SearchTranslationsCase.kt",
    "content": "package com.michaldrabik.ui_search.cases\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.Translation\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SearchTranslationsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val translationsRepository: TranslationsRepository,\n) {\n\n  fun getLanguage() = translationsRepository.getLanguage()\n\n  suspend fun loadTranslation(show: Show): Translation? = withContext(dispatchers.IO) {\n    val language = getLanguage()\n    if (language == Config.DEFAULT_LANGUAGE) {\n      return@withContext Translation.EMPTY\n    }\n    translationsRepository.loadTranslation(show, language)\n  }\n\n  suspend fun loadTranslation(movie: Movie): Translation? = withContext(dispatchers.IO) {\n    val language = getLanguage()\n    if (language == Config.DEFAULT_LANGUAGE) {\n      return@withContext Translation.EMPTY\n    }\n    translationsRepository.loadTranslation(movie, language)\n  }\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/recycler/SearchAdapter.kt",
    "content": "package com.michaldrabik.ui_search.recycler\n\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseAdapter\nimport com.michaldrabik.ui_search.views.SearchItemView\n\nclass SearchAdapter(\n  private val itemClickListener: (SearchListItem) -> Unit,\n  private val itemLongClickListener: (SearchListItem) -> Unit,\n  private val missingImageListener: (SearchListItem, Boolean) -> Unit,\n  listChangeListener: () -> Unit,\n) : BaseAdapter<SearchListItem>(\n  listChangeListener = listChangeListener\n) {\n\n  override val asyncDiffer = AsyncListDiffer(this, SearchItemDiffCallback())\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    BaseViewHolder(\n      SearchItemView(parent.context).apply {\n        itemClickListener = this@SearchAdapter.itemClickListener\n        itemLongClickListener = this@SearchAdapter.itemLongClickListener\n        missingImageListener = this@SearchAdapter.missingImageListener\n      }\n    )\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    (holder.itemView as SearchItemView).bind(item)\n  }\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/recycler/SearchItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_search.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass SearchItemDiffCallback : DiffUtil.ItemCallback<SearchListItem>() {\n\n  override fun areItemsTheSame(oldItem: SearchListItem, newItem: SearchListItem) =\n    oldItem.id == newItem.id\n\n  override fun areContentsTheSame(oldItem: SearchListItem, newItem: SearchListItem) =\n    oldItem.image == newItem.image &&\n      oldItem.isFollowed == newItem.isFollowed &&\n      oldItem.isWatchlist == newItem.isWatchlist &&\n      oldItem.isLoading == newItem.isLoading &&\n      oldItem.spoilers == newItem.spoilers &&\n      oldItem.translation == newItem.translation\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/recycler/SearchListItem.kt",
    "content": "package com.michaldrabik.ui_search.recycler\n\nimport com.michaldrabik.ui_base.common.ListItem\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.Translation\nimport java.util.UUID\n\ndata class SearchListItem(\n  val id: UUID,\n  override val show: Show,\n  override val image: Image,\n  override val isLoading: Boolean = false,\n  val movie: Movie,\n  val score: Float,\n  val isFollowed: Boolean = false,\n  val isWatchlist: Boolean = false,\n  val translation: Translation? = null,\n  val spoilers: SpoilersSettings\n) : ListItem {\n\n  val isShow = show != Show.EMPTY\n  val isMovie = movie != Movie.EMPTY\n\n  val votes = if (isShow) show.votes else movie.votes\n  val title = if (isShow) show.title else movie.title\n  val overview = if (isShow) show.overview else movie.overview\n  val year = if (isShow) show.year else movie.year\n  val network = if (isShow) show.network else \"\"\n\n  override fun isSameAs(other: ListItem) = (id == (other as SearchListItem).id)\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/recycler/suggestions/SuggestionAdapter.kt",
    "content": "package com.michaldrabik.ui_search.recycler.suggestions\n\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseAdapter\nimport com.michaldrabik.ui_search.recycler.SearchListItem\nimport com.michaldrabik.ui_search.views.SearchSuggestionView\n\nclass SuggestionAdapter(\n  private val itemClickListener: (SearchListItem) -> Unit,\n  private val missingImageListener: (SearchListItem, Boolean) -> Unit,\n  private val missingTranslationListener: (SearchListItem) -> Unit\n) : BaseAdapter<SearchListItem>() {\n\n  override val asyncDiffer = AsyncListDiffer(this, SuggestionItemDiffCallback())\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    BaseViewHolder(\n      SearchSuggestionView(parent.context).apply {\n        itemClickListener = this@SuggestionAdapter.itemClickListener\n        missingImageListener = this@SuggestionAdapter.missingImageListener\n        missingTranslationListener = this@SuggestionAdapter.missingTranslationListener\n      }\n    )\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    (holder.itemView as SearchSuggestionView).bind(item)\n  }\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/recycler/suggestions/SuggestionItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_search.recycler.suggestions\n\nimport androidx.recyclerview.widget.DiffUtil\nimport com.michaldrabik.ui_search.recycler.SearchListItem\n\nclass SuggestionItemDiffCallback : DiffUtil.ItemCallback<SearchListItem>() {\n\n  override fun areItemsTheSame(oldItem: SearchListItem, newItem: SearchListItem) =\n    oldItem.id == newItem.id\n\n  override fun areContentsTheSame(oldItem: SearchListItem, newItem: SearchListItem) =\n    oldItem.image == newItem.image &&\n      oldItem.isFollowed == newItem.isFollowed &&\n      oldItem.isWatchlist == newItem.isWatchlist &&\n      oldItem.spoilers == newItem.spoilers &&\n      oldItem.isLoading == newItem.isLoading &&\n      oldItem.translation == newItem.translation\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/utilities/SearchLayoutManagerProvider.kt",
    "content": "package com.michaldrabik.ui_search.utilities\n\nimport android.content.Context\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\n\ninternal object SearchLayoutManagerProvider {\n\n  fun provideLayoutManger(\n    context: Context,\n    gridSpanSize: Int\n  ): RecyclerView.LayoutManager {\n    return if (context.isTablet()) {\n      GridLayoutManager(context, gridSpanSize)\n    } else {\n      LinearLayoutManager(context, VERTICAL, false)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/utilities/SearchOptions.kt",
    "content": "package com.michaldrabik.ui_search.utilities\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\n\ndata class SearchOptions(\n  val filters: List<Mode> = emptyList(),\n  val sortOrder: SortOrder = SortOrder.RANK,\n  val sortType: SortType = SortType.ASCENDING\n)\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/utilities/TextWatcherAdapter.kt",
    "content": "package com.michaldrabik.ui_search.utilities\n\nimport android.text.Editable\nimport android.text.TextWatcher\n\ninterface TextWatcherAdapter : TextWatcher {\n  override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit\n  override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit\n  override fun afterTextChanged(text: Editable?) = Unit\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/views/InitialSearchView.kt",
    "content": "package com.michaldrabik.ui_search.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.Gravity.CENTER\nimport android.widget.LinearLayout\nimport com.michaldrabik.ui_search.R\n\nclass InitialSearchView : LinearLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  init {\n    inflate(context, R.layout.view_search_initial, this)\n    orientation = VERTICAL\n    gravity = CENTER\n  }\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/views/RecentSearchView.kt",
    "content": "package com.michaldrabik.ui_search.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_model.RecentSearch\nimport com.michaldrabik.ui_search.databinding.ViewSearchRecentBinding\n\nclass RecentSearchView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewSearchRecentBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n  }\n\n  fun bind(item: RecentSearch) {\n    binding.searchRecentText.text = item.text\n  }\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/views/SearchFiltersView.kt",
    "content": "package com.michaldrabik.ui_search.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.forEach\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_search.R\nimport com.michaldrabik.ui_search.databinding.ViewSearchFiltersBinding\n\nclass SearchFiltersView : FrameLayout, CoordinatorLayout.AttachedBehavior {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewSearchFiltersBinding.inflate(LayoutInflater.from(context), this, true)\n\n  init {\n    binding.viewSearchFiltersShowsChip.setOnCheckedChangeListener { _, _ -> onChipCheckChange() }\n    binding.viewSearchFiltersMoviesChip.setOnCheckedChangeListener { _, _ -> onChipCheckChange() }\n  }\n\n  var onChipsChangeListener: ((List<Mode>) -> Unit)? = null\n  var onSortClickListener: ((SortOrder, SortType) -> Unit)? = null\n\n  var isListenerEnabled = true\n\n  private fun onChipCheckChange() {\n    with(binding) {\n      val ids = viewSearchFiltersChipGroup.checkedChipIds\n        .filterNot { it == viewSearchFiltersSortChip.id }\n        .map {\n          when (it) {\n            viewSearchFiltersShowsChip.id -> Mode.SHOWS\n            viewSearchFiltersMoviesChip.id -> Mode.MOVIES\n            else -> throw IllegalStateException()\n          }\n        }\n      onChipsChangeListener?.invoke(ids)\n    }\n  }\n\n  override fun setEnabled(enabled: Boolean) {\n    binding.viewSearchFiltersChipGroup.forEach {\n      it.isEnabled = enabled\n    }\n  }\n\n  fun setSorting(sortOrder: SortOrder, sortType: SortType) {\n    with(binding) {\n      viewSearchFiltersSortChip.text = context.getString(sortOrder.displayString)\n      viewSearchFiltersSortChip.onClick {\n        onSortClickListener?.invoke(sortOrder, sortType)\n      }\n      val sortIcon = when (sortType) {\n        SortType.ASCENDING -> R.drawable.ic_arrow_alt_up\n        SortType.DESCENDING -> R.drawable.ic_arrow_alt_down\n      }\n      viewSearchFiltersSortChip.closeIcon = ContextCompat.getDrawable(context, sortIcon)\n    }\n  }\n\n  fun setTypes(types: List<Mode>) {\n    isListenerEnabled = false\n    binding.viewSearchFiltersShowsChip.isChecked = Mode.SHOWS in types\n    binding.viewSearchFiltersMoviesChip.isChecked = Mode.MOVIES in types\n    isListenerEnabled = true\n  }\n\n  fun setEnabledTypes(types: List<Mode>) {\n    val hasShows = types.contains(Mode.SHOWS)\n    val hasMovies = types.contains(Mode.MOVIES)\n    with(binding) {\n      viewSearchFiltersShowsChip.isEnabled = hasShows\n      viewSearchFiltersShowsChip.visibleIf(hasShows)\n      viewSearchFiltersMoviesChip.isEnabled = hasMovies\n      viewSearchFiltersMoviesChip.visibleIf(hasMovies)\n    }\n  }\n\n  override fun getBehavior() = ScrollableViewBehaviour()\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/views/SearchItemView.kt",
    "content": "package com.michaldrabik.ui_search.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_search.R\nimport com.michaldrabik.ui_search.databinding.ViewShowSearchBinding\nimport com.michaldrabik.ui_search.recycler.SearchListItem\n\n@SuppressLint(\"SetTextI18n\")\nclass SearchItemView : ShowView<SearchListItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val view = ViewShowSearchBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    with(view.showSearchRoot) {\n      onClick { itemClickListener?.invoke(item) }\n      onLongClick { itemLongClickListener?.invoke(item) }\n    }\n  }\n\n  private val spaceNano by lazy { context.dimenToPx(R.dimen.spaceNano).toFloat() }\n\n  override val imageView: ImageView = view.showSearchImage\n  override val placeholderView: ImageView = view.showSearchPlaceholder\n\n  private lateinit var item: SearchListItem\n\n  override fun bind(item: SearchListItem) {\n    clear()\n    this.item = item\n    with(view) {\n      showSearchTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.title\n        else item.translation?.title\n\n      bindDescription(item)\n\n      val year = if (item.year > 0) item.year.toString() else \"\"\n      showSearchNetwork.text =\n        if (item.network.isNotBlank()) context.getString(R.string.textNetwork, year, item.network)\n        else String.format(\"%s\", year)\n\n      showSearchBadge.visibleIf(item.isFollowed)\n      showSearchWatchlistBadge.visibleIf(item.isWatchlist)\n\n      showSearchPlaceholder.setImageResource(if (item.isMovie) R.drawable.ic_film else R.drawable.ic_television)\n      showSearchIcon.setImageResource(if (item.isMovie) R.drawable.ic_film else R.drawable.ic_television)\n      showSearchNetwork.translationY = if (item.isMovie) 0F else spaceNano\n      loadImage(item)\n    }\n  }\n\n  private fun bindDescription(item: SearchListItem) {\n    with(view) {\n      var isSpoilerHidden = false\n      var overview = if (item.translation?.overview.isNullOrBlank()) {\n        item.overview\n      } else {\n        item.translation?.overview\n      }\n\n      if (item.isShow) {\n        val isMyHidden = item.spoilers.isMyShowsHidden && item.isFollowed\n        val isWatchlistHidden = item.spoilers.isWatchlistShowsHidden && item.isWatchlist\n        val isNotCollectedHidden = item.spoilers.isNotCollectedShowsHidden && (!item.isFollowed && !item.isWatchlist)\n        if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n          showSearchDescription.tag = overview.toString()\n          overview = SPOILERS_REGEX.replace(overview.toString(), SPOILERS_HIDE_SYMBOL)\n          isSpoilerHidden = true\n        }\n      }\n\n      if (item.isMovie) {\n        val isMyHidden = item.spoilers.isMyMoviesHidden && item.isFollowed\n        val isWatchlistHidden = item.spoilers.isWatchlistMoviesHidden && item.isWatchlist\n        val isNotCollectedHidden = item.spoilers.isNotCollectedMoviesHidden && (!item.isFollowed && !item.isWatchlist)\n        if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n          showSearchDescription.tag = overview.toString()\n          overview = SPOILERS_REGEX.replace(overview.toString(), SPOILERS_HIDE_SYMBOL)\n          isSpoilerHidden = true\n        }\n      }\n\n      with(showSearchDescription) {\n        text = overview\n        visibleIf(item.overview.isNotBlank())\n        if (isSpoilerHidden && item.spoilers.isTapToReveal) {\n          onClick { view ->\n            view.tag?.let { text = it.toString() }\n            view.isClickable = false\n          }\n        }\n      }\n    }\n  }\n\n  private fun clear() {\n    with(view) {\n      showSearchTitle.text = \"\"\n      showSearchDescription.text = \"\"\n      showSearchNetwork.text = \"\"\n      showSearchPlaceholder.gone()\n      showSearchBadge.gone()\n      Glide.with(this@SearchItemView).clear(showSearchImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-search/src/main/java/com/michaldrabik/ui_search/views/SearchSuggestionView.kt",
    "content": "package com.michaldrabik.ui_search.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_search.R\nimport com.michaldrabik.ui_search.databinding.ViewSuggestionSearchBinding\nimport com.michaldrabik.ui_search.recycler.SearchListItem\n\n@SuppressLint(\"SetTextI18n\")\nclass SearchSuggestionView : ShowView<SearchListItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val view = ViewSuggestionSearchBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    view.suggestionRoot.onClick { itemClickListener?.invoke(item) }\n    imageLoadCompleteListener = { loadTranslation() }\n  }\n\n  override val imageView: ImageView = view.suggestionImage\n  override val placeholderView: ImageView = view.suggestionPlaceholder\n\n  private lateinit var item: SearchListItem\n\n  override fun bind(item: SearchListItem) {\n    clear()\n    this.item = item\n    with(view) {\n      if (item.isMovie) suggestionPlaceholder.setImageResource(R.drawable.ic_film)\n\n      val translationTitle = item.translation?.title\n      suggestionTitle.text =\n        if (translationTitle.isNullOrBlank()) item.title\n        else translationTitle\n\n      bindDescription(item)\n\n      suggestionNetwork.text =\n        if (item.isShow) {\n          if (item.year > 0) context.getString(R.string.textNetwork, item.network, item.year.toString())\n          else String.format(\"%s\", item.network)\n        } else {\n          String.format(\"%s\", item.year)\n        }\n\n      suggestionBadge.visibleIf(item.isFollowed)\n      suggestionWatchlistBadge.visibleIf(item.isWatchlist)\n      suggestionDescription.visibleIf(item.overview.isNotBlank())\n      suggestionNetwork.visibleIf(item.network.isNotBlank() || item.year > 0)\n\n      loadImage(item)\n    }\n  }\n\n  private fun bindDescription(item: SearchListItem) {\n    with(view) {\n      var isSpoilerHidden = false\n      var overview = if (item.translation?.overview.isNullOrBlank()) {\n        item.overview\n      } else {\n        item.translation?.overview\n      }\n\n      if (item.isShow) {\n        val isMyHidden = item.spoilers.isMyShowsHidden && item.isFollowed\n        val isWatchlistHidden = item.spoilers.isWatchlistShowsHidden && item.isWatchlist\n        val isNotCollectedHidden = item.spoilers.isNotCollectedShowsHidden && (!item.isFollowed && !item.isWatchlist)\n        if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n          suggestionDescription.tag = overview.toString()\n          overview = SPOILERS_REGEX.replace(overview.toString(), SPOILERS_HIDE_SYMBOL)\n          isSpoilerHidden = true\n        }\n      }\n\n      if (item.isMovie) {\n        val isMyHidden = item.spoilers.isMyMoviesHidden && item.isFollowed\n        val isWatchlistHidden = item.spoilers.isWatchlistMoviesHidden && item.isWatchlist\n        val isNotCollectedHidden = item.spoilers.isNotCollectedMoviesHidden && (!item.isFollowed && !item.isWatchlist)\n        if (isMyHidden || isWatchlistHidden || isNotCollectedHidden) {\n          suggestionDescription.tag = overview.toString()\n          overview = SPOILERS_REGEX.replace(overview.toString(), SPOILERS_HIDE_SYMBOL)\n          isSpoilerHidden = true\n        }\n      }\n\n      with(suggestionDescription) {\n        text = overview\n        visibleIf(item.overview.isNotBlank())\n        if (isSpoilerHidden && item.spoilers.isTapToReveal) {\n          onClick { view ->\n            view.tag?.let { text = it.toString() }\n            view.isClickable = false\n          }\n        }\n      }\n    }\n  }\n\n  private fun loadTranslation() {\n    if (item.translation == null) {\n      missingTranslationListener?.invoke(item)\n    }\n  }\n\n  private fun clear() {\n    with(view) {\n      suggestionTitle.text = \"\"\n      suggestionDescription.text = \"\"\n      suggestionNetwork.text = \"\"\n      suggestionPlaceholder.setImageResource(R.drawable.ic_television)\n      suggestionPlaceholder.gone()\n      Glide.with(this@SearchSuggestionView).clear(suggestionImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-search/src/main/res/color/selector_search_chip_background.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?android:windowBackground\" android:state_checked=\"true\" />\n  <item android:color=\"?android:windowBackground\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "ui-search/src/main/res/color/selector_search_chip_text.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?attr/textColorChipSelected\" android:state_checked=\"true\" />\n  <item android:color=\"?attr/textColorChip\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "ui-search/src/main/res/color-notnight/selector_search_chip_background.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <item android:color=\"?attr/colorBackgroundChipSelected\" android:state_checked=\"true\" />\n  <item android:color=\"?android:windowBackground\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "ui-search/src/main/res/drawable/bg_suggestions.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <solid android:color=\"?attr/colorSearchViewBackground\" />\n  <corners\n    android:bottomLeftRadius=\"8dp\"\n    android:bottomRightRadius=\"8dp\"\n    />\n</shape>"
  },
  {
    "path": "ui-search/src/main/res/layout/fragment_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/searchRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"true\"\n  >\n\n  <androidx.swiperefreshlayout.widget.SwipeRefreshLayout\n    android:id=\"@+id/searchSwipeRefresh\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    >\n\n    <androidx.recyclerview.widget.RecyclerView\n      android:id=\"@+id/searchRecycler\"\n      style=\"@style/ScrollbarsStyle\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:background=\"@android:color/transparent\"\n      android:clipToPadding=\"false\"\n      android:layoutAnimation=\"@anim/anim_recycler_fall_down\"\n      android:overScrollMode=\"never\"\n      android:paddingStart=\"@dimen/searchRecyclerHorizontalPadding\"\n      android:paddingTop=\"@dimen/searchRecyclerTopPadding\"\n      android:paddingEnd=\"@dimen/searchRecyclerHorizontalPadding\"\n      android:paddingBottom=\"@dimen/spaceMedium\"\n      android:scrollbars=\"vertical\"\n      />\n\n  </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>\n\n  <com.michaldrabik.ui_search.views.SearchFiltersView\n    android:id=\"@+id/searchFiltersView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"18dp\"\n    android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n    android:visibility=\"gone\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/searchViewLayout\"\n    tools:visibility=\"visible\"\n    />\n\n  <View\n    android:id=\"@+id/separator\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"36dp\"\n    android:background=\"?android:windowBackground\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/suggestionsRecycler\"\n    style=\"@style/ScrollbarsStyle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/bg_suggestions\"\n    android:clipChildren=\"true\"\n    android:elevation=\"@dimen/elevationSuggestions\"\n    android:overScrollMode=\"never\"\n    android:paddingTop=\"24dp\"\n    android:paddingBottom=\"1px\"\n    android:translationY=\"-24dp\"\n    android:translationZ=\"0dp\"\n    android:visibility=\"gone\"\n    app:layout_constrainedHeight=\"true\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"@id/searchViewLayout\"\n    app:layout_constraintHeight_max=\"@dimen/suggestionsMaxHeight\"\n    app:layout_constraintStart_toStartOf=\"@id/searchViewLayout\"\n    app:layout_constraintTop_toBottomOf=\"@id/searchViewLayout\"\n    app:layout_constraintVertical_bias=\"0\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.SearchView\n    android:id=\"@+id/searchViewLayout\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"@dimen/searchViewHeight\"\n    android:layout_marginLeft=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginRight=\"@dimen/screenMarginHorizontal\"\n    android:translationZ=\"3dp\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <LinearLayout\n    android:id=\"@+id/searchRecentsLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:orientation=\"vertical\"\n    app:layout_constraintTop_toBottomOf=\"@id/searchViewLayout\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/searchRecentsClearButton\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:text=\"@string/textClear\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:visibility=\"gone\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@+id/searchRecentsLayout\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.EmptySearchView\n    android:id=\"@+id/searchEmptyView\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:visibility=\"gone\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@+id/searchViewLayout\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.michaldrabik.ui_search.views.InitialSearchView\n    android:id=\"@+id/searchInitialView\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:visibility=\"gone\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@+id/searchViewLayout\"\n    tools:visibility=\"visible\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-search/src/main/res/layout/view_search_filters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  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/viewSearchFiltersRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <com.google.android.material.chip.ChipGroup\n    android:id=\"@+id/viewSearchFiltersChipGroup\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    app:chipSpacingHorizontal=\"@dimen/spaceSmall\"\n    app:layout_behavior=\"com.michaldrabik.ui_base.common.behaviour.ScrollableViewBehaviour\"\n    >\n\n    <com.google.android.material.chip.Chip\n      android:id=\"@+id/viewSearchFiltersSortChip\"\n      style=\"@style/ShowlyChip.Sort\"\n      android:text=\"@string/textSortName\"\n      />\n\n    <com.google.android.material.chip.Chip\n      android:id=\"@+id/viewSearchFiltersShowsChip\"\n      style=\"@style/ShowlyChip.Filter\"\n      android:text=\"@string/textShows\"\n      app:chipBackgroundColor=\"@color/selector_chip_background\"\n      app:chipStrokeColor=\"@color/selector_chip_stroke\"\n      />\n\n    <com.google.android.material.chip.Chip\n      android:id=\"@+id/viewSearchFiltersMoviesChip\"\n      style=\"@style/ShowlyChip.Filter\"\n      android:text=\"@string/textMovies\"\n      android:textColor=\"@color/selector_chip_text\"\n      app:chipBackgroundColor=\"@color/selector_chip_background\"\n      app:chipStrokeColor=\"@color/selector_chip_stroke\"\n      />\n\n  </com.google.android.material.chip.ChipGroup>\n\n</FrameLayout>"
  },
  {
    "path": "ui-search/src/main/res/layout/view_search_initial.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"wrap_content\"\n  android:layout_height=\"wrap_content\"\n  android:gravity=\"center\"\n  android:orientation=\"vertical\"\n  tools:background=\"@color/colorBackground\"\n  tools:parentTag=\"android.widget.LinearLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <ImageView\n    android:layout_width=\"80dp\"\n    android:layout_height=\"80dp\"\n    android:adjustViewBounds=\"true\"\n    app:srcCompat=\"@drawable/ic_search\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n  <TextView\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/textSearchInitial1\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"16sp\"\n    />\n\n  <TextView\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textSearchInitial2\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    />\n\n</merge>\n"
  },
  {
    "path": "ui-search/src/main/res/layout/view_search_recent.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:background=\"@color/colorBackground\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/searchRecentRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    >\n\n    <ImageView\n      android:id=\"@+id/searchRecentIcon\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_history\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <TextView\n      android:id=\"@+id/searchRecentText\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:ellipsize=\"end\"\n      android:maxLines=\"1\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/searchRecentIcon\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:text=\"Game of Thrones\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-search/src/main/res/layout/view_show_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/showSearchRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:clipToPadding=\"false\"\n    android:paddingStart=\"@dimen/searchViewItemPaddingHorizontal\"\n    android:paddingTop=\"@dimen/spaceSmall\"\n    android:paddingEnd=\"@dimen/searchViewItemPaddingHorizontal\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <ImageView\n      android:id=\"@+id/showSearchImage\"\n      android:layout_width=\"@dimen/searchViewImageWidth\"\n      android:layout_height=\"@dimen/searchViewImageHeight\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/showSearchPlaceholder\"\n      android:layout_width=\"@dimen/searchViewImageWidth\"\n      android:layout_height=\"@dimen/searchViewImageHeight\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"20dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/showSearchBadge\"\n      style=\"@style/Badge\"\n      android:layout_width=\"18dp\"\n      android:layout_height=\"18dp\"\n      android:layout_marginTop=\"2dp\"\n      android:layout_marginEnd=\"12dp\"\n      app:layout_constraintEnd_toStartOf=\"@id/showSearchTitle\"\n      app:layout_constraintTop_toTopOf=\"@id/showSearchRoot\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/showSearchWatchlistBadge\"\n      style=\"@style/Badge.Watchlist\"\n      android:layout_width=\"18dp\"\n      android:layout_height=\"18dp\"\n      android:layout_marginTop=\"2dp\"\n      android:layout_marginEnd=\"12dp\"\n      app:layout_constraintEnd_toStartOf=\"@id/showSearchTitle\"\n      app:layout_constraintTop_toTopOf=\"@id/showSearchRoot\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/showSearchTitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"16sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/showSearchDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/showSearchImage\"\n      app:layout_constraintTop_toBottomOf=\"@+id/showSearchNetwork\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Game of Thrones\"\n      />\n\n    <ImageView\n      android:id=\"@+id/showSearchIcon\"\n      android:layout_width=\"20dp\"\n      android:layout_height=\"14dp\"\n      android:layout_marginStart=\"10dp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/showSearchNetwork\"\n      app:layout_constraintStart_toEndOf=\"@id/showSearchImage\"\n      app:layout_constraintTop_toTopOf=\"@id/showSearchNetwork\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorAccent\"\n      />\n\n    <TextView\n      android:id=\"@+id/showSearchNetwork\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMicro\"\n      android:layout_marginBottom=\"@dimen/spaceMicro\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?attr/colorAccent\"\n      android:textSize=\"10sp\"\n      app:layout_constraintBottom_toTopOf=\"@+id/showSearchTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/showSearchIcon\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Netflix\"\n      />\n\n    <TextView\n      android:id=\"@+id/showSearchDescription\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start\"\n      android:maxLines=\"2\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/showSearchImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/showSearchTitle\"\n      tools:text=\"Lorem Ipsum\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-search/src/main/res/layout/view_suggestion_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/suggestionRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:paddingStart=\"@dimen/spaceMedium\"\n    android:paddingTop=\"@dimen/spaceSmall\"\n    android:paddingEnd=\"@dimen/spaceMedium\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <ImageView\n      android:id=\"@+id/suggestionImage\"\n      android:layout_width=\"@dimen/searchSuggestionViewImageWidth\"\n      android:layout_height=\"@dimen/searchSuggestionViewImageHeight\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationTiny\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/suggestionPlaceholder\"\n      android:layout_width=\"@dimen/searchSuggestionViewImageWidth\"\n      android:layout_height=\"@dimen/searchSuggestionViewImageHeight\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationTiny\"\n      android:padding=\"12dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/suggestionBadge\"\n      style=\"@style/Badge\"\n      android:layout_width=\"14dp\"\n      android:layout_height=\"14dp\"\n      android:layout_marginTop=\"1dp\"\n      android:layout_marginEnd=\"12dp\"\n      app:layout_constraintEnd_toStartOf=\"@id/suggestionTitle\"\n      app:layout_constraintTop_toTopOf=\"@id/suggestionRoot\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/suggestionWatchlistBadge\"\n      style=\"@style/Badge.Watchlist\"\n      android:layout_width=\"14dp\"\n      android:layout_height=\"14dp\"\n      android:layout_marginTop=\"1dp\"\n      android:layout_marginEnd=\"12dp\"\n      app:layout_constraintEnd_toStartOf=\"@id/suggestionTitle\"\n      app:layout_constraintTop_toTopOf=\"@id/suggestionRoot\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/suggestionTitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/suggestionDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/suggestionImage\"\n      app:layout_constraintTop_toBottomOf=\"@+id/suggestionNetwork\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Game of Thrones\"\n      />\n\n    <TextView\n      android:id=\"@+id/suggestionNetwork\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?attr/colorAccent\"\n      android:textSize=\"10sp\"\n      app:layout_constraintBottom_toTopOf=\"@+id/suggestionTitle\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/suggestionImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Netflix\"\n      />\n\n    <TextView\n      android:id=\"@+id/suggestionDescription\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:ellipsize=\"end\"\n      android:gravity=\"start\"\n      android:maxLines=\"2\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"10sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/suggestionImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/suggestionTitle\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Lorem Ipsum\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-search/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"suggestionsMaxHeight\">280dp</dimen>\n\n  <dimen name=\"searchRecyclerTopPadding\">119dp</dimen>\n  <dimen name=\"searchRecyclerHorizontalPadding\">0dp</dimen>\n  <dimen name=\"searchChipsHorizontalPadding\">14dp</dimen>\n</resources>"
  },
  {
    "path": "ui-search/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSearchInitial1\">What are you looking for?</string>\n  <string name=\"textSearchInitial2\">Search powered by Trakt.tv</string>\n  <string name=\"textClear\">Clear recent searches</string>\n</resources>"
  },
  {
    "path": "ui-search/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSearchInitial1\">ما الذي تبحث عنه؟</string>\n  <string name=\"textSearchInitial2\">نتائج البحث من موقع Trakt.tv</string>\n  <string name=\"textClear\">مسح سجلات البحث</string>\n</resources>\n"
  },
  {
    "path": "ui-search/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSearchInitial1\">Nach was suchst du?</string>\n  <string name=\"textSearchInitial2\">Suche powerd by Trakt.tv</string>\n  <string name=\"textClear\">Letzte Suchanfragen löschen</string>\n</resources>\n"
  },
  {
    "path": "ui-search/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSearchInitial1\">¿Qué estás buscando?</string>\n  <string name=\"textSearchInitial2\">Búsqueda proporcionada por Trakt.tv</string>\n  <string name=\"textClear\">Borrar búsquedas recientes</string>\n</resources>\n"
  },
  {
    "path": "ui-search/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSearchInitial1\">Mitä etsit?</string>\n  <string name=\"textSearchInitial2\">Haun voimanlähteenä Trakt.tv</string>\n  <string name=\"textClear\">Tyhjennä aiemmat haut</string>\n</resources>\n"
  },
  {
    "path": "ui-search/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSearchInitial1\">Que recherchez-vous ?</string>\n  <string name=\"textSearchInitial2\">Recherche effectuée par Trakt.tv</string>\n  <string name=\"textClear\">Effacer les recherches récentes</string>\n</resources>\n"
  },
  {
    "path": "ui-search/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSearchInitial1\">Cosa stai cercando?</string>\n  <string name=\"textSearchInitial2\">Ricerca offerta da Trakt.tv</string>\n  <string name=\"textClear\">Elimina le ricerche recenti</string>\n</resources>\n"
  },
  {
    "path": "ui-search/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSearchInitial1\">Czego dzisiaj szukasz?</string>\n  <string name=\"textSearchInitial2\">Wyszukiwarka wspierana przez Trakt.tv</string>\n  <string name=\"textClear\">Wyczyść ostatnio wyszukiwane</string>\n</resources>\n"
  },
  {
    "path": "ui-search/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSearchInitial1\">O que você está procurando?</string>\n  <string name=\"textSearchInitial2\">Pesquisa fornecida por Trakt.tv</string>\n  <string name=\"textClear\">Limpar pesquisas recentes</string>\n</resources>\n"
  },
  {
    "path": "ui-search/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSearchInitial1\">Что вы ищете?</string>\n  <string name=\"textSearchInitial2\">Поиск с помощью Trakt.tv</string>\n  <string name=\"textClear\">Очистить историю поиска</string>\n</resources>\n"
  },
  {
    "path": "ui-search/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"searchRecyclerHorizontalPadding\">12dp</dimen>\n  <dimen name=\"suggestionsMaxHeight\">320dp</dimen>\n</resources>"
  },
  {
    "path": "ui-search/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSearchInitial1\">Ne arıyorsunuz?</string>\n  <string name=\"textSearchInitial2\">Arama özelliği Trakt.tv hizmetiyle desteklenmektedir</string>\n  <string name=\"textClear\">Son aramaları temizle</string>\n</resources>\n"
  },
  {
    "path": "ui-search/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSearchInitial1\">Що ви шукаєте?</string>\n  <string name=\"textSearchInitial2\">Пошук за допомогою Trakt.tv</string>\n  <string name=\"textClear\">Очистити історію пошуку</string>\n</resources>\n"
  },
  {
    "path": "ui-search/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSearchInitial1\">Bạn đang tìm kiếm gì?</string>\n  <string name=\"textSearchInitial2\">Tìm kiếm được cung cấp bởi Trakt.tv</string>\n  <string name=\"textClear\">Xóa các tìm kiếm gần đây</string>\n</resources>\n"
  },
  {
    "path": "ui-search/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textSearchInitial1\">您想要查找什么？</string>\n  <string name=\"textSearchInitial2\">由 Trakt.tv 提供搜索服务</string>\n  <string name=\"textClear\">清除最近的搜索记录</string>\n</resources>\n"
  },
  {
    "path": "ui-search/src/test/java/com.michaldrabik.ui_search/BaseMockTest.kt",
    "content": "package com.michaldrabik.ui_search\n\nimport com.michaldrabik.common_test.MainDispatcherRule\nimport com.michaldrabik.common_test.UnconfinedCoroutineDispatchers\nimport io.mockk.MockKAnnotations\nimport org.junit.Before\nimport org.junit.Rule\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nabstract class BaseMockTest {\n\n  @get:Rule\n  val mainDispatcherRule = MainDispatcherRule()\n  protected val testDispatchers = UnconfinedCoroutineDispatchers()\n\n  @Before\n  open fun setUp() {\n    MockKAnnotations.init(this)\n  }\n}\n"
  },
  {
    "path": "ui-search/src/test/java/com.michaldrabik.ui_search/SearchViewModelTest.kt",
    "content": "package com.michaldrabik.ui_search\n\nimport androidx.lifecycle.viewModelScope\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageStatus\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.RecentSearch\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_search.cases.SearchFiltersCase\nimport com.michaldrabik.ui_search.cases.SearchInvalidateItemCase\nimport com.michaldrabik.ui_search.cases.SearchQueryCase\nimport com.michaldrabik.ui_search.cases.SearchRecentsCase\nimport com.michaldrabik.ui_search.cases.SearchSortingCase\nimport com.michaldrabik.ui_search.cases.SearchSuggestionsCase\nimport com.michaldrabik.ui_search.cases.SearchTranslationsCase\nimport com.michaldrabik.ui_search.helpers.TestData\nimport com.michaldrabik.ui_search.recycler.SearchListItem\nimport io.mockk.Called\nimport io.mockk.Runs\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.confirmVerified\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.just\nimport io.mockk.mockk\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.flow.toList\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.runTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n@OptIn(ExperimentalCoroutinesApi::class)\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass SearchViewModelTest : BaseMockTest() {\n\n  @MockK lateinit var searchQueryCase: SearchQueryCase\n  @MockK lateinit var searchFiltersCase: SearchFiltersCase\n  @MockK lateinit var searchSortingCase: SearchSortingCase\n  @MockK lateinit var searchInvalidateCase: SearchInvalidateItemCase\n  @MockK lateinit var searchTranslationsCase: SearchTranslationsCase\n  @MockK lateinit var recentSearchesCase: SearchRecentsCase\n  @MockK lateinit var suggestionsCase: SearchSuggestionsCase\n  @MockK lateinit var showsImagesProvider: ShowImagesProvider\n  @MockK lateinit var moviesImagesProvider: MovieImagesProvider\n\n  private lateinit var SUT: SearchViewModel\n\n  private val stateResult = mutableListOf<SearchUiState>()\n  private val messagesResult = mutableListOf<MessageEvent>()\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n\n    coEvery { searchFiltersCase.isMoviesEnabled } returns true\n\n    SUT = SearchViewModel(\n      searchQueryCase,\n      searchFiltersCase,\n      searchSortingCase,\n      searchTranslationsCase,\n      searchInvalidateCase,\n      recentSearchesCase,\n      suggestionsCase,\n      showsImagesProvider,\n      moviesImagesProvider\n    )\n  }\n\n  @After\n  fun tearDown() {\n    stateResult.clear()\n    messagesResult.clear()\n    SUT.viewModelScope.cancel()\n  }\n\n  @Test\n  fun `Should preload suggestions`() = runTest {\n    coEvery { suggestionsCase.preloadCache() } just Runs\n    SUT.preloadSuggestions()\n    coVerify(exactly = 1) { suggestionsCase.preloadCache() }\n  }\n\n  @Test\n  fun `Should load recent searches`() = runTest {\n    val recentSearchItem = RecentSearch(\"text\")\n    coEvery { recentSearchesCase.getRecentSearches(any()) } returns listOf(recentSearchItem)\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.loadRecentSearches()\n\n    with(stateResult.last()) {\n      assertThat(recentSearchItems).hasSize(1)\n      assertThat(recentSearchItems).contains(recentSearchItem)\n      assertThat(isInitial).isFalse()\n    }\n\n    coVerify(exactly = 1) { recentSearchesCase.getRecentSearches(5) }\n    confirmVerified(recentSearchesCase)\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should clear recent searches`() = runTest {\n    coEvery { recentSearchesCase.clearRecentSearches() } just Runs\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.clearRecentSearches()\n\n    with(stateResult.last()) {\n      assertThat(recentSearchItems).isEmpty()\n      assertThat(isInitial).isTrue()\n    }\n\n    coVerify(exactly = 1) { recentSearchesCase.clearRecentSearches() }\n    confirmVerified(recentSearchesCase)\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should not run search if query is blank`() = runTest {\n    SUT.search(\"  \")\n    coVerify(exactly = 0) { searchQueryCase.searchByQuery(any()) }\n  }\n\n  @Test\n  fun `Should not store recent search if query is blank`() = runTest {\n    SUT.saveRecentSearch(\"   \")\n    coVerify(exactly = 0) { recentSearchesCase.saveRecentSearch(any()) }\n  }\n\n  @Test\n  fun `Should store recent search if query is not blank`() = runTest {\n    coEvery { recentSearchesCase.saveRecentSearch(any()) } just Runs\n    SUT.saveRecentSearch(\"test \")\n    coVerify(exactly = 1) { recentSearchesCase.saveRecentSearch(any()) }\n  }\n\n  @Test\n  fun `Should not update filters if they are the same`() = runTest {\n    val item = mockk<SearchListItem>()\n    coEvery { searchQueryCase.searchByQuery(any()) } returns listOf(item)\n    coEvery { searchFiltersCase.filter(any(), any()) } returns true\n    coEvery { searchSortingCase.sort(any()) } returns compareBy { it.id }\n\n    SUT.search(\"test\")\n    SUT.setFilters(listOf(Mode.SHOWS))\n\n    // Second call should not induce additional filtering and sorting\n    SUT.setFilters(listOf(Mode.SHOWS))\n\n    coVerify(exactly = 1) { searchFiltersCase.filter(any(), any()) }\n    coVerify(exactly = 1) { searchSortingCase.sort(any()) }\n  }\n\n  @Test\n  fun `Should update filters`() = runTest {\n    val item = mockk<SearchListItem>()\n    coEvery { searchQueryCase.searchByQuery(any()) } returns listOf(item)\n    coEvery { searchFiltersCase.filter(any(), any()) } returns true\n    coEvery { searchSortingCase.sort(any()) } returns compareBy { it.id }\n\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.search(\"test\")\n    SUT.setFilters(listOf(Mode.SHOWS))\n\n    with(stateResult.last()) {\n      assertThat(searchItems).containsExactly(item)\n      assertThat(searchOptions?.filters).containsExactly(Mode.SHOWS)\n      assertThat(resetScroll?.consume()).isTrue()\n    }\n    coVerify(exactly = 1) { searchFiltersCase.filter(any(), any()) }\n    coVerify(exactly = 1) { searchSortingCase.sort(any()) }\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should not update sort order if they are the same`() = runTest {\n    val item = mockk<SearchListItem>()\n    coEvery { searchQueryCase.searchByQuery(any()) } returns listOf(item)\n    coEvery { searchFiltersCase.filter(any(), any()) } returns true\n    coEvery { searchSortingCase.sort(any()) } returns compareBy { it.id }\n\n    SUT.search(\"test\")\n    SUT.setSortOrder(SortOrder.NEWEST, SortType.DESCENDING)\n\n    // Second call should not induce additional filtering and sorting\n    SUT.setSortOrder(SortOrder.NEWEST, SortType.DESCENDING)\n\n    coVerify(exactly = 1) { searchFiltersCase.filter(any(), any()) }\n    coVerify(exactly = 1) { searchSortingCase.sort(any()) }\n  }\n\n  @Test\n  fun `Should update sort order`() = runTest {\n    val item = mockk<SearchListItem>()\n    coEvery { searchQueryCase.searchByQuery(any()) } returns listOf(item)\n    coEvery { searchFiltersCase.filter(any(), any()) } returns true\n    coEvery { searchSortingCase.sort(any()) } returns compareBy { it.id }\n\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.search(\"test\")\n    SUT.setSortOrder(SortOrder.NEWEST, SortType.DESCENDING)\n\n    with(stateResult.last()) {\n      assertThat(searchItems).containsExactly(item)\n      assertThat(searchOptions?.sortOrder).isEqualTo(SortOrder.NEWEST)\n      assertThat(searchOptions?.sortType).isEqualTo(SortType.DESCENDING)\n      assertThat(resetScroll?.consume()).isTrue()\n    }\n    coVerify(exactly = 1) { searchFiltersCase.filter(any(), any()) }\n    coVerify(exactly = 1) { searchSortingCase.sort(any()) }\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should clear suggestions properly`() = runTest {\n    val item = mockk<SearchListItem>()\n    coEvery { suggestionsCase.loadSuggestions(any()) } returns listOf(item)\n\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.loadSuggestions(\"test\")\n    SUT.clearSuggestions()\n\n    assertThat(stateResult[2].suggestionsItems).containsExactly(item)\n    assertThat(stateResult[3].suggestionsItems).isEmpty()\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should not load suggestions if query length is less than 2`() = runTest {\n    val item = mockk<SearchListItem>()\n    coEvery { suggestionsCase.loadSuggestions(any()) } returns listOf(item)\n\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.loadSuggestions(\"x\")\n    assertThat(stateResult[2].suggestionsItems).isEmpty()\n\n    SUT.loadSuggestions(\"xx\")\n    assertThat(stateResult[3].suggestionsItems).containsExactly(item)\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should load suggestions properly`() = runTest {\n    val item = mockk<SearchListItem>()\n\n    coEvery { suggestionsCase.loadSuggestions(any()) } returns listOf(item, item)\n\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.loadSuggestions(\"xxxxx\")\n\n    assertThat(stateResult.last().suggestionsItems).hasSize(2)\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should update missing image for show properly`() = runTest {\n    val item = TestData.SEARCH_LIST_ITEM.copy(\n      show = Show.EMPTY.copy(title = \"test\")\n    )\n    coEvery { showsImagesProvider.loadRemoteImage(any(), any(), any()) } returns Image.createUnavailable(ImageType.POSTER)\n    coEvery { searchQueryCase.searchByQuery(any()) } returns listOf(item)\n    coEvery { recentSearchesCase.saveRecentSearch(any()) } just Runs\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.search(\"test\")\n    SUT.loadMissingImage(item, true)\n\n    with(stateResult.last()) {\n      assertThat(searchItems).hasSize(1)\n      assertThat(searchItems?.last()?.image?.status).isEqualTo(ImageStatus.UNAVAILABLE)\n    }\n\n    coVerify(exactly = 1) { showsImagesProvider.loadRemoteImage(any(), any(), any()) }\n    coVerify { moviesImagesProvider wasNot Called }\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should update missing image for movie properly`() = runTest {\n    val item = TestData.SEARCH_LIST_ITEM.copy(\n      movie = Movie.EMPTY.copy(title = \"test\")\n    )\n    coEvery { moviesImagesProvider.loadRemoteImage(any(), any(), any()) } returns Image.createUnavailable(ImageType.POSTER)\n    coEvery { searchQueryCase.searchByQuery(any()) } returns listOf(item)\n    coEvery { recentSearchesCase.saveRecentSearch(any()) } just Runs\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.search(\"test\")\n    SUT.loadMissingImage(item, true)\n\n    with(stateResult.last()) {\n      assertThat(searchItems).hasSize(1)\n      assertThat(searchItems?.last()?.image?.status).isEqualTo(ImageStatus.UNAVAILABLE)\n    }\n\n    coVerify(exactly = 1) { moviesImagesProvider.loadRemoteImage(any(), any(), any()) }\n    coVerify { showsImagesProvider wasNot Called }\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should update missing suggestion image for show properly`() = runTest {\n    val item = TestData.SEARCH_LIST_ITEM.copy(\n      show = Show.EMPTY.copy(title = \"test\")\n    )\n    coEvery { showsImagesProvider.loadRemoteImage(any(), any(), any()) } returns Image.createUnavailable(ImageType.POSTER)\n    coEvery { suggestionsCase.loadSuggestions((any())) } returns listOf(item)\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.loadSuggestions(\"test\")\n    SUT.loadMissingSuggestionImage(item, true)\n\n    with(stateResult.last()) {\n      assertThat(suggestionsItems).hasSize(1)\n      assertThat(suggestionsItems?.last()?.image?.status).isEqualTo(ImageStatus.UNAVAILABLE)\n    }\n\n    coVerify(exactly = 1) { showsImagesProvider.loadRemoteImage(any(), any(), any()) }\n    coVerify { moviesImagesProvider wasNot Called }\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should update missing suggestion image for movie properly`() = runTest {\n    val item = TestData.SEARCH_LIST_ITEM.copy(\n      movie = Movie.EMPTY.copy(title = \"test\")\n    )\n    coEvery { showsImagesProvider.loadRemoteImage(any(), any(), any()) } returns Image.createUnavailable(ImageType.POSTER)\n    coEvery { suggestionsCase.loadSuggestions((any())) } returns listOf(item)\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.loadSuggestions(\"test\")\n    SUT.loadMissingSuggestionImage(item, true)\n\n    with(stateResult.last()) {\n      assertThat(suggestionsItems).hasSize(1)\n      assertThat(suggestionsItems?.last()?.image?.status).isEqualTo(ImageStatus.UNAVAILABLE)\n    }\n\n    coVerify(exactly = 1) { moviesImagesProvider.loadRemoteImage(any(), any(), any()) }\n    coVerify { showsImagesProvider wasNot Called }\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should update missing suggestion translation for show properly`() = runTest {\n    val item = TestData.SEARCH_LIST_ITEM.copy(\n      show = Show.EMPTY.copy(title = \"test\")\n    )\n    coEvery { searchTranslationsCase.getLanguage() } returns \"pl\"\n    coEvery { searchTranslationsCase.loadTranslation(any<Show>()) } returns Translation.EMPTY\n    coEvery { suggestionsCase.loadSuggestions((any())) } returns listOf(item)\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.loadSuggestions(\"test\")\n    SUT.loadMissingSuggestionTranslation(item)\n\n    with(stateResult.last()) {\n      assertThat(suggestionsItems).hasSize(1)\n      assertThat(suggestionsItems?.last()?.translation).isEqualTo(Translation.EMPTY)\n    }\n\n    coVerify(exactly = 1) { searchTranslationsCase.loadTranslation(any<Show>()) }\n    coVerify(exactly = 0) { searchTranslationsCase.loadTranslation(any<Movie>()) }\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should update missing suggestion translation for movie properly`() = runTest {\n    val item = TestData.SEARCH_LIST_ITEM.copy(\n      movie = Movie.EMPTY.copy(title = \"test\")\n    )\n    coEvery { searchTranslationsCase.getLanguage() } returns \"pl\"\n    coEvery { searchTranslationsCase.loadTranslation(any<Movie>()) } returns Translation.EMPTY\n    coEvery { suggestionsCase.loadSuggestions((any())) } returns listOf(item)\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.loadSuggestions(\"test\")\n    SUT.loadMissingSuggestionTranslation(item)\n\n    with(stateResult.last()) {\n      assertThat(suggestionsItems).hasSize(1)\n      assertThat(suggestionsItems?.last()?.translation).isEqualTo(Translation.EMPTY)\n    }\n\n    coVerify(exactly = 1) { searchTranslationsCase.loadTranslation(any<Movie>()) }\n    coVerify(exactly = 0) { searchTranslationsCase.loadTranslation(any<Show>()) }\n\n    job.cancel()\n  }\n\n  @Test\n  fun `Should not update missing suggestion translation if default language`() = runTest {\n    coEvery { searchTranslationsCase.getLanguage() } returns \"en\"\n    val item = TestData.SEARCH_LIST_ITEM\n\n    SUT.loadMissingSuggestionTranslation(item)\n\n    coVerify(exactly = 0) { searchTranslationsCase.loadTranslation(any<Movie>()) }\n    coVerify(exactly = 0) { searchTranslationsCase.loadTranslation(any<Show>()) }\n  }\n\n  @Test\n  fun `Should not update missing suggestion translation if already has translation`() = runTest {\n    coEvery { searchTranslationsCase.getLanguage() } returns \"pl\"\n    val item = TestData.SEARCH_LIST_ITEM.copy(translation = Translation.EMPTY)\n\n    SUT.loadMissingSuggestionTranslation(item)\n\n    coVerify(exactly = 0) { searchTranslationsCase.loadTranslation(any<Movie>()) }\n    coVerify(exactly = 0) { searchTranslationsCase.loadTranslation(any<Show>()) }\n  }\n}\n"
  },
  {
    "path": "ui-search/src/test/java/com.michaldrabik.ui_search/cases/SearchFiltersCaseTest.kt",
    "content": "package com.michaldrabik.ui_search.cases\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_search.BaseMockTest\nimport com.michaldrabik.ui_search.recycler.SearchListItem\nimport com.michaldrabik.ui_search.utilities.SearchOptions\nimport io.mockk.clearAllMocks\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.confirmVerified\nimport io.mockk.impl.annotations.RelaxedMockK\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.test.runTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n@OptIn(ExperimentalCoroutinesApi::class)\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass SearchFiltersCaseTest : BaseMockTest() {\n\n  @RelaxedMockK lateinit var settingsRepository: SettingsRepository\n  @RelaxedMockK lateinit var item: SearchListItem\n\n  private lateinit var SUT: SearchFiltersCase\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    coEvery { settingsRepository.isMoviesEnabled } returns true\n    SUT = SearchFiltersCase(settingsRepository)\n  }\n\n  @After\n  fun tearDown() {\n    confirmVerified(settingsRepository)\n    clearAllMocks()\n  }\n\n  @Test\n  fun `Should pass shows and movies if filters are empty`() = runTest {\n    val options = SearchOptions()\n    val result = SUT.filter(options, item)\n    assertThat(result).isTrue()\n  }\n\n  @Test\n  fun `Should pass shows and movies if filters contain shows and movies`() = runTest {\n    val options = SearchOptions(filters = listOf(Mode.SHOWS, Mode.MOVIES))\n    val result = SUT.filter(options, item)\n    assertThat(result).isTrue()\n  }\n\n  @Test\n  fun `Should not pass shows if filters contain only movie`() = runTest {\n    val options = SearchOptions(filters = listOf(Mode.MOVIES))\n    coEvery { item.isShow } returns true\n    coEvery { item.isMovie } returns false\n\n    val result = SUT.filter(options, item)\n\n    coVerify(exactly = 1) { settingsRepository.isMoviesEnabled }\n    assertThat(result).isFalse()\n  }\n\n  @Test\n  fun `Should not pass movies if filters contain only show`() = runTest {\n    val options = SearchOptions(filters = listOf(Mode.SHOWS))\n    coEvery { item.isShow } returns false\n    coEvery { item.isMovie } returns true\n\n    val result = SUT.filter(options, item)\n\n    coVerify(exactly = 0) { settingsRepository.isMoviesEnabled }\n    assertThat(result).isFalse()\n  }\n\n  @Test\n  fun `Should not pass movies if movies are disabled`() = runTest {\n    val options = SearchOptions(filters = listOf(Mode.MOVIES))\n    coEvery { settingsRepository.isMoviesEnabled } returns false\n\n    coEvery { item.isShow } returns false\n    coEvery { item.isMovie } returns true\n\n    val result = SUT.filter(options, item)\n\n    coVerify(exactly = 1) { settingsRepository.isMoviesEnabled }\n    assertThat(result).isFalse()\n  }\n}\n"
  },
  {
    "path": "ui-search/src/test/java/com.michaldrabik.ui_search/cases/SearchQueryCaseTest.kt",
    "content": "package com.michaldrabik.ui_search.cases\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.data_remote.trakt.TraktRemoteDataSource\nimport com.michaldrabik.data_remote.trakt.model.SearchResult\nimport com.michaldrabik.data_remote.trakt.model.Show\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_search.BaseMockTest\nimport io.mockk.clearAllMocks\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.impl.annotations.RelaxedMockK\nimport io.mockk.mockk\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.test.runTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n@OptIn(ExperimentalCoroutinesApi::class)\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass SearchQueryCaseTest : BaseMockTest() {\n\n  @RelaxedMockK lateinit var cloud: RemoteDataSource\n  @RelaxedMockK lateinit var traktApi: TraktRemoteDataSource\n  @RelaxedMockK lateinit var mappers: Mappers\n  @RelaxedMockK lateinit var settingsRepository: SettingsRepository\n  @RelaxedMockK lateinit var showsRepository: ShowsRepository\n  @RelaxedMockK lateinit var moviesRepository: MoviesRepository\n  @RelaxedMockK lateinit var translationsRepository: TranslationsRepository\n  @RelaxedMockK lateinit var showImagesProvider: ShowImagesProvider\n  @RelaxedMockK lateinit var movieImagesProvider: MovieImagesProvider\n\n  private lateinit var SUT: SearchQueryCase\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n\n    coEvery { cloud.trakt } returns traktApi\n    coEvery { settingsRepository.isMoviesEnabled } returns true\n    coEvery { translationsRepository.getLanguage() } returns \"en\"\n\n    coEvery { showImagesProvider.findCachedImage(any(), any()) } returns Image.createUnknown(ImageType.POSTER)\n    coEvery { movieImagesProvider.findCachedImage(any(), any()) } returns Image.createUnknown(ImageType.POSTER)\n\n    coEvery { showsRepository.myShows.loadAllIds() } returns emptyList()\n    coEvery { showsRepository.watchlistShows.loadAllIds() } returns emptyList()\n    coEvery { moviesRepository.myMovies.loadAllIds() } returns emptyList()\n    coEvery { moviesRepository.watchlistMovies.loadAllIds() } returns emptyList()\n\n    SUT = SearchQueryCase(\n      testDispatchers,\n      cloud,\n      mappers,\n      settingsRepository,\n      showsRepository,\n      moviesRepository,\n      translationsRepository,\n      showImagesProvider,\n      movieImagesProvider\n    )\n  }\n\n  @After\n  fun tearDown() {\n    clearAllMocks()\n  }\n\n  @Test\n  fun `Should run search query and return results sorted by score`() = runTest {\n    val show = mockk<Show> {\n      coEvery { votes } returnsMany listOf(10, 20, 30)\n    }\n    val item1 = SearchResult(score = 1F, show = show, movie = null, person = null)\n    val item2 = SearchResult(score = 2F, show = show, movie = null, person = null)\n    val item3 = SearchResult(score = 3F, show = show, movie = null, person = null)\n\n    coEvery { traktApi.fetchSearch(any(), any()) } returns listOf(item1, item2, item3)\n\n    val result = SUT.searchByQuery(\"test\")\n\n    assertThat(result).hasSize(3)\n    assertThat(result[0].score).isEqualTo(3F)\n    assertThat(result[1].score).isEqualTo(2F)\n    assertThat(result[2].score).isEqualTo(1F)\n    coVerify(exactly = 1) { traktApi.fetchSearch(\"test\", any()) }\n    coVerify(exactly = 3) { showImagesProvider.findCachedImage(any(), any()) }\n    coVerify(exactly = 0) { movieImagesProvider.findCachedImage(any(), any()) }\n  }\n}\n"
  },
  {
    "path": "ui-search/src/test/java/com.michaldrabik.ui_search/cases/SearchRecentsCaseTest.kt",
    "content": "package com.michaldrabik.ui_search.cases\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.dao.RecentSearchDao\nimport com.michaldrabik.data_local.database.model.RecentSearch\nimport com.michaldrabik.ui_search.BaseMockTest\nimport io.mockk.Runs\nimport io.mockk.clearAllMocks\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.confirmVerified\nimport io.mockk.impl.annotations.RelaxedMockK\nimport io.mockk.just\nimport io.mockk.slot\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.test.runBlockingTest\nimport kotlinx.coroutines.test.runTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass SearchRecentsCaseTest : BaseMockTest() {\n\n  @RelaxedMockK lateinit var database: LocalDataSource\n  @RelaxedMockK lateinit var recentSearchDao: RecentSearchDao\n  @RelaxedMockK lateinit var recentSearch: RecentSearch\n\n  private lateinit var SUT: SearchRecentsCase\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n\n    coEvery { recentSearch.text } returnsMany listOf(\"1\", \"2\", \"3\")\n    coEvery { database.recentSearch } returns recentSearchDao\n\n    SUT = SearchRecentsCase(testDispatchers, database)\n  }\n\n  @After\n  fun tearDown() {\n    confirmVerified(recentSearchDao)\n    clearAllMocks()\n  }\n\n  @Test\n  fun `Should return recent searches with a limit properly`() = runTest {\n    val limit = 3\n    coEvery { recentSearchDao.getAll(any()) } returns listOf(recentSearch, recentSearch, recentSearch)\n\n    val result = SUT.getRecentSearches(limit)\n    assertThat(result).hasSize(limit)\n\n    assertThat(result[0].text).isEqualTo(\"1\")\n    assertThat(result[1].text).isEqualTo(\"2\")\n    assertThat(result[2].text).isEqualTo(\"3\")\n\n    coVerify(exactly = 1) { recentSearchDao.getAll(any()) }\n  }\n\n  @Test\n  fun `Should clear recent searches properly`() = runBlockingTest {\n    SUT.clearRecentSearches()\n    coVerify(exactly = 1) { recentSearchDao.deleteAll() }\n  }\n\n  @Test\n  fun `Should save recent searches properly`() = runBlockingTest {\n    val slot = slot<List<RecentSearch>>()\n    coEvery { recentSearchDao.upsert(capture(slot)) } just Runs\n\n    SUT.saveRecentSearch(\"test\")\n\n    coVerify(exactly = 1) { recentSearchDao.upsert(any()) }\n    assertThat(slot.captured).hasSize(1)\n    assertThat(slot.captured.first().text).contains(\"test\")\n  }\n}\n"
  },
  {
    "path": "ui-search/src/test/java/com.michaldrabik.ui_search/cases/SearchSortingCaseTest.kt",
    "content": "package com.michaldrabik.ui_search.cases\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SortOrder\nimport com.michaldrabik.ui_model.SortType\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_search.BaseMockTest\nimport com.michaldrabik.ui_search.recycler.SearchListItem\nimport com.michaldrabik.ui_search.utilities.SearchOptions\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.test.runTest\nimport org.junit.Assert.assertThrows\nimport org.junit.Before\nimport org.junit.Test\nimport java.util.UUID\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass SearchSortingCaseTest : BaseMockTest() {\n\n  private lateinit var SUT: SearchSortingCase\n\n  private val testList = mutableListOf<SearchListItem>()\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    SUT = SearchSortingCase()\n\n    val element = SearchListItem(\n      id = UUID.randomUUID(),\n      show = Show.EMPTY,\n      movie = Movie.EMPTY,\n      image = Image.createUnknown(ImageType.POSTER),\n      spoilers = SpoilersSettings.INITIAL,\n      score = 1F,\n    )\n\n    testList.add(element.copy(show = Show.EMPTY.copy(title = \"Test2\", year = 2000), score = 2F))\n    testList.add(element.copy(show = Show.EMPTY.copy(title = \"Test1\", year = 1000), score = 1F))\n    testList.add(element.copy(show = Show.EMPTY.copy(title = \"Test3\", year = 3000), score = 3F))\n  }\n\n  @Test\n  fun `Should properly sort ascending by name`() = runTest {\n    val options = SearchOptions(sortOrder = SortOrder.NAME, sortType = SortType.ASCENDING)\n\n    val result = testList.sortedWith(SUT.sort(options))\n\n    assertThat(result[0].title).isEqualTo(\"Test1\")\n    assertThat(result[1].title).isEqualTo(\"Test2\")\n    assertThat(result[2].title).isEqualTo(\"Test3\")\n  }\n\n  @Test\n  fun `Should properly sort descending by name`() = runTest {\n    val options = SearchOptions(sortOrder = SortOrder.NAME, sortType = SortType.DESCENDING)\n\n    val result = testList.sortedWith(SUT.sort(options))\n\n    assertThat(result[0].title).isEqualTo(\"Test3\")\n    assertThat(result[1].title).isEqualTo(\"Test2\")\n    assertThat(result[2].title).isEqualTo(\"Test1\")\n  }\n\n  @Test\n  fun `Should properly sort ascending by rank`() = runTest {\n    val options = SearchOptions(sortOrder = SortOrder.RANK, sortType = SortType.ASCENDING)\n\n    val result = testList.sortedWith(SUT.sort(options))\n\n    assertThat(result[0].title).isEqualTo(\"Test3\")\n    assertThat(result[1].title).isEqualTo(\"Test2\")\n    assertThat(result[2].title).isEqualTo(\"Test1\")\n  }\n\n  @Test\n  fun `Should properly sort descending by rank`() = runTest {\n    val options = SearchOptions(sortOrder = SortOrder.RANK, sortType = SortType.DESCENDING)\n\n    val result = testList.sortedWith(SUT.sort(options))\n\n    assertThat(result[0].title).isEqualTo(\"Test1\")\n    assertThat(result[1].title).isEqualTo(\"Test2\")\n    assertThat(result[2].title).isEqualTo(\"Test3\")\n  }\n\n  @Test\n  fun `Should properly sort ascending by release date`() = runTest {\n    val options = SearchOptions(sortOrder = SortOrder.NEWEST, sortType = SortType.ASCENDING)\n\n    val result = testList.sortedWith(SUT.sort(options))\n\n    assertThat(result[0].title).isEqualTo(\"Test1\")\n    assertThat(result[1].title).isEqualTo(\"Test2\")\n    assertThat(result[2].title).isEqualTo(\"Test3\")\n  }\n\n  @Test\n  fun `Should properly sort descending by release date`() = runTest {\n    val options = SearchOptions(sortOrder = SortOrder.NEWEST, sortType = SortType.DESCENDING)\n\n    val result = testList.sortedWith(SUT.sort(options))\n\n    assertThat(result[0].title).isEqualTo(\"Test3\")\n    assertThat(result[1].title).isEqualTo(\"Test2\")\n    assertThat(result[2].title).isEqualTo(\"Test1\")\n  }\n\n  @Test\n  fun `Should fail if unsupported sort order`() = runTest {\n    val options = SearchOptions(sortOrder = SortOrder.RECENTLY_WATCHED)\n    assertThrows(IllegalStateException::class.java) {\n      testList.sortedWith(SUT.sort(options))\n    }\n  }\n}\n"
  },
  {
    "path": "ui-search/src/test/java/com.michaldrabik.ui_search/cases/SearchSuggestionsCaseTest.kt",
    "content": "package com.michaldrabik.ui_search.cases\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.dao.MoviesDao\nimport com.michaldrabik.data_local.database.dao.ShowsDao\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_search.BaseMockTest\nimport io.mockk.clearAllMocks\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.impl.annotations.RelaxedMockK\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.test.runTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass SearchSuggestionsCaseTest : BaseMockTest() {\n\n  @RelaxedMockK lateinit var database: LocalDataSource\n  @RelaxedMockK lateinit var showsDao: ShowsDao\n  @RelaxedMockK lateinit var moviesDao: MoviesDao\n  @RelaxedMockK lateinit var mappers: Mappers\n  @RelaxedMockK lateinit var showsRepository: ShowsRepository\n  @RelaxedMockK lateinit var moviesRepository: MoviesRepository\n  @RelaxedMockK lateinit var settingsRepository: SettingsRepository\n  @RelaxedMockK lateinit var translationsRepository: TranslationsRepository\n  @RelaxedMockK lateinit var showImagesProvider: ShowImagesProvider\n  @RelaxedMockK lateinit var movieImagesProvider: MovieImagesProvider\n\n  private lateinit var SUT: SearchSuggestionsCase\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n\n    coEvery { settingsRepository.isMoviesEnabled } returns true\n    coEvery { translationsRepository.getLanguage() } returns \"en\"\n    coEvery { database.shows } returns showsDao\n    coEvery { database.movies } returns moviesDao\n\n    SUT = SearchSuggestionsCase(\n      dispatchers = testDispatchers,\n      localSource = database,\n      mappers = mappers,\n      showsRepository = showsRepository,\n      moviesRepository = moviesRepository,\n      translationsRepository = translationsRepository,\n      settingsRepository = settingsRepository,\n      showsImagesProvider = showImagesProvider,\n      moviesImagesProvider = movieImagesProvider\n    )\n  }\n\n  @After\n  fun tearDown() {\n    clearAllMocks()\n  }\n\n  @Test\n  fun `Should skip preload local shows cache if already loaded`() = runTest {\n    SUT.preloadCache() // Initial preload. Db data should be loaded\n    SUT.preloadCache() // Further preload. Db data should not be loaded\n    coVerify(exactly = 1) { showsDao.getAll() }\n  }\n\n  @Test\n  fun `Should skip preload local movies cache if already loaded`() = runTest {\n    SUT.preloadCache() // Initial preload. Db data should be loaded\n    SUT.preloadCache() // Further preload. Db data should not be loaded\n    coVerify(exactly = 1) { moviesDao.getAll() }\n  }\n\n  @Test\n  fun `Should skip preload local movies cache if movies disabled`() = runTest {\n    coEvery { settingsRepository.isMoviesEnabled } returns false\n\n    SUT.preloadCache()\n    coVerify(exactly = 0) { moviesDao.getAll() }\n  }\n\n  @Test\n  fun `Should skip preload local shows translations cache if default language`() = runTest {\n    SUT.preloadCache()\n    coVerify(exactly = 0) { translationsRepository.loadAllShowsLocal(any()) }\n  }\n\n  @Test\n  fun `Should skip preload local movies translations cache if default language`() = runTest {\n    SUT.preloadCache()\n    coVerify(exactly = 0) { translationsRepository.loadAllMoviesLocal(any()) }\n  }\n\n  @Test\n  fun `Should skip preload local movies translations cache if not default language but movies are disabled`() =\n    runTest {\n      coEvery { translationsRepository.getLanguage() } returns \"br\"\n      coEvery { settingsRepository.isMoviesEnabled } returns false\n\n      SUT.preloadCache()\n      coVerify(exactly = 0) { translationsRepository.loadAllMoviesLocal(any()) }\n    }\n\n  @Test\n  fun `Should preload local cache`() = runTest {\n    SUT.preloadCache()\n    coVerify(exactly = 1) { showsDao.getAll() }\n    coVerify(exactly = 1) { moviesDao.getAll() }\n  }\n\n  @Test\n  fun `Should preload local translations cache`() = runTest {\n    coEvery { translationsRepository.getLanguage() } returns \"br\"\n\n    SUT.preloadCache()\n\n    coVerify(exactly = 1) { translationsRepository.loadAllShowsLocal(\"br\") }\n    coVerify(exactly = 1) { translationsRepository.loadAllMoviesLocal(\"br\") }\n  }\n\n  @Test\n  fun `Should return empty list if query is blank`() = runTest {\n    val result = SUT.loadSuggestions(\"   \")\n\n    assertThat(result).isEmpty()\n    coVerify(exactly = 0) { showsDao.getAll() }\n    coVerify(exactly = 0) { moviesDao.getAll() }\n  }\n\n  @Test\n  fun `Should clear local caches properly`() = runTest {\n    coEvery { translationsRepository.getLanguage() } returns \"br\"\n\n    SUT.preloadCache()\n    SUT.clearCache()\n    SUT.preloadCache()\n\n    coVerify(exactly = 2) { showsDao.getAll() }\n    coVerify(exactly = 2) { moviesDao.getAll() }\n    coVerify(exactly = 2) { translationsRepository.loadAllShowsLocal(\"br\") }\n    coVerify(exactly = 2) { translationsRepository.loadAllMoviesLocal(\"br\") }\n  }\n}\n"
  },
  {
    "path": "ui-search/src/test/java/com.michaldrabik.ui_search/cases/SearchTranslationsCaseTest.kt",
    "content": "package com.michaldrabik.ui_search.cases\n\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_search.BaseMockTest\nimport io.mockk.clearAllMocks\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.confirmVerified\nimport io.mockk.impl.annotations.RelaxedMockK\nimport io.mockk.mockk\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.test.runTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass SearchTranslationsCaseTest : BaseMockTest() {\n\n  @RelaxedMockK lateinit var translationsRepository: TranslationsRepository\n\n  private lateinit var SUT: SearchTranslationsCase\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n    SUT = SearchTranslationsCase(testDispatchers, translationsRepository)\n  }\n\n  @After\n  fun tearDown() {\n    clearAllMocks()\n  }\n\n  @Test\n  fun `Should return empty show translation if language is default`() = runTest {\n    coEvery { translationsRepository.getLanguage() } returns \"en\"\n\n    val item = mockk<Show>()\n    val result = SUT.loadTranslation(item)\n\n    assertThat(result).isEqualTo(Translation.EMPTY)\n    coVerify(exactly = 1) { translationsRepository.getLanguage() }\n    confirmVerified(translationsRepository)\n  }\n\n  @Test\n  fun `Should return empty movie translation if language is default`() = runTest {\n    coEvery { translationsRepository.getLanguage() } returns \"en\"\n\n    val item = mockk<Movie>()\n    val result = SUT.loadTranslation(item)\n\n    assertThat(result).isEqualTo(Translation.EMPTY)\n    coVerify(exactly = 1) { translationsRepository.getLanguage() }\n    confirmVerified(translationsRepository)\n  }\n\n  @Test\n  fun `Should return show translation if language is not default`() = runTest {\n    coEvery { translationsRepository.getLanguage() } returns \"pl\"\n    coEvery { translationsRepository.loadTranslation(any<Show>(), any(), any()) } returns Translation.EMPTY\n\n    val item = mockk<Show>()\n    val result = SUT.loadTranslation(item)\n\n    assertThat(result).isNotNull()\n    coVerify(exactly = 1) { translationsRepository.getLanguage() }\n    coVerify(exactly = 1) { translationsRepository.loadTranslation(any<Show>(), any(), any()) }\n    confirmVerified(translationsRepository)\n  }\n\n  @Test\n  fun `Should return movie translation if language is not default`() = runTest {\n    coEvery { translationsRepository.getLanguage() } returns \"pl\"\n    coEvery { translationsRepository.loadTranslation(any<Movie>(), any(), any()) } returns Translation.EMPTY\n\n    val item = mockk<Movie>()\n    val result = SUT.loadTranslation(item)\n\n    assertThat(result).isNotNull()\n    coVerify(exactly = 1) { translationsRepository.getLanguage() }\n    coVerify(exactly = 1) { translationsRepository.loadTranslation(any<Movie>(), any(), any()) }\n    confirmVerified(translationsRepository)\n  }\n}\n"
  },
  {
    "path": "ui-search/src/test/java/com.michaldrabik.ui_search/helpers/TestData.kt",
    "content": "package com.michaldrabik.ui_search.helpers\n\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_search.recycler.SearchListItem\nimport java.util.UUID\n\nobject TestData {\n\n  val SEARCH_LIST_ITEM = SearchListItem(\n    id = UUID.randomUUID(),\n    show = Show.EMPTY,\n    movie = Movie.EMPTY,\n    image = Image.createUnknown(ImageType.POSTER),\n    translation = null,\n    score = 0F,\n    isFollowed = false,\n    isLoading = false,\n    isWatchlist = false,\n    spoilers = SpoilersSettings.INITIAL\n  )\n}\n"
  },
  {
    "path": "ui-settings/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-settings/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    buildConfig = true\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    buildConfigField 'int', 'VER_CODE', \"${versions.versionCode}\"\n    buildConfigField 'String', 'VER_NAME', \"\\\"${versions.versionName}\\\"\"\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_settings'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-local')\n  implementation project(':data-remote')\n  implementation project(':repository')\n  implementation project(':ui-base')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  api libs.phoenix\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-settings/src/androidTest/java/com/michaldrabik/ui_settings/ExampleInstrumentedTest.kt",
    "content": "package com.michaldrabik.ui_settings\n\n// import androidx.test.platform.app.InstrumentationRegistry\n// import androidx.test.ext.junit.runners.AndroidJUnit4\n//\n// import org.junit.Test\n// import org.junit.runner.RunWith\n//\n// import org.junit.Assert.*\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)\n// class ExampleInstrumentedTest {\n//    @Test\n//    fun useAppContext() {\n//        // Context of the app under test.\n//        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n//        assertEquals(\"com.michaldrabik.ui_settings.test\", appContext.packageName)\n//    }\n// }\n"
  },
  {
    "path": "ui-settings/src/main/AndroidManifest.xml",
    "content": "<manifest\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n  <queries>\n    <intent>\n      <action android:name=\"android.intent.action.SENDTO\" />\n      <category android:name=\"android.intent.category.DEFAULT\" />\n      <data android:scheme=\"mailto\" />\n    </intent>\n  </queries>\n\n</manifest>\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/SettingsFragment.kt",
    "content": "package com.michaldrabik.ui_settings\n\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.common.Config.SHOW_PREMIUM\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.OnTraktAuthorizeListener\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_settings.databinding.FragmentSettingsBinding\nimport com.michaldrabik.ui_settings.sections.spoilers.SettingsSpoilersFragment\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass SettingsFragment : BaseFragment<SettingsViewModel>(R.layout.fragment_settings), OnTraktAuthorizeListener {\n\n  companion object {\n    const val REQUEST_SETTINGS = \"REQUEST_SETTINGS\"\n  }\n\n  override val viewModel by viewModels<SettingsViewModel>()\n  private val binding by viewBinding(FragmentSettingsBinding::bind)\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    setFragmentResultListener(REQUEST_SETTINGS) { _, _ ->\n      childFragmentManager.fragments.forEach { fragment ->\n        (fragment as? SettingsSpoilersFragment)?.refreshSettings()\n      }\n    }\n  }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.messageFlow.collect { showSnack(it) } },\n      doAfterLaunch = { viewModel.loadSettings() }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      settingsToolbar.setNavigationOnClickListener { activity?.onBackPressed() }\n      settingsPremium.onClick { navigateTo(R.id.actionSettingsFragmentToPremium) }\n      settingsRoot.doOnApplyWindowInsets { view, insets, padding, _ ->\n        val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top\n        view.updatePadding(top = padding.top + inset)\n      }\n    }\n  }\n\n  private fun render(uiState: SettingsUiState) {\n    uiState.run {\n      binding.settingsPremium.visibleIf(!isPremium && SHOW_PREMIUM)\n    }\n  }\n\n  override fun onAuthorizationResult(authData: Uri?) {\n    childFragmentManager.fragments.forEach {\n      (it as? OnTraktAuthorizeListener)?.onAuthorizationResult(authData)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/SettingsUiState.kt",
    "content": "package com.michaldrabik.ui_settings\n\ndata class SettingsUiState(\n  val isPremium: Boolean = false,\n)\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/SettingsViewModel.kt",
    "content": "package com.michaldrabik.ui_settings\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SettingsViewModel @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val premiumState = MutableStateFlow(false)\n\n  fun loadSettings() {\n    viewModelScope.launch {\n      premiumState.value = settingsRepository.isPremium\n    }\n  }\n\n  val uiState = premiumState\n    .map { SettingsUiState(it) }\n    .stateIn(\n      scope = viewModelScope,\n      started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n      initialValue = SettingsUiState()\n    )\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/helpers/AppLanguage.kt",
    "content": "package com.michaldrabik.ui_settings.helpers\n\nimport androidx.annotation.StringRes\nimport com.michaldrabik.ui_settings.R\n\nenum class AppLanguage(\n  val code: String,\n  val displayNameRaw: String,\n  @StringRes val displayName: Int,\n) {\n  ENGLISH(\"en\", \"English\", R.string.textLanguageEnglish),\n  GERMAN(\"de\", \"German\", R.string.textLanguageGerman),\n  FRENCH(\"fr\", \"French\", R.string.textLanguageFrench),\n  ITALIAN(\"it\", \"Italian\", R.string.textLanguageItalian),\n  SPANISH(\"es\", \"Spanish\", R.string.textLanguageSpanish),\n  PORTUGAL_BRASIL(\"pt\", \"Portuguese\", R.string.textLanguagePortugalBrasil),\n  POLISH(\"pl\", \"Polish\", R.string.textLanguagePolish),\n  RUSSIAN(\"ru\", \"Russian\", R.string.textLanguageRussian),\n  UKRAINIAN(\"uk\", \"Ukrainian\", R.string.textLanguageUkrainian),\n  FINNISH(\"fi\", \"Finnish\", R.string.textLanguageFinnish),\n  TURKISH(\"tr\", \"Turkish\", R.string.textLanguageTurkish),\n  ARABIC(\"ar\", \"Arabic\", R.string.textLanguageArabic),\n  CHINESE(\"zh\", \"Chinese Simplified\", R.string.textLanguageChinese),\n  VIETNAMESE(\"vi\", \"Vietnamese\", R.string.textLanguageVietnamese);\n\n  companion object {\n    fun fromCode(code: String) = values().first { it.code == code }\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/helpers/AppTheme.kt",
    "content": "package com.michaldrabik.ui_settings.helpers\n\nimport androidx.annotation.StringRes\nimport androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM\nimport androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO\nimport androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES\nimport com.michaldrabik.ui_settings.R\n\nenum class AppTheme(\n  val code: Int,\n  @StringRes val displayName: Int\n) {\n  DARK(MODE_NIGHT_YES, R.string.textThemeDark),\n  LIGHT(MODE_NIGHT_NO, R.string.textThemeLight),\n  SYSTEM(MODE_NIGHT_FOLLOW_SYSTEM, R.string.textThemeSystem);\n\n  companion object {\n    fun fromCode(code: Int) = values().first { it.code == code }\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/helpers/WidgetTransparency.kt",
    "content": "package com.michaldrabik.ui_settings.helpers\n\nimport androidx.annotation.StringRes\nimport com.michaldrabik.ui_settings.R\n\nenum class WidgetTransparency(\n  val value: Int,\n  @StringRes val displayName: Int\n) {\n  SOLID(100, R.string.textTransparency100),\n  LOW(75, R.string.textTransparency25),\n  MEDIUM(50, R.string.textTransparency50),\n  HIGH(25, R.string.textTransparency75),\n  TRANSPARENT(0, R.string.textTransparency0);\n\n  companion object {\n    fun fromValue(value: Int) = values().first { it.value == value }\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/general/SettingsGeneralFragment.kt",
    "content": "package com.michaldrabik.ui_settings.sections.general\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.core.content.ContextCompat\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.jakewharton.processphoenix.ProcessPhoenix\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Config.SHOW_PREMIUM\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.AppCountry\nimport com.michaldrabik.ui_base.dates.AppDateFormat\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.PremiumFeature\nimport com.michaldrabik.ui_model.ProgressNextEpisodeType\nimport com.michaldrabik.ui_model.ProgressNextEpisodeType.LAST_WATCHED\nimport com.michaldrabik.ui_model.ProgressNextEpisodeType.OLDEST\nimport com.michaldrabik.ui_model.Settings\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ITEM\nimport com.michaldrabik.ui_settings.R\nimport com.michaldrabik.ui_settings.databinding.FragmentSettingsGeneralBinding\nimport com.michaldrabik.ui_settings.helpers.AppLanguage\nimport com.michaldrabik.ui_settings.helpers.AppTheme\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass SettingsGeneralFragment : BaseFragment<SettingsGeneralViewModel>(R.layout.fragment_settings_general) {\n\n  override val viewModel by viewModels<SettingsGeneralViewModel>()\n  private val binding by viewBinding(FragmentSettingsGeneralBinding::bind)\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      doAfterLaunch = { viewModel.loadSettings() }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      settingsTheme.visibleIf(SHOW_PREMIUM)\n      settingsThemeValue.visibleIf(SHOW_PREMIUM)\n      settingsNewsEnabled.visibleIf(SHOW_PREMIUM)\n      settingsNewsEnabledSwitch.visibleIf(SHOW_PREMIUM)\n      settingsTabletColumns.visibleIf(isTablet)\n\n      settingsIncludeSpecials.onClick {\n        viewModel.enableSpecialSeasons(!settingsIncludeSpecialsSwitch.isChecked)\n      }\n      settingsMoviesEnabled.onClick {\n        viewModel.enableMovies(!settingsMoviesEnabledSwitch.isChecked)\n      }\n      settingsStreamingsEnabled.onClick {\n        viewModel.enableStreamings(!settingsStreamingsEnabledSwitch.isChecked)\n      }\n    }\n  }\n\n  private fun render(uiState: SettingsGeneralUiState) {\n    with(binding) {\n      with(uiState) {\n        settingsRecentShowsAmount.onClick { showRecentShowsDialog(settings) }\n\n        settingsMoviesEnabledSwitch.isChecked = moviesEnabled\n        settingsNewsEnabledSwitch.isChecked = newsEnabled\n        settingsNewsEnabledSwitch.isEnabled = isPremium\n        settingsStreamingsEnabledSwitch.isChecked = streamingsEnabled\n\n        renderSettings(settings)\n        renderLanguage(language)\n        renderTheme(theme, isPremium)\n        renderCountry(country)\n        renderProgressType(progressNextType)\n        renderProgressUpcoming(progressUpcomingDays)\n        renderDateFormat(dateFormat, language)\n        renderTabletColumns(tabletColumns)\n\n        settingsTheme.alpha = if (isPremium) 1F else 0.5F\n        settingsNewsEnabled.alpha = if (isPremium) 1F else 0.5F\n        if (isPremium) {\n          settingsThemeTitle.setCompoundDrawables(null, null, null, null)\n          settingsNewsEnabledTitle.setCompoundDrawables(null, null, null, null)\n        }\n\n        settingsNewsEnabled.onClick {\n          onPremiumAction(isPremium, settingsNewsEnabled.tag) {\n            viewModel.enableNews(!settingsNewsEnabledSwitch.isChecked)\n          }\n        }\n\n        if (restartApp) restartApp()\n      }\n    }\n  }\n\n  private fun renderSettings(settings: Settings?) {\n    if (settings == null) return\n    with(binding) {\n      settingsIncludeSpecialsSwitch.isChecked = settings.specialSeasonsEnabled\n    }\n  }\n\n  private fun renderLanguage(language: AppLanguage) {\n    with(binding) {\n      settingsLanguageValue.setText(language.displayName)\n      settingsLanguage.onClick { showLanguageDialog(language) }\n    }\n  }\n\n  private fun renderTheme(\n    theme: AppTheme,\n    isPremium: Boolean,\n  ) {\n    with(binding) {\n      settingsThemeValue.setText(theme.displayName)\n      settingsTheme.onClick {\n        onPremiumAction(isPremium, tag) {\n          showThemeDialog(theme)\n        }\n      }\n    }\n  }\n\n  private fun renderTabletColumns(columns: Int) {\n    with(binding) {\n      settingsTabletColumnsValue.text = columns.toString()\n      settingsTabletColumns.onClick {\n        showTabletColumnsDialog(columns)\n      }\n    }\n  }\n\n  private fun renderCountry(country: AppCountry?) {\n    if (country == null) return\n    with(binding) {\n      settingsCountryValue.text = country.displayName\n      settingsCountry.onClick { showCountryDialog(country) }\n    }\n  }\n\n  private fun renderProgressUpcoming(progressUpcomingDays: Long?) {\n    if (progressUpcomingDays == null) return\n    with(binding) {\n      settingsUpcomingValue.text = if (progressUpcomingDays > 0L) {\n        getString(R.string.textDays, progressUpcomingDays)\n      } else {\n        getString(R.string.textDisabled)\n      }\n      settingsUpcomingSection.onClick {\n        showProgressUpcomingDialog(progressUpcomingDays)\n      }\n    }\n  }\n\n  private fun renderProgressType(type: ProgressNextEpisodeType?) {\n    if (type == null) return\n    with(binding) {\n      settingsProgressNextValue.text = when (type) {\n        LAST_WATCHED -> getString(R.string.textNextEpisodeLastWatched)\n        OLDEST -> getString(R.string.textNextEpisodeOldest)\n      }\n      settingsProgressNext.onClick { showProgressTypeDialog(type) }\n    }\n  }\n\n  private fun renderDateFormat(\n    format: AppDateFormat?,\n    language: AppLanguage,\n  ) {\n    if (format == null) return\n    with(binding) {\n      settingsDateFormatValue.text = DateFormatProvider\n        .loadSettingsFormat(format, language.code)\n        .format(nowUtc().toLocalZone())\n      settingsDateFormat.onClick { showDateFormatDialog(format, language) }\n    }\n  }\n\n  private fun onPremiumAction(\n    isPremium: Boolean,\n    tag: Any?,\n    action: () -> Unit,\n  ) {\n    if (isPremium) {\n      action()\n      return\n    }\n    val args = bundleOf()\n    if (tag != null) {\n      val feature = PremiumFeature.fromTag(requireContext(), tag.toString())\n      feature?.let {\n        args.putSerializable(ARG_ITEM, feature)\n      }\n    }\n    navigateTo(R.id.actionSettingsFragmentToPremium, args)\n  }\n\n  private fun showLanguageDialog(language: AppLanguage) {\n    val options = AppLanguage.values()\n    val selected = options.indexOf(language)\n\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setSingleChoiceItems(options.map { getString(it.displayName) }.toTypedArray(), selected) { dialog, index ->\n        if (index != selected) {\n          viewModel.setLanguage(options[index])\n        }\n        dialog.dismiss()\n      }\n      .show()\n  }\n\n  private fun showProgressUpcomingDialog(days: Long) {\n    val options = Config.PROGRESS_UPCOMING_OPTIONS\n    val selected = options.indexOfFirst { it.toLong() == days }\n\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setSingleChoiceItems(\n        options.map { if (it == 0) getString(R.string.textDisabled) else getString(R.string.textDays, it) }.toTypedArray(),\n        selected\n      ) { dialog, index ->\n        if (index != selected) {\n          viewModel.setProgressUpcomingDays(options[index].toLong())\n        }\n        dialog.dismiss()\n      }\n      .show()\n  }\n\n  private fun showThemeDialog(theme: AppTheme) {\n    val options = AppTheme.values()\n    val selected = options.indexOf(theme)\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setSingleChoiceItems(options.map { getString(it.displayName) }.toTypedArray(), selected) { dialog, index ->\n        if (index != selected) {\n          viewModel.setTheme(options[index])\n          AppCompatDelegate.setDefaultNightMode(options[index].code)\n        }\n        dialog.dismiss()\n      }\n      .show()\n  }\n\n  private fun showTabletColumnsDialog(columns: Int) {\n    val options = arrayOf(1, 2)\n    val selected = options.indexOf(columns)\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setSingleChoiceItems(options.map { it.toString() }.toTypedArray(), selected) { dialog, index ->\n        if (index != selected) {\n          viewModel.setTabletColumns(options[index])\n        }\n        dialog.dismiss()\n      }\n      .show()\n  }\n\n  private fun showCountryDialog(country: AppCountry) {\n    val options = AppCountry.values()\n    val selected = options.indexOf(country)\n\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setSingleChoiceItems(options.map { it.displayName }.toTypedArray(), selected) { dialog, index ->\n        if (index != selected) {\n          viewModel.setCountry(options[index])\n        }\n        dialog.dismiss()\n      }\n      .show()\n  }\n\n  private fun showProgressTypeDialog(type: ProgressNextEpisodeType) {\n    val options = ProgressNextEpisodeType.values()\n    val displayOptions = options.map {\n      val option = when (it) {\n        LAST_WATCHED -> R.string.textNextEpisodeLastWatched\n        OLDEST -> R.string.textNextEpisodeOldest\n      }\n      getString(option)\n    }\n    val selected = options.indexOf(type)\n\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setSingleChoiceItems(displayOptions.toTypedArray(), selected) { dialog, index ->\n        if (index != selected) {\n          viewModel.setProgressType(options[index])\n        }\n        dialog.dismiss()\n      }\n      .show()\n  }\n\n  private fun showDateFormatDialog(\n    format: AppDateFormat,\n    language: AppLanguage,\n  ) {\n    val options = AppDateFormat.values()\n    val selected = options.indexOf(format)\n\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog_SmallText)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setSingleChoiceItems(\n        options.map {\n          DateFormatProvider.loadSettingsFormat(it, language.code).format(nowUtc().toLocalZone())\n        }.toTypedArray(),\n        selected\n      ) { dialog, index ->\n        if (index != selected) {\n          viewModel.setDateFormat(options[index], requireAppContext())\n        }\n        dialog.dismiss()\n      }\n      .show()\n  }\n\n  private fun showRecentShowsDialog(settings: Settings?) {\n    if (settings == null) return\n\n    val options = Config.MY_SHOWS_RECENTS_OPTIONS.map { it.toString() }.toTypedArray()\n    val default = options.indexOf(settings.myRecentsAmount.toString())\n\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setSingleChoiceItems(options, default) { dialog, index ->\n        viewModel.setRecentShowsAmount(options[index].toInt())\n        dialog.dismiss()\n      }\n      .show()\n  }\n\n  private fun restartApp() {\n    try {\n      ProcessPhoenix.triggerRebirth(requireAppContext())\n    } catch (error: Throwable) {\n      Runtime.getRuntime().exit(0)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/general/SettingsGeneralUiState.kt",
    "content": "package com.michaldrabik.ui_settings.sections.general\n\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.common.AppCountry\nimport com.michaldrabik.ui_base.dates.AppDateFormat\nimport com.michaldrabik.ui_model.ProgressNextEpisodeType\nimport com.michaldrabik.ui_model.Settings\nimport com.michaldrabik.ui_settings.helpers.AppLanguage\nimport com.michaldrabik.ui_settings.helpers.AppTheme\n\ndata class SettingsGeneralUiState(\n  val settings: Settings? = null,\n  val isPremium: Boolean = false,\n  val language: AppLanguage = AppLanguage.ENGLISH,\n  val theme: AppTheme = AppTheme.DARK,\n  val country: AppCountry? = null,\n  val dateFormat: AppDateFormat? = null,\n  val moviesEnabled: Boolean = true,\n  val newsEnabled: Boolean = false,\n  val streamingsEnabled: Boolean = true,\n  val restartApp: Boolean = false,\n  val progressNextType: ProgressNextEpisodeType? = null,\n  val progressUpcomingDays: Long? = null,\n  val tabletColumns: Int = Config.DEFAULT_LISTS_GRID_SPAN,\n)\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/general/SettingsGeneralViewModel.kt",
    "content": "package com.michaldrabik.ui_settings.sections.general\n\nimport android.content.Context\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.core.os.LocaleListCompat\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.Analytics\nimport com.michaldrabik.ui_base.common.AppCountry\nimport com.michaldrabik.ui_base.dates.AppDateFormat\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.combine\nimport com.michaldrabik.ui_model.ProgressNextEpisodeType\nimport com.michaldrabik.ui_model.Settings\nimport com.michaldrabik.ui_settings.helpers.AppLanguage\nimport com.michaldrabik.ui_settings.helpers.AppTheme\nimport com.michaldrabik.ui_settings.sections.general.cases.SettingsGeneralMainCase\nimport com.michaldrabik.ui_settings.sections.general.cases.SettingsGeneralStreamingsCase\nimport com.michaldrabik.ui_settings.sections.general.cases.SettingsGeneralThemesCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SettingsGeneralViewModel @Inject constructor(\n  private val mainCase: SettingsGeneralMainCase,\n  private val themesCase: SettingsGeneralThemesCase,\n  private val streamingsCase: SettingsGeneralStreamingsCase,\n) : ViewModel() {\n\n  private val settingsState = MutableStateFlow<Settings?>(null)\n  private val languageState = MutableStateFlow(AppLanguage.ENGLISH)\n  private val themeState = MutableStateFlow(AppTheme.DARK)\n  private val countryState = MutableStateFlow<AppCountry?>(null)\n  private val dateFormatState = MutableStateFlow<AppDateFormat?>(null)\n  private val moviesEnabledState = MutableStateFlow(true)\n  private val newsEnabledState = MutableStateFlow(false)\n  private val streamingsEnabledState = MutableStateFlow(true)\n  private val premiumState = MutableStateFlow(false)\n  private val restartAppState = MutableStateFlow(false)\n  private val progressTypeState = MutableStateFlow<ProgressNextEpisodeType?>(null)\n  private val progressUpcomingDaysState = MutableStateFlow<Long?>(null)\n  private val tabletsColumnsState = MutableStateFlow(Config.DEFAULT_LISTS_GRID_SPAN)\n\n  fun loadSettings() {\n    viewModelScope.launch {\n      refreshSettings()\n    }\n  }\n\n  private suspend fun refreshSettings(restartApp: Boolean = false) {\n    settingsState.value = mainCase.getSettings()\n    languageState.value = mainCase.getLanguage()\n    themeState.value = themesCase.getTheme()\n    countryState.value = mainCase.getCountry()\n    dateFormatState.value = mainCase.getDateFormat()\n    moviesEnabledState.value = mainCase.isMoviesEnabled()\n    newsEnabledState.value = mainCase.isNewsEnabled()\n    streamingsEnabledState.value = mainCase.isStreamingsEnabled()\n    premiumState.value = mainCase.isPremium()\n    progressTypeState.value = mainCase.getProgressType()\n    progressUpcomingDaysState.value = mainCase.getProgressUpcomingDays()\n    tabletsColumnsState.value = mainCase.getTabletsColumns()\n    restartAppState.value = restartApp\n  }\n\n  fun setRecentShowsAmount(amount: Int) {\n    viewModelScope.launch {\n      mainCase.setRecentShowsAmount(amount)\n      refreshSettings()\n      Analytics.logSettingsRecentlyAddedAmount(amount.toLong())\n    }\n  }\n\n  fun enableSpecialSeasons(enable: Boolean) {\n    viewModelScope.launch {\n      mainCase.enableSpecialSeasons(enable)\n      refreshSettings()\n      Analytics.logSettingsSpecialSeasons(enable)\n    }\n  }\n\n  fun enableMovies(enable: Boolean) {\n    viewModelScope.launch {\n      mainCase.enableMovies(enable)\n      delay(300)\n      refreshSettings(restartApp = true)\n    }\n    Analytics.logSettingsMoviesEnabled(enable)\n  }\n\n  fun enableNews(enable: Boolean) {\n    viewModelScope.launch {\n      mainCase.enableNews(enable)\n      delay(300)\n      refreshSettings(restartApp = true)\n    }\n    Analytics.logSettingsNewsEnabled(enable)\n  }\n\n  fun enableStreamings(enable: Boolean) {\n    viewModelScope.launch {\n      mainCase.enableStreamings(enable)\n      refreshSettings()\n    }\n    Analytics.logSettingsStreamingsEnabled(enable)\n  }\n\n  fun setLanguage(language: AppLanguage) {\n    viewModelScope.launch {\n      mainCase.setLanguage(language)\n      val locales = LocaleListCompat.forLanguageTags(language.code)\n      AppCompatDelegate.setApplicationLocales(locales)\n    }\n    Analytics.logSettingsLanguage(language.code)\n  }\n\n  fun setTheme(theme: AppTheme) {\n    viewModelScope.launch {\n      themesCase.setTheme(theme)\n      refreshSettings()\n    }\n    Analytics.logSettingsTheme(theme.code)\n  }\n\n  fun setTabletColumns(columns: Int) {\n    viewModelScope.launch {\n      mainCase.setTabletsColumns(columns)\n      refreshSettings()\n    }\n  }\n\n  fun setCountry(country: AppCountry) {\n    viewModelScope.launch {\n      mainCase.setCountry(country)\n      streamingsCase.deleteCache()\n      refreshSettings()\n    }\n    Analytics.logSettingsCountry(country.code)\n  }\n\n  fun setProgressType(type: ProgressNextEpisodeType) {\n    viewModelScope.launch {\n      mainCase.setProgressType(type)\n      refreshSettings()\n    }\n    Analytics.logSettingsProgressType(type.name)\n  }\n\n  fun setProgressUpcomingDays(days: Long) {\n    viewModelScope.launch {\n      mainCase.setProgressUpcomingDays(days)\n      refreshSettings()\n    }\n  }\n\n  fun setDateFormat(format: AppDateFormat, context: Context) {\n    viewModelScope.launch {\n      mainCase.setDateFormat(format, context)\n      refreshSettings()\n    }\n  }\n\n  val uiState = combine(\n    settingsState,\n    premiumState,\n    languageState,\n    themeState,\n    countryState,\n    dateFormatState,\n    moviesEnabledState,\n    newsEnabledState,\n    streamingsEnabledState,\n    progressTypeState,\n    restartAppState,\n    progressUpcomingDaysState,\n    tabletsColumnsState\n  ) { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13 ->\n    SettingsGeneralUiState(\n      settings = s1,\n      isPremium = s2,\n      language = s3,\n      theme = s4,\n      country = s5,\n      dateFormat = s6,\n      moviesEnabled = s7,\n      newsEnabled = s8,\n      streamingsEnabled = s9,\n      progressNextType = s10,\n      restartApp = s11,\n      progressUpcomingDays = s12,\n      tabletColumns = s13\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = SettingsGeneralUiState()\n  )\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/general/cases/SettingsGeneralMainCase.kt",
    "content": "package com.michaldrabik.ui_settings.sections.general.cases\n\nimport android.content.Context\nimport android.os.Build\nimport android.os.Build.VERSION_CODES.TIRAMISU\nimport androidx.appcompat.app.AppCompatDelegate\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.AppCountry\nimport com.michaldrabik.ui_base.common.WidgetsProvider\nimport com.michaldrabik.ui_base.dates.AppDateFormat\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport com.michaldrabik.ui_model.ProgressNextEpisodeType\nimport com.michaldrabik.ui_model.Settings\nimport com.michaldrabik.ui_settings.helpers.AppLanguage\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SettingsGeneralMainCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val settingsRepository: SettingsRepository,\n  private val announcementManager: AnnouncementManager,\n) {\n\n  suspend fun getSettings(): Settings = withContext(dispatchers.IO) {\n    settingsRepository.load()\n  }\n\n  suspend fun setRecentShowsAmount(amount: Int) {\n    check(amount in Config.MY_SHOWS_RECENTS_OPTIONS)\n    withContext(dispatchers.IO) {\n      val settings = settingsRepository.load()\n      settings.let {\n        val new = it.copy(myRecentsAmount = amount)\n        settingsRepository.update(new)\n      }\n    }\n  }\n\n  suspend fun enableSpecialSeasons(enable: Boolean) {\n    val settings = settingsRepository.load()\n    settings.let {\n      val new = it.copy(specialSeasonsEnabled = enable)\n      settingsRepository.update(new)\n    }\n  }\n\n  fun isMoviesEnabled() = settingsRepository.isMoviesEnabled\n\n  suspend fun enableMovies(enable: Boolean) {\n    val newMode = if (!enable) Mode.SHOWS else settingsRepository.mode\n    settingsRepository.run {\n      isMoviesEnabled = enable\n      mode = newMode\n    }\n    announcementManager.refreshMoviesAnnouncements()\n  }\n\n  fun isNewsEnabled() = settingsRepository.isNewsEnabled\n\n  fun enableNews(enable: Boolean) {\n    settingsRepository.run {\n      isNewsEnabled = enable\n    }\n  }\n\n  fun isStreamingsEnabled() = settingsRepository.streamingsEnabled\n\n  fun enableStreamings(enable: Boolean) {\n    settingsRepository.run {\n      streamingsEnabled = enable\n    }\n  }\n\n  suspend fun getLanguage(): AppLanguage {\n    if (Build.VERSION.SDK_INT >= TIRAMISU) {\n      val locales = AppCompatDelegate.getApplicationLocales()\n      if (!locales.isEmpty) {\n        val locale = locales.get(0)!!.language\n        val language = AppLanguage.fromCode(locale)\n        if (settingsRepository.language != locale) {\n          setLanguage(language)\n        }\n        return language\n      }\n    }\n    return AppLanguage.fromCode(settingsRepository.language)\n  }\n\n  suspend fun setLanguage(language: AppLanguage) {\n    settingsRepository.run {\n      this.language = language.code\n      val unused = AppLanguage.values()\n        .filter { it.code != Config.DEFAULT_LANGUAGE && it != language }\n        .map { it.code }\n      clearUnusedTranslations(unused)\n      clearLanguageLogs()\n    }\n  }\n\n  fun getCountry() = AppCountry.fromCode(settingsRepository.country)\n\n  fun setCountry(country: AppCountry) {\n    settingsRepository.country = country.code\n  }\n\n  fun getProgressType() = settingsRepository.progressNextEpisodeType\n\n  fun setProgressType(type: ProgressNextEpisodeType) {\n    settingsRepository.progressNextEpisodeType = type\n  }\n\n  fun getProgressUpcomingDays() = settingsRepository.progressUpcomingDays\n\n  fun setProgressUpcomingDays(days: Long) {\n    settingsRepository.progressUpcomingDays = days\n  }\n\n  fun isPremium() = settingsRepository.isPremium\n\n  fun setDateFormat(format: AppDateFormat, context: Context) {\n    settingsRepository.dateFormat = format.name\n    (context.applicationContext as WidgetsProvider).run {\n      requestShowsWidgetsUpdate()\n      requestMoviesWidgetsUpdate()\n    }\n  }\n\n  fun getDateFormat() = AppDateFormat.valueOf(settingsRepository.dateFormat)\n\n  fun setTabletsColumns(columns: Int) {\n    settingsRepository.viewMode.tabletGridSpanSize = columns\n  }\n\n  fun getTabletsColumns(): Int {\n    return settingsRepository.viewMode.tabletGridSpanSize\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/general/cases/SettingsGeneralStreamingsCase.kt",
    "content": "package com.michaldrabik.ui_settings.sections.general.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.movies.MovieStreamingsRepository\nimport com.michaldrabik.repository.shows.ShowStreamingsRepository\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SettingsGeneralStreamingsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val showStreamingsRepository: ShowStreamingsRepository,\n  private val movieStreamingsRepository: MovieStreamingsRepository,\n) {\n\n  suspend fun deleteCache() = withContext(dispatchers.IO) {\n    showStreamingsRepository.deleteCache()\n    movieStreamingsRepository.deleteCache()\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/general/cases/SettingsGeneralThemesCase.kt",
    "content": "package com.michaldrabik.ui_settings.sections.general.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_settings.helpers.AppTheme\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SettingsGeneralThemesCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setTheme(theme: AppTheme) {\n    settingsRepository.theme = theme.code\n  }\n\n  fun getTheme() = AppTheme.fromCode(settingsRepository.theme)\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/misc/SettingsMiscFragment.kt",
    "content": "package com.michaldrabik.ui_settings.sections.misc\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.openWebUrl\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_settings.BuildConfig\nimport com.michaldrabik.ui_settings.R\nimport com.michaldrabik.ui_settings.databinding.FragmentSettingsMiscBinding\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass SettingsMiscFragment :\n  BaseFragment<SettingsMiscViewModel>(R.layout.fragment_settings_misc) {\n\n  override val viewModel by viewModels<SettingsMiscViewModel>()\n  private val binding by viewBinding(FragmentSettingsMiscBinding::bind)\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.messageFlow.collect { showSnack(it) } },\n      doAfterLaunch = { viewModel.loadSettings() }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      settingsContactDevs.onClick { openWebLink(Config.GITHUB_ISSUE_URL) }\n      settingsDeleteCache.onClick { viewModel.deleteImagesCache(requireAppContext()) }\n\n      settingsGithubIcon.onClick { openWebLink(Config.GITHUB_URL) }\n      settingsTraktIcon.onClick { openWebLink(Config.TRAKT_URL) }\n      settingsTmdbIcon.onClick { openWebLink(Config.TMDB_URL) }\n      settingsJustWatchIcon.onClick { openWebLink(Config.JUST_WATCH_URL) }\n    }\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun render(uiState: SettingsMiscUiState) {\n    uiState.run {\n      with(binding) {\n        userId.let { settingsUserId.text = it }\n        settingsVersion.text = \"v${BuildConfig.VER_NAME} (${BuildConfig.VER_CODE})\"\n      }\n    }\n  }\n\n  private fun openWebLink(url: String) {\n    openWebUrl(url) ?: showSnack(MessageEvent.Info(R.string.errorCouldNotFindApp))\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/misc/SettingsMiscUiState.kt",
    "content": "package com.michaldrabik.ui_settings.sections.misc\n\ndata class SettingsMiscUiState(\n  val userId: String = \"\",\n)\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/misc/SettingsMiscViewModel.kt",
    "content": "package com.michaldrabik.ui_settings.sections.misc\n\nimport android.content.Context\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_settings.R\nimport com.michaldrabik.ui_settings.sections.misc.cases.SettingsMiscCacheCase\nimport com.michaldrabik.ui_settings.sections.misc.cases.SettingsMiscUserCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SettingsMiscViewModel @Inject constructor(\n  private val userCase: SettingsMiscUserCase,\n  private val cacheCase: SettingsMiscCacheCase,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val userIdState = MutableStateFlow(\"\")\n  private val loadingState = MutableStateFlow(false)\n\n  fun loadSettings() {\n    viewModelScope.launch {\n      userIdState.value = userCase.getUserId()\n    }\n  }\n\n  fun deleteImagesCache(context: Context) {\n    viewModelScope.launch {\n      withContext(Dispatchers.IO) { Glide.get(context).clearDiskCache() }\n      Glide.get(context).clearMemory()\n      cacheCase.deleteImagesCache()\n      messageChannel.send(MessageEvent.Info(R.string.textImagesCacheCleared))\n    }\n  }\n\n  val uiState = combine(\n    userIdState,\n    loadingState\n  ) { s1, _ ->\n    SettingsMiscUiState(\n      userId = s1,\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = SettingsMiscUiState()\n  )\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/misc/cases/SettingsMiscCacheCase.kt",
    "content": "package com.michaldrabik.ui_settings.sections.misc.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SettingsMiscCacheCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val showsImagesProvider: ShowImagesProvider,\n  private val moviesImagesProvider: MovieImagesProvider,\n) {\n\n  suspend fun deleteImagesCache() {\n    withContext(dispatchers.IO) {\n      showsImagesProvider.deleteLocalCache()\n      moviesImagesProvider.deleteLocalCache()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/misc/cases/SettingsMiscUserCase.kt",
    "content": "package com.michaldrabik.ui_settings.sections.misc.cases\n\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SettingsMiscUserCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun getUserId() = settingsRepository.userId\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/notifications/SettingsNotificationsFragment.kt",
    "content": "package com.michaldrabik.ui_settings.sections.notifications\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.activity.result.contract.ActivityResultContracts.RequestPermission\nimport androidx.core.content.ContextCompat\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.NotificationDelay\nimport com.michaldrabik.ui_model.Settings\nimport com.michaldrabik.ui_settings.R\nimport com.michaldrabik.ui_settings.databinding.FragmentSettingsNotificationsBinding\nimport com.michaldrabik.ui_settings.sections.notifications.SettingsNotificationsUiEvent.RequestNotificationsPermission\nimport com.michaldrabik.ui_settings.sections.notifications.views.NotificationsRationaleView\nimport dagger.hilt.android.AndroidEntryPoint\n\n@SuppressLint(\"InlinedApi\")\n@AndroidEntryPoint\nclass SettingsNotificationsFragment :\n  BaseFragment<SettingsNotificationsViewModel>(R.layout.fragment_settings_notifications) {\n\n  override val viewModel by viewModels<SettingsNotificationsViewModel>()\n  private val binding by viewBinding(FragmentSettingsNotificationsBinding::bind)\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = { viewModel.loadSettings(requireAppContext()) }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      settingsShowsNotifications.onClick {\n        viewModel.enableNotifications(!settingsShowsNotificationsSwitch.isChecked, requireAppContext())\n      }\n    }\n  }\n\n  private fun showWhenToNotifyDialog(settings: Settings) {\n    val options = NotificationDelay.values()\n    val default = options.indexOf(settings.episodesNotificationsDelay)\n\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setSingleChoiceItems(options.map { getString(it.stringRes) }.toTypedArray(), default) { dialog, index ->\n        viewModel.setWhenToNotify(options[index])\n        dialog.dismiss()\n      }\n      .show()\n  }\n\n  private fun render(uiState: SettingsNotificationsUiState) {\n    uiState.run {\n      settings?.let {\n        renderSettings(it)\n      }\n    }\n  }\n\n  private fun renderSettings(settings: Settings) {\n    with(binding) {\n      settingsShowsNotificationsSwitch.isChecked = settings.episodesNotificationsEnabled\n      settingsWhenToNotifyValue.run {\n        setText(settings.episodesNotificationsDelay.stringRes)\n        settingsWhenToNotify.onClick { showWhenToNotifyDialog(settings) }\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is RequestNotificationsPermission -> {\n        if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {\n          showNotificationsRationaleDialog()\n        } else {\n          requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)\n        }\n      }\n    }\n  }\n\n  private fun showNotificationsRationaleDialog() {\n    val context = requireContext()\n    val view = NotificationsRationaleView(context)\n    MaterialAlertDialogBuilder(context, R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(context, R.drawable.bg_dialog))\n      .setView(view)\n      .setPositiveButton(R.string.textYes) { _, _ ->\n        requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)\n      }\n      .setNegativeButton(R.string.textCancel) { _, _ -> }\n      .show()\n  }\n\n  private val requestPermissionLauncher = registerForActivityResult(RequestPermission()) { isGranted ->\n    if (isGranted) {\n      viewModel.enableNotifications(true, requireAppContext())\n    }\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/notifications/SettingsNotificationsUiEvent.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_settings.sections.notifications\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\ninternal sealed class SettingsNotificationsUiEvent<T>(action: T) : Event<T>(action) {\n  data object RequestNotificationsPermission : SettingsNotificationsUiEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/notifications/SettingsNotificationsUiState.kt",
    "content": "package com.michaldrabik.ui_settings.sections.notifications\n\nimport com.michaldrabik.ui_model.Settings\n\ndata class SettingsNotificationsUiState(\n  val settings: Settings? = null,\n)\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/notifications/SettingsNotificationsViewModel.kt",
    "content": "package com.michaldrabik.ui_settings.sections.notifications\n\nimport android.content.Context\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.ui_base.Analytics\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.NotificationDelay\nimport com.michaldrabik.ui_model.Settings\nimport com.michaldrabik.ui_settings.sections.notifications.SettingsNotificationsUiEvent.RequestNotificationsPermission\nimport com.michaldrabik.ui_settings.sections.notifications.cases.SettingsNotificationsMainCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SettingsNotificationsViewModel @Inject constructor(\n  private val mainCase: SettingsNotificationsMainCase,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val settingsState = MutableStateFlow<Settings?>(null)\n  private val loadingState = MutableStateFlow(false)\n\n  fun loadSettings(context: Context) {\n    viewModelScope.launch {\n      ensureNotificationsPermission(context)\n      refreshSettings()\n    }\n  }\n\n  fun enableNotifications(enable: Boolean, context: Context) {\n    viewModelScope.launch {\n      if (enable && !ensureNotificationsPermission(context)) {\n        eventChannel.send(RequestNotificationsPermission)\n        return@launch\n      }\n      mainCase.enableNotifications(enable)\n      refreshSettings()\n      Analytics.logSettingsAnnouncements(enable)\n    }\n  }\n\n  fun setWhenToNotify(delay: NotificationDelay) {\n    viewModelScope.launch {\n      mainCase.setWhenToNotify(delay)\n      refreshSettings()\n      Analytics.logSettingsWhenToNotify(delay.name)\n    }\n  }\n\n  private suspend fun refreshSettings() {\n    settingsState.value = mainCase.getSettings()\n  }\n\n  private suspend fun ensureNotificationsPermission(context: Context): Boolean {\n    val areNotificationsEnabled = NotificationManagerCompat\n      .from(context.applicationContext)\n      .areNotificationsEnabled()\n\n    if (!areNotificationsEnabled) {\n      mainCase.enableNotifications(false)\n    }\n\n    return areNotificationsEnabled\n  }\n\n  val uiState = combine(\n    settingsState,\n    loadingState\n  ) { s1, _ ->\n    SettingsNotificationsUiState(\n      settings = s1,\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = SettingsNotificationsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/notifications/cases/SettingsNotificationsMainCase.kt",
    "content": "package com.michaldrabik.ui_settings.sections.notifications.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport com.michaldrabik.ui_model.NotificationDelay\nimport com.michaldrabik.ui_model.Settings\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SettingsNotificationsMainCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val settingsRepository: SettingsRepository,\n  private val announcementManager: AnnouncementManager,\n) {\n\n  suspend fun getSettings(): Settings = withContext(dispatchers.IO) {\n    settingsRepository.load()\n  }\n\n  suspend fun enableNotifications(enable: Boolean) {\n    val settings = settingsRepository.load()\n    settings.let {\n      val new = it.copy(episodesNotificationsEnabled = enable)\n      settingsRepository.update(new)\n\n      announcementManager.refreshShowsAnnouncements()\n      announcementManager.refreshMoviesAnnouncements()\n    }\n  }\n\n  suspend fun setWhenToNotify(delay: NotificationDelay) {\n    val settings = settingsRepository.load()\n    settings.let {\n      val new = it.copy(episodesNotificationsDelay = delay)\n      settingsRepository.update(new)\n      announcementManager.refreshShowsAnnouncements()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/notifications/views/NotificationsRationaleView.kt",
    "content": "package com.michaldrabik.ui_settings.sections.notifications.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_settings.databinding.ViewNotificationsRationaleBinding\n\n@SuppressLint(\"SetTextI18n\")\nclass NotificationsRationaleView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewNotificationsRationaleBinding.inflate(LayoutInflater.from(context), this)\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/spoilers/SettingsSpoilersFragment.kt",
    "content": "package com.michaldrabik.ui_settings.sections.spoilers\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_settings.R\nimport com.michaldrabik.ui_settings.databinding.FragmentSettingsSpoilersBinding\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass SettingsSpoilersFragment :\n  BaseFragment<SettingsSpoilersViewModel>(R.layout.fragment_settings_spoilers) {\n\n  override val navigationId = R.id.settingsFragment\n\n  override val viewModel by viewModels<SettingsSpoilersViewModel>()\n  private val binding by viewBinding(FragmentSettingsSpoilersBinding::bind)\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      doAfterLaunch = { viewModel.loadSettings() }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      settingsSpoilersShows.onClick {\n        navigateToSafe(R.id.actionSettingsFragmentToSpoilersShows)\n      }\n      settingsSpoilersMovies.onClick {\n        navigateToSafe(R.id.actionSettingsFragmentToSpoilersMovies)\n      }\n      settingsSpoilersEpisodes.onClick {\n        navigateToSafe(R.id.actionSettingsFragmentToSpoilersEpisodes)\n      }\n      settingsSpoilersTapToReveal.onClick {\n        viewModel.setTapToReveal(!settingsSpoilersTapToRevealSwitch.isChecked)\n      }\n    }\n  }\n\n  private fun render(uiState: SettingsSpoilersUiState) {\n    uiState.run {\n      with(binding) {\n        settingsSpoilersShowsCheck.visibleIf(hasShowsSettingActive)\n        settingsSpoilersMoviesCheck.visibleIf(hasMoviesSettingActive)\n        settingsSpoilersEpisodesCheck.visibleIf(hasEpisodesSettingActive)\n        settingsSpoilersTapToRevealSwitch.isChecked = isTapToReveal\n      }\n    }\n  }\n\n  fun refreshSettings() = viewModel.loadSettings()\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/spoilers/SettingsSpoilersUiState.kt",
    "content": "package com.michaldrabik.ui_settings.sections.spoilers\n\ndata class SettingsSpoilersUiState(\n  val hasShowsSettingActive: Boolean = false,\n  val hasMoviesSettingActive: Boolean = false,\n  val hasEpisodesSettingActive: Boolean = false,\n  val isTapToReveal: Boolean = false,\n)\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/spoilers/SettingsSpoilersViewModel.kt",
    "content": "package com.michaldrabik.ui_settings.sections.spoilers\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_settings.sections.spoilers.helpers.SettingsSpoilersHelper\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SettingsSpoilersViewModel @Inject constructor(\n  private val settingsRepository: SettingsSpoilersRepository,\n  private val spoilersHelper: SettingsSpoilersHelper\n) : ViewModel() {\n\n  private val hasShowsSettingsActive = MutableStateFlow(false)\n  private val hasMoviesSettingsActive = MutableStateFlow(false)\n  private val hasEpisodesSettingsActive = MutableStateFlow(false)\n  private val tapToRevealState = MutableStateFlow(false)\n\n  fun loadSettings() {\n    viewModelScope.launch {\n      refreshSettings()\n    }\n  }\n\n  fun setTapToReveal(enable: Boolean) {\n    viewModelScope.launch {\n      settingsRepository.isTapToReveal = enable\n      refreshSettings()\n    }\n  }\n\n  private fun refreshSettings() {\n    with(settingsRepository.getAll()) {\n      hasShowsSettingsActive.value = spoilersHelper.hasActiveShowsSettings(this)\n      hasMoviesSettingsActive.value = spoilersHelper.hasActiveMoviesSettings(this)\n      hasEpisodesSettingsActive.value = spoilersHelper.hasActiveEpisodesSettings(this)\n      tapToRevealState.value = isTapToReveal\n    }\n  }\n\n  val uiState = combine(\n    hasShowsSettingsActive,\n    hasMoviesSettingsActive,\n    hasEpisodesSettingsActive,\n    tapToRevealState,\n  ) { s1, s2, s3, s4 ->\n    SettingsSpoilersUiState(\n      hasShowsSettingActive = s1,\n      hasMoviesSettingActive = s2,\n      hasEpisodesSettingActive = s3,\n      isTapToReveal = s4,\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = SettingsSpoilersUiState()\n  )\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/spoilers/episodes/SpoilersEpisodesBottomSheet.kt",
    "content": "package com.michaldrabik.ui_settings.sections.spoilers.episodes\n\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.setCheckedSilent\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_settings.R\nimport com.michaldrabik.ui_settings.SettingsFragment.Companion.REQUEST_SETTINGS\nimport com.michaldrabik.ui_settings.databinding.SheetSpoilersEpisodesBinding\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass SpoilersEpisodesBottomSheet : BaseBottomSheetFragment(R.layout.sheet_spoilers_episodes) {\n\n  private val viewModel by viewModels<SpoilersEpisodesViewModel>()\n  private val binding by viewBinding(SheetSpoilersEpisodesBinding::bind)\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      doAfterLaunch = { viewModel.refreshSettings() }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      episodesHideTitle.onClick {\n        hideTitleListener.invoke(it, !episodesHideTitleSwitch.isChecked)\n      }\n      episodesHideDescription.onClick {\n        hideDescriptionListener.invoke(it, !episodesHideDescriptionSwitch.isChecked)\n      }\n      episodesHideRating.onClick {\n        hideRatingListener.invoke(it, !episodesHideRatingSwitch.isChecked)\n      }\n      episodesHideImages.onClick {\n        hideImageListener.invoke(it, !episodesHideImagesSwitch.isChecked)\n      }\n      closeButton.onClick { dismiss() }\n    }\n  }\n\n  private fun render(uiState: SpoilersEpisodesUiState) {\n    uiState.settings.run {\n      with(binding) {\n        episodesHideTitleSwitch.setCheckedSilent(isEpisodeTitleHidden, hideTitleListener)\n        episodesHideDescriptionSwitch.setCheckedSilent(isEpisodeDescriptionHidden, hideDescriptionListener)\n        episodesHideRatingSwitch.setCheckedSilent(isEpisodeRatingHidden, hideRatingListener)\n        episodesHideImagesSwitch.setCheckedSilent(isEpisodeImageHidden, hideImageListener)\n      }\n    }\n  }\n\n  override fun onDismiss(dialog: DialogInterface) {\n    setFragmentResult(REQUEST_SETTINGS, Bundle.EMPTY)\n    super.onDismiss(dialog)\n  }\n\n  private val hideTitleListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideTitle(isChecked)\n  }\n  private val hideDescriptionListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideDescription(isChecked)\n  }\n  private val hideRatingListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideRating(isChecked)\n  }\n  private val hideImageListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideImage(isChecked)\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/spoilers/episodes/SpoilersEpisodesUiState.kt",
    "content": "package com.michaldrabik.ui_settings.sections.spoilers.episodes\n\nimport com.michaldrabik.ui_model.SpoilersSettings\n\ndata class SpoilersEpisodesUiState(\n  val settings: SpoilersSettings = SpoilersSettings.INITIAL,\n)\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/spoilers/episodes/SpoilersEpisodesViewModel.kt",
    "content": "package com.michaldrabik.ui_settings.sections.spoilers.episodes\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SpoilersEpisodesViewModel @Inject constructor(\n  private val settingsRepository: SettingsSpoilersRepository\n) : ViewModel() {\n\n  private val initialState = SpoilersEpisodesUiState()\n  private val settingsState = MutableStateFlow(initialState.settings)\n\n  fun refreshSettings() {\n    settingsState.update { settingsRepository.getAll() }\n  }\n\n  fun setHideTitle(hide: Boolean) {\n    settingsRepository.isEpisodesTitleHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideDescription(hide: Boolean) {\n    settingsRepository.isEpisodesDescriptionHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideRating(hide: Boolean) {\n    settingsRepository.isEpisodesRatingHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideImage(hide: Boolean) {\n    settingsRepository.isEpisodesImageHidden = hide\n    refreshSettings()\n  }\n\n  val uiState = settingsState\n    .map { SpoilersEpisodesUiState(it) }\n    .stateIn(\n      scope = viewModelScope,\n      started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n      initialValue = SpoilersEpisodesUiState()\n    )\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/spoilers/helpers/SettingsSpoilersHelper.kt",
    "content": "package com.michaldrabik.ui_settings.sections.spoilers.helpers\n\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport javax.inject.Inject\n\nclass SettingsSpoilersHelper @Inject constructor() {\n\n  fun hasActiveShowsSettings(settings: SpoilersSettings): Boolean {\n    return settings.isMyShowsHidden ||\n      settings.isWatchlistShowsHidden ||\n      settings.isHiddenShowsHidden ||\n      settings.isNotCollectedShowsHidden ||\n      settings.isMyShowsRatingsHidden ||\n      settings.isWatchlistShowsRatingsHidden ||\n      settings.isHiddenShowsRatingsHidden ||\n      settings.isNotCollectedShowsRatingsHidden\n  }\n\n  fun hasActiveMoviesSettings(settings: SpoilersSettings): Boolean {\n    return settings.isMyMoviesHidden ||\n      settings.isWatchlistMoviesHidden ||\n      settings.isHiddenMoviesHidden ||\n      settings.isNotCollectedMoviesHidden ||\n      settings.isMyMoviesRatingsHidden ||\n      settings.isWatchlistMoviesRatingsHidden ||\n      settings.isHiddenMoviesRatingsHidden ||\n      settings.isNotCollectedMoviesRatingsHidden\n  }\n\n  fun hasActiveEpisodesSettings(settings: SpoilersSettings): Boolean {\n    return settings.isEpisodeDescriptionHidden ||\n      settings.isEpisodeImageHidden ||\n      settings.isEpisodeRatingHidden ||\n      settings.isEpisodeTitleHidden\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/spoilers/movies/SpoilersMoviesBottomSheet.kt",
    "content": "package com.michaldrabik.ui_settings.sections.spoilers.movies\n\nimport android.annotation.SuppressLint\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.setCheckedSilent\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_settings.R\nimport com.michaldrabik.ui_settings.SettingsFragment.Companion.REQUEST_SETTINGS\nimport com.michaldrabik.ui_settings.databinding.SheetSpoilersMoviesBinding\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass SpoilersMoviesBottomSheet : BaseBottomSheetFragment(R.layout.sheet_spoilers_movies) {\n\n  private val viewModel by viewModels<SpoilersMoviesViewModel>()\n  private val binding by viewBinding(SheetSpoilersMoviesBinding::bind)\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      doAfterLaunch = { viewModel.refreshSettings() }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      myMoviesDescription.onClick {\n        myMoviesListener.invoke(it, !myMoviesSwitch.isChecked)\n      }\n      myMoviesRatingDescription.onClick {\n        myMoviesRatingsListener.invoke(it, !myMoviesRatingsSwitch.isChecked)\n      }\n      watchlistMoviesDescription.onClick {\n        watchlistMoviesListener.invoke(it, !watchlistMoviesSwitch.isChecked)\n      }\n      watchlistMoviesRatingDescription.onClick {\n        watchlistMoviesRatingsListener.invoke(it, !watchlistMoviesRatingsSwitch.isChecked)\n      }\n      hiddenMoviesDescription.onClick {\n        hiddenMoviesListener.invoke(it, !hiddenMoviesSwitch.isChecked)\n      }\n      hiddenMoviesRatingDescription.onClick {\n        hiddenMoviesRatingsListener.invoke(it, !hiddenMoviesRatingsSwitch.isChecked)\n      }\n      notCollectedMoviesDescription.onClick {\n        notCollectedMoviesListener.invoke(it, !notCollectedMoviesSwitch.isChecked)\n      }\n      notCollectedMoviesRatingDescription.onClick {\n        notCollectedMoviesRatingsListener.invoke(it, !notCollectedMoviesRatingsSwitch.isChecked)\n      }\n      closeButton.onClick { dismiss() }\n    }\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun render(uiState: SpoilersMoviesUiState) {\n    uiState.settings.run {\n      with(binding) {\n        notCollectedMoviesSwitch.setCheckedSilent(isNotCollectedMoviesHidden, notCollectedMoviesListener)\n        notCollectedMoviesRatingsSwitch.setCheckedSilent(isNotCollectedMoviesRatingsHidden, notCollectedMoviesRatingsListener)\n        myMoviesSwitch.setCheckedSilent(isMyMoviesHidden, myMoviesListener)\n        myMoviesRatingsSwitch.setCheckedSilent(isMyMoviesRatingsHidden, myMoviesRatingsListener)\n        watchlistMoviesSwitch.setCheckedSilent(isWatchlistMoviesHidden, watchlistMoviesListener)\n        watchlistMoviesRatingsSwitch.setCheckedSilent(isWatchlistMoviesRatingsHidden, watchlistMoviesRatingsListener)\n        hiddenMoviesSwitch.setCheckedSilent(isHiddenMoviesHidden, hiddenMoviesListener)\n        hiddenMoviesRatingsSwitch.setCheckedSilent(isHiddenMoviesRatingsHidden, hiddenMoviesRatingsListener)\n      }\n    }\n  }\n\n  override fun onDismiss(dialog: DialogInterface) {\n    setFragmentResult(REQUEST_SETTINGS, Bundle.EMPTY)\n    super.onDismiss(dialog)\n  }\n\n  private val notCollectedMoviesListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideNotCollectedMovies(isChecked)\n  }\n  private val notCollectedMoviesRatingsListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideNotCollectedRatingsMovies(isChecked)\n  }\n  private val myMoviesListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideMyMovies(isChecked)\n  }\n  private val myMoviesRatingsListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideMyRatingsMovies(isChecked)\n  }\n  private val watchlistMoviesListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideWatchlistMovies(isChecked)\n  }\n  private val watchlistMoviesRatingsListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideWatchlistRatingsMovies(isChecked)\n  }\n  private val hiddenMoviesListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideHiddenMovies(isChecked)\n  }\n  private val hiddenMoviesRatingsListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideHiddenRatingsMovies(isChecked)\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/spoilers/movies/SpoilersMoviesUiState.kt",
    "content": "package com.michaldrabik.ui_settings.sections.spoilers.movies\n\nimport com.michaldrabik.ui_model.SpoilersSettings\n\ndata class SpoilersMoviesUiState(\n  val settings: SpoilersSettings = SpoilersSettings.INITIAL,\n)\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/spoilers/movies/SpoilersMoviesViewModel.kt",
    "content": "package com.michaldrabik.ui_settings.sections.spoilers.movies\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SpoilersMoviesViewModel @Inject constructor(\n  private val settingsRepository: SettingsSpoilersRepository\n) : ViewModel() {\n\n  private val initialState = SpoilersMoviesUiState()\n  private val settingsState = MutableStateFlow(initialState.settings)\n\n  fun refreshSettings() {\n    settingsState.update { settingsRepository.getAll() }\n  }\n\n  fun setHideMyMovies(hide: Boolean) {\n    settingsRepository.isMyMoviesHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideMyRatingsMovies(hide: Boolean) {\n    settingsRepository.isMyMoviesRatingsHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideWatchlistMovies(hide: Boolean) {\n    settingsRepository.isWatchlistMoviesHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideWatchlistRatingsMovies(hide: Boolean) {\n    settingsRepository.isWatchlistMoviesRatingsHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideHiddenMovies(hide: Boolean) {\n    settingsRepository.isHiddenMoviesHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideHiddenRatingsMovies(hide: Boolean) {\n    settingsRepository.isHiddenMoviesRatingsHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideNotCollectedMovies(hide: Boolean) {\n    settingsRepository.isUncollectedMoviesHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideNotCollectedRatingsMovies(hide: Boolean) {\n    settingsRepository.isUncollectedMoviesRatingsHidden = hide\n    refreshSettings()\n  }\n\n  val uiState = settingsState\n    .map { SpoilersMoviesUiState(it) }\n    .stateIn(\n      scope = viewModelScope,\n      started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n      initialValue = SpoilersMoviesUiState()\n    )\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/spoilers/shows/SpoilersShowsBottomSheet.kt",
    "content": "package com.michaldrabik.ui_settings.sections.spoilers.shows\n\nimport android.annotation.SuppressLint\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.ui_base.BaseBottomSheetFragment\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.setCheckedSilent\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_settings.R\nimport com.michaldrabik.ui_settings.SettingsFragment.Companion.REQUEST_SETTINGS\nimport com.michaldrabik.ui_settings.databinding.SheetSpoilersShowsBinding\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass SpoilersShowsBottomSheet : BaseBottomSheetFragment(R.layout.sheet_spoilers_shows) {\n\n  private val viewModel by viewModels<SpoilersShowsViewModel>()\n  private val binding by viewBinding(SheetSpoilersShowsBinding::bind)\n\n  override fun getTheme(): Int = R.style.CustomBottomSheetDialog\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      doAfterLaunch = { viewModel.refreshSettings() }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      myShowsDescription.onClick {\n        myShowsListener.invoke(it, !myShowsSwitch.isChecked)\n      }\n      myShowsRatingDescription.onClick {\n        myShowsRatingsListener.invoke(it, !myShowsRatingsSwitch.isChecked)\n      }\n      watchlistShowsDescription.onClick {\n        watchlistShowsListener.invoke(it, !watchlistShowsSwitch.isChecked)\n      }\n      watchlistShowsRatingDescription.onClick {\n        watchlistShowsRatingsListener.invoke(it, !watchlistShowsRatingsSwitch.isChecked)\n      }\n      hiddenShowsDescription.onClick {\n        hiddenShowsListener.invoke(it, !hiddenShowsSwitch.isChecked)\n      }\n      hiddenShowsRatingDescription.onClick {\n        hiddenShowsRatingsListener.invoke(it, !hiddenShowsRatingsSwitch.isChecked)\n      }\n      notCollectedShowsDescription.onClick {\n        notCollectedShowsListener.invoke(it, !notCollectedShowsSwitch.isChecked)\n      }\n      notCollectedShowsRatingDescription.onClick {\n        notCollectedShowsRatingsListener.invoke(it, !notCollectedShowsRatingsSwitch.isChecked)\n      }\n      closeButton.onClick { dismiss() }\n    }\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  private fun render(uiState: SpoilersShowsUiState) {\n    uiState.settings.run {\n      with(binding) {\n        notCollectedShowsSwitch.setCheckedSilent(isNotCollectedShowsHidden, notCollectedShowsListener)\n        notCollectedShowsRatingsSwitch.setCheckedSilent(isNotCollectedShowsRatingsHidden, notCollectedShowsRatingsListener)\n        myShowsSwitch.setCheckedSilent(isMyShowsHidden, myShowsListener)\n        myShowsRatingsSwitch.setCheckedSilent(isMyShowsRatingsHidden, myShowsRatingsListener)\n        watchlistShowsSwitch.setCheckedSilent(isWatchlistShowsHidden, watchlistShowsListener)\n        watchlistShowsRatingsSwitch.setCheckedSilent(isWatchlistShowsRatingsHidden, watchlistShowsRatingsListener)\n        hiddenShowsSwitch.setCheckedSilent(isHiddenShowsHidden, hiddenShowsListener)\n        hiddenShowsRatingsSwitch.setCheckedSilent(isHiddenShowsRatingsHidden, hiddenShowsRatingsListener)\n      }\n    }\n  }\n\n  override fun onDismiss(dialog: DialogInterface) {\n    setFragmentResult(REQUEST_SETTINGS, Bundle.EMPTY)\n    super.onDismiss(dialog)\n  }\n\n  private val notCollectedShowsListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideNotCollectedShows(isChecked)\n  }\n  private val notCollectedShowsRatingsListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideNotCollectedRatingsShows(isChecked)\n  }\n  private val myShowsListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideMyShows(isChecked)\n  }\n  private val myShowsRatingsListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideMyRatingsShows(isChecked)\n  }\n  private val watchlistShowsListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideWatchlistShows(isChecked)\n  }\n  private val watchlistShowsRatingsListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideWatchlistRatingsShows(isChecked)\n  }\n  private val hiddenShowsListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideHiddenShows(isChecked)\n  }\n  private val hiddenShowsRatingsListener: (View, Boolean) -> Unit = { _, isChecked ->\n    viewModel.setHideHiddenRatingsShows(isChecked)\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/spoilers/shows/SpoilersShowsUiState.kt",
    "content": "package com.michaldrabik.ui_settings.sections.spoilers.shows\n\nimport com.michaldrabik.ui_model.SpoilersSettings\n\ndata class SpoilersShowsUiState(\n  val settings: SpoilersSettings = SpoilersSettings.INITIAL,\n)\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/spoilers/shows/SpoilersShowsViewModel.kt",
    "content": "package com.michaldrabik.ui_settings.sections.spoilers.shows\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SpoilersShowsViewModel @Inject constructor(\n  private val settingsRepository: SettingsSpoilersRepository\n) : ViewModel() {\n\n  private val initialState = SpoilersShowsUiState()\n  private val settingsState = MutableStateFlow(initialState.settings)\n\n  fun refreshSettings() {\n    settingsState.update { settingsRepository.getAll() }\n  }\n\n  fun setHideMyShows(hide: Boolean) {\n    settingsRepository.isMyShowsHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideMyRatingsShows(hide: Boolean) {\n    settingsRepository.isMyShowsRatingsHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideWatchlistShows(hide: Boolean) {\n    settingsRepository.isWatchlistShowsHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideWatchlistRatingsShows(hide: Boolean) {\n    settingsRepository.isWatchlistShowsRatingsHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideHiddenShows(hide: Boolean) {\n    settingsRepository.isHiddenShowsHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideHiddenRatingsShows(hide: Boolean) {\n    settingsRepository.isHiddenShowsRatingsHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideNotCollectedShows(hide: Boolean) {\n    settingsRepository.isUncollectedShowsHidden = hide\n    refreshSettings()\n  }\n\n  fun setHideNotCollectedRatingsShows(hide: Boolean) {\n    settingsRepository.isUncollectedShowsRatingsHidden = hide\n    refreshSettings()\n  }\n\n  val uiState = settingsState\n    .map { SpoilersShowsUiState(it) }\n    .stateIn(\n      scope = viewModelScope,\n      started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n      initialValue = SpoilersShowsUiState()\n    )\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/trakt/SettingsTraktFragment.kt",
    "content": "package com.michaldrabik.ui_settings.sections.trakt\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport androidx.activity.result.contract.ActivityResultContracts.RequestPermission\nimport androidx.core.content.ContextCompat\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.viewModels\nimport androidx.work.WorkInfo.State\nimport androidx.work.WorkManager\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.data_remote.Config.TRAKT_AUTHORIZE_URL\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.OnTraktAuthorizeListener\nimport com.michaldrabik.ui_base.trakt.TraktSyncWorker\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.openWebUrl\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.PremiumFeature\nimport com.michaldrabik.ui_model.TraktSyncSchedule.OFF\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ITEM\nimport com.michaldrabik.ui_settings.R\nimport com.michaldrabik.ui_settings.databinding.FragmentSettingsTraktBinding\nimport com.michaldrabik.ui_settings.sections.trakt.SettingsTraktUiEvent.RequestNotificationsPermission\nimport com.michaldrabik.ui_settings.sections.trakt.SettingsTraktUiEvent.StartAuthorization\nimport com.michaldrabik.ui_settings.sections.trakt.views.TraktNotificationsRationaleView\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass SettingsTraktFragment :\n  BaseFragment<SettingsTraktViewModel>(R.layout.fragment_settings_trakt),\n  OnTraktAuthorizeListener {\n\n  override val viewModel by viewModels<SettingsTraktViewModel>()\n  private val binding by viewBinding(FragmentSettingsTraktBinding::bind)\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupWorkManager()\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      { viewModel.messageFlow.collect { showSnack(it) } },\n      doAfterLaunch = { viewModel.loadSettings() }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      settingsTraktQuickRate.visibleIf(Config.SHOW_PREMIUM)\n      settingsTraktQuickRemove.onClick {\n        viewModel.enableQuickRemove(!settingsTraktQuickRemoveSwitch.isChecked)\n      }\n      settingsTraktSync.onClick {\n        navigateTo(R.id.actionSettingsFragmentToTraktSync)\n      }\n    }\n  }\n\n  private fun setupWorkManager() {\n    WorkManager.getInstance(requireAppContext())\n      .getWorkInfosByTagLiveData(TraktSyncWorker.TAG_ID)\n      .observe(viewLifecycleOwner) {\n        binding.settingsTraktSyncProgress.visibleIf(it.any { work -> work.state == State.RUNNING })\n      }\n  }\n\n  private fun render(uiState: SettingsTraktUiState) {\n    uiState.run {\n      with(binding) {\n        settingsTraktAuthorizeProgress.visibleIf(isSigningIn)\n        settingsTraktSync.visibleIf(isSignedInTrakt)\n        settingsTraktQuickSync.visibleIf(isSignedInTrakt)\n        settingsTraktQuickRemove.visibleIf(isSignedInTrakt)\n        settingsTraktQuickRate.visibleIf(isSignedInTrakt && Config.SHOW_PREMIUM)\n        settingsTraktIcon.visibleIf(!isSignedInTrakt && !isSigningIn)\n        settingsTraktAuthorizeIcon.visibleIf(isSignedInTrakt)\n\n        settingsTraktQuickRateSwitch.isChecked = settings?.traktQuickRateEnabled ?: false\n        settingsTraktQuickRate.alpha = if (isPremium) 1F else 0.5F\n        settingsTraktQuickRate.onClick { view ->\n          onPremiumAction(isPremium, view.tag) {\n            viewModel.enableQuickRate(!settingsTraktQuickRateSwitch.isChecked)\n          }\n        }\n        if (isPremium) {\n          settingsTraktQuickRateTitle.setCompoundDrawables(null, null, null, null)\n        }\n\n        settingsTraktQuickRemoveSwitch.isChecked = settings?.traktQuickRemoveEnabled ?: false\n        settingsTraktQuickRemove.onClick {\n          viewModel.enableQuickRemove(!settingsTraktQuickRemoveSwitch.isChecked)\n        }\n\n        settingsTraktQuickSyncSwitch.isChecked = settings?.traktQuickSyncEnabled ?: false\n        settingsTraktQuickSync.onClick {\n          val isChecked = settingsTraktQuickSyncSwitch.isChecked\n          viewModel.enableQuickSync(!isChecked)\n          if (!isChecked && settings?.traktSyncSchedule != OFF) {\n            showQuickSyncConfirmationDialog()\n          }\n        }\n\n        settingsTraktAuthorize.onClick {\n          if (isSignedInTrakt) {\n            showLogoutDialog()\n          } else if (!isSigningIn) {\n            viewModel.startAuthorization(requireAppContext())\n          }\n        }\n\n        settingsTraktAuthorizeSummary.text = when {\n          isSignedInTrakt -> when {\n            traktUsername.isNotEmpty() -> getString(R.string.textSettingsTraktAuthorizeSummarySignOutUser, traktUsername)\n            else -> getString(R.string.textSettingsTraktAuthorizeSummarySignOut)\n          }\n          else -> getString(R.string.textSettingsTraktAuthorizeSummarySignIn)\n        }\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      StartAuthorization -> openTraktAuthWebsite()\n      RequestNotificationsPermission -> showNotificationsRationaleDialog()\n    }\n  }\n\n  private fun onPremiumAction(\n    isPremium: Boolean,\n    tag: Any?,\n    action: () -> Unit,\n  ) {\n    if (isPremium) {\n      action()\n      return\n    }\n    val args = bundleOf()\n    if (tag != null) {\n      val feature = PremiumFeature.fromTag(requireContext(), tag.toString())\n      feature?.let {\n        args.putSerializable(ARG_ITEM, feature)\n      }\n    }\n    navigateTo(R.id.actionSettingsFragmentToPremium, args)\n  }\n\n  private fun showQuickSyncConfirmationDialog() {\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .setTitle(R.string.textSettingsQuickSyncConfirmationTitle)\n      .setMessage(R.string.textSettingsQuickSyncConfirmationMessage)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setPositiveButton(R.string.textTurnOff) { _, _ ->\n        viewModel.setTraktSyncSchedule(OFF)\n      }\n      .setNegativeButton(R.string.textNotNow) { _, _ -> }\n      .show()\n  }\n\n  private fun showLogoutDialog() {\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setTitle(R.string.textSettingsLogoutTitle)\n      .setMessage(R.string.textSettingsLogoutMessage)\n      .setPositiveButton(R.string.textYes) { _, _ ->\n        viewModel.logoutTrakt()\n      }\n      .setNegativeButton(R.string.textCancel) { _, _ -> }\n      .show()\n  }\n\n  @SuppressLint(\"InlinedApi\")\n  private fun showNotificationsRationaleDialog() {\n    val context = requireContext()\n    val view = TraktNotificationsRationaleView(context)\n    MaterialAlertDialogBuilder(context, R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(context, R.drawable.bg_dialog))\n      .setView(view)\n      .setPositiveButton(R.string.textYes) { _, _ ->\n        requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)\n      }\n      .setNegativeButton(R.string.textNo) { _, _ -> openTraktAuthWebsite() }\n      .show()\n  }\n\n  private fun openTraktAuthWebsite() {\n    openWebUrl(TRAKT_AUTHORIZE_URL) ?: showSnack(MessageEvent.Error(R.string.errorCouldNotFindApp))\n  }\n\n  override fun onAuthorizationResult(authData: Uri?) {\n    viewModel.authorizeTrakt(authData)\n  }\n\n  private val requestPermissionLauncher =\n    registerForActivityResult(RequestPermission()) { _ ->\n      openTraktAuthWebsite()\n    }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/trakt/SettingsTraktUiEvent.kt",
    "content": "package com.michaldrabik.ui_settings.sections.trakt\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\nsealed class SettingsTraktUiEvent<T>(action: T) : Event<T>(action) {\n  data object StartAuthorization : SettingsTraktUiEvent<Unit>(Unit)\n  data object RequestNotificationsPermission : SettingsTraktUiEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/trakt/SettingsTraktUiState.kt",
    "content": "package com.michaldrabik.ui_settings.sections.trakt\n\nimport com.michaldrabik.ui_model.Settings\n\ndata class SettingsTraktUiState(\n  val settings: Settings? = null,\n  val isSignedInTrakt: Boolean = false,\n  val isSigningIn: Boolean = false,\n  val traktUsername: String = \"\",\n  val isPremium: Boolean = false,\n)\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/trakt/SettingsTraktViewModel.kt",
    "content": "package com.michaldrabik.ui_settings.sections.trakt\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError\nimport com.michaldrabik.ui_base.Analytics\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.utilities.extensions.withApiAtLeast\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Settings\nimport com.michaldrabik.ui_model.TraktSyncSchedule\nimport com.michaldrabik.ui_settings.R\nimport com.michaldrabik.ui_settings.sections.trakt.SettingsTraktUiEvent.RequestNotificationsPermission\nimport com.michaldrabik.ui_settings.sections.trakt.SettingsTraktUiEvent.StartAuthorization\nimport com.michaldrabik.ui_settings.sections.trakt.cases.SettingsRatingsCase\nimport com.michaldrabik.ui_settings.sections.trakt.cases.SettingsTraktCase\nimport com.michaldrabik.ui_settings.sections.trakt.cases.SettingsTraktMainCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SettingsTraktViewModel @Inject constructor(\n  private val mainCase: SettingsTraktMainCase,\n  private val traktCase: SettingsTraktCase,\n  private val ratingsCase: SettingsRatingsCase,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val settingsState = MutableStateFlow<Settings?>(null)\n\n  private val premiumState = MutableStateFlow(false)\n  private val signedInTraktState = MutableStateFlow(false)\n  private val signingInState = MutableStateFlow(false)\n  private val traktNameState = MutableStateFlow(\"\")\n\n  fun loadSettings() {\n    viewModelScope.launch {\n      refreshSettings()\n    }\n  }\n\n  private suspend fun refreshSettings() {\n    settingsState.value = mainCase.getSettings()\n    signedInTraktState.value = traktCase.isTraktAuthorized()\n    traktNameState.value = traktCase.getTraktUsername()\n    premiumState.value = mainCase.isPremium()\n  }\n\n  private fun preloadRatings() {\n    viewModelScope.launch {\n      try {\n        ratingsCase.preloadRatings()\n      } catch (error: Throwable) {\n        Timber.e(\"Failed to preload some of ratings\")\n        rethrowCancellation(error)\n      }\n    }\n  }\n\n  fun startAuthorization(context: Context) {\n    viewModelScope.launch {\n      withApiAtLeast(33) {\n        val areNotificationsEnabled = NotificationManagerCompat\n          .from(context.applicationContext)\n          .areNotificationsEnabled()\n\n        if (!areNotificationsEnabled) {\n          eventChannel.send(RequestNotificationsPermission)\n          return@launch\n        }\n      }\n      eventChannel.send(StartAuthorization)\n    }\n  }\n\n  fun authorizeTrakt(authData: Uri?) {\n    if (authData == null) {\n      Logger.record(Error(\"authData is null\"), \"SettingsViewModel::authorizeTrakt()\")\n      return\n    }\n    viewModelScope.launch {\n      try {\n        signingInState.value = true\n        traktCase.authorizeTrakt(authData)\n        traktCase.enableTraktQuickRemove(true)\n        refreshSettings()\n        preloadRatings()\n        messageChannel.send(MessageEvent.Info(R.string.textTraktLoginSuccess))\n        Analytics.logTraktLogin()\n      } catch (error: Throwable) {\n        Logger.record(error, \"SettingsViewModel::authorizeTrakt()\")\n        when (ErrorHelper.parse(error)) {\n          is ShowlyError.CoroutineCancellation -> rethrowCancellation(error)\n          is ShowlyError.AccountLockedError -> messageChannel.send(MessageEvent.Error(R.string.errorTraktLocked))\n          else -> messageChannel.send(MessageEvent.Error(R.string.errorAuthorization))\n        }\n      } finally {\n        signingInState.value = false\n      }\n    }\n  }\n\n  fun logoutTrakt() {\n    viewModelScope.launch {\n      traktCase.logoutTrakt()\n      messageChannel.send(MessageEvent.Info(R.string.textTraktLogoutSuccess))\n      refreshSettings()\n      Analytics.logTraktLogout()\n    }\n  }\n\n  fun enableQuickRate(enable: Boolean) {\n    viewModelScope.launch {\n      traktCase.enableTraktQuickRate(enable)\n      refreshSettings()\n      Analytics.logSettingsTraktQuickRate(enable)\n    }\n  }\n\n  fun enableQuickRemove(enable: Boolean) {\n    viewModelScope.launch {\n      traktCase.enableTraktQuickRemove(enable)\n      refreshSettings()\n      Analytics.logSettingsTraktQuickRemove(enable)\n    }\n  }\n\n  fun enableQuickSync(enable: Boolean) {\n    viewModelScope.launch {\n      traktCase.enableTraktQuickSync(enable)\n      refreshSettings()\n      Analytics.logSettingsTraktQuickSync(enable)\n    }\n  }\n\n  fun setTraktSyncSchedule(schedule: TraktSyncSchedule) {\n    viewModelScope.launch {\n      traktCase.setTraktSyncSchedule(schedule)\n      refreshSettings()\n    }\n  }\n\n  val uiState = combine(\n    settingsState,\n    signingInState,\n    signedInTraktState,\n    traktNameState,\n    premiumState\n  ) { s1, s2, s3, s4, s5 ->\n    SettingsTraktUiState(\n      settings = s1,\n      isSigningIn = s2,\n      isSignedInTrakt = s3,\n      traktUsername = s4,\n      isPremium = s5\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = SettingsTraktUiState()\n  )\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/trakt/cases/SettingsRatingsCase.kt",
    "content": "package com.michaldrabik.ui_settings.sections.trakt.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SettingsRatingsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val userTraktManager: UserTraktManager,\n  private val settingsRepository: SettingsRepository,\n  private val ratingsRepository: RatingsRepository,\n) {\n\n  suspend fun preloadRatings() = withContext(dispatchers.IO) {\n    if (userTraktManager.isAuthorized()) {\n      userTraktManager.checkAuthorization()\n      with(ratingsRepository) {\n        shows.preloadRatings()\n        if (settingsRepository.isMoviesEnabled) {\n          movies.preloadRatings()\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/trakt/cases/SettingsTraktCase.kt",
    "content": "package com.michaldrabik.ui_settings.sections.trakt.cases\n\nimport android.net.Uri\nimport androidx.work.WorkManager\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.sources.TraktSyncLogLocalDataSource\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.trakt.TraktSyncWorker\nimport com.michaldrabik.ui_model.Settings\nimport com.michaldrabik.ui_model.TraktSyncSchedule\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SettingsTraktCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val settingsRepository: SettingsRepository,\n  private val ratingsRepository: RatingsRepository,\n  private val syncLogLocalSource: TraktSyncLogLocalDataSource,\n  private val userManager: UserTraktManager,\n  private val workManager: WorkManager\n) {\n\n  suspend fun enableTraktQuickSync(enable: Boolean) {\n    val settings = settingsRepository.load()\n    settings.let {\n      val new = it.copy(traktQuickSyncEnabled = enable)\n      settingsRepository.update(new)\n    }\n  }\n\n  suspend fun enableTraktQuickRemove(enable: Boolean) {\n    val settings = settingsRepository.load()\n    settings.let {\n      val new = it.copy(traktQuickRemoveEnabled = enable)\n      settingsRepository.update(new)\n    }\n  }\n\n  suspend fun enableTraktQuickRate(enable: Boolean) {\n    val settings = settingsRepository.load()\n    settings.let {\n      val new = it.copy(traktQuickRateEnabled = enable)\n      settingsRepository.update(new)\n    }\n  }\n\n  suspend fun setTraktSyncSchedule(schedule: TraktSyncSchedule) {\n    val settings = settingsRepository.load()\n    settings.let {\n      val new = it.copy(traktSyncSchedule = schedule)\n      settingsRepository.update(new)\n    }\n    TraktSyncWorker.schedulePeriodic(workManager, schedule, cancelExisting = true)\n  }\n\n  suspend fun authorizeTrakt(authData: Uri) {\n    val code = authData.getQueryParameter(\"code\")\n    if (code.isNullOrBlank()) {\n      throw IllegalStateException(\"Invalid Trakt authorization code.\")\n    }\n    withContext(dispatchers.IO) {\n      userManager.authorize(code)\n    }\n  }\n\n  suspend fun logoutTrakt() {\n\n    suspend fun disableTraktFeatures() {\n      val settings = settingsRepository.load()\n      settings.let {\n        val defaults = Settings.createInitial()\n        val new = it.copy(\n          traktQuickSyncEnabled = defaults.traktQuickSyncEnabled,\n          traktQuickRemoveEnabled = defaults.traktQuickRemoveEnabled,\n          traktQuickRateEnabled = defaults.traktQuickRateEnabled\n        )\n        settingsRepository.update(new)\n      }\n      setTraktSyncSchedule(TraktSyncSchedule.OFF)\n    }\n\n    userManager.revokeToken()\n    syncLogLocalSource.deleteAll()\n    ratingsRepository.clear()\n    disableTraktFeatures()\n    TraktSyncWorker.cancelAllPeriodic(workManager)\n  }\n\n  fun isTraktAuthorized() = userManager.isAuthorized()\n\n  suspend fun getTraktUsername() =\n    withContext(dispatchers.IO) {\n      userManager.getUsername()\n    }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/trakt/cases/SettingsTraktMainCase.kt",
    "content": "package com.michaldrabik.ui_settings.sections.trakt.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.Settings\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SettingsTraktMainCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun getSettings(): Settings = withContext(dispatchers.IO) {\n    settingsRepository.load()\n  }\n\n  fun isPremium() = settingsRepository.isPremium\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/trakt/views/TraktNotificationsRationaleView.kt",
    "content": "package com.michaldrabik.ui_settings.sections.trakt.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_settings.databinding.ViewTraktNotificationsRationaleBinding\n\n@SuppressLint(\"SetTextI18n\")\nclass TraktNotificationsRationaleView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewTraktNotificationsRationaleBinding.inflate(LayoutInflater.from(context), this)\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/widgets/SettingsWidgetsFragment.kt",
    "content": "package com.michaldrabik.ui_settings.sections.widgets\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.content.ContextCompat\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.PremiumFeature\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ITEM\nimport com.michaldrabik.ui_settings.R\nimport com.michaldrabik.ui_settings.databinding.FragmentSettingsWidgetsBinding\nimport com.michaldrabik.ui_settings.helpers.AppTheme\nimport com.michaldrabik.ui_settings.helpers.WidgetTransparency\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass SettingsWidgetsFragment :\n  BaseFragment<SettingsWidgetsViewModel>(R.layout.fragment_settings_widgets) {\n\n  override val viewModel by viewModels<SettingsWidgetsViewModel>()\n  private val binding by viewBinding(FragmentSettingsWidgetsBinding::bind)\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      doAfterLaunch = { viewModel.loadSettings() }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      settingsWidgetsTheme.visibleIf(Config.SHOW_PREMIUM)\n      settingsWidgetsThemeValue.visibleIf(Config.SHOW_PREMIUM)\n      settingsWidgetsTransparency.visibleIf(Config.SHOW_PREMIUM)\n      settingsWidgetsTransparencyValue.visibleIf(Config.SHOW_PREMIUM)\n\n      settingsWidgetsLabels.onClick {\n        viewModel.enableWidgetsTitles(!settingsWidgetsLabelsSwitch.isChecked, requireAppContext())\n      }\n    }\n  }\n\n  private fun render(uiState: SettingsWidgetsUiState) {\n    uiState.run {\n      with(binding) {\n        settings?.let {\n          settingsWidgetsLabelsSwitch.isChecked = it.widgetsShowLabel\n        }\n        themeWidgets?.let {\n          settingsWidgetsThemeValue.setText(it.displayName)\n          settingsWidgetsTheme.onClick { view ->\n            onPremiumAction(uiState.isPremium, view.tag) {\n              showWidgetsThemeDialog(themeWidgets)\n            }\n          }\n        }\n        widgetsTransparency.let {\n          settingsWidgetsTransparencyValue.setText(it.displayName)\n          settingsWidgetsTransparency.onClick { view ->\n            onPremiumAction(uiState.isPremium, view.tag) {\n              showWidgetsTransparencyDialog(widgetsTransparency)\n            }\n          }\n        }\n        isPremium.let {\n          val alpha = if (it) 1F else 0.5F\n          settingsWidgetsTheme.alpha = alpha\n          settingsWidgetsThemeValue.alpha = alpha\n          settingsWidgetsTransparency.alpha = alpha\n          if (it) {\n            settingsWidgetsThemeTitle.setCompoundDrawables(null, null, null, null)\n            settingsWidgetsTransparencyTitle.setCompoundDrawables(null, null, null, null)\n          }\n        }\n      }\n    }\n  }\n\n  private fun onPremiumAction(\n    isPremium: Boolean,\n    tag: Any?,\n    action: () -> Unit,\n  ) {\n    if (isPremium) {\n      action()\n      return\n    }\n    val args = bundleOf()\n    if (tag != null) {\n      val feature = PremiumFeature.fromTag(requireContext(), tag.toString())\n      feature?.let {\n        args.putSerializable(ARG_ITEM, feature)\n      }\n    }\n    navigateTo(R.id.actionSettingsFragmentToPremium, args)\n  }\n\n  private fun showWidgetsThemeDialog(theme: AppTheme) {\n    val options = AppTheme.values().filter { it != AppTheme.SYSTEM }\n    val selected = options.indexOf(theme)\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setSingleChoiceItems(options.map { getString(it.displayName) }.toTypedArray(), selected) { dialog, index ->\n        if (index != selected) {\n          viewModel.setWidgetsTheme(options[index], requireAppContext())\n        }\n        dialog.dismiss()\n      }\n      .show()\n  }\n\n  private fun showWidgetsTransparencyDialog(transparency: WidgetTransparency) {\n    val options = WidgetTransparency.values()\n    val selected = options.indexOf(transparency)\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setSingleChoiceItems(options.map { getString(it.displayName) }.toTypedArray(), selected) { dialog, index ->\n        if (index != selected) {\n          viewModel.setWidgetsTransparency(options[index], requireAppContext())\n        }\n        dialog.dismiss()\n      }\n      .show()\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/widgets/SettingsWidgetsUiState.kt",
    "content": "package com.michaldrabik.ui_settings.sections.widgets\n\nimport com.michaldrabik.ui_model.Settings\nimport com.michaldrabik.ui_settings.helpers.AppTheme\nimport com.michaldrabik.ui_settings.helpers.WidgetTransparency\n\ndata class SettingsWidgetsUiState(\n  val settings: Settings? = null,\n  val themeWidgets: AppTheme? = AppTheme.DARK,\n  val widgetsTransparency: WidgetTransparency = WidgetTransparency.SOLID,\n  val isPremium: Boolean = false,\n)\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/widgets/SettingsWidgetsViewModel.kt",
    "content": "package com.michaldrabik.ui_settings.sections.widgets\n\nimport android.content.Context\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.ui_base.Analytics\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_model.Settings\nimport com.michaldrabik.ui_settings.helpers.AppTheme\nimport com.michaldrabik.ui_settings.helpers.WidgetTransparency\nimport com.michaldrabik.ui_settings.sections.widgets.cases.SettingsWidgetsMainCase\nimport com.michaldrabik.ui_settings.sections.widgets.cases.SettingsWidgetsThemesCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SettingsWidgetsViewModel @Inject constructor(\n  private val mainCase: SettingsWidgetsMainCase,\n  private val themesCase: SettingsWidgetsThemesCase,\n) : ViewModel() {\n\n  private val settingsState = MutableStateFlow<Settings?>(null)\n\n  private val widgetThemeState = MutableStateFlow(AppTheme.DARK)\n  private val widgetTransparencyState = MutableStateFlow(WidgetTransparency.SOLID)\n  private val premiumState = MutableStateFlow(false)\n\n  fun loadSettings() {\n    viewModelScope.launch {\n      refreshSettings()\n    }\n  }\n\n  fun enableWidgetsTitles(enable: Boolean, context: Context) {\n    viewModelScope.launch {\n      mainCase.enableWidgetsTitles(enable, context)\n      refreshSettings()\n    }\n    Analytics.logSettingsWidgetsTitlesEnabled(enable)\n  }\n\n  fun setWidgetsTheme(theme: AppTheme, context: Context) {\n    viewModelScope.launch {\n      themesCase.setWidgetsTheme(theme, context)\n      refreshSettings()\n    }\n    Analytics.logSettingsWidgetsTheme(theme.code)\n  }\n\n  fun setWidgetsTransparency(transparency: WidgetTransparency, context: Context) {\n    viewModelScope.launch {\n      themesCase.setWidgetsTransparency(transparency, context)\n      refreshSettings()\n    }\n  }\n\n  private suspend fun refreshSettings() {\n    settingsState.value = mainCase.getSettings()\n    widgetThemeState.value = themesCase.getWidgetsTheme()\n    widgetTransparencyState.value = themesCase.getWidgetsTransparency()\n    premiumState.value = mainCase.isPremium()\n  }\n\n  val uiState = combine(\n    settingsState,\n    premiumState,\n    widgetThemeState,\n    widgetTransparencyState\n  ) { s1, s2, s3, s4 ->\n    SettingsWidgetsUiState(\n      settings = s1,\n      isPremium = s2,\n      themeWidgets = s3,\n      widgetsTransparency = s4,\n\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = SettingsWidgetsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/widgets/cases/SettingsWidgetsMainCase.kt",
    "content": "package com.michaldrabik.ui_settings.sections.widgets.cases\n\nimport android.content.Context\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.WidgetsProvider\nimport com.michaldrabik.ui_model.Settings\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SettingsWidgetsMainCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun getSettings(): Settings = withContext(dispatchers.IO) {\n    settingsRepository.load()\n  }\n\n  fun isPremium() = settingsRepository.isPremium\n\n  suspend fun enableWidgetsTitles(enable: Boolean, context: Context) {\n    val settings = settingsRepository.load()\n    settings.let {\n      val new = it.copy(widgetsShowLabel = enable)\n      settingsRepository.update(new)\n    }\n    (context.applicationContext as WidgetsProvider).run {\n      requestShowsWidgetsUpdate()\n      requestMoviesWidgetsUpdate()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/java/com/michaldrabik/ui_settings/sections/widgets/cases/SettingsWidgetsThemesCase.kt",
    "content": "package com.michaldrabik.ui_settings.sections.widgets.cases\n\nimport android.content.Context\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.common.WidgetsProvider\nimport com.michaldrabik.ui_settings.helpers.AppTheme\nimport com.michaldrabik.ui_settings.helpers.WidgetTransparency\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SettingsWidgetsThemesCase @Inject constructor(\n  private val settingsRepository: SettingsRepository,\n) {\n\n  fun setWidgetsTheme(theme: AppTheme, context: Context) {\n    settingsRepository.widgets.widgetsTheme = theme.code\n    reloadWidgets(context)\n  }\n\n  fun setWidgetsTransparency(transparency: WidgetTransparency, context: Context) {\n    settingsRepository.widgets.widgetsTransparency = transparency.value\n    reloadWidgets(context)\n  }\n\n  fun getWidgetsTheme() = AppTheme.fromCode(settingsRepository.widgets.widgetsTheme)\n\n  fun getWidgetsTransparency() = WidgetTransparency.fromValue(settingsRepository.widgets.widgetsTransparency)\n\n  private fun reloadWidgets(context: Context) {\n    (context.applicationContext as WidgetsProvider).run {\n      requestShowsWidgetsUpdate()\n      requestMoviesWidgetsUpdate()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-settings/src/main/res/drawable/bg_premium_ad.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"6dp\" />\n  <stroke android:width=\"1dp\" android:color=\"?android:attr/textColorPrimary\"/>\n</shape>"
  },
  {
    "path": "ui-settings/src/main/res/layout/fragment_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView\n  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/settingsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipToPadding=\"false\"\n  android:fillViewport=\"true\"\n  android:overScrollMode=\"never\"\n  android:scrollbars=\"none\"\n  tools:background=\"@color/colorBackground\"\n  tools:layout_marginTop=\"-0dp\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsContent\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    >\n\n    <com.google.android.material.appbar.MaterialToolbar\n      android:id=\"@+id/settingsToolbar\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:background=\"?android:windowBackground\"\n      android:elevation=\"@dimen/elevationNormal\"\n      app:contentInsetStartWithNavigation=\"0dp\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:navigationIcon=\"@drawable/ic_arrow_back\"\n      app:title=\"@string/textSettings\"\n      />\n\n    <com.michaldrabik.ui_base.common.views.PremiumAdView\n      android:id=\"@+id/settingsPremium\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"100dp\"\n      android:layout_marginStart=\"@dimen/settingsPremiumAdMargin\"\n      android:layout_marginTop=\"@dimen/spaceTiny\"\n      android:layout_marginEnd=\"@dimen/settingsPremiumAdMargin\"\n      android:visibility=\"visible\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsToolbar\"\n      />\n\n    <androidx.fragment.app.FragmentContainerView\n      android:id=\"@+id/settingsCategoryTrakt\"\n      android:name=\"com.michaldrabik.ui_settings.sections.trakt.SettingsTraktFragment\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsCategoryGeneral\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsPremium\"\n      app:layout_goneMarginTop=\"0dp\"\n      tools:layout=\"@layout/fragment_settings_trakt\"\n      />\n\n    <androidx.fragment.app.FragmentContainerView\n      android:id=\"@+id/settingsCategoryGeneral\"\n      android:name=\"com.michaldrabik.ui_settings.sections.general.SettingsGeneralFragment\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      app:layout_constraintBottom_toTopOf=\"@+id/settingsCategoryNotifications\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsCategoryTrakt\"\n      tools:layout=\"@layout/fragment_settings_general\"\n      />\n\n    <androidx.fragment.app.FragmentContainerView\n      android:id=\"@+id/settingsCategorySpoilers\"\n      android:name=\"com.michaldrabik.ui_settings.sections.spoilers.SettingsSpoilersFragment\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsCategoryWidgets\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@+id/settingsCategoryNotifications\"\n      tools:layout=\"@layout/fragment_settings_spoilers\"\n      />\n\n    <androidx.fragment.app.FragmentContainerView\n      android:id=\"@+id/settingsCategoryNotifications\"\n      android:name=\"com.michaldrabik.ui_settings.sections.notifications.SettingsNotificationsFragment\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsCategorySpoilers\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsCategoryGeneral\"\n      tools:layout=\"@layout/fragment_settings_notifications\"\n      />\n\n    <androidx.fragment.app.FragmentContainerView\n      android:id=\"@+id/settingsCategoryWidgets\"\n      android:name=\"com.michaldrabik.ui_settings.sections.widgets.SettingsWidgetsFragment\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsCategoryMisc\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsCategorySpoilers\"\n      tools:layout=\"@layout/fragment_settings_widgets\"\n      />\n\n    <androidx.fragment.app.FragmentContainerView\n      android:id=\"@+id/settingsCategoryMisc\"\n      android:name=\"com.michaldrabik.ui_settings.sections.misc.SettingsMiscFragment\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsCategoryWidgets\"\n      tools:layout=\"@layout/fragment_settings_misc\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</ScrollView>\n"
  },
  {
    "path": "ui-settings/src/main/res/layout/fragment_settings_general.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/settingsContent\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <TextView\n    android:id=\"@+id/settingsCategoryGeneral\"\n    style=\"@style/Settings.Category\"\n    android:text=\"@string/textSettingsGeneral\"\n    app:layout_constraintBottom_toTopOf=\"@id/settingsLanguage\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_constraintVertical_bias=\"0\"\n    app:layout_constraintVertical_chainStyle=\"packed\"\n    app:layout_goneMarginTop=\"@dimen/spaceBig\"\n    />\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsLanguage\"\n    style=\"@style/Settings.ItemLayout\"\n    app:layout_constraintBottom_toTopOf=\"@+id/settingsCountry\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsCategoryGeneral\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsLanguageTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsMyShowsLanguageTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsLanguageDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsLanguageDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:text=\"@string/textSettingsMyShowsLanguageSummary\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsLanguageValue\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsLanguageTitle\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsLanguageValue\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:text=\"@string/textLanguageEnglish\"\n      android:textColor=\"?attr/colorAccent\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsLanguage\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsLanguage\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsCountry\"\n    style=\"@style/Settings.ItemLayout\"\n    app:layout_constraintBottom_toTopOf=\"@+id/settingsTheme\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsLanguage\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsCountryTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsMyShowsCountryTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsCountryDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsCountryDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:text=\"@string/textSettingsMyShowsCountrySummary\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsCountryValue\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsCountryTitle\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsCountryValue\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:textColor=\"?attr/colorAccent\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsCountry\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsCountry\"\n      tools:text=\"@tools:sample/lorem\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsTheme\"\n    style=\"@style/Settings.ItemLayout\"\n    android:tag=\"@string/tagTheme\"\n    app:layout_constraintBottom_toTopOf=\"@+id/settingsDateFormat\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsCountry\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsThemeTitle\"\n      style=\"@style/Settings.Title\"\n      android:drawablePadding=\"@dimen/spaceSmall\"\n      android:text=\"@string/textSettingsThemeTitle\"\n      app:drawableStartCompat=\"@drawable/ic_crown_small\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsThemeDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsThemeDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:text=\"@string/textSettingsThemeSummary\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsThemeValue\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsThemeTitle\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsThemeValue\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:textColor=\"?attr/colorAccent\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsTheme\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsTheme\"\n      tools:text=\"@tools:sample/lorem\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <LinearLayout\n    android:id=\"@+id/settingsDateFormat\"\n    style=\"@style/Settings.ItemLayout\"\n    app:layout_constraintBottom_toTopOf=\"@id/settingsTabletColumns\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsTheme\"\n    >\n\n    <TextView\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsDateFormatTitle\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsDateFormatValue\"\n      style=\"@style/Settings.Summary\"\n      tools:text=\"MMM dd, yyyy HH:mm (EEEE)\"\n      />\n\n  </LinearLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsTabletColumns\"\n    style=\"@style/Settings.ItemLayout\"\n    android:tag=\"@string/tagTheme\"\n    app:layout_constraintBottom_toTopOf=\"@+id/settingsMoviesEnabled\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsDateFormat\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsTabletColumnsTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsTabletColumnsTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsTabletColumnsDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsTabletColumnsDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:text=\"@string/textSettingsTabletColumnsSummary\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsTabletColumnsValue\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsTabletColumnsTitle\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsTabletColumnsValue\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:textColor=\"?attr/colorAccent\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsTabletColumns\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsTabletColumns\"\n      tools:text=\"@tools:sample/lorem\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsMoviesEnabled\"\n    style=\"@style/Settings.ItemLayout\"\n    app:layout_constraintBottom_toTopOf=\"@id/settingsNewsEnabled\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsTabletColumns\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsMoviesEnabledTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsMoviesEnabledTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsMoviesEnabledDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsMoviesEnabledDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:text=\"@string/textSettingsMoviesEnabledSummary\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsMoviesEnabledSwitch\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsMoviesEnabledTitle\"\n      />\n\n    <com.google.android.material.switchmaterial.SwitchMaterial\n      android:id=\"@+id/settingsMoviesEnabledSwitch\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"0dp\"\n      android:background=\"@null\"\n      android:checked=\"true\"\n      android:clickable=\"false\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsMoviesEnabled\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsMoviesEnabled\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsNewsEnabled\"\n    style=\"@style/Settings.ItemLayout\"\n    android:tag=\"@string/tagNews\"\n    app:layout_constraintBottom_toTopOf=\"@id/settingsStreamingsEnabled\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsMoviesEnabled\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsNewsEnabledTitle\"\n      style=\"@style/Settings.Title\"\n      android:drawablePadding=\"@dimen/spaceSmall\"\n      android:text=\"@string/textSettingsNewsEnabledTitle\"\n      app:drawableStartCompat=\"@drawable/ic_crown_small\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsNewsEnabledDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsNewsEnabledDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:text=\"@string/textSettingsNewsEnabledSummary\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsNewsEnabledSwitch\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsNewsEnabledTitle\"\n      />\n\n    <com.google.android.material.switchmaterial.SwitchMaterial\n      android:id=\"@+id/settingsNewsEnabledSwitch\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"0dp\"\n      android:background=\"@null\"\n      android:checked=\"true\"\n      android:clickable=\"false\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsNewsEnabled\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsNewsEnabled\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsStreamingsEnabled\"\n    style=\"@style/Settings.ItemLayout\"\n    app:layout_constraintBottom_toTopOf=\"@+id/settingsIncludeSpecials\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@+id/settingsNewsEnabled\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsStreamingsEnabledTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsStreamingServicesTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsStreamingsEnabledDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsStreamingsEnabledDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:text=\"@string/textSettingsStreamingServicesMessage\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsStreamingsEnabledSwitch\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsStreamingsEnabledTitle\"\n      />\n\n    <com.google.android.material.switchmaterial.SwitchMaterial\n      android:id=\"@+id/settingsStreamingsEnabledSwitch\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"0dp\"\n      android:background=\"@null\"\n      android:checked=\"true\"\n      android:clickable=\"false\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsStreamingsEnabled\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsStreamingsEnabled\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsIncludeSpecials\"\n    style=\"@style/Settings.ItemLayout\"\n    app:layout_constraintBottom_toTopOf=\"@id/settingsSeparator1\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsStreamingsEnabled\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsIncludeSpecialsTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsIncludeSpecialsTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsIncludeSpecialsDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsIncludeSpecialsDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:text=\"@string/textSettingsIncludeSpecialsSummary\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsIncludeSpecialsSwitch\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsIncludeSpecialsTitle\"\n      />\n\n    <com.google.android.material.switchmaterial.SwitchMaterial\n      android:id=\"@+id/settingsIncludeSpecialsSwitch\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"0dp\"\n      android:background=\"@null\"\n      android:checked=\"true\"\n      android:clickable=\"false\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsIncludeSpecials\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsIncludeSpecials\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <View\n    android:id=\"@+id/settingsSeparator1\"\n    style=\"@style/Settings.Separator\"\n    app:layout_constraintBottom_toTopOf=\"@id/settingsProgressNext\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsIncludeSpecials\"\n    />\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsProgressNext\"\n    style=\"@style/Settings.ItemLayout\"\n    app:layout_constrainedWidth=\"true\"\n    app:layout_constraintBottom_toTopOf=\"@+id/settingsUpcomingSection\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsSeparator1\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsProgressNextTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsProgressNextEpisodeTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsProgressNextDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsProgressNextDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_width=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:text=\"@string/textSettingsProgressNextEpisodeMessage\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsProgressNextValue\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsProgressNextTitle\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsProgressNextValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"end\"\n      android:maxLines=\"2\"\n      android:text=\"@string/textNextEpisodeLastWatched\"\n      android:textColor=\"?attr/colorAccent\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsUpcomingSection\"\n    style=\"@style/Settings.ItemLayout\"\n    app:layout_constraintBottom_toTopOf=\"@id/settingsRecentShowsAmount\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsProgressNext\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsUpcomingSectionTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsUpcomingSectionTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsUpcomingSectionDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsUpcomingSectionDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:text=\"@string/textSettingsUpcomingSectionSummary\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsUpcomingValue\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsUpcomingSectionTitle\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsUpcomingValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"end\"\n      android:maxLines=\"1\"\n      android:textColor=\"?attr/colorAccent\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:text=\"@tools:sample/lorem\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <LinearLayout\n    android:id=\"@+id/settingsRecentShowsAmount\"\n    style=\"@style/Settings.ItemLayout\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsUpcomingSection\"\n    >\n\n    <TextView\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsRecentShowsTitle\"\n      />\n\n    <TextView\n      style=\"@style/Settings.Summary\"\n      android:text=\"@string/textSettingsRecentShowsSummary\"\n      />\n\n  </LinearLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "ui-settings/src/main/res/layout/fragment_settings_misc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  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/settingsMiscContent\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:orientation=\"vertical\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <TextView\n    android:id=\"@+id/settingsCategoryMisc\"\n    style=\"@style/Settings.Category\"\n    android:text=\"@string/textSettingsMisc\"\n    />\n\n  <LinearLayout\n    android:id=\"@+id/settingsContactDevs\"\n    style=\"@style/Settings.ItemLayout\"\n    android:layout_width=\"match_parent\"\n    >\n\n    <TextView\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsContactDevsTitle\"\n      />\n\n    <TextView\n      style=\"@style/Settings.Summary\"\n      android:text=\"@string/textSettingsContactDevsSummary\"\n      />\n\n  </LinearLayout>\n\n  <LinearLayout\n    android:id=\"@+id/settingsDeleteCache\"\n    style=\"@style/Settings.ItemLayout\"\n    android:layout_width=\"match_parent\"\n    >\n\n    <TextView\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsDeleteCacheTitle\"\n      />\n\n    <TextView\n      style=\"@style/Settings.Summary\"\n      android:text=\"@string/textSettingsDeleteCacheSummary\"\n      />\n\n  </LinearLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    >\n\n    <ImageView\n      android:id=\"@+id/settingsTmdbIcon\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"30dp\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:adjustViewBounds=\"true\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsJustWatchDisclaimer\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsGithubIcon\"\n      app:layout_constraintStart_toEndOf=\"@id/settingsTraktIcon\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_tmdb\"\n      />\n\n    <ImageView\n      android:id=\"@+id/settingsTraktIcon\"\n      android:layout_width=\"60dp\"\n      android:layout_height=\"40dp\"\n      android:layout_marginTop=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:adjustViewBounds=\"true\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsJustWatchDisclaimer\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsTmdbIcon\"\n      app:layout_constraintHorizontal_chainStyle=\"packed\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_trakt\"\n      />\n\n    <ImageView\n      android:id=\"@+id/settingsGithubIcon\"\n      android:layout_width=\"60dp\"\n      android:layout_height=\"40dp\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:adjustViewBounds=\"true\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsJustWatchDisclaimer\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/settingsTmdbIcon\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_github\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsJustWatchDisclaimer\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginTop=\"@dimen/spaceNormal\"\n      android:layout_marginBottom=\"@dimen/spaceNormal\"\n      android:text=\"@string/textSettingsStreamingsDisclaimer\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsVersion\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsJustWatchIcon\"\n      app:layout_constraintHorizontal_chainStyle=\"packed\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsTraktIcon\"\n      />\n\n    <ImageView\n      android:id=\"@+id/settingsJustWatchIcon\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"10dp\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:adjustViewBounds=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsJustWatchDisclaimer\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/settingsJustWatchDisclaimer\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsJustWatchDisclaimer\"\n      app:srcCompat=\"@drawable/ic_justwatch\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsVersion\"\n      style=\"@style/Settings.Summary\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsUserId\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsJustWatchDisclaimer\"\n      tools:text=\"Version: 1.1.3\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsUserId\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginBottom=\"@dimen/spaceBig\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsVersion\"\n      tools:text=\"123456789\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</LinearLayout>"
  },
  {
    "path": "ui-settings/src/main/res/layout/fragment_settings_notifications.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/settingsContent\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <TextView\n    android:id=\"@+id/settingsCategoryNotifications\"\n    style=\"@style/Settings.Category\"\n    android:text=\"@string/textSettingsNotifications\"\n    app:layout_constraintBottom_toTopOf=\"@id/settingsShowsNotifications\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <LinearLayout\n      android:id=\"@+id/settingsShowsNotifications\"\n      style=\"@style/Settings.ItemLayout\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsWhenToNotify\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsCategoryNotifications\"\n      >\n\n    <TextView\n        style=\"@style/Settings.Title\"\n        android:text=\"@string/textSettingsPushNotificationsTitle\" />\n\n    <TextView\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceHuge\"\n      android:text=\"@string/textSettingsShowsNotificationsSummary\"\n      />\n\n  </LinearLayout>\n\n  <com.google.android.material.switchmaterial.SwitchMaterial\n    android:id=\"@+id/settingsShowsNotificationsSwitch\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"0dp\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:checked=\"true\"\n    android:clickable=\"false\"\n    app:layout_constraintBottom_toBottomOf=\"@id/settingsShowsNotifications\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/settingsShowsNotifications\"\n    />\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsWhenToNotify\"\n    style=\"@style/Settings.ItemLayout\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsShowsNotifications\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsWhenToNotifyTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsShowsNotificationsWhenTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsWhenToNotifyDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsWhenToNotifyDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:text=\"@string/textSettingsShowsNotificationsWhenSummary\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsWhenToNotifyValue\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintHorizontal_chainStyle=\"spread_inside\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsWhenToNotifyTitle\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsWhenToNotifyValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:text=\"@string/textSettingsShowsNotificationsWhenAvailable\"\n      android:textColor=\"?attr/colorAccent\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/settingsWhenToNotifyDescription\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "ui-settings/src/main/res/layout/fragment_settings_spoilers.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  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/settingsSpoilersContent\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:orientation=\"vertical\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <TextView\n    android:id=\"@+id/settingsCategorySpoilers\"\n    style=\"@style/Settings.Category\"\n    android:text=\"@string/textSettingsSpoilers\"\n    />\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsSpoilersShows\"\n    style=\"@style/Settings.ItemLayout\"\n    android:layout_width=\"match_parent\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsSpoilersShowsTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsSpoilersShows\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsSpoilersShowsSummary\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsSpoilersShowsSummary\"\n      style=\"@style/Settings.Summary\"\n      android:text=\"@string/textSettingsSpoilersShowsSummary\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsSpoilersShowsTitle\"\n      />\n\n    <ImageView\n      android:id=\"@+id/settingsSpoilersShowsCheck\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_check\"\n      app:tint=\"?attr/colorAccent\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsSpoilersMovies\"\n    style=\"@style/Settings.ItemLayout\"\n    android:layout_width=\"match_parent\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsSpoilersMoviesTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsSpoilersMovies\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsSpoilersMoviesSummary\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsSpoilersMoviesSummary\"\n      style=\"@style/Settings.Summary\"\n      android:text=\"@string/textSettingsSpoilersMoviesSummary\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsSpoilersMoviesTitle\"\n      />\n\n    <ImageView\n      android:id=\"@+id/settingsSpoilersMoviesCheck\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_check\"\n      app:tint=\"?attr/colorAccent\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsSpoilersEpisodes\"\n    style=\"@style/Settings.ItemLayout\"\n    android:layout_width=\"match_parent\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsSpoilersEpisodesTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsSpoilersEpisodes\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsSpoilersEpisodesSummary\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsSpoilersEpisodesSummary\"\n      style=\"@style/Settings.Summary\"\n      android:text=\"@string/textSettingsSpoilersEpisodesSummary\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsSpoilersEpisodesTitle\"\n      />\n\n    <ImageView\n      android:id=\"@+id/settingsSpoilersEpisodesCheck\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_check\"\n      app:tint=\"?attr/colorAccent\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsSpoilersTapToReveal\"\n    style=\"@style/Settings.ItemLayout\"\n    android:layout_width=\"match_parent\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsSpoilersTapToRevealTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsSpoilersTapToRevealTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsSpoilersTapToDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsSpoilersTapToDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:text=\"@string/textSettingsSpoilersTapToRevealDescription\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsSpoilersTapToRevealSwitch\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsSpoilersTapToRevealTitle\"\n      />\n\n    <com.google.android.material.switchmaterial.SwitchMaterial\n      android:id=\"@+id/settingsSpoilersTapToRevealSwitch\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"0dp\"\n      android:background=\"@null\"\n      android:checked=\"false\"\n      android:clickable=\"false\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</LinearLayout>"
  },
  {
    "path": "ui-settings/src/main/res/layout/fragment_settings_trakt.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/settingsContent\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <TextView\n    android:id=\"@+id/settingsCategoryTrakt\"\n    style=\"@style/Settings.Category\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:text=\"@string/textSettingsTrakt\"\n    app:layout_constraintBottom_toTopOf=\"@id/settingsTraktAuthorize\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_constraintVertical_bias=\"0\"\n    app:layout_constraintVertical_chainStyle=\"packed\"\n    app:layout_goneMarginTop=\"@dimen/spaceBig\"\n    />\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsTraktAuthorize\"\n    style=\"@style/Settings.ItemLayout\"\n    app:layout_constraintBottom_toTopOf=\"@id/settingsTraktSync\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsCategoryTrakt\"\n    >\n\n    <androidx.constraintlayout.widget.Barrier\n      android:id=\"@+id/barrier1\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      app:barrierDirection=\"start\"\n      app:constraint_referenced_ids=\"settingsTraktAuthorizeProgress, settingsTraktAuthorizeIcon, settingsTraktIcon\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsTraktAuthorizeTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsTraktAuthorizeTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsTraktAuthorizeSummary\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsTraktAuthorizeSummary\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/barrier1\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsTraktAuthorizeTitle\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/settingsTraktAuthorizeProgress\"\n      style=\"@style/ProgressBar.Accent\"\n      android:layout_width=\"24dp\"\n      android:layout_height=\"24dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsTraktAuthorize\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsTraktAuthorize\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/settingsTraktAuthorizeIcon\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"0dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsTraktAuthorize\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsTraktAuthorize\"\n      app:srcCompat=\"@drawable/ic_check\"\n      app:tint=\"?attr/colorAccent\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/settingsTraktIcon\"\n      android:layout_width=\"26dp\"\n      android:layout_height=\"26dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsTraktAuthorize\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsTraktAuthorize\"\n      app:srcCompat=\"@drawable/ic_trakt\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsTraktSync\"\n    style=\"@style/Settings.ItemLayout\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/settingsTraktQuickSync\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsTraktAuthorize\"\n    tools:visibility=\"visible\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsTraktSyncTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsTraktSyncTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsTraktSyncDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsTraktSyncDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:text=\"@string/textSettingsTraktSyncSummary\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsTraktSyncProgress\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsTraktSyncTitle\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/settingsTraktSyncProgress\"\n      style=\"@style/ProgressBar.Accent\"\n      android:layout_width=\"24dp\"\n      android:layout_height=\"24dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsTraktSync\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsTraktSync\"\n      tools:visibility=\"visible\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsTraktQuickSync\"\n    style=\"@style/Settings.ItemLayout\"\n    app:layout_constraintBottom_toTopOf=\"@id/settingsTraktQuickRemove\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsTraktSync\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsTraktQuickSyncTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsTraktQuickSyncTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsTraktQuickSyncDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsTraktQuickSyncDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:text=\"@string/textSettingsTraktQuickSyncSummary\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsTraktQuickSyncSwitch\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsTraktQuickSyncTitle\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <com.google.android.material.switchmaterial.SwitchMaterial\n      android:id=\"@+id/settingsTraktQuickSyncSwitch\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"0dp\"\n      android:background=\"@null\"\n      android:checked=\"false\"\n      android:clickable=\"false\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsTraktQuickSync\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsTraktQuickSync\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsTraktQuickRemove\"\n    style=\"@style/Settings.ItemLayout\"\n    app:layout_constraintBottom_toTopOf=\"@id/settingsTraktQuickRate\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsTraktQuickSync\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsTraktQuickRemoveTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsTraktQuickRemoveTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsTraktQuickRemoveDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsTraktQuickRemoveDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:text=\"@string/textSettingsTraktQuickRemoveSummary\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsTraktQuickRemoveSwitch\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsTraktQuickRemoveTitle\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <com.google.android.material.switchmaterial.SwitchMaterial\n      android:id=\"@+id/settingsTraktQuickRemoveSwitch\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"0dp\"\n      android:background=\"@null\"\n      android:checked=\"false\"\n      android:clickable=\"false\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsTraktQuickRemove\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsTraktQuickRemove\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsTraktQuickRate\"\n    style=\"@style/Settings.ItemLayoutNoRipple\"\n    android:tag=\"@string/tagQuickRating\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsTraktQuickRemove\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsTraktQuickRateTitle\"\n      style=\"@style/Settings.Title\"\n      android:drawablePadding=\"@dimen/spaceSmall\"\n      android:text=\"@string/textSettingsTraktQuickRateTitle\"\n      app:drawableStartCompat=\"@drawable/ic_crown_small\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsTraktQuickRateDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsTraktQuickRateDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:background=\"@null\"\n      android:text=\"@string/textSettingsTraktQuickRateSummary\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsTraktQuickRateSwitch\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsTraktQuickRateTitle\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <com.google.android.material.switchmaterial.SwitchMaterial\n      android:id=\"@+id/settingsTraktQuickRateSwitch\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"0dp\"\n      android:checked=\"false\"\n      android:clickable=\"false\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsTraktQuickRate\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsTraktQuickRate\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "ui-settings/src/main/res/layout/fragment_settings_widgets.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/settingsContent\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <TextView\n    android:id=\"@+id/settingsCategoryWidgets\"\n    style=\"@style/Settings.Category\"\n    android:text=\"@string/textSettingsWidgets\"\n    app:layout_constraintBottom_toTopOf=\"@id/settingsWidgetsTheme\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsWidgetsLabels\"\n    style=\"@style/Settings.ItemLayout\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@+id/settingsWidgetsTransparency\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsWidgetsLabelsTitle\"\n      style=\"@style/Settings.Title\"\n      android:text=\"@string/textSettingsWidgetsLabelsTitle\"\n      app:layout_constraintBottom_toTopOf=\"@id/settingsWidgetsLabelsDescription\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsWidgetsLabelsDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:text=\"@string/textSettingsWidgetsLabelsSummary\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsWidgetsLabelsSwitch\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsWidgetsLabelsTitle\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <com.google.android.material.switchmaterial.SwitchMaterial\n      android:id=\"@+id/settingsWidgetsLabelsSwitch\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"0dp\"\n      android:background=\"@null\"\n      android:checked=\"true\"\n      android:clickable=\"false\"\n      app:layout_constraintBottom_toBottomOf=\"@id/settingsWidgetsLabels\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/settingsWidgetsLabels\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n  <LinearLayout\n    android:id=\"@+id/settingsWidgetsTheme\"\n    style=\"@style/Settings.ItemLayout\"\n    android:tag=\"@string/tagTheme\"\n    app:layout_constraintBottom_toTopOf=\"@+id/settingsWidgetsTransparency\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"0.0\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsCategoryWidgets\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsWidgetsThemeTitle\"\n      style=\"@style/Settings.Title\"\n      android:drawablePadding=\"@dimen/spaceSmall\"\n      android:text=\"@string/textSettingsThemeWidgetsTitle\"\n      app:drawableStartCompat=\"@drawable/ic_crown_small\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      />\n\n    <TextView\n      style=\"@style/Settings.Summary\"\n      android:layout_width=\"wrap_content\"\n      android:text=\"@string/textSettingsThemeWidgetsSummary\"\n      />\n\n  </LinearLayout>\n\n  <TextView\n    android:id=\"@+id/settingsWidgetsThemeValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/settingsValueMargin\"\n    android:gravity=\"end\"\n    android:minWidth=\"50dp\"\n    android:text=\"@string/textThemeDark\"\n    android:textColor=\"?attr/colorAccent\"\n    app:layout_constraintBottom_toBottomOf=\"@id/settingsWidgetsTheme\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/settingsWidgetsTheme\"\n    />\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/settingsWidgetsTransparency\"\n    style=\"@style/Settings.ItemLayout\"\n    android:tag=\"@string/tagWidgetTransparency\"\n    app:layout_constraintBottom_toTopOf=\"@id/settingsWidgetsLabels\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/settingsWidgetsTheme\"\n    >\n\n    <TextView\n      android:id=\"@+id/settingsWidgetsTransparencyTitle\"\n      style=\"@style/Settings.Title\"\n      android:drawablePadding=\"@dimen/spaceSmall\"\n      android:text=\"@string/textSettingsWidgetsTransparencyTitle\"\n      app:drawableStartCompat=\"@drawable/ic_crown_small\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsWidgetsTransparencyDescription\"\n      style=\"@style/Settings.Summary\"\n      android:layout_width=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:text=\"@string/textSettingsWidgetsTransparencySummary\"\n      app:layout_constrainedWidth=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/settingsWidgetsTransparencyValue\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsWidgetsTransparencyTitle\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <TextView\n      android:id=\"@+id/settingsWidgetsTransparencyValue\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"end\"\n      android:minWidth=\"50dp\"\n      android:text=\"@string/textNo\"\n      android:textColor=\"?attr/colorAccent\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "ui-settings/src/main/res/layout/sheet_spoilers_episodes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:overScrollMode=\"never\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/bg_bottom_sheet\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:focusableInTouchMode=\"true\"\n    tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n    tools:theme=\"@style/AppTheme\"\n    >\n\n    <LinearLayout\n      android:id=\"@+id/settingsLayout\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"vertical\"\n      app:layout_constraintBottom_toTopOf=\"@id/closeButton\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      >\n\n      <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/episodesLayout\"\n        style=\"@style/Settings.ItemLayout\"\n        android:layout_width=\"match_parent\"\n        >\n\n        <TextView\n          android:id=\"@+id/episodesTitle\"\n          style=\"@style/Settings.Title\"\n          android:text=\"@string/textSettingsSpoilersEpisodes\"\n          android:textStyle=\"bold\"\n          app:layout_constraintBottom_toTopOf=\"@id/episodesHideTitle\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"parent\"\n          app:layout_constraintVertical_chainStyle=\"spread\"\n          />\n\n        <TextView\n          android:id=\"@+id/episodesHideTitle\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginTop=\"@dimen/spaceTiny\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersEpisodesTitle\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toTopOf=\"@id/episodesHideDescription\"\n          app:layout_constraintEnd_toStartOf=\"@id/episodesHideTitleSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/episodesTitle\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/episodesHideTitleSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/episodesHideTitle\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/episodesHideTitle\"\n          />\n\n        <TextView\n          android:id=\"@+id/episodesHideDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginTop=\"@dimen/spaceNormal\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersEpisodesDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toTopOf=\"@id/episodesHideRating\"\n          app:layout_constraintEnd_toStartOf=\"@id/episodesHideDescriptionSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/episodesHideTitle\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/episodesHideDescriptionSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/episodesHideDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/episodesHideDescription\"\n          />\n\n        <TextView\n          android:id=\"@+id/episodesHideRating\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginTop=\"@dimen/spaceNormal\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersEpisodesRating\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toTopOf=\"@id/episodesHideImages\"\n          app:layout_constraintEnd_toStartOf=\"@id/episodesHideRatingSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/episodesHideDescription\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/episodesHideRatingSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/episodesHideRating\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/episodesHideRating\"\n          />\n\n        <TextView\n          android:id=\"@+id/episodesHideImages\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginTop=\"@dimen/spaceNormal\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersEpisodesImage\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toBottomOf=\"parent\"\n          app:layout_constraintEnd_toStartOf=\"@id/episodesHideImagesSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/episodesHideRating\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/episodesHideImagesSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/episodesHideImages\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/episodesHideImages\"\n          />\n\n      </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </LinearLayout>\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/closeButton\"\n      style=\"@style/RoundMaterialButton\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_margin=\"@dimen/spaceNormal\"\n      android:backgroundTint=\"@color/selector_main_button\"\n      android:gravity=\"center\"\n      android:text=\"@string/textClose\"\n      android:textColor=\"?attr/textColorOnSurface\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsLayout\"\n      app:rippleColor=\"?android:attr/textColorPrimary\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</androidx.core.widget.NestedScrollView>\n"
  },
  {
    "path": "ui-settings/src/main/res/layout/sheet_spoilers_movies.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:overScrollMode=\"never\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/bg_bottom_sheet\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:focusableInTouchMode=\"true\"\n    tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n    tools:theme=\"@style/AppTheme\"\n    >\n\n    <LinearLayout\n      android:id=\"@+id/settingsLayout\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"vertical\"\n      app:layout_constraintBottom_toTopOf=\"@id/closeButton\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      >\n\n      <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/notCollectedMoviesLayout\"\n        style=\"@style/Settings.ItemLayout\"\n        android:layout_width=\"match_parent\"\n        >\n\n        <TextView\n          android:id=\"@+id/notCollectedMoviesTitle\"\n          style=\"@style/Settings.Title\"\n          android:text=\"@string/textSettingsSpoilersMoviesOverviewsNotCollectedTitle\"\n          android:textStyle=\"bold\"\n          app:layout_constraintBottom_toTopOf=\"@id/notCollectedMoviesDescription\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"parent\"\n          app:layout_constraintVertical_chainStyle=\"spread\"\n          />\n\n        <TextView\n          android:id=\"@+id/notCollectedMoviesDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersMoviesOverviewsNotCollectedDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toTopOf=\"@id/notCollectedMoviesRatingDescription\"\n          app:layout_constraintEnd_toStartOf=\"@id/notCollectedMoviesSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/notCollectedMoviesTitle\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/notCollectedMoviesSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/notCollectedMoviesDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/notCollectedMoviesDescription\"\n          />\n\n        <TextView\n          android:id=\"@+id/notCollectedMoviesRatingDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginTop=\"@dimen/spaceNormal\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersMoviesRatingsNotCollectedDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toBottomOf=\"parent\"\n          app:layout_constraintEnd_toStartOf=\"@id/notCollectedMoviesRatingsSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/notCollectedMoviesDescription\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/notCollectedMoviesRatingsSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/notCollectedMoviesRatingDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/notCollectedMoviesRatingDescription\"\n          />\n\n      </androidx.constraintlayout.widget.ConstraintLayout>\n\n      <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/myMoviesLayout\"\n        style=\"@style/Settings.ItemLayout\"\n        android:layout_width=\"match_parent\"\n        android:paddingTop=\"0dp\"\n        >\n\n        <TextView\n          android:id=\"@+id/myMoviesTitle\"\n          style=\"@style/Settings.Title\"\n          android:text=\"@string/textSettingsSpoilersMoviesOverviewsMyTitle\"\n          android:textStyle=\"bold\"\n          app:layout_constraintBottom_toTopOf=\"@id/myMoviesDescription\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"parent\"\n          app:layout_constraintVertical_chainStyle=\"spread\"\n          />\n\n        <TextView\n          android:id=\"@+id/myMoviesDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersMoviesOverviewsMyDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toTopOf=\"@id/myMoviesRatingDescription\"\n          app:layout_constraintEnd_toStartOf=\"@id/myMoviesSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/myMoviesTitle\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/myMoviesSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/myMoviesDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/myMoviesDescription\"\n          />\n\n        <TextView\n          android:id=\"@+id/myMoviesRatingDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginTop=\"@dimen/spaceNormal\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersMoviesRatingsMyDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toBottomOf=\"parent\"\n          app:layout_constraintEnd_toStartOf=\"@id/myMoviesRatingsSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/myMoviesDescription\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/myMoviesRatingsSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/myMoviesRatingDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/myMoviesRatingDescription\"\n          />\n\n      </androidx.constraintlayout.widget.ConstraintLayout>\n\n      <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/watchlistMoviesLayout\"\n        style=\"@style/Settings.ItemLayout\"\n        android:layout_width=\"match_parent\"\n        android:paddingTop=\"0dp\"\n        >\n\n        <TextView\n          android:id=\"@+id/watchlistMoviesTitle\"\n          style=\"@style/Settings.Title\"\n          android:text=\"@string/textSettingsSpoilersMoviesOverviewsWatchlistTitle\"\n          android:textStyle=\"bold\"\n          app:layout_constraintBottom_toTopOf=\"@id/watchlistMoviesDescription\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"parent\"\n          app:layout_constraintVertical_chainStyle=\"spread\"\n          />\n\n        <TextView\n          android:id=\"@+id/watchlistMoviesDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersMoviesOverviewsWatchlistDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toTopOf=\"@id/watchlistMoviesRatingDescription\"\n          app:layout_constraintEnd_toStartOf=\"@id/watchlistMoviesSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/watchlistMoviesTitle\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/watchlistMoviesSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/watchlistMoviesDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/watchlistMoviesDescription\"\n          />\n\n        <TextView\n          android:id=\"@+id/watchlistMoviesRatingDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginTop=\"@dimen/spaceNormal\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersMoviesRatingsWatchlistDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toBottomOf=\"parent\"\n          app:layout_constraintEnd_toStartOf=\"@id/watchlistMoviesRatingsSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/watchlistMoviesDescription\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/watchlistMoviesRatingsSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/watchlistMoviesRatingDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/watchlistMoviesRatingDescription\"\n          />\n\n      </androidx.constraintlayout.widget.ConstraintLayout>\n\n      <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/hiddenMoviesLayout\"\n        style=\"@style/Settings.ItemLayout\"\n        android:layout_width=\"match_parent\"\n        android:paddingTop=\"0dp\"\n        >\n\n        <TextView\n          android:id=\"@+id/hiddenMoviesTitle\"\n          style=\"@style/Settings.Title\"\n          android:text=\"@string/textSettingsSpoilersMoviesOverviewsHiddenTitle\"\n          android:textStyle=\"bold\"\n          app:layout_constraintBottom_toTopOf=\"@id/hiddenMoviesDescription\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"parent\"\n          app:layout_constraintVertical_chainStyle=\"spread\"\n          />\n\n        <TextView\n          android:id=\"@+id/hiddenMoviesDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersMoviesOverviewsHiddenDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toTopOf=\"@id/hiddenMoviesRatingDescription\"\n          app:layout_constraintEnd_toStartOf=\"@id/hiddenMoviesSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/hiddenMoviesTitle\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/hiddenMoviesSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/hiddenMoviesDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/hiddenMoviesDescription\"\n          />\n\n        <TextView\n          android:id=\"@+id/hiddenMoviesRatingDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginTop=\"@dimen/spaceNormal\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersMoviesRatingsHiddenDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toBottomOf=\"parent\"\n          app:layout_constraintEnd_toStartOf=\"@id/hiddenMoviesRatingsSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/hiddenMoviesDescription\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/hiddenMoviesRatingsSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/hiddenMoviesRatingDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/hiddenMoviesRatingDescription\"\n          />\n      </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </LinearLayout>\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/closeButton\"\n      style=\"@style/RoundMaterialButton\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_margin=\"@dimen/spaceNormal\"\n      android:backgroundTint=\"@color/selector_main_button\"\n      android:gravity=\"center\"\n      android:text=\"@string/textClose\"\n      android:textColor=\"?attr/textColorOnSurface\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsLayout\"\n      app:rippleColor=\"?android:attr/textColorPrimary\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n</androidx.core.widget.NestedScrollView>\n"
  },
  {
    "path": "ui-settings/src/main/res/layout/sheet_spoilers_shows.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:overScrollMode=\"never\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/bg_bottom_sheet\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:focusableInTouchMode=\"true\"\n    tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n    tools:theme=\"@style/AppTheme\"\n    >\n\n    <LinearLayout\n      android:id=\"@+id/settingsLayout\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"vertical\"\n      app:layout_constraintBottom_toTopOf=\"@id/closeButton\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      >\n\n      <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/notCollectedShowsLayout\"\n        style=\"@style/Settings.ItemLayout\"\n        android:layout_width=\"match_parent\"\n        >\n\n        <TextView\n          android:id=\"@+id/notCollectedShowsTitle\"\n          style=\"@style/Settings.Title\"\n          android:text=\"@string/textSettingsSpoilersShowsOverviewsNotCollectedTitle\"\n          android:textStyle=\"bold\"\n          app:layout_constraintBottom_toTopOf=\"@id/notCollectedShowsDescription\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"parent\"\n          app:layout_constraintVertical_chainStyle=\"spread\"\n          />\n\n        <TextView\n          android:id=\"@+id/notCollectedShowsDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersShowsOverviewsNotCollectedDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toTopOf=\"@id/notCollectedShowsRatingDescription\"\n          app:layout_constraintEnd_toStartOf=\"@id/notCollectedShowsSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/notCollectedShowsTitle\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/notCollectedShowsSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/notCollectedShowsDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/notCollectedShowsDescription\"\n          />\n\n        <TextView\n          android:id=\"@+id/notCollectedShowsRatingDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginTop=\"@dimen/spaceNormal\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersShowsRatingsNotCollectedDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toBottomOf=\"parent\"\n          app:layout_constraintEnd_toStartOf=\"@id/notCollectedShowsRatingsSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/notCollectedShowsDescription\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/notCollectedShowsRatingsSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/notCollectedShowsRatingDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/notCollectedShowsRatingDescription\"\n          />\n\n      </androidx.constraintlayout.widget.ConstraintLayout>\n\n      <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/myShowsLayout\"\n        style=\"@style/Settings.ItemLayout\"\n        android:layout_width=\"match_parent\"\n        android:paddingTop=\"0dp\"\n        >\n\n        <TextView\n          android:id=\"@+id/myShowsTitle\"\n          style=\"@style/Settings.Title\"\n          android:text=\"@string/textSettingsSpoilersShowsOverviewsMyTitle\"\n          android:textStyle=\"bold\"\n          app:layout_constraintBottom_toTopOf=\"@id/myShowsDescription\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"parent\"\n          app:layout_constraintVertical_chainStyle=\"spread\"\n          />\n\n        <TextView\n          android:id=\"@+id/myShowsDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersShowsOverviewsMyDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toTopOf=\"@id/myShowsRatingDescription\"\n          app:layout_constraintEnd_toStartOf=\"@id/myShowsSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/myShowsTitle\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/myShowsSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/myShowsDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/myShowsDescription\"\n          />\n\n        <TextView\n          android:id=\"@+id/myShowsRatingDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginTop=\"@dimen/spaceNormal\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersShowsRatingsMyDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toBottomOf=\"parent\"\n          app:layout_constraintEnd_toStartOf=\"@id/myShowsRatingsSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/myShowsDescription\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/myShowsRatingsSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/myShowsRatingDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/myShowsRatingDescription\"\n          />\n\n      </androidx.constraintlayout.widget.ConstraintLayout>\n\n      <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/watchlistShowsLayout\"\n        style=\"@style/Settings.ItemLayout\"\n        android:layout_width=\"match_parent\"\n        android:paddingTop=\"0dp\"\n        >\n\n        <TextView\n          android:id=\"@+id/watchlistShowsTitle\"\n          style=\"@style/Settings.Title\"\n          android:text=\"@string/textSettingsSpoilersShowsOverviewsWatchlistTitle\"\n          android:textStyle=\"bold\"\n          app:layout_constraintBottom_toTopOf=\"@id/watchlistShowsDescription\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"parent\"\n          app:layout_constraintVertical_chainStyle=\"spread\"\n          />\n\n        <TextView\n          android:id=\"@+id/watchlistShowsDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersShowsOverviewsWatchlistDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toTopOf=\"@id/watchlistShowsRatingDescription\"\n          app:layout_constraintEnd_toStartOf=\"@id/watchlistShowsSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/watchlistShowsTitle\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/watchlistShowsSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/watchlistShowsDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/watchlistShowsDescription\"\n          />\n\n        <TextView\n          android:id=\"@+id/watchlistShowsRatingDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginTop=\"@dimen/spaceNormal\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersShowsRatingsWatchlistDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toBottomOf=\"parent\"\n          app:layout_constraintEnd_toStartOf=\"@id/watchlistShowsRatingsSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/watchlistShowsDescription\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/watchlistShowsRatingsSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/watchlistShowsRatingDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/watchlistShowsRatingDescription\"\n          />\n\n      </androidx.constraintlayout.widget.ConstraintLayout>\n\n      <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/hiddenShowsLayout\"\n        style=\"@style/Settings.ItemLayout\"\n        android:layout_width=\"match_parent\"\n        android:paddingTop=\"0dp\"\n        >\n\n        <TextView\n          android:id=\"@+id/hiddenShowsTitle\"\n          style=\"@style/Settings.Title\"\n          android:text=\"@string/textSettingsSpoilersShowsOverviewsHiddenTitle\"\n          android:textStyle=\"bold\"\n          app:layout_constraintBottom_toTopOf=\"@id/hiddenShowsDescription\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"parent\"\n          app:layout_constraintVertical_chainStyle=\"spread\"\n          />\n\n        <TextView\n          android:id=\"@+id/hiddenShowsDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersShowsOverviewsHiddenDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toTopOf=\"@id/hiddenShowsRatingDescription\"\n          app:layout_constraintEnd_toStartOf=\"@id/hiddenShowsSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/hiddenShowsTitle\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/hiddenShowsSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/hiddenShowsDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/hiddenShowsDescription\"\n          />\n\n        <TextView\n          android:id=\"@+id/hiddenShowsRatingDescription\"\n          style=\"@style/Settings.Summary\"\n          android:layout_marginTop=\"@dimen/spaceNormal\"\n          android:layout_marginEnd=\"@dimen/spaceNormal\"\n          android:text=\"@string/textSettingsSpoilersShowsRatingsHiddenDescription\"\n          app:layout_constrainedWidth=\"true\"\n          app:layout_constraintBottom_toBottomOf=\"parent\"\n          app:layout_constraintEnd_toStartOf=\"@id/hiddenShowsRatingsSwitch\"\n          app:layout_constraintHorizontal_bias=\"0\"\n          app:layout_constraintStart_toStartOf=\"parent\"\n          app:layout_constraintTop_toBottomOf=\"@id/hiddenShowsDescription\"\n          />\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n          android:id=\"@+id/hiddenShowsRatingsSwitch\"\n          style=\"@style/ShowlyCheckbox\"\n          android:layout_width=\"30dp\"\n          android:layout_height=\"30dp\"\n          android:checked=\"false\"\n          app:layout_constraintBottom_toBottomOf=\"@id/hiddenShowsRatingDescription\"\n          app:layout_constraintEnd_toEndOf=\"parent\"\n          app:layout_constraintTop_toTopOf=\"@id/hiddenShowsRatingDescription\"\n          />\n      </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </LinearLayout>\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/closeButton\"\n      style=\"@style/RoundMaterialButton\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_margin=\"@dimen/spaceNormal\"\n      android:backgroundTint=\"@color/selector_main_button\"\n      android:gravity=\"center\"\n      android:text=\"@string/textClose\"\n      android:textColor=\"?attr/textColorOnSurface\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/settingsLayout\"\n      app:rippleColor=\"?android:attr/textColorPrimary\"\n      />\n\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n</androidx.core.widget.NestedScrollView>\n"
  },
  {
    "path": "ui-settings/src/main/res/layout/view_notifications_rationale.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@drawable/bg_dialog\"\n    android:elevation=\"@dimen/elevationNormal\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingTop=\"@dimen/spaceBig\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <ImageView\n      android:id=\"@+id/viewIcon\"\n      android:layout_width=\"100dp\"\n      android:layout_height=\"100dp\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_notification_bell\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceMedium\"\n      android:text=\"@string/textSettingsNotifications\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"24sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewIcon\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMessage\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"32dp\"\n      android:layout_marginBottom=\"@dimen/spaceNormal\"\n      android:maxLines=\"10\"\n      android:text=\"@string/textSettingsShowsNotificationsRationale\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewYesButton\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewTitle\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/viewYesButton\"\n      style=\"@style/RoundTextButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:text=\"@string/textYes\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/viewNoButton\"\n      style=\"@style/RoundTextButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:text=\"@string/textNo\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewYesButton\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-settings/src/main/res/layout/view_trakt_notifications_rationale.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@drawable/bg_dialog\"\n    android:elevation=\"@dimen/elevationNormal\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingTop=\"@dimen/spaceBig\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <ImageView\n      android:id=\"@+id/viewIcon\"\n      android:layout_width=\"100dp\"\n      android:layout_height=\"100dp\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_notification_bell\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceMedium\"\n      android:text=\"@string/textTraktSync\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"24sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewIcon\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMessage\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"32dp\"\n      android:layout_marginBottom=\"@dimen/spaceNormal\"\n      android:maxLines=\"10\"\n      android:text=\"@string/textTraktNotificationsRationale\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewYesButton\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewTitle\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/viewYesButton\"\n      style=\"@style/RoundTextButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:text=\"@string/textYes\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/viewNoButton\"\n      style=\"@style/RoundTextButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:text=\"@string/textNo\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewYesButton\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-settings/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"settingsPremiumAdMargin\">16dp</dimen>\n  <dimen name=\"settingsValueMargin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">You\\'ve been successfully authorized.</string>\n  <string name=\"textTraktLogoutSuccess\">You\\'ve been signed out.</string>\n  <string name=\"textImagesCacheCleared\">Images cache has been cleared.</string>\n  <string name=\"textTurnOff\">Turn Off</string>\n\n  <string name=\"textSettings\">Settings</string>\n  <string name=\"textSettingsTrakt\">Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Trakt.tv Authorization</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignIn\">Click to sign in into your Trakt.tv account.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOut\">Signed in. Click to sign out.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOutUser\">Signed in as %1$s. Click to sign out.</string>\n  <string name=\"textSettingsTraktSyncTitle\">Trakt.tv Sync</string>\n  <string name=\"textSettingsTraktSyncSummary\">Sync data between Trakt.tv and Showly.</string>\n  <string name=\"textSettingsTraktQuickSyncTitle\">Trakt.tv Instant Upload</string>\n  <string name=\"textSettingsTraktQuickSyncSummary\">Instantly upload data to Trakt.tv after you add it in the app.</string>\n  <string name=\"textSettingsTraktQuickRemoveTitle\">Trakt.tv Data Remove</string>\n  <string name=\"textSettingsTraktQuickRemoveSummary\">Offer to remove data from your Trakt.tv account after you remove it in the app.</string>\n  <string name=\"textSettingsTraktQuickRateTitle\">Trakt.tv Quick Rate</string>\n  <string name=\"textSettingsTraktQuickRateSummary\">Rate episodes and movies whenever you mark them as watched in \\'Progress\\' screen.</string>\n  <string name=\"textSettingsGeneral\">General &amp; UX</string>\n  <string name=\"textSettingsRecentShowsTitle\">Recently Added Amount</string>\n  <string name=\"textSettingsRecentShowsSummary\">Number of recent items to display in \\'Collection\\'.</string>\n  <string name=\"textSettingsUpcomingSectionTitle\">Upcoming Section</string>\n  <string name=\"textSettingsUpcomingSectionSummary\">Enable/disable \\'Upcoming\\' section in \\'Progress\\' tab.</string>\n  <string name=\"textSettingsMyShowsSectionsTitle\">My Shows Sections</string>\n  <string name=\"textSettingsMyShowsSectionSummary\">Enable/disable sections in \\'My Shows\\' tab.</string>\n  <string name=\"textSettingsMyMoviesSectionsTitle\">My Movies Sections</string>\n  <string name=\"textSettingsMyMoviesSectionSummary\">Enable/disable sections in \\'My Movies\\' tab.</string>\n  <string name=\"textSettingsThemeTitle\">Theme</string>\n  <string name=\"textSettingsThemeSummary\">Select main theme.</string>\n  <string name=\"textSettingsThemeWidgetsTitle\">Theme</string>\n  <string name=\"textSettingsThemeWidgetsSummary\">Select widgets theme.</string>\n  <string name=\"textSettingsWidgetsTransparencyTitle\">Background</string>\n  <string name=\"textSettingsWidgetsTransparencySummary\">Set level of widgets background transparency.</string>\n  <string name=\"textSettingsMyShowsCountryTitle\">Region</string>\n  <string name=\"textSettingsMyShowsCountrySummary\">Select region to use with streaming services data.</string>\n  <string name=\"textSettingsMyShowsLanguageTitle\">Language</string>\n  <string name=\"textSettingsMyShowsLanguageSummary\">Descriptions and titles will be translated when available. App will restart.</string>\n  <string name=\"textSettingsDateFormatTitle\">Date / Time Format</string>\n  <string name=\"textSettingsIncludeSpecialsTitle\">Special Seasons</string>\n  <string name=\"textSettingsIncludeSpecialsSummary\">Display \\'Special\\' seasons in show\\'s details screen.</string>\n  <string name=\"textSettingsNotifications\">Notifications</string>\n  <string name=\"textSettingsPushNotificationsTitle\">Push Notifications</string>\n  <string name=\"textSettingsShowsNotificationsSummary\">Receive notifications about new movies, episodes, seasons for shows you are following.</string>\n  <string name=\"textSettingsShowsNotificationsRationale\">Please grant the application a permission to display notifications.\\n\\nWould you like to do it now?</string>\n  <string name=\"textSettingsMisc\">Misc</string>\n  <string name=\"textSettingsContactDevsTitle\">Contact Developers</string>\n  <string name=\"textSettingsContactDevsSummary\">Found a problem? Feel free to open an issue on GitHub.</string>\n  <string name=\"textSettingsDeleteCacheTitle\">Clear Images Cache</string>\n  <string name=\"textSettingsDeleteCacheSummary\">Click to clear local images cache. This may help if shows images are not being loaded correctly.</string>\n  <string name=\"textSettingsQuickSyncConfirmationTitle\">Disable Automatic Sync</string>\n  <string name=\"textSettingsQuickSyncConfirmationMessage\">\\nIt seems like Automatic Sync is already turned on.\\n\\nWould you like to turn it off before enabling instant sync (advised)?\\n</string>\n  <string name=\"textSettingsLogoutTitle\">Trakt.tv Sign Out</string>\n  <string name=\"textSettingsLogoutMessage\">\\nAre you sure you want to sign out?\\n</string>\n  <string name=\"textSettingsMoviesEnabledTitle\">Movies</string>\n  <string name=\"textSettingsMoviesEnabledSummary\">Enable/disable movies support. App will restart.</string>\n  <string name=\"textSettingsNewsEnabledTitle\">News</string>\n  <string name=\"textSettingsNewsEnabledSummary\">Enable/disable news section. App will restart.</string>\n  <string name=\"textSettingsWidgets\">Widgets</string>\n  <string name=\"textSettingsWidgetsLabelsTitle\">Titles</string>\n  <string name=\"textSettingsWidgetsLabelsSummary\">Display translucent widgets titles.</string>\n  <string name=\"textSettingsStreamingsDisclaimer\">Streaming services data is provided by</string>\n  <string name=\"textSettingsStreamingServicesTitle\">Streaming Services</string>\n  <string name=\"textSettingsStreamingServicesMessage\">Display streaming services data.</string>\n  <string name=\"textSettingsProgressNextEpisodeTitle\">Next Episode</string>\n  <string name=\"textSettingsProgressNextEpisodeMessage\">Calculate next episode using:</string>\n  <string name=\"textSettingsTabletColumnsTitle\">Lists Columns Number</string>\n  <string name=\"textSettingsTabletColumnsSummary\">Select preferred columns number for lists.</string>\n\n  <string name=\"textSettingsSpoilers\">Spoilers</string>\n\n  <string name=\"textSettingsSpoilersShows\">Shows</string>\n  <string name=\"textSettingsSpoilersShowsSummary\">Manage spoiler settings for shows.</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyTitle\">My Shows</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyDescription\">Hide <b>description</b> of shows added to My Shows</string>\n  <string name=\"textSettingsSpoilersShowsRatingsMyDescription\">Hide <b>rating</b> of shows added to My Shows</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistTitle\">Watchlist</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistDescription\">Hide <b>description</b> of shows added to Watchlist</string>\n  <string name=\"textSettingsSpoilersShowsRatingsWatchlistDescription\">Hide <b>rating</b> of shows added to Watchlist</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenTitle\">Hidden</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenDescription\">Hide <b>description</b> of shows added to Hidden</string>\n  <string name=\"textSettingsSpoilersShowsRatingsHiddenDescription\">Hide <b>rating</b> of shows added to Hidden</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedTitle\">Not Collected</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedDescription\">Hide <b>description</b> of shows that are not in your Collection</string>\n  <string name=\"textSettingsSpoilersShowsRatingsNotCollectedDescription\">Hide <b>rating</b> of shows that are not in your Collection</string>\n\n  <string name=\"textSettingsSpoilersMovies\">Movies</string>\n  <string name=\"textSettingsSpoilersMoviesSummary\">Manage spoiler settings for movies.</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyTitle\">My Movies</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyDescription\">Hide <b>description</b> of movies added to My Movies</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsMyDescription\">Hide <b>rating</b> of movies added to My Movies</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistTitle\">Watchlist</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistDescription\">Hide <b>description</b> of movies added to Watchlist</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsWatchlistDescription\">Hide <b>rating</b> of movies added to Watchlist</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenTitle\">Hidden</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenDescription\">Hide <b>description</b> of movies added to Hidden</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsHiddenDescription\">Hide <b>rating</b> of movies added to Hidden</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedTitle\">Not Collected</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedDescription\">Hide <b>description</b> of movies that are not in your Collection</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsNotCollectedDescription\">Hide <b>rating</b> of movies that are not in your Collection</string>\n\n  <string name=\"textSettingsSpoilersEpisodes\">Episodes</string>\n  <string name=\"textSettingsSpoilersEpisodesSummary\">Manage spoiler settings for episodes.</string>\n  <string name=\"textSettingsSpoilersEpisodesImage\">Hide <b>image</b> of unwatched episodes.</string>\n  <string name=\"textSettingsSpoilersEpisodesTitle\">Hide <b>title</b> of unwatched episodes.</string>\n  <string name=\"textSettingsSpoilersEpisodesDescription\">Hide <b>description</b> of unwatched episodes.</string>\n  <string name=\"textSettingsSpoilersEpisodesRating\">Hide <b>rating</b> of unwatched episodes.</string>\n\n  <string name=\"textSettingsSpoilersTapToRevealTitle\">Tap To Reveal</string>\n  <string name=\"textSettingsSpoilersTapToRevealDescription\">Tap hidden content to temporarily reveal it.</string>\n\n  <string name=\"textNextEpisodeLastWatched\">Last watched episode</string>\n  <string name=\"textNextEpisodeOldest\">Oldest unwatched episode</string>\n\n  <string name=\"textLanguageEnglish\" translatable=\"false\">English</string>\n  <string name=\"textLanguagePolish\" translatable=\"false\">Polski</string>\n  <string name=\"textLanguageArabic\" translatable=\"false\">العربية</string>\n  <string name=\"textLanguageChinese\" translatable=\"false\">中文简体</string>\n  <string name=\"textLanguageSpanish\" translatable=\"false\">Español</string>\n  <string name=\"textLanguageItalian\" translatable=\"false\">Italiano</string>\n  <string name=\"textLanguageFrench\" translatable=\"false\">Français</string>\n  <string name=\"textLanguagePortugalBrasil\" translatable=\"false\">Português brasileiro</string>\n  <string name=\"textLanguageTurkish\" translatable=\"false\">Türkçe</string>\n  <string name=\"textLanguageUkrainian\" translatable=\"false\">Українська</string>\n  <string name=\"textLanguageGerman\" translatable=\"false\">Deutsch</string>\n  <string name=\"textLanguageRussian\" translatable=\"false\">Pусский</string>\n  <string name=\"textLanguageFinnish\" translatable=\"false\">Suomi</string>\n  <string name=\"textLanguageVietnamese\" translatable=\"false\">Vietnamese</string>\n\n  <string name=\"textThemeDark\">Dark</string>\n  <string name=\"textThemeLight\">Light</string>\n  <string name=\"textThemeSystem\">System</string>\n  <string name=\"textTransparency0\" translatable=\"false\">100%</string>\n  <string name=\"textTransparency25\" translatable=\"false\">25%</string>\n  <string name=\"textTransparency50\" translatable=\"false\">50%</string>\n  <string name=\"textTransparency75\" translatable=\"false\">75%</string>\n  <string name=\"textTransparency100\">Opaque</string>\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values/styles.xml",
    "content": "<resources>\n\n  <style name=\"Settings\" />\n\n  <style name=\"Settings.Category\" parent=\"Settings\">\n    <item name=\"android:layout_width\">wrap_content</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:paddingStart\">@dimen/spaceNormal</item>\n    <item name=\"android:paddingEnd\">@dimen/spaceNormal</item>\n    <item name=\"android:layout_marginTop\">@dimen/spaceBig</item>\n    <item name=\"android:textColor\">?attr/colorAccent</item>\n    <item name=\"android:textSize\">14sp</item>\n    <item name=\"android:textStyle\">bold</item>\n  </style>\n\n  <style name=\"Settings.Title\" parent=\"Settings\">\n    <item name=\"android:layout_width\">wrap_content</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:textColor\">?android:attr/textColorPrimary</item>\n    <item name=\"android:textSize\">16sp</item>\n  </style>\n\n  <style name=\"Settings.Summary\" parent=\"Settings\">\n    <item name=\"android:layout_width\">wrap_content</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:textColor\">?android:attr/textColorSecondary</item>\n    <item name=\"android:textSize\">12sp</item>\n    <item name=\"android:maxLines\">3</item>\n  </style>\n\n  <style name=\"Settings.ItemLayout\" parent=\"Settings\">\n    <item name=\"android:layout_width\">0dp</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:paddingStart\">@dimen/spaceNormal</item>\n    <item name=\"android:paddingEnd\">@dimen/spaceNormal</item>\n    <item name=\"android:paddingTop\">14dp</item>\n    <item name=\"android:paddingBottom\">14dp</item>\n    <item name=\"android:orientation\">vertical</item>\n    <item name=\"android:background\">?android:attr/selectableItemBackground</item>\n  </style>\n\n  <style name=\"Settings.ItemLayoutNoRipple\" parent=\"Settings.ItemLayout\">\n    <item name=\"android:background\">@null</item>\n  </style>\n\n  <style name=\"Settings.Separator\" parent=\"Settings\">\n    <item name=\"android:layout_width\">0dp</item>\n    <item name=\"android:layout_height\">1dp</item>\n    <item name=\"android:layout_marginStart\">@dimen/spaceNormal</item>\n    <item name=\"android:layout_marginEnd\">@dimen/spaceNormal</item>\n    <item name=\"android:layout_marginTop\">@dimen/spaceSmall</item>\n    <item name=\"android:layout_marginBottom\">6dp</item>\n    <item name=\"android:background\">?attr/colorSeparator</item>\n  </style>\n\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">تمت المصادقة بنجاح.</string>\n  <string name=\"textTraktLogoutSuccess\">تم تسجيل خروجك.</string>\n  <string name=\"textImagesCacheCleared\">تم مسح ذاكرة التخزين المؤقتة للصور.</string>\n  <string name=\"textTurnOff\">إيقاف التشغيل</string>\n  <string name=\"textSettings\">الإعدادات</string>\n  <string name=\"textSettingsTrakt\">Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">مصادقة Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignIn\">إضغط لتسجيل الدخول لحسابك في Trakt.tv.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOut\">تم تسجيل الدخول. انقر لتسجيل الخروج.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOutUser\">تم تسجيل الدخول ك %1$s. إضغط لتسجيل الخروج.</string>\n  <string name=\"textSettingsTraktSyncTitle\">مزامنة Trakt.tv</string>\n  <string name=\"textSettingsTraktSyncSummary\">مزامنة البيانات بين Trakt.tv و Showly.</string>\n  <string name=\"textSettingsTraktQuickSyncTitle\">الرفع الفوري ل Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickSyncSummary\">رفع فوري لبيانات المسلسلات والأفلام ل Trakt.tv بعد أن تقوم بإضافتهم في Showly.</string>\n  <string name=\"textSettingsTraktQuickRemoveTitle\">الحذف الفوري في Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickRemoveSummary\">حذف فوري لبيانات المسلسلات والأفلام في Trakt.tv بعد أن تحذفهم في Showly.</string>\n  <string name=\"textSettingsTraktQuickRateTitle\">التقييم السريع على Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickRateSummary\">قيّم الحلقات والأفلام كلما وضعت علامة على انك شاهدتهم في قسم \\'مستوى التقدم\\'.</string>\n  <string name=\"textSettingsGeneral\">عام و واجهة المستخدم</string>\n  <string name=\"textSettingsRecentShowsTitle\">عدد المحتويات الأخيرة</string>\n  <string name=\"textSettingsRecentShowsSummary\">عدد المسلسلات والأفلام الأخيرة التي تابعتها التي سيتم عرضها في صفحة \\'المجموعة\\'.</string>\n  <string name=\"textSettingsUpcomingSectionTitle\">المحتويات القادمة</string>\n  <string name=\"textSettingsUpcomingSectionSummary\">تفعيل/إلغاء تفعيل \\\"المحتويات القادمة\\\" في صفحة \\\"مستوى التقدم\\\".</string>\n  <string name=\"textSettingsMyShowsSectionsTitle\">أقسام صفحة مسلسلاتي</string>\n  <string name=\"textSettingsMyShowsSectionSummary\">تفعيل/إلغاء تفعيل أقسام في صفحة \\\"مسلسلاتي\\\".</string>\n  <string name=\"textSettingsMyMoviesSectionsTitle\">أقسام صفحة أفلامي</string>\n  <string name=\"textSettingsMyMoviesSectionSummary\">تفعيل/إلغاء تفعيل أقسام في صفحة \\\"أفلامي\\\".</string>\n  <string name=\"textSettingsThemeTitle\">المظهر</string>\n  <string name=\"textSettingsThemeSummary\">إختر المظهر الرئيسي.</string>\n  <string name=\"textSettingsThemeWidgetsTitle\">المظهر</string>\n  <string name=\"textSettingsThemeWidgetsSummary\">إختر مظهر التطبيقات المصغّرة.</string>\n  <string name=\"textSettingsWidgetsTransparencyTitle\">الخلفية</string>\n  <string name=\"textSettingsWidgetsTransparencySummary\">تعيين مستوى شفافية الخلفية للتطبيقات المصغّرة.</string>\n  <string name=\"textSettingsMyShowsCountryTitle\">المنطقة</string>\n  <string name=\"textSettingsMyShowsCountrySummary\">حدد المنطقة لاستخدامها مع بيانات خدمات البث.</string>\n  <string name=\"textSettingsMyShowsLanguageTitle\">اللغة</string>\n  <string name=\"textSettingsMyShowsLanguageSummary\">سيتم إعادة تشغيل التطبيق، وسيتم عرض عنوان وقصة اي فيلم او مسلسل او حلقة بالعربية إذا توفرت.</string>\n  <string name=\"textSettingsDateFormatTitle\">تنسيق التاريخ والوقت</string>\n  <string name=\"textSettingsIncludeSpecialsTitle\">الحلقات الخاصة</string>\n  <string name=\"textSettingsIncludeSpecialsSummary\">عرض \\'الحلقات الخاصة\\' في صفحة تفاصيل المسلسل</string>\n  <string name=\"textSettingsNotifications\">الإشعارات</string>\n  <string name=\"textSettingsPushNotificationsTitle\">ضبط الإشعارات</string>\n  <string name=\"textSettingsShowsNotificationsSummary\">تلقي إشعارات للأفلام والحلقات والمواسم الجديدة للمحتويات التي تتابعها.</string>\n  <string name=\"textSettingsShowsNotificationsRationale\">الرجاء منح التطبيق الإذن لعرض الإشعارات. \\n\\nهل ترغب في القيام بذلك الآن؟</string>\n  <string name=\"textSettingsMisc\">إعدادات متنوعة</string>\n  <string name=\"textSettingsContactDevsTitle\">التواصل مع المطورين</string>\n  <string name=\"textSettingsContactDevsSummary\">واجهتك مشكلة؟انقر هنا لفتح مشكلة على جيثب.</string>\n  <string name=\"textSettingsDeleteCacheTitle\">مسح بيانات الصور في الذاكرة المؤقتة</string>\n  <string name=\"textSettingsDeleteCacheSummary\">إضغط لتنفيذ الإجراء، إذا واجهتك مشكلة في تحميل الصور، جرب الضغط هنا.</string>\n  <string name=\"textSettingsQuickSyncConfirmationTitle\">تعطيل المزامنة التلقائية</string>\n  <string name=\"textSettingsQuickSyncConfirmationMessage\">\\nيبدو أنك مفعل المزامنة التلقائية مسبقاً.\\n\\nأترغب في إطفاء المزامنة التلقائية لتفعيل المزامنة الفورية؟ (يُنصح به)\\n</string>\n  <string name=\"textSettingsLogoutTitle\">تسجيل الخروج من Trakt.tv</string>\n  <string name=\"textSettingsLogoutMessage\">\\nأترغب فِعلاً في تسجيل الخروج؟\\n</string>\n  <string name=\"textSettingsMoviesEnabledTitle\">أفلام</string>\n  <string name=\"textSettingsMoviesEnabledSummary\">تفعيل/إلغاء تفعيل عرض الأفلام في التطبيق، سيتم إعادة تشغيله.</string>\n  <string name=\"textSettingsNewsEnabledTitle\">الأخبار</string>\n  <string name=\"textSettingsNewsEnabledSummary\">تفعيل/إلغاء تفعيل قِسم الأخبار في التطبيق، سيتم إعادة تشغيله.</string>\n  <string name=\"textSettingsWidgets\">التطبيقات المصغّرة</string>\n  <string name=\"textSettingsWidgetsLabelsTitle\">العناوين</string>\n  <string name=\"textSettingsWidgetsLabelsSummary\">إظهار العناوين في التطبيقات المصغّرة.</string>\n  <string name=\"textSettingsStreamingsDisclaimer\">يتم توفير بيانات خدمات البث بواسطة</string>\n  <string name=\"textSettingsStreamingServicesTitle\">خدمات البث</string>\n  <string name=\"textSettingsStreamingServicesMessage\">إظهار بيانات خدمات البث.</string>\n  <string name=\"textSettingsProgressNextEpisodeTitle\">الحلقة التالية</string>\n  <string name=\"textSettingsProgressNextEpisodeMessage\">حساب الحلقة التالية باِستخدام:</string>\n  <string name=\"textSettingsTabletColumnsTitle\">عدد أعمدة القوائم</string>\n  <string name=\"textSettingsTabletColumnsSummary\">حدد عدد الأعمدة المفضلة للقوائم.</string>\n  <string name=\"textSettingsSpoilers\">الحرق</string>\n  <string name=\"textSettingsSpoilersShows\">مسلسلات</string>\n  <string name=\"textSettingsSpoilersShowsSummary\">إدارة إعدادات الحرق للمسلسلات.</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyTitle\">مسلسلاتي</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyDescription\">إخفاء <b>وصف</b> المسلسلات المضافة إلى مسلسلاتي</string>\n  <string name=\"textSettingsSpoilersShowsRatingsMyDescription\">إخفاء <b>تقييم</b> المسلسلات المضافة إلى مسلسلاتي</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistTitle\">قائمة المشاهدة</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistDescription\">إخفاء <b>وصف</b> المسلسلات المضافة إلى قائمة المشاهدة</string>\n  <string name=\"textSettingsSpoilersShowsRatingsWatchlistDescription\">إخفاء <b>تقييم</b> المسلسلات المضافة إلى قائمة المشاهدة</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenTitle\">المخفية</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenDescription\">إخفاء <b>وصف</b> المسلسلات المضافة إلى المخفية</string>\n  <string name=\"textSettingsSpoilersShowsRatingsHiddenDescription\">إخفاء <b>تقييم</b> المسلسلات المضافة إلى المخفية</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedTitle\">ليست في المجموعة</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedDescription\">إخفاء <b>وصف</b> المسلسلات التي ليست في مجموعتك</string>\n  <string name=\"textSettingsSpoilersShowsRatingsNotCollectedDescription\">إخفاء <b>تقييم</b> المسلسلات التي ليست في مجموعتك</string>\n  <string name=\"textSettingsSpoilersMovies\">أفلام</string>\n  <string name=\"textSettingsSpoilersMoviesSummary\">إدارة إعدادات الحرق للأفلام.</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyTitle\">أفلامي</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyDescription\">إخفاء <b>وصف</b> الأفلام المضافة إلى أفلامي</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsMyDescription\">إخفاء <b>تقييم</b> الأفلام المضافة إلى أفلامي</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistTitle\">قائمة المشاهدة</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistDescription\">إخفاء <b>وصف</b> الأفلام المضافة إلى قائمة المشاهدة</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsWatchlistDescription\">إخفاء <b>تقييم</b> الأفلام المضافة إلى قائمة المشاهدة</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenTitle\">المخفية</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenDescription\">إخفاء <b>وصف</b> الأفلام المضافة إلى المخفية</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsHiddenDescription\">إخفاء <b>تقييم</b> الأفلام المضافة إلى المخفية</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedTitle\">ليست في المجموعة</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedDescription\">إخفاء <b>وصف</b> الأفلام التي ليست في مجموعتك</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsNotCollectedDescription\">إخفاء <b>تقييم</b> الأفلام التي ليست في مجموعتك</string>\n  <string name=\"textSettingsSpoilersEpisodes\">حلقات</string>\n  <string name=\"textSettingsSpoilersEpisodesSummary\">إدارة إعدادات الحرق للحلقات.</string>\n  <string name=\"textSettingsSpoilersEpisodesImage\">إخفاء <b>صورة</b> الحلَقات التي لم تتم مشاهدتها.</string>\n  <string name=\"textSettingsSpoilersEpisodesTitle\">إخفاء <b>عنوان</b> الحلَقات التي لم تتم مشاهدتها.</string>\n  <string name=\"textSettingsSpoilersEpisodesDescription\">إخفاء <b>وصف</b> الحلَقات التي لم تتم مشاهدتها.</string>\n  <string name=\"textSettingsSpoilersEpisodesRating\">إخفاء <b>تقييم</b> الحلَقات التي لم تتم مشاهدتها.</string>\n  <string name=\"textSettingsSpoilersTapToRevealTitle\">انقر للكَشف</string>\n  <string name=\"textSettingsSpoilersTapToRevealDescription\">انقر على المحتوى المخفي للكشف عنه مؤقتًا.</string>\n  <string name=\"textNextEpisodeLastWatched\">آخر حلقة شاهدتها</string>\n  <string name=\"textNextEpisodeOldest\">أقدم حلقة لم تشاهدها</string>\n  <string name=\"textThemeDark\">داكن</string>\n  <string name=\"textThemeLight\">فاتح</string>\n  <string name=\"textThemeSystem\">إستخدم مظهر النظام</string>\n  <string name=\"textTransparency100\">غير شفاف</string>\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Du wurdest erfolgreich autorisiert.</string>\n  <string name=\"textTraktLogoutSuccess\">Du hast dich erfolgreich ausgeloggt.</string>\n  <string name=\"textImagesCacheCleared\">Bildcache wurde gelöscht.</string>\n  <string name=\"textTurnOff\">Ausschalten</string>\n  <string name=\"textSettings\">Einstellungen</string>\n  <string name=\"textSettingsTrakt\">Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Trakt.tv Autorisierung</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignIn\">Tippe hier um dich mit deinem Trakt.tv Account einzuloggen.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOut\">Eingeloggt. Tippe hier zum ausloggen.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOutUser\">Eingeloggt als %1$s. Tippe hier zum ausloggen.</string>\n  <string name=\"textSettingsTraktSyncTitle\">Trakt.tv Sync</string>\n  <string name=\"textSettingsTraktSyncSummary\">Synchronisiere deine Daten zwischen Trakt.tv und Showly.</string>\n  <string name=\"textSettingsTraktQuickSyncTitle\">Sofortupload zu Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickSyncSummary\">Lade deine Daten sofort auf Trakt.tv hoch, nachdem du sie in der App hinzugefügt hast.</string>\n  <string name=\"textSettingsTraktQuickRemoveTitle\">Trakt.tv Daten Entfernen</string>\n  <string name=\"textSettingsTraktQuickRemoveSummary\">Option um deine Daten von Trakt.tv zu entfernen, nachdem du diese innerhalb der App gelöscht hast.</string>\n  <string name=\"textSettingsTraktQuickRateTitle\">Trakt.tv Schnellrate</string>\n  <string name=\"textSettingsTraktQuickRateSummary\">Bewerte Episoden und Filme, wenn du sie auf dem Bildschirm \\\"Fortschritt\\\" als gesehen markierst.</string>\n  <string name=\"textSettingsGeneral\">Allgemein &amp; UX</string>\n  <string name=\"textSettingsRecentShowsTitle\">Kürzlich hinzugefügte Menge</string>\n  <string name=\"textSettingsRecentShowsSummary\">Anzahl an Einträgen, die unter \\'Sammlung\\' zu sehen sind.</string>\n  <string name=\"textSettingsUpcomingSectionTitle\">\\'Demnächst\\' Abschnitt</string>\n  <string name=\"textSettingsUpcomingSectionSummary\">Aktiviere/deaktiviere den Abschnitt \\'Demnächst\\' im \\'Fortschritte\\'.</string>\n  <string name=\"textSettingsMyShowsSectionsTitle\">Meine Serien Abschnitt</string>\n  <string name=\"textSettingsMyShowsSectionSummary\">Aktiviere/Deaktiviere die Abschnitte im \\'Meine Serien\\' Tab.</string>\n  <string name=\"textSettingsMyMoviesSectionsTitle\">Meine Filme Abschnitt</string>\n  <string name=\"textSettingsMyMoviesSectionSummary\">Aktiviere/Deaktiviere die Abschnitte im \\'Meine Filme\\' Tab.</string>\n  <string name=\"textSettingsThemeTitle\">Thema</string>\n  <string name=\"textSettingsThemeSummary\">Thema auswählen.</string>\n  <string name=\"textSettingsThemeWidgetsTitle\">Thema</string>\n  <string name=\"textSettingsThemeWidgetsSummary\">Widget-Thema auswählen.</string>\n  <string name=\"textSettingsWidgetsTransparencyTitle\">Hintergrund</string>\n  <string name=\"textSettingsWidgetsTransparencySummary\">Hintergrundtransparenz der Widgets.</string>\n  <string name=\"textSettingsMyShowsCountryTitle\">Region</string>\n  <string name=\"textSettingsMyShowsCountrySummary\">Wähle deine Region aus, die für Streaming Service Informationen verwendet werden soll.</string>\n  <string name=\"textSettingsMyShowsLanguageTitle\">Sprache</string>\n  <string name=\"textSettingsMyShowsLanguageSummary\">Beschreibungen &amp; Titel werden übersetzt, sofern eine Übersetzung vorhanden ist.</string>\n  <string name=\"textSettingsDateFormatTitle\">Datum/Uhrzeit-Format</string>\n  <string name=\"textSettingsIncludeSpecialsTitle\">Spezial Staffel</string>\n  <string name=\"textSettingsIncludeSpecialsSummary\">Zeige \\'Spezial\\' Staffeln in den Seriendetails.</string>\n  <string name=\"textSettingsNotifications\">Benachrichtigungen </string>\n  <string name=\"textSettingsPushNotificationsTitle\">Pushbenachrichtigungen</string>\n  <string name=\"textSettingsShowsNotificationsSummary\">Erhalte Benachrichtigungen über neue Filme, Folgen &amp; Staffeln von Serien die du gespeichert hast.</string>\n  <string name=\"textSettingsMisc\">Gemischt</string>\n  <string name=\"textSettingsContactDevsTitle\">Entwickler kontaktieren</string>\n  <string name=\"textSettingsContactDevsSummary\">Du hast einen Fehler entdeckt oder ein Problem? Öffne gerne ein Issue auf GitHub.</string>\n  <string name=\"textSettingsDeleteCacheTitle\">Lösche Bildcache</string>\n  <string name=\"textSettingsDeleteCacheSummary\">Klicke hier, um den lokalen Bildcache zu löschen. Dies kann hilfreich sein, wenn Bilder nicht korrekt geladen werden.</string>\n  <string name=\"textSettingsQuickSyncConfirmationTitle\">Deaktiviere die automatische Synchronisation</string>\n  <string name=\"textSettingsQuickSyncConfirmationMessage\">\\nEs scheint, als sei die automatische Synchronisation bereits aktiviert.\\n\\nMöchtest du diese Einstellung deaktivieren bevor du Instant Synchronisation aktivierst (Empfohlen!)?\\n</string>\n  <string name=\"textSettingsLogoutTitle\">Bei Trakt.tv Ausloggen</string>\n  <string name=\"textSettingsLogoutMessage\">\\nBist du sicher, dass du dich ausloggen möchtest?\\n</string>\n  <string name=\"textSettingsMoviesEnabledTitle\">Filme</string>\n  <string name=\"textSettingsMoviesEnabledSummary\">Aktiviere/Deaktiviere die Unterstützung fur Filme. Die App wird neu gestartet.</string>\n  <string name=\"textSettingsNewsEnabledTitle\">News</string>\n  <string name=\"textSettingsNewsEnabledSummary\">Aktiviere/deaktiviere News. App wird neu starten.</string>\n  <string name=\"textSettingsWidgets\">Widgets</string>\n  <string name=\"textSettingsWidgetsLabelsTitle\">Titel</string>\n  <string name=\"textSettingsWidgetsLabelsSummary\">Zeigen Sie transparente Widgets-Titel an.</string>\n  <string name=\"textSettingsStreamingsDisclaimer\">Streaming-Daten werden bereitgestellt von</string>\n  <string name=\"textSettingsStreamingServicesTitle\">Streaming Services</string>\n  <string name=\"textSettingsStreamingServicesMessage\">Streaming-Daten anzeigen.</string>\n  <string name=\"textSettingsProgressNextEpisodeTitle\">Nächste Folge</string>\n  <string name=\"textSettingsProgressNextEpisodeMessage\">Nächste Folge berechnen mit:</string>\n  <string name=\"textNextEpisodeLastWatched\">Letzte Folge angesehen</string>\n  <string name=\"textNextEpisodeOldest\">Älteste ungesehene Folge</string>\n  <string name=\"textThemeDark\">Dunkel</string>\n  <string name=\"textThemeLight\">Hell</string>\n  <string name=\"textThemeSystem\">System</string>\n  <string name=\"textTransparency100\">Undurchsichtig</string>\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Has sido autorizado/a con éxito.</string>\n  <string name=\"textTraktLogoutSuccess\">Se ha cerrado la sesión.</string>\n  <string name=\"textImagesCacheCleared\">Se ha borrado la caché de imágenes.</string>\n  <string name=\"textTurnOff\">Apagar</string>\n  <string name=\"textSettings\">Ajustes</string>\n  <string name=\"textSettingsTrakt\">Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Autorización de Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignIn\">Haz clic para iniciar sesión en tu cuenta de Trakt.tv.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOut\">Has iniciado sesión. Haz clic para cerrarla.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOutUser\">Registrado/a como %1$s. Haz clic para cerrar sesión.</string>\n  <string name=\"textSettingsTraktSyncTitle\">Sincronizar Trakt.tv</string>\n  <string name=\"textSettingsTraktSyncSummary\">Sincronizar datos entre Trakt.tv y Showly.</string>\n  <string name=\"textSettingsTraktQuickSyncTitle\">Subida Instantánea Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickSyncSummary\">Sube datos instantáneamente a Trakt.tv después de añadirlos en la aplicación.</string>\n  <string name=\"textSettingsTraktQuickRemoveTitle\">Eliminar Datos de Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickRemoveSummary\">Opción para eliminar datos de tu cuenta Trakt.tv después de eliminarlos en la aplicación.</string>\n  <string name=\"textSettingsTraktQuickRateTitle\">Valoración Rápida de Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickRateSummary\">Califica episodios y películas cada vez que los marques como vistos en la pantalla de \\'Progreso\\'.</string>\n  <string name=\"textSettingsGeneral\">General &amp; UX</string>\n  <string name=\"textSettingsRecentShowsTitle\">Cantidad Añadidos Recientemente </string>\n  <string name=\"textSettingsRecentShowsSummary\">Número de elementos recientes que se mostrarán en \\'Colección\\'.</string>\n  <string name=\"textSettingsUpcomingSectionTitle\">Sección Próximamente</string>\n  <string name=\"textSettingsUpcomingSectionSummary\">Habilitar/deshabilitar la sección \\'Próximamente\\' en la pestaña \\'Progreso\\'.</string>\n  <string name=\"textSettingsMyShowsSectionsTitle\">Secciones de Mis Series</string>\n  <string name=\"textSettingsMyShowsSectionSummary\">Habilitar/deshabilitar secciones en la pestaña \\'Mis Series\\'.</string>\n  <string name=\"textSettingsMyMoviesSectionsTitle\">Secciones de Mis Películas</string>\n  <string name=\"textSettingsMyMoviesSectionSummary\">Habilitar/deshabilitar secciones en la pestaña \\'Mis Películas\\'.</string>\n  <string name=\"textSettingsThemeTitle\">Tema</string>\n  <string name=\"textSettingsThemeSummary\">Seleccionar tema principal.</string>\n  <string name=\"textSettingsThemeWidgetsTitle\">Tema</string>\n  <string name=\"textSettingsThemeWidgetsSummary\">Seleccionar tema de widgets.</string>\n  <string name=\"textSettingsWidgetsTransparencyTitle\">Fondo</string>\n  <string name=\"textSettingsWidgetsTransparencySummary\">Establecer nivel de transparencia del fondo de los widgets.</string>\n  <string name=\"textSettingsMyShowsCountryTitle\">Región</string>\n  <string name=\"textSettingsMyShowsCountrySummary\">Selecciona la región para usar con los datos de servicios de streaming.</string>\n  <string name=\"textSettingsMyShowsLanguageTitle\">Idioma</string>\n  <string name=\"textSettingsMyShowsLanguageSummary\">Las descripciones y los títulos se traducirán cuando estén disponibles. La aplicación se reiniciará.</string>\n  <string name=\"textSettingsDateFormatTitle\">Formato de fecha/hora</string>\n  <string name=\"textSettingsIncludeSpecialsTitle\">Temporadas Especiales</string>\n  <string name=\"textSettingsIncludeSpecialsSummary\">Muestra temporadas \\'Especiales\\' en la pantalla de detalles de la serie.</string>\n  <string name=\"textSettingsNotifications\">Notificaciones</string>\n  <string name=\"textSettingsPushNotificationsTitle\">Notificaciones Push</string>\n  <string name=\"textSettingsShowsNotificationsSummary\">Recibir notificaciones sobre nuevas películas, episodios y temporadas de las series que estás siguiendo.</string>\n  <string name=\"textSettingsShowsNotificationsRationale\">Por favor, concede a la aplicación permiso para mostrar notificaciones.\\n\\n¿Te gustaría hacerlo ahora?</string>\n  <string name=\"textSettingsMisc\">Misceláneo</string>\n  <string name=\"textSettingsContactDevsTitle\">Contactar Desarrolladores</string>\n  <string name=\"textSettingsContactDevsSummary\">¿Tienes algún problema? Haga clic aquí para abrir un problema en GitHub.</string>\n  <string name=\"textSettingsDeleteCacheTitle\">Borrar caché de imágenes</string>\n  <string name=\"textSettingsDeleteCacheSummary\">Haz clic para borrar la caché de imágenes locales. Esto puede ayudar si las imágenes de las series no se cargan correctamente.</string>\n  <string name=\"textSettingsQuickSyncConfirmationTitle\">Deshabilitar la Sincronización Automática</string>\n  <string name=\"textSettingsQuickSyncConfirmationMessage\">\\nParece que la sincronización automática ya está activada.\\n\\n¿Te gustaría apagarla antes de habilitar la sincronización instantánea (recomendado)?\\n</string>\n  <string name=\"textSettingsLogoutTitle\">Cerrar Sesión Trakt.tv</string>\n  <string name=\"textSettingsLogoutMessage\">\\n¿Estás seguro/a de que quieres cerrar sesión?\\n</string>\n  <string name=\"textSettingsMoviesEnabledTitle\">Películas</string>\n  <string name=\"textSettingsMoviesEnabledSummary\">Habilitar/deshabilitar soporte de películas. La aplicación se reiniciará.</string>\n  <string name=\"textSettingsNewsEnabledTitle\">Noticias</string>\n  <string name=\"textSettingsNewsEnabledSummary\">Habilitar/deshabilitar sección de noticias. La aplicación se reiniciará.</string>\n  <string name=\"textSettingsWidgets\">Widgets</string>\n  <string name=\"textSettingsWidgetsLabelsTitle\">Títulos</string>\n  <string name=\"textSettingsWidgetsLabelsSummary\">Muestra títulos de widgets translúcidos.</string>\n  <string name=\"textSettingsStreamingsDisclaimer\">Datos de servicios de streaming proporcionados por</string>\n  <string name=\"textSettingsStreamingServicesTitle\">Servicios de Streaming</string>\n  <string name=\"textSettingsStreamingServicesMessage\">Mostrar datos de servicios de streaming.</string>\n  <string name=\"textSettingsProgressNextEpisodeTitle\">Siguiente Episodio</string>\n  <string name=\"textSettingsProgressNextEpisodeMessage\">Calcular el siguiente episodio usando:</string>\n  <string name=\"textSettingsTabletColumnsTitle\">Número de Columnas de Listas</string>\n  <string name=\"textSettingsTabletColumnsSummary\">Selecciona el número de columnas preferido para las listas.</string>\n  <string name=\"textSettingsSpoilers\">Spoilers</string>\n  <string name=\"textSettingsSpoilersShows\">Series</string>\n  <string name=\"textSettingsSpoilersShowsSummary\">Administrar ajustes de spoiler para series.</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyTitle\">Mis Series</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyDescription\">Ocultar <b>descripción</b> de las series añadidas a Mis Series</string>\n  <string name=\"textSettingsSpoilersShowsRatingsMyDescription\">Ocultar <b>valoración</b> de series añadidas a Mis Series</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistTitle\">Pendientes</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistDescription\">Ocultar <b>descripción</b> de las series añadidas a Pendientes</string>\n  <string name=\"textSettingsSpoilersShowsRatingsWatchlistDescription\">Ocultar <b>valoración</b> de series añadidas a Pendientes</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenTitle\">Ocultos</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenDescription\">Ocultar <b>descripción</b> de las series añadidas a Ocultos</string>\n  <string name=\"textSettingsSpoilersShowsRatingsHiddenDescription\">Ocultar <b>valoración</b> de series añadidas a Ocultos</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedTitle\">No Coleccionado</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedDescription\">Ocultar <b>descripción</b> de series que no están en tu Colección</string>\n  <string name=\"textSettingsSpoilersShowsRatingsNotCollectedDescription\">Ocultar <b>valoración</b> de series que no están en tu Colección</string>\n  <string name=\"textSettingsSpoilersMovies\">Películas</string>\n  <string name=\"textSettingsSpoilersMoviesSummary\">Administrar ajustes de spoiler para películas.</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyTitle\">Mis Películas</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyDescription\">Ocultar <b>descripción</b> de películas añadidas a Mis Películas</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsMyDescription\">Ocultar <b>valoración</b> de películas añadidas a Mis Películas</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistTitle\">Pendientes</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistDescription\">Ocultar <b>descripción</b> de películas añadidas a Pendientes</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsWatchlistDescription\">Ocultar <b>valoración</b> de películas añadidas a Pendientes</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenTitle\">Ocultos</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenDescription\">Ocultar <b>descripción</b> de películas añadidas a Ocultos</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsHiddenDescription\">Ocultar <b>valoración</b> de películas añadidas a Ocultos</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedTitle\">No Coleccionado</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedDescription\">Ocultar <b>descripción</b> de películas que no están en tu Colección</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsNotCollectedDescription\">Ocultar <b>calificación</b> de películas que no están en tu Colección</string>\n  <string name=\"textSettingsSpoilersEpisodes\">Episodios</string>\n  <string name=\"textSettingsSpoilersEpisodesSummary\">Administrar ajustes de spoiler para episodios.</string>\n  <string name=\"textSettingsSpoilersEpisodesImage\">Ocultar <b>imagen</b> de los episodios no vistos.</string>\n  <string name=\"textSettingsSpoilersEpisodesTitle\">Ocultar <b>título</b> de los episodios no vistos.</string>\n  <string name=\"textSettingsSpoilersEpisodesDescription\">Ocultar <b>descripción</b> de los episodios no vistos.</string>\n  <string name=\"textSettingsSpoilersEpisodesRating\">Ocultar <b>valoración</b> de los episodios no vistos.</string>\n  <string name=\"textSettingsSpoilersTapToRevealTitle\">Pulsar para revelar</string>\n  <string name=\"textSettingsSpoilersTapToRevealDescription\">Toca el contenido oculto para revelarlo temporalmente.</string>\n  <string name=\"textNextEpisodeLastWatched\">Último episodio visto</string>\n  <string name=\"textNextEpisodeOldest\">Episodio más antiguo no visto</string>\n  <string name=\"textThemeDark\">Oscuro</string>\n  <string name=\"textThemeLight\">Claro</string>\n  <string name=\"textThemeSystem\">Sistema</string>\n  <string name=\"textTransparency100\">Opaco</string>\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Olet tunnistautunut.</string>\n  <string name=\"textTraktLogoutSuccess\">Olet kirjautunut ulos.</string>\n  <string name=\"textImagesCacheCleared\">Kuvavälimuisti tyhjennettiin.</string>\n  <string name=\"textTurnOff\">Poista käytöstä</string>\n  <string name=\"textSettings\">Asetukset</string>\n  <string name=\"textSettingsTrakt\">Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Trakt.tv-valtuutus</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignIn\">Napauta kirjautuaksesi Trakt.tv-tilillesi.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOut\">Kirjautuneena. Kirjaudu ulos napauttamalla.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOutUser\">Kirjautuneena tunnuksella %1$s. Kirjaudu ulos napauttamalla.</string>\n  <string name=\"textSettingsTraktSyncTitle\">Trakt.tv-synkronointi</string>\n  <string name=\"textSettingsTraktSyncSummary\">Synkronoi tiedot Trakt.tv:n ja Showlyn välillä.</string>\n  <string name=\"textSettingsTraktQuickSyncTitle\">Trakt.tv-pikalähetys</string>\n  <string name=\"textSettingsTraktQuickSyncSummary\">Lähetä tiedot Trakt.tv-kokoelmaasi heti kun lisäät ne sovellukseen.</string>\n  <string name=\"textSettingsTraktQuickRemoveTitle\">Poista tiedot Trakt.tv-tililtä</string>\n  <string name=\"textSettingsTraktQuickRemoveSummary\">Tarjoa tietojen poistoa Trakt.tv-tililtäsi, kun olet poistanut ne sovelluksesta.</string>\n  <string name=\"textSettingsTraktQuickRateTitle\">Trakt.tv-pika-arviointi</string>\n  <string name=\"textSettingsTraktQuickRateSummary\">Arvioi jaksot ja elokuvat aina kun merkitset ne katsotuiksi Katselutila-osiossa.</string>\n  <string name=\"textSettingsGeneral\">Yleiset ja käyttölittymä</string>\n  <string name=\"textSettingsRecentShowsTitle\">Hiljattain lisättyjä</string>\n  <string name=\"textSettingsRecentShowsSummary\">Kokoelmassa näytettävien, hiljattain lisättyjen kohteiden määrä.</string>\n  <string name=\"textSettingsUpcomingSectionTitle\">Tulevien osio</string>\n  <string name=\"textSettingsUpcomingSectionSummary\">Ota Katselutilan Tulevat-osio käyttöön/poista se käytöstä.</string>\n  <string name=\"textSettingsMyShowsSectionsTitle\">Omien sarjojen osiot</string>\n  <string name=\"textSettingsMyShowsSectionSummary\">Ota omien sarjojen osioita käyttöön/poista niitä käytöstä.</string>\n  <string name=\"textSettingsMyMoviesSectionsTitle\">Omien elokuvien osiot</string>\n  <string name=\"textSettingsMyMoviesSectionSummary\">Ota omien elokuvien osioita käyttöön/poista niitä käytöstä.</string>\n  <string name=\"textSettingsThemeTitle\">Teema</string>\n  <string name=\"textSettingsThemeSummary\">Valitse pääteema.</string>\n  <string name=\"textSettingsThemeWidgetsTitle\">Teema</string>\n  <string name=\"textSettingsThemeWidgetsSummary\">Valitse widgettien teema.</string>\n  <string name=\"textSettingsWidgetsTransparencyTitle\">Tausta</string>\n  <string name=\"textSettingsWidgetsTransparencySummary\">Aseta widgettien taustan läpinäkyvyys.</string>\n  <string name=\"textSettingsMyShowsCountryTitle\">Sijainti</string>\n  <string name=\"textSettingsMyShowsCountrySummary\">Valitse suoratoistopalveluiden tiedoille käytettävä sijainti.</string>\n  <string name=\"textSettingsMyShowsLanguageTitle\">Kieli</string>\n  <string name=\"textSettingsMyShowsLanguageSummary\">Kuvaukset ja otsikot käännetään kun saatavilla. Sovellus käynnistyy uudelleen.</string>\n  <string name=\"textSettingsDateFormatTitle\">Päivämäärän/ajan muoto</string>\n  <string name=\"textSettingsIncludeSpecialsTitle\">Erikoiskaudet</string>\n  <string name=\"textSettingsIncludeSpecialsSummary\">Näytä erikoistuotantokaudet sarjojen tiedoissa.</string>\n  <string name=\"textSettingsNotifications\">Ilmoitukset</string>\n  <string name=\"textSettingsPushNotificationsTitle\">Push-ilmoitukset</string>\n  <string name=\"textSettingsShowsNotificationsSummary\">Vastaanota ilmoituksia uusista elokuvista sekä seuraamiesi sarjojen jaksoista ja tuotantokausista.</string>\n  <string name=\"textSettingsShowsNotificationsRationale\">Myönnä sovellukselle ilmoitusten näyttöoikeus.\\n\\nHaluatko tämän nyt?</string>\n  <string name=\"textSettingsMisc\">Sekalaiset</string>\n  <string name=\"textSettingsContactDevsTitle\">Ota yhteyttä kehittäjiin</string>\n  <string name=\"textSettingsContactDevsSummary\">Onko ongelmia? Lähetä meille sähköpostia napauttamalla (showlyapp@gmail.com).</string>\n  <string name=\"textSettingsRateAppTitle\">Anna palautetta</string>\n  <string name=\"textSettingsRateAppSummary\">Nautitko Showlysta? Arvostele se Play Kaupassa, kiitos!</string>\n  <string name=\"textSettingsDeleteCacheTitle\">Tyhjennä kuvavälimuisti</string>\n  <string name=\"textSettingsDeleteCacheSummary\">Tyhjennä paikallinen kuvavälimuisti napauttamalla. Tämä saattaa auttaa, jos sarjojen kuvat eivät lataudu oikein.</string>\n  <string name=\"textSettingsQuickSyncConfirmationTitle\">Poista automaattinen synkronointi käytöstä</string>\n  <string name=\"textSettingsQuickSyncConfirmationMessage\">\\nAutomaattinen synkronointi näyttäisi olevan jo käytössä.\\n\\nHaluatko poistaa sen käytöstä ennen pikasynkronoinnin käyttöönottoa (suositeltavaa)?\\n</string>\n  <string name=\"textSettingsLogoutTitle\">Trakt.tv-uloskirjaus</string>\n  <string name=\"textSettingsLogoutMessage\">\\nHaluatko varmasti kirjautua ulos?\\n</string>\n  <string name=\"textSettingsMoviesEnabledTitle\">Elokuvat</string>\n  <string name=\"textSettingsMoviesEnabledSummary\">Ota elokuvat käyttöön/poista ne käytöstä. Sovellus käynnistyy uudelleen.</string>\n  <string name=\"textSettingsNewsEnabledTitle\">Uutiset</string>\n  <string name=\"textSettingsNewsEnabledSummary\">Ota uutiset käyttöön/poista ne käytöstä. Sovellus käynnistyy uudelleen.</string>\n  <string name=\"textSettingsWidgets\">Widgetit</string>\n  <string name=\"textSettingsWidgetsLabelsTitle\">Nimet</string>\n  <string name=\"textSettingsWidgetsLabelsSummary\">Näytä läpikuultavat widgettien nimet.</string>\n  <string name=\"textSettingsStreamingsDisclaimer\">Suoratoistopalveluiden tiedot tarjoaa</string>\n  <string name=\"textSettingsStreamingServicesTitle\">Suoratoistopalvelut</string>\n  <string name=\"textSettingsStreamingServicesMessage\">Näytä suoratoistopalveluiden tiedot.</string>\n  <string name=\"textSettingsProgressNextEpisodeTitle\">Seuraava jakso</string>\n  <string name=\"textSettingsProgressNextEpisodeMessage\">Seuraavan jakson laskentaperuste:</string>\n  <string name=\"textSettingsTabletColumnsTitle\">Listausten sarakemäärä</string>\n  <string name=\"textSettingsTabletColumnsSummary\">Valitse listauksissa näytettävä sarakkeiden määrä.</string>\n  <string name=\"textSettingsSpoilers\">Juonipaljastukset</string>\n  <string name=\"textSettingsSpoilersShows\">Sarjat</string>\n  <string name=\"textSettingsSpoilersShowsSummary\">Hallitse sarjojen jounipaljastusasetuksia.</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyTitle\">Omat sarjat</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyDescription\">Piilota omiin sarjoihin lisättyjen sarjojen <b>kuvaukset</b>.</string>\n  <string name=\"textSettingsSpoilersShowsRatingsMyDescription\">Piilota omiin sarjoihin lisättyjen sarjojen <b>arviot</b>.</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistTitle\">Katselulista</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistDescription\">Piilota katselulistalle lisättyjen sarjojen <b>kuvaukset</b>.</string>\n  <string name=\"textSettingsSpoilersShowsRatingsWatchlistDescription\">Piilota katselulistalle lisättyjen sarjojen <b>arviot</b>.</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenTitle\">Piilotetut</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenDescription\">Piilota piilotettuihin lisättyjen sarjojen <b>kuvaukset</b>.</string>\n  <string name=\"textSettingsSpoilersShowsRatingsHiddenDescription\">Piilota piilotettuihin lisättyjen sarjojen <b>arviot</b>.</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedTitle\">Kokoelman ulkopuoliset</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedDescription\">Piilota kokoelman ulkopuolisten sarjojen <b>kuvaukset</b>.</string>\n  <string name=\"textSettingsSpoilersShowsRatingsNotCollectedDescription\">Piilota kokoelman ulkopuolisten sarjojen <b>arviot</b>.</string>\n  <string name=\"textSettingsSpoilersMovies\">Elokuvat</string>\n  <string name=\"textSettingsSpoilersMoviesSummary\">Hallitse elokuvien jounipaljastusasetuksia.</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyTitle\">Omat elokuvat</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyDescription\">Piilota omiin elokuviin lisättyjen elokuvien <b>kuvaukset</b>.</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsMyDescription\">Piilota omiin elokuviin lisättyjen elokuvien <b>arviot</b>.</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistTitle\">Katselulista</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistDescription\">Piilota katselulistalle lisättyjen elokuvien <b>kuvaukset</b>.</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsWatchlistDescription\">Piilota katselulistalle lisättyjen elokuvien <b>arviot</b>.</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenTitle\">Piilotetut</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenDescription\">Piilota piilotettuihin lisättyjen elokuvien <b>kuvaukset</b>.</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsHiddenDescription\">Piilota piilotettuihin lisättyjen elokuvien <b>arviot</b>.</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedTitle\">Kokoelman ulkopuoliset</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedDescription\">Piilota kokoelman ulkopuolisten elokuvien <b>kuvaukset</b>.</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsNotCollectedDescription\">Piilota kokoelman ulkopuolisten elokuvien <b>arviot</b>.</string>\n  <string name=\"textSettingsSpoilersEpisodes\">jaksoa</string>\n  <string name=\"textSettingsSpoilersEpisodesSummary\">Hallitse jaksojen jounipaljastusasetuksia.</string>\n  <string name=\"textSettingsSpoilersEpisodesImage\">Piilota katsomattomien jaksojen <b>kuva</b>.</string>\n  <string name=\"textSettingsSpoilersEpisodesTitle\">Piilota katsomattomien jaksojen <b>nimi</b>.</string>\n  <string name=\"textSettingsSpoilersEpisodesDescription\">Piilota katsomattomien jaksojen <b>kuvaus</b>.</string>\n  <string name=\"textSettingsSpoilersEpisodesRating\">Piilota katsomattomien jaksojen <b>arvio</b>.</string>\n  <string name=\"textSettingsSpoilersTapToRevealTitle\">Paljasta napauttamalla</string>\n  <string name=\"textSettingsSpoilersTapToRevealDescription\">Paljasta piilotettu sisältö väliaikaisesti napauttamalla sitä.</string>\n  <string name=\"textNextEpisodeLastWatched\">Viimeksi katsottu jakso</string>\n  <string name=\"textNextEpisodeOldest\">Vanhin katsomaton jakso</string>\n  <string name=\"textThemeDark\">Tumma</string>\n  <string name=\"textThemeLight\">Vaalea</string>\n  <string name=\"textThemeSystem\">Järjestelmä</string>\n  <string name=\"textTransparency100\">Läpinäkymätön</string>\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Vous avez été autorisé avec succès.</string>\n  <string name=\"textTraktLogoutSuccess\">Vous venez d\\'être déconnecté.</string>\n  <string name=\"textImagesCacheCleared\">La mémoire cache des images a été nettoyée.</string>\n  <string name=\"textTurnOff\">Éteindre</string>\n  <string name=\"textSettings\">Paramètres</string>\n  <string name=\"textSettingsTrakt\">Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Autorisation pour Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignIn\">Cliquez pour vous connecter à votre compte Trakt.tv.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOut\">Connecté. Cliquez pour vous déconnecter.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOutUser\">Connecté en tant que %1$s.\\nCliquez pour vous déconnecter.</string>\n  <string name=\"textSettingsTraktSyncTitle\">Synchronisation Trakt.tv</string>\n  <string name=\"textSettingsTraktSyncSummary\">Synchronisation des données entre Trakt.tv et Showly.</string>\n  <string name=\"textSettingsTraktQuickSyncTitle\">Téléversement Instantané Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickSyncSummary\">Téléversement instantané des données vers Trakt.tv après les avoir ajoutées dans l\\'application.</string>\n  <string name=\"textSettingsTraktQuickRemoveTitle\">Suppression des données Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickRemoveSummary\">Proposer de supprimer les données de votre compte Trakt.tv après les avoir supprimés dans l\\'application.</string>\n  <string name=\"textSettingsTraktQuickRateTitle\">Notation rapide sur Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickRateSummary\">Évaluez les épisodes et les films chaque fois que vous les marquez comme visionnés dans l\\'écran « Progression ».</string>\n  <string name=\"textSettingsGeneral\">Général &amp; Expérience utilisateur</string>\n  <string name=\"textSettingsRecentShowsTitle\">Nombre d\\'éléments récents</string>\n  <string name=\"textSettingsRecentShowsSummary\">Nombre d\\'éléments récents à afficher dans \\'Collection\\'.</string>\n  <string name=\"textSettingsUpcomingSectionTitle\">Section à venir</string>\n  <string name=\"textSettingsUpcomingSectionSummary\">Activer/désactiver la section \\'À venir\\' dans l\\'onglet \\'Progression\\'.</string>\n  <string name=\"textSettingsMyShowsSectionsTitle\">Catégories dans Mes Séries</string>\n  <string name=\"textSettingsMyShowsSectionSummary\">Activer/désactiver les catégories dans l\\'onglet \\'Mes Séries\\'.</string>\n  <string name=\"textSettingsMyMoviesSectionsTitle\">Catégories de Mes Films</string>\n  <string name=\"textSettingsMyMoviesSectionSummary\">Activer/désactiver les catégories dans l\\'onglet \\'Mes Films\\'.</string>\n  <string name=\"textSettingsThemeTitle\">Thème</string>\n  <string name=\"textSettingsThemeSummary\">Sélectionnez le thème principal.</string>\n  <string name=\"textSettingsThemeWidgetsTitle\">Thème</string>\n  <string name=\"textSettingsThemeWidgetsSummary\">Sélectionnez le thème des widgets.</string>\n  <string name=\"textSettingsWidgetsTransparencyTitle\">Fond</string>\n  <string name=\"textSettingsWidgetsTransparencySummary\">Définir le niveau de transparence du fond des widgets.</string>\n  <string name=\"textSettingsMyShowsCountryTitle\">Région</string>\n  <string name=\"textSettingsMyShowsCountrySummary\">Sélectionnez la région à utiliser avec les données des services de streaming.</string>\n  <string name=\"textSettingsMyShowsLanguageTitle\">Langue</string>\n  <string name=\"textSettingsMyShowsLanguageSummary\">Les titres et descriptions seront traduits quand ils seront disponibles. L\\'application va redémarrer.</string>\n  <string name=\"textSettingsDateFormatTitle\">Format date / heure</string>\n  <string name=\"textSettingsIncludeSpecialsTitle\">Saisons spéciales</string>\n  <string name=\"textSettingsIncludeSpecialsSummary\">Afficher les saisons \\'spéciales\\' dans l\\'écran détaillé de la série.</string>\n  <string name=\"textSettingsNotifications\">Notifications</string>\n  <string name=\"textSettingsPushNotificationsTitle\">Notifications instantanées</string>\n  <string name=\"textSettingsShowsNotificationsSummary\">Recevoir des notifications pour les nouveaux films, épisodes, saisons pour les séries que vous suivez.</string>\n  <string name=\"textSettingsShowsNotificationsRationale\">Veuillez accorder à l\\'application la permission d\\'afficher les notifications.\\n\\nVoulez-vous le faire maintenant ?</string>\n  <string name=\"textSettingsMisc\">Divers</string>\n  <string name=\"textSettingsContactDevsTitle\">Contacter les développeurs</string>\n  <string name=\"textSettingsContactDevsSummary\">Un problème? Cliquez ici pour ouvrir un problème sur GitHub.</string>\n  <string name=\"textSettingsDeleteCacheTitle\">Vider la mémoire cache des images</string>\n  <string name=\"textSettingsDeleteCacheSummary\">Cliquez pour vider la mémoire cache des images. Cela peut aider si les images des séries ne se chargent pas correctement.</string>\n  <string name=\"textSettingsQuickSyncConfirmationTitle\">Désactiver la Synchronisation Automatique</string>\n  <string name=\"textSettingsQuickSyncConfirmationMessage\">\\nIl semblerait que la Synchronisation Automatique soit déjà activée.\\n\\nVoulez-vous la désactiver avant d\\'activer la synchronisation instantanée (conseillé) ?\\n</string>\n  <string name=\"textSettingsLogoutTitle\">Déconnexion de Trakt.tv</string>\n  <string name=\"textSettingsLogoutMessage\">\\nVoulez-vous vraiment vous déconnecter ?\\n</string>\n  <string name=\"textSettingsMoviesEnabledTitle\">Films</string>\n  <string name=\"textSettingsMoviesEnabledSummary\">Activer/désactiver la prise en compte des films. L\\'application va redémarrer.</string>\n  <string name=\"textSettingsNewsEnabledTitle\">Actualités</string>\n  <string name=\"textSettingsNewsEnabledSummary\">Activer/désactiver la section Actualités. L\\'application va redémarrer.</string>\n  <string name=\"textSettingsWidgets\">Widgets</string>\n  <string name=\"textSettingsWidgetsLabelsTitle\">Titres</string>\n  <string name=\"textSettingsWidgetsLabelsSummary\">Afficher les titres translucides des widgets.</string>\n  <string name=\"textSettingsStreamingsDisclaimer\">Les données des services de streaming sont fournies par</string>\n  <string name=\"textSettingsStreamingServicesTitle\">Services de streaming</string>\n  <string name=\"textSettingsStreamingServicesMessage\">Afficher les données des services de streaming.</string>\n  <string name=\"textSettingsProgressNextEpisodeTitle\">Prochain épisode</string>\n  <string name=\"textSettingsProgressNextEpisodeMessage\">Calculer le prochain épisode en utilisant :</string>\n  <string name=\"textSettingsTabletColumnsTitle\">Nombre de colonnes des listes</string>\n  <string name=\"textSettingsTabletColumnsSummary\">Sélectionnez le nombre de colonnes préféré pour les listes.</string>\n  <string name=\"textSettingsSpoilers\">Spoilers</string>\n  <string name=\"textSettingsSpoilersShows\">Séries</string>\n  <string name=\"textSettingsSpoilersShowsSummary\">Gérer les paramètres de spoiler pour les séries.</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyTitle\">Mes séries</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyDescription\">Cacher la <b>description</b> des séries ajoutées à Mes séries</string>\n  <string name=\"textSettingsSpoilersShowsRatingsMyDescription\">Cacher la <b>note</b> des séries ajoutées à Mes séries</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistTitle\">Watchlist</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistDescription\">Cacher la <b>description</b> des séries ajoutées à la Watchlist</string>\n  <string name=\"textSettingsSpoilersShowsRatingsWatchlistDescription\">Cacher la <b>note</b> des séries ajoutées à la Watchlist</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenTitle\">Masqué</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenDescription\">Cacher la <b>description</b> des séries ajoutées à Masqué</string>\n  <string name=\"textSettingsSpoilersShowsRatingsHiddenDescription\">Cacher la <b>note</b> des séries ajoutées à Masqué</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedTitle\">Non collectés</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedDescription\">Cacher la <b>description</b> des séries qui ne sont pas dans votre Collection</string>\n  <string name=\"textSettingsSpoilersShowsRatingsNotCollectedDescription\">Cacher la <b>note</b> des séries qui ne sont pas dans votre Collection</string>\n  <string name=\"textSettingsSpoilersMovies\">Films</string>\n  <string name=\"textSettingsSpoilersMoviesSummary\">Gérer les paramètres de spoiler pour les films.</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyTitle\">Mes films</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyDescription\">Cacher la <b>description</b> des films ajoutés à Mes films</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsMyDescription\">Cacher la <b>note</b> des films ajoutés à Mes films</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistTitle\">Watchlist</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistDescription\">Cacher la <b>description</b> des films ajoutés à la Watchlist</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsWatchlistDescription\">Cacher la <b>note</b> des films ajoutés à la Watchlist</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenTitle\">Masqué</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenDescription\">Cacher la <b>description</b> des films ajoutés à Masqué</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsHiddenDescription\">Cacher la <b>note</b> des films ajoutés à Masqué</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedTitle\">Non collectés</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedDescription\">Cacher la <b>description</b> des films qui ne sont pas dans votre Collection</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsNotCollectedDescription\">Cacher la <b>note</b> des films qui ne sont pas dans votre Collection</string>\n  <string name=\"textSettingsSpoilersEpisodes\">Épisodes</string>\n  <string name=\"textSettingsSpoilersEpisodesSummary\">Gérer les paramètres de spoiler pour les épisodes.</string>\n  <string name=\"textSettingsSpoilersEpisodesImage\">Cacher l\\'<b>image</b> des épisodes non vus.</string>\n  <string name=\"textSettingsSpoilersEpisodesTitle\">Cacher le <b>titre</b> des épisodes non vus.</string>\n  <string name=\"textSettingsSpoilersEpisodesDescription\">Cacher la <b>description</b> des épisodes non vus.</string>\n  <string name=\"textSettingsSpoilersEpisodesRating\">Cacher la <b>note</b> des épisodes non vus.</string>\n  <string name=\"textSettingsSpoilersTapToRevealTitle\">Appuyer pour révéler</string>\n  <string name=\"textSettingsSpoilersTapToRevealDescription\">Appuyer sur le contenu masqué pour le révéler temporairement.</string>\n  <string name=\"textNextEpisodeLastWatched\">Le dernier épisode vu</string>\n  <string name=\"textNextEpisodeOldest\">Le plus ancien épisode non vu</string>\n  <string name=\"textThemeDark\">Sombre</string>\n  <string name=\"textThemeLight\">Clair</string>\n  <string name=\"textThemeSystem\">Système</string>\n  <string name=\"textTransparency100\">Opaque</string>\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Sei stato autorizzato correttamente.</string>\n  <string name=\"textTraktLogoutSuccess\">Sei uscito.</string>\n  <string name=\"textImagesCacheCleared\">La cache delle immagini è stata pulita.</string>\n  <string name=\"textTurnOff\">Disattiva</string>\n  <string name=\"textSettings\">Impostazioni</string>\n  <string name=\"textSettingsTrakt\">Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Autorizzazione Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignIn\">Clicca qui per effettuare l\\'accesso al tuo account Trakt.tv.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOut\">Accesso effettuato. Clicca qui per uscire.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOutUser\">Accesso effettuato come %1$s. Clicca qui per uscire.</string>\n  <string name=\"textSettingsTraktSyncTitle\">Sincronizzazione Trakt.tv</string>\n  <string name=\"textSettingsTraktSyncSummary\">Sincronizza dati fra Trakt.tv e Showly.</string>\n  <string name=\"textSettingsTraktQuickSyncTitle\">Caricamento istantaneo su Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickSyncSummary\">Carica istantaneamente i dati su Trakt.tv dopo averli aggiunti all\\'app.</string>\n  <string name=\"textSettingsTraktQuickRemoveTitle\">Rimozione dati Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickRemoveSummary\">Rimuovere i dati dal tuo account Trakt.tv dopo averli rimossi dall\\'app.</string>\n  <string name=\"textSettingsTraktQuickRateTitle\">Valutazione rapida Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickRateSummary\">Valuta episodi e film quando li contrassegni come visti nella sezione \\'Progressi\\'.</string>\n  <string name=\"textSettingsGeneral\">Generale &amp; UX</string>\n  <string name=\"textSettingsRecentShowsTitle\">Aggiunti di recente</string>\n  <string name=\"textSettingsRecentShowsSummary\">Numero di elementi recenti da mostrare in \\'Raccolta\\'.</string>\n  <string name=\"textSettingsUpcomingSectionTitle\">Sezione \\\"In arrivo\\\"</string>\n  <string name=\"textSettingsUpcomingSectionSummary\">Attiva/disattiva la sezione \\\"In arrivo\\\" nella scheda \\\"Progressi\\\".</string>\n  <string name=\"textSettingsMyShowsSectionsTitle\">Sezioni \\\"I miei show\\\"</string>\n  <string name=\"textSettingsMyShowsSectionSummary\">Attiva/disattiva le sezioni nella pagina \\\"I miei show\\\".</string>\n  <string name=\"textSettingsMyMoviesSectionsTitle\">Sezioni \\\"I miei film\\\"</string>\n  <string name=\"textSettingsMyMoviesSectionSummary\">Attiva/disattiva le sezioni nella pagina \\'I miei film\\'.</string>\n  <string name=\"textSettingsThemeTitle\">Tema</string>\n  <string name=\"textSettingsThemeSummary\">Selezione il tema principale.</string>\n  <string name=\"textSettingsThemeWidgetsTitle\">Tema</string>\n  <string name=\"textSettingsThemeWidgetsSummary\">Seleziona il tema per i widget.</string>\n  <string name=\"textSettingsWidgetsTransparencyTitle\">Sfondo</string>\n  <string name=\"textSettingsWidgetsTransparencySummary\">Imposta il livello di trasparenza dello sfondo dei widget.</string>\n  <string name=\"textSettingsMyShowsCountryTitle\">Regione</string>\n  <string name=\"textSettingsMyShowsCountrySummary\">Selezionare la regione da utilizzare con i dati dei servizi di streaming.</string>\n  <string name=\"textSettingsMyShowsLanguageTitle\">Lingua</string>\n  <string name=\"textSettingsMyShowsLanguageSummary\">Le descrizioni e i titoli verranno tradotti quando possibile. L\\'app verrà riavviata.</string>\n  <string name=\"textSettingsDateFormatTitle\">Formato data / ora</string>\n  <string name=\"textSettingsIncludeSpecialsTitle\">Stagioni speciali</string>\n  <string name=\"textSettingsIncludeSpecialsSummary\">Mostra le stagioni \\\"Speciali\\\" nei dettagli dello show.</string>\n  <string name=\"textSettingsNotifications\">Notifiche</string>\n  <string name=\"textSettingsPushNotificationsTitle\">Notifiche istantanee</string>\n  <string name=\"textSettingsShowsNotificationsSummary\">Ricevi notifiche riguardo nuovi film, episodi e stagioni per gli show che segui.</string>\n  <string name=\"textSettingsShowsNotificationsRationale\">È necessario concedere all\\'applicazione un permesso per visualizzare le notifiche.\\n\\nVuoi farlo ora?</string>\n  <string name=\"textSettingsMisc\">Varie</string>\n  <string name=\"textSettingsContactDevsTitle\">Contatta gli sviluppatori</string>\n  <string name=\"textSettingsContactDevsSummary\">Hai un problema? Fare clic qui per aprire un problema su GitHub.</string>\n  <string name=\"textSettingsDeleteCacheTitle\">Pulisci la cache delle immagini</string>\n  <string name=\"textSettingsDeleteCacheSummary\">Clicca per pulire la cache delle immagini. Questo potrebbe aiutare nel caso le immagini degli show non venissero scaricate correttamente.</string>\n  <string name=\"textSettingsQuickSyncConfirmationTitle\">Disattiva la sincronizzazione automatica</string>\n  <string name=\"textSettingsQuickSyncConfirmationMessage\">\\nSembra che la sincronizzazione automatica sia attiva.\\n\\nVuoi disattivarla prima di attivare la sincronizzazione istantanea (consigliato)?\\n</string>\n  <string name=\"textSettingsLogoutTitle\">Esci da Trakt.tv</string>\n  <string name=\"textSettingsLogoutMessage\">\\nSei sicuro di voler uscire?\\n</string>\n  <string name=\"textSettingsMoviesEnabledTitle\">Film</string>\n  <string name=\"textSettingsMoviesEnabledSummary\">Attiva/disattiva il supporto ai film. L\\'app verrà riavviata.</string>\n  <string name=\"textSettingsNewsEnabledTitle\">Notizie</string>\n  <string name=\"textSettingsNewsEnabledSummary\">Attiva/disattiva la sezione notizie. L\\'app verrà riavviata.</string>\n  <string name=\"textSettingsWidgets\">Widget</string>\n  <string name=\"textSettingsWidgetsLabelsTitle\">Titoli</string>\n  <string name=\"textSettingsWidgetsLabelsSummary\">Mostra i titoli semitrasparenti per le sezioni dei widget.</string>\n  <string name=\"textSettingsStreamingsDisclaimer\">I dati dei servizi di streaming sono forniti da</string>\n  <string name=\"textSettingsStreamingServicesTitle\">Servizi di streaming</string>\n  <string name=\"textSettingsStreamingServicesMessage\">Mostra dati sui servizi di streaming.</string>\n  <string name=\"textSettingsProgressNextEpisodeTitle\">Prossimo episodio</string>\n  <string name=\"textSettingsProgressNextEpisodeMessage\">Calcola l\\'episodio successivo usando:</string>\n  <string name=\"textSettingsTabletColumnsTitle\">Numero di colonne delle liste</string>\n  <string name=\"textSettingsTabletColumnsSummary\">Seleziona il numero di colonne preferito per le liste.</string>\n  <string name=\"textSettingsSpoilers\">Spoiler</string>\n  <string name=\"textSettingsSpoilersShows\">Show</string>\n  <string name=\"textSettingsSpoilersShowsSummary\">Gestisci le impostazioni degli spoiler per gli show.</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyTitle\">I miei show</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyDescription\">Nascondi la <b>descrizione</b> degli show aggiunti a \\\"I miei show\\\"</string>\n  <string name=\"textSettingsSpoilersShowsRatingsMyDescription\">Nascondi la <b>valutazione</b> degli show aggiunti a \\\"I miei show\\\"</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistTitle\">Da vedere</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistDescription\">Nascondi la <b>descrizione</b> degli show aggiunti a \\\"Da vedere\\\"</string>\n  <string name=\"textSettingsSpoilersShowsRatingsWatchlistDescription\">Nascondi la <b>valutazione</b> degli show aggiunti a \\\"Da vedere\\\"</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenTitle\">Nascosti</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenDescription\">Nascondi la <b>descrizione</b> degli show aggiunti a \\\"Nascosti\\\"</string>\n  <string name=\"textSettingsSpoilersShowsRatingsHiddenDescription\">Nascondi la <b>valutazione</b> degli show aggiunti a \\\"Nascosti\\\"</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedTitle\">Non collezionato</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedDescription\">Nascondi la <b>descrizione</b> degli spettacoli che non sono nella tua collezione</string>\n  <string name=\"textSettingsSpoilersShowsRatingsNotCollectedDescription\">Nascondi la <b>valutazione</b> degli spettacoli che non sono nella tua collezione</string>\n  <string name=\"textSettingsSpoilersMovies\">Film</string>\n  <string name=\"textSettingsSpoilersMoviesSummary\">Gestisci le impostazioni degli spoiler per i film.</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyTitle\">I miei film</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyDescription\">Nascondi la <b>descrizione</b> dei film aggiunti a \\\"I miei film\\\"</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsMyDescription\">Nascondi la <b>valutazione</b> dei film aggiunti a \\\"I miei film\\\"</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistTitle\">Da vedere</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistDescription\">Nascondi la <b>descrizione</b> dei film aggiunti a \\\"Da vedere\\\"</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsWatchlistDescription\">Nascondi la <b>valutazione</b> dei film aggiunti a \\\"Da vedere\\\"</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenTitle\">Nascosti</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenDescription\">Nascondi la <b>descrizione</b> dei film aggiunti a \\\"Nascosti\\\"</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsHiddenDescription\">Nascondi la <b>valutazione</b> dei film aggiunti a \\\"Nascosti\\\"</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedTitle\">Non collezionato</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedDescription\">Nascondi la <b>descrizione</b> dei film che non sono nella tua collezione</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsNotCollectedDescription\">Nascondi la <b>valutazione</b> dei film che non sono nella tua collezione</string>\n  <string name=\"textSettingsSpoilersEpisodes\">Episodi</string>\n  <string name=\"textSettingsSpoilersEpisodesSummary\">Gestisci le impostazioni degli spoiler per gli episodi.</string>\n  <string name=\"textSettingsSpoilersEpisodesImage\">Nascondi l\\'<b>immagine</b> degli episodi non visti.</string>\n  <string name=\"textSettingsSpoilersEpisodesTitle\">Nascondi il <b>titolo</b> degli episodi non visti.</string>\n  <string name=\"textSettingsSpoilersEpisodesDescription\">Nascondi la <b>descrizione</b> degli episodi non visti.</string>\n  <string name=\"textSettingsSpoilersEpisodesRating\">Nascondi la <b>valutazione</b> degli episodi non visti.</string>\n  <string name=\"textSettingsSpoilersTapToRevealTitle\">Tocca per mostrare</string>\n  <string name=\"textSettingsSpoilersTapToRevealDescription\">Tocca il contenuto nascosto per mostrarlo temporaneamente.</string>\n  <string name=\"textNextEpisodeLastWatched\">Ultimo episodio visto</string>\n  <string name=\"textNextEpisodeOldest\">Episodio non visto più vecchio</string>\n  <string name=\"textThemeDark\">Scuro</string>\n  <string name=\"textThemeLight\">Chiaro</string>\n  <string name=\"textThemeSystem\">Sistema</string>\n  <string name=\"textTransparency100\">Opaco</string>\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Zalogowano.</string>\n  <string name=\"textTraktLogoutSuccess\">Wylogowano.</string>\n  <string name=\"textImagesCacheCleared\">Pamięć podręczna obrazków została wyczyszczona.</string>\n  <string name=\"textTurnOff\">Wyłącz</string>\n  <string name=\"textSettings\">Ustawienia</string>\n  <string name=\"textSettingsTrakt\">Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Autoryzacja Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignIn\">Kliknij aby zalogować się do konta Trakt.tv.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOut\">Zalogowano. Kliknij aby wylogować.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOutUser\">Zalogowano jako %1$s.\\nKliknij aby wylogować.</string>\n  <string name=\"textSettingsTraktSyncTitle\">Synchronizacja Trakt.tv</string>\n  <string name=\"textSettingsTraktSyncSummary\">Synchronizuj dane między Trakt.tv i Showly</string>\n  <string name=\"textSettingsTraktQuickSyncTitle\">Trakt.tv Szybki Upload</string>\n  <string name=\"textSettingsTraktQuickSyncSummary\">Natychmiast wyślij odcinki i seriale \\'Na Później\\' do Trakt.tv po dodaniu w aplikacji.</string>\n  <string name=\"textSettingsTraktQuickRemoveTitle\">Trakt.tv Usuwanie Danych</string>\n  <string name=\"textSettingsTraktQuickRemoveSummary\">Podczas usuwania danych z aplikacji, proponuj również usunięcie ich z konta Trakt.tv.</string>\n  <string name=\"textSettingsTraktQuickRateTitle\">Trakt.tv Szybkie Oceny</string>\n  <string name=\"textSettingsTraktQuickRateSummary\">Oceń odcinki i filmy za każdym razem, gdy oznaczasz je jako obejrzane na ekranie \\\"Postępu\\\".</string>\n  <string name=\"textSettingsGeneral\">Ogólne</string>\n  <string name=\"textSettingsRecentShowsTitle\">Ilość Ostatnio Dodanych</string>\n  <string name=\"textSettingsRecentShowsSummary\">Liczba ostatnio dodanych elementów w zakładce \\'Kolekcja\\'.</string>\n  <string name=\"textSettingsUpcomingSectionTitle\">Sekcja \\'Nadchodzące\\'</string>\n  <string name=\"textSettingsUpcomingSectionSummary\">Włącz/wyłącz sekcję \\\"Nadchodzące\\\" w zakładce \\\"Postęp\\\".</string>\n  <string name=\"textSettingsMyShowsSectionsTitle\">Moje Seriale - Sekcje</string>\n  <string name=\"textSettingsMyShowsSectionSummary\">Włącz/wyłącz sekcje w zakładce \\'Moje Seriale\\'.</string>\n  <string name=\"textSettingsMyMoviesSectionsTitle\">Moje Filmy - Sekcje</string>\n  <string name=\"textSettingsMyMoviesSectionSummary\">Włącz/wyłącz sekcje w zakładce \\'Moje Filmy\\'.</string>\n  <string name=\"textSettingsThemeTitle\">Motyw</string>\n  <string name=\"textSettingsThemeSummary\">Wybierz główny motyw.</string>\n  <string name=\"textSettingsThemeWidgetsTitle\">Motyw</string>\n  <string name=\"textSettingsThemeWidgetsSummary\">Wybierz motyw widgetów.</string>\n  <string name=\"textSettingsWidgetsTransparencyTitle\">Tło</string>\n  <string name=\"textSettingsWidgetsTransparencySummary\">Ustaw poziom przezroczystości tła widgetów.</string>\n  <string name=\"textSettingsMyShowsCountryTitle\">Region</string>\n  <string name=\"textSettingsMyShowsCountrySummary\">Wybierz region dla danych serwisów streamingowych.</string>\n  <string name=\"textSettingsMyShowsLanguageTitle\">Język</string>\n  <string name=\"textSettingsMyShowsLanguageSummary\">Opisy będą tłumaczone na bieżąco w miarę dostępności. Aplikacja zostanie ponownie uruchomiona.</string>\n  <string name=\"textSettingsDateFormatTitle\">Format Daty i Czasu</string>\n  <string name=\"textSettingsIncludeSpecialsTitle\">Sezony Specjalne</string>\n  <string name=\"textSettingsIncludeSpecialsSummary\">Pokaż \\'Specjalne\\' sezony na ekranie informacji o serialu.</string>\n  <string name=\"textSettingsNotifications\">Powiadomienia</string>\n  <string name=\"textSettingsPushNotificationsTitle\">Powiadomienia Push</string>\n  <string name=\"textSettingsShowsNotificationsSummary\">Otrzymuj powiadomienia o nowych filmach, odcinkach, sezonach.</string>\n  <string name=\"textSettingsShowsNotificationsRationale\">Aplikacja potrzebuje twojej zgody aby poprawnie wyświetlać powiadomienia.\\n\\nCzy chcesz jej udzielić teraz?</string>\n  <string name=\"textSettingsMisc\">Różne</string>\n  <string name=\"textSettingsContactDevsTitle\">Kontakt</string>\n  <string name=\"textSettingsContactDevsSummary\">Masz problem/pytanie? Kliknij tutaj, aby otworzyć problem w GitHub.</string>\n  <string name=\"textSettingsDeleteCacheTitle\">Wyczyść Obrazki</string>\n  <string name=\"textSettingsDeleteCacheSummary\">Kliknij aby wyczyścić pamięć podręczną obrazków jeżeli występują problemy z ich ładowaniem.</string>\n  <string name=\"textSettingsQuickSyncConfirmationTitle\">Wyłącz Planowane Synchronizacje</string>\n  <string name=\"textSettingsQuickSyncConfirmationMessage\">\\nPlanowane synchronizacje są obecnie włączone.\\n\\nWyłączyć przed włączeniem Szybkiej Synchronizacji (zalecane)?\\n</string>\n  <string name=\"textSettingsLogoutTitle\">Trakt.tv Wylogowanie</string>\n  <string name=\"textSettingsLogoutMessage\">\\nCzy na pewno chcesz się wylogować?\\n</string>\n  <string name=\"textSettingsMoviesEnabledTitle\">Filmy</string>\n  <string name=\"textSettingsMoviesEnabledSummary\">Włącz/wyłącz filmy. Aplikacja zostanie ponownie uruchomiona.</string>\n  <string name=\"textSettingsNewsEnabledTitle\">Newsy</string>\n  <string name=\"textSettingsNewsEnabledSummary\">Włącz/wyłącz sekcję newsów. Aplikacja zostanie ponownie uruchomiona.</string>\n  <string name=\"textSettingsWidgets\">Widgety</string>\n  <string name=\"textSettingsWidgetsLabelsTitle\">Pokaż Tytuły</string>\n  <string name=\"textSettingsWidgetsLabelsSummary\">Pokazuj przezroczyste tytuły widgetów.</string>\n  <string name=\"textSettingsStreamingsDisclaimer\">Dane serwisów streamingowych dostarcza</string>\n  <string name=\"textSettingsStreamingServicesTitle\">Serwisy Streamingowe</string>\n  <string name=\"textSettingsStreamingServicesMessage\">Wyświetlaj dane usług streamingowych.</string>\n  <string name=\"textSettingsProgressNextEpisodeTitle\">Następny Odcinek</string>\n  <string name=\"textSettingsProgressNextEpisodeMessage\">Wybieraj następny odcinek z użyciem:</string>\n  <string name=\"textSettingsTabletColumnsTitle\">Liczba kolumn w listach</string>\n  <string name=\"textSettingsTabletColumnsSummary\">Wybierz preferowaną liczbę kolumn dla list.</string>\n  <string name=\"textSettingsSpoilers\">Spoilery</string>\n  <string name=\"textSettingsSpoilersShows\">Seriale</string>\n  <string name=\"textSettingsSpoilersShowsSummary\">Ustawienia spoilerów dla seriali.</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyTitle\">Moje Seriale</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyDescription\">Ukryj <b>opis</b> seriali dodanych do Moich Seriali</string>\n  <string name=\"textSettingsSpoilersShowsRatingsMyDescription\">Ukryj <b>ocenę</b> seriali dodanych do Moich Seriali</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistTitle\">Na Później</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistDescription\">Ukryj <b>opis</b> seriali dodanych do Na Później</string>\n  <string name=\"textSettingsSpoilersShowsRatingsWatchlistDescription\">Ukryj <b>ocenę</b> seriali dodanych do Na Później</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenTitle\">Ukryte</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenDescription\">Ukryj <b>opis</b> seriali dodanych do Ukrytych</string>\n  <string name=\"textSettingsSpoilersShowsRatingsHiddenDescription\">Ukryj <b>opis</b> seriali dodanych do Ukrytych</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedTitle\">Nie w kolekcji</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedDescription\">Ukryj <b>opis</b> seriali, które nie są w twojej kolekcji</string>\n  <string name=\"textSettingsSpoilersShowsRatingsNotCollectedDescription\">Ukryj <b>ocenę</b> seriali, które nie są w twojej kolekcji</string>\n  <string name=\"textSettingsSpoilersMovies\">Filmy</string>\n  <string name=\"textSettingsSpoilersMoviesSummary\">Ustawienia spoilerów dla filmów.</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyTitle\">Moje Filmy</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyDescription\">Ukryj <b>opis</b> filmów dodanych do Moich Filmów</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsMyDescription\">Ukryj <b>ocenę</b> filmów dodanych do Moich Filmów</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistTitle\">Na Później</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistDescription\">Ukryj <b>opis</b> filmów dodanych do Na Później</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsWatchlistDescription\">Ukryj <b>ocenę</b> filmów dodanych do Na Później</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenTitle\">Ukryte</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenDescription\">Ukryj <b>opis</b> filmów dodanych do Ukrytych</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsHiddenDescription\">Ukryj <b>ocenę</b> filmów dodanych do Ukrytych</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedTitle\">Nie w kolekcji</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedDescription\">Ukryj <b>opis</b> filmów, które nie są w twojej kolekcji</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsNotCollectedDescription\">Ukryj <b>ocenę</b> filmów, które nie są w twojej kolekcji</string>\n  <string name=\"textSettingsSpoilersEpisodes\">Odcinki</string>\n  <string name=\"textSettingsSpoilersEpisodesSummary\">Ustawienia spoilerów dla odcinków.</string>\n  <string name=\"textSettingsSpoilersEpisodesImage\">Ukryj <b>zdjęcie</b> nieobejrzanych odcinków.</string>\n  <string name=\"textSettingsSpoilersEpisodesTitle\">Ukryj <b>tytuł</b> nieobejrzanych odcinków.</string>\n  <string name=\"textSettingsSpoilersEpisodesDescription\">Ukryj <b>opis</b> nieobejrzanych odcinków.</string>\n  <string name=\"textSettingsSpoilersEpisodesRating\">Ukryj <b>ocenę</b> nieobejrzanych odcinków.</string>\n  <string name=\"textSettingsSpoilersTapToRevealTitle\">Dotknij aby ujawnić</string>\n  <string name=\"textSettingsSpoilersTapToRevealDescription\">Dotknij ukrytej zawartości, aby tymczasowo ją ujawnić.</string>\n  <string name=\"textNextEpisodeLastWatched\">Ostatnio obejrzany odcinek</string>\n  <string name=\"textNextEpisodeOldest\">Najstarszy nieobejrzany odcinek</string>\n  <string name=\"textThemeDark\">Ciemny</string>\n  <string name=\"textThemeLight\">Jasny</string>\n  <string name=\"textThemeSystem\">Systemowy</string>\n  <string name=\"textTransparency100\">Nieprzezroczysty</string>\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Você foi autorizado com sucesso.</string>\n  <string name=\"textTraktLogoutSuccess\">Você foi desconectado.</string>\n  <string name=\"textImagesCacheCleared\">O cache foi liberado.</string>\n  <string name=\"textTurnOff\">Desativar</string>\n  <string name=\"textSettings\">Configurações</string>\n  <string name=\"textSettingsTrakt\">Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Autorização de Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignIn\">Clique para entrar na sua conta Trakt.tv.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOut\">Conectado. Clique para sair.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOutUser\">Conectado como %1$s. Clique para sair.</string>\n  <string name=\"textSettingsTraktSyncTitle\">Sincronizar Trakt.tv</string>\n  <string name=\"textSettingsTraktSyncSummary\">Sincronizar dados entre Trakt.tv e Showly.</string>\n  <string name=\"textSettingsTraktQuickSyncTitle\">Envio instantâneo de Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickSyncSummary\">Envie instantaneamente dados para Trakt.tv depois de adicioná-los no aplicativo.</string>\n  <string name=\"textSettingsTraktQuickRemoveTitle\">Remoção de Dados Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickRemoveSummary\">Ofereça-se para remover dados da sua conta Trakt.tv após removê-los do aplicativo.</string>\n  <string name=\"textSettingsTraktQuickRateTitle\">Avaliação rápida do Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickRateSummary\">Avalie episódios e filmes sempre que você marcá-los como assistidos na tela \\'Progresso\\'.</string>\n  <string name=\"textSettingsGeneral\">Geral &amp; UX</string>\n  <string name=\"textSettingsRecentShowsTitle\">Quantidade adicionada recentemente</string>\n  <string name=\"textSettingsRecentShowsSummary\">Número de itens recentes a serem exibidos em \\'Coleção\\'.</string>\n  <string name=\"textSettingsUpcomingSectionTitle\">Próximos</string>\n  <string name=\"textSettingsUpcomingSectionSummary\">Ativar/desativar a seção \\'Em breve\\' na aba \\'Progresso\\'.</string>\n  <string name=\"textSettingsMyShowsSectionsTitle\">Minhas séries</string>\n  <string name=\"textSettingsMyShowsSectionSummary\">Ativar/desativar seções na guia \\'Minhas séries\\'.</string>\n  <string name=\"textSettingsMyMoviesSectionsTitle\">Meus filmes</string>\n  <string name=\"textSettingsMyMoviesSectionSummary\">Ativar/desativar seções na guia \\'Meus Filmes\\'.</string>\n  <string name=\"textSettingsThemeTitle\">Tema</string>\n  <string name=\"textSettingsThemeSummary\">Selecione o tema principal.</string>\n  <string name=\"textSettingsThemeWidgetsTitle\">Tema</string>\n  <string name=\"textSettingsThemeWidgetsSummary\">Selecione o tema dos widgets.</string>\n  <string name=\"textSettingsWidgetsTransparencyTitle\">Plano de fundo</string>\n  <string name=\"textSettingsWidgetsTransparencySummary\">Nível de transparência de fundo dos Widgets.</string>\n  <string name=\"textSettingsMyShowsCountryTitle\">Região</string>\n  <string name=\"textSettingsMyShowsCountrySummary\">Selecione uma região para usar com os dados de serviços de streaming.</string>\n  <string name=\"textSettingsMyShowsLanguageTitle\">Idioma</string>\n  <string name=\"textSettingsMyShowsLanguageSummary\">Descrições e títulos serão traduzidos quando disponível. O aplicativo será reiniciado.</string>\n  <string name=\"textSettingsDateFormatTitle\">Formato data/hora</string>\n  <string name=\"textSettingsIncludeSpecialsTitle\">Temporadas especiais</string>\n  <string name=\"textSettingsIncludeSpecialsSummary\">Exibir temporadas \\'Especiais\\' na tela de detalhes da série.</string>\n  <string name=\"textSettingsNotifications\">Notificações</string>\n  <string name=\"textSettingsPushNotificationsTitle\">Notificações push</string>\n  <string name=\"textSettingsShowsNotificationsSummary\">Receber notificações sobre novos filmes, episódios, temporadas para séries que você está seguindo.</string>\n  <string name=\"textSettingsMisc\">Outros</string>\n  <string name=\"textSettingsContactDevsTitle\">Contate os desenvolvedores</string>\n  <string name=\"textSettingsContactDevsSummary\">Tem um problema? Clique aqui para abrir um problema no GitHub.</string>\n  <string name=\"textSettingsDeleteCacheTitle\">Limpar cache de imagens</string>\n  <string name=\"textSettingsDeleteCacheSummary\">Clique para limpar o cache de imagens locais. Isso pode ajudar se imagens não estão sendo carregadas corretamente.</string>\n  <string name=\"textSettingsQuickSyncConfirmationTitle\">Desativar a sincronização automática</string>\n  <string name=\"textSettingsQuickSyncConfirmationMessage\">\\nParece que a sincronização automática já está ativada.\\n\\nVocê gostaria de desativá-la antes de ativar a sincronização instantânea (recomendada)?\\n</string>\n  <string name=\"textSettingsLogoutTitle\">Trakt.tv Sair</string>\n  <string name=\"textSettingsLogoutMessage\">\\nTem certeza de que quer sair?\\n</string>\n  <string name=\"textSettingsMoviesEnabledTitle\">Filmes</string>\n  <string name=\"textSettingsMoviesEnabledSummary\">Ativar/desativar o suporte a filmes. O aplicativo reiniciará.</string>\n  <string name=\"textSettingsNewsEnabledTitle\">Notícias</string>\n  <string name=\"textSettingsNewsEnabledSummary\">Ativar/desativar a seção de notícias. O aplicativo será reiniciado.</string>\n  <string name=\"textSettingsWidgets\">Widgets</string>\n  <string name=\"textSettingsWidgetsLabelsTitle\">Títulos</string>\n  <string name=\"textSettingsWidgetsLabelsSummary\">Exibir títulos de widgets translúcidos.</string>\n  <string name=\"textSettingsStreamingsDisclaimer\">Dados de serviços de streaming são fornecidos por</string>\n  <string name=\"textSettingsStreamingServicesTitle\">Serviços de streaming</string>\n  <string name=\"textSettingsStreamingServicesMessage\">Exibir dados de serviços de streaming.</string>\n  <string name=\"textSettingsProgressNextEpisodeTitle\">Próximo Episódio</string>\n  <string name=\"textSettingsProgressNextEpisodeMessage\">Calcular próximo episódio usando:</string>\n  <string name=\"textNextEpisodeLastWatched\">Último episódio assistido</string>\n  <string name=\"textNextEpisodeOldest\">Episódio mais antigo não assistido</string>\n  <string name=\"textThemeDark\">Escuro</string>\n  <string name=\"textThemeLight\">Claro</string>\n  <string name=\"textThemeSystem\">Sistema</string>\n  <string name=\"textTransparency100\">Opaco</string>\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Вы успешно авторизовались.</string>\n  <string name=\"textTraktLogoutSuccess\">Вы вышли.</string>\n  <string name=\"textImagesCacheCleared\">Кэш изображений был очищен.</string>\n  <string name=\"textTurnOff\">Выключить</string>\n  <string name=\"textSettings\">Настройки</string>\n  <string name=\"textSettingsTrakt\">Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Авторизация Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignIn\">Нажмите, чтобы войти в свой аккаунт Trakt.tv.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOut\">Вход выполнен. Нажмите, чтобы выйти.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOutUser\">Вы вошли как %1$s. Нажмите, чтобы выйти.</string>\n  <string name=\"textSettingsTraktSyncTitle\">Синхронизация Trakt.tv</string>\n  <string name=\"textSettingsTraktSyncSummary\">Синхронизация данных между Trakt.tv и Showly.</string>\n  <string name=\"textSettingsTraktQuickSyncTitle\">Мгновенная загрузка Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickSyncSummary\">Мгновенная загрузка данных в Trakt.tv после добавления их в приложении.</string>\n  <string name=\"textSettingsTraktQuickRemoveTitle\">Удаление данных из Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickRemoveSummary\">Предложение удалить данные с вашего аккаунта Trakt.tv после удаления их в приложении.</string>\n  <string name=\"textSettingsTraktQuickRateTitle\">Быстрый рейтинг Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickRateSummary\">Оцените эпизоды и фильмы, когда вы помечаете их как просмотренные на экране «Прогресс».</string>\n  <string name=\"textSettingsGeneral\">Общие &amp; UX</string>\n  <string name=\"textSettingsRecentShowsTitle\">Количество недавно добавленных</string>\n  <string name=\"textSettingsRecentShowsSummary\">Количество недавно добавленных для отображения в \\'Коллекциях\\'.</string>\n  <string name=\"textSettingsUpcomingSectionTitle\">Секция Предстоящие</string>\n  <string name=\"textSettingsUpcomingSectionSummary\">Включить/выключить раздел «Предстоящие» на вкладке «Прогресс».</string>\n  <string name=\"textSettingsMyShowsSectionsTitle\">Вкладка Мои сериалы</string>\n  <string name=\"textSettingsMyShowsSectionSummary\">Включить/отключить секции во вкладке «Мои сериалы».</string>\n  <string name=\"textSettingsMyMoviesSectionsTitle\">Вкладка Мои фильмы</string>\n  <string name=\"textSettingsMyMoviesSectionSummary\">Включить/отключить секции во вкладке «Мои фильмы».</string>\n  <string name=\"textSettingsThemeTitle\">Тема</string>\n  <string name=\"textSettingsThemeSummary\">Выберите главную тему.</string>\n  <string name=\"textSettingsThemeWidgetsTitle\">Тема</string>\n  <string name=\"textSettingsThemeWidgetsSummary\">Выберите тему виджетов.</string>\n  <string name=\"textSettingsWidgetsTransparencyTitle\">Фон</string>\n  <string name=\"textSettingsWidgetsTransparencySummary\">Установить уровень прозрачности фона виджетов.</string>\n  <string name=\"textSettingsMyShowsCountryTitle\">Регион</string>\n  <string name=\"textSettingsMyShowsCountrySummary\">Выберите регион для использования с данными потоковых сервисов.</string>\n  <string name=\"textSettingsMyShowsLanguageTitle\">Язык</string>\n  <string name=\"textSettingsMyShowsLanguageSummary\">Описания и названия будут переведены, когда будут доступны. Приложение будет перезапущено.</string>\n  <string name=\"textSettingsDateFormatTitle\">Формат даты/времени</string>\n  <string name=\"textSettingsIncludeSpecialsTitle\">Специальные сезоны</string>\n  <string name=\"textSettingsIncludeSpecialsSummary\">Отображать \\\"Специальные\\\" сезоны в экране деталей сериала.</string>\n  <string name=\"textSettingsNotifications\">Уведомления</string>\n  <string name=\"textSettingsPushNotificationsTitle\">Push-уведомления</string>\n  <string name=\"textSettingsShowsNotificationsSummary\">Получать уведомления о новых фильмах, эпизодах, сезонах для сериалов, на которые вы подписаны.</string>\n  <string name=\"textSettingsShowsNotificationsRationale\">Пожалуйста, предоставьте приложению разрешение на отображение уведомлений.\\n\\nХотели бы вы сделать это сейчас?</string>\n  <string name=\"textSettingsMisc\">Прочее</string>\n  <string name=\"textSettingsContactDevsTitle\">Связаться с разработчиками</string>\n  <string name=\"textSettingsContactDevsSummary\">Возникли проблемы? Нажмите здесь, чтобы открыть вопрос на GitHub.</string>\n  <string name=\"textSettingsDeleteCacheTitle\">Очистить кеш изображений</string>\n  <string name=\"textSettingsDeleteCacheSummary\">Нажмите, чтобы очистить кэш локальных изображений. Это может помочь, если изображения загружаются некорректно.</string>\n  <string name=\"textSettingsQuickSyncConfirmationTitle\">Отключить автоматическую синхронизацию</string>\n  <string name=\"textSettingsQuickSyncConfirmationMessage\">\\nпохоже, автоматическая синхронизация уже включена.\\n\\nХотите отключить ее перед включением мгновенной синхронизации (рекомендуется)?\\n</string>\n  <string name=\"textSettingsLogoutTitle\">Выход из Trakt.tv</string>\n  <string name=\"textSettingsLogoutMessage\">\\nВы уверены, что хотите выйти?\\n</string>\n  <string name=\"textSettingsMoviesEnabledTitle\">Фильмы</string>\n  <string name=\"textSettingsMoviesEnabledSummary\">Включить/отключить поддержку фильмов. Приложение будет перезапущено.</string>\n  <string name=\"textSettingsNewsEnabledTitle\">Новости</string>\n  <string name=\"textSettingsNewsEnabledSummary\">Включить/отключить секцию новостей. Приложение будет перезапущено.</string>\n  <string name=\"textSettingsWidgets\">Виджеты</string>\n  <string name=\"textSettingsWidgetsLabelsTitle\">Названия</string>\n  <string name=\"textSettingsWidgetsLabelsSummary\">Отображение полупрозрачных заголовков виджетов.</string>\n  <string name=\"textSettingsStreamingsDisclaimer\">Данные сервисов вещания предоставляются</string>\n  <string name=\"textSettingsStreamingServicesTitle\">Стриминговые сервисы</string>\n  <string name=\"textSettingsStreamingServicesMessage\">Отображать данные потоковых служб.</string>\n  <string name=\"textSettingsProgressNextEpisodeTitle\">Следующая серия</string>\n  <string name=\"textSettingsProgressNextEpisodeMessage\">Вычислить следующий эпизод с помощью:</string>\n  <string name=\"textSettingsSpoilers\">Спойлеры</string>\n  <string name=\"textSettingsSpoilersShows\">Сериалы</string>\n  <string name=\"textSettingsSpoilersShowsSummary\">Управление настройками спойлеров для сериалов.</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyTitle\">Мои сериалы</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyDescription\">Скрыть <b>описание</b> сериалов, добавленных в Мои сериалы</string>\n  <string name=\"textSettingsSpoilersShowsRatingsMyDescription\">Скрыть <b>рейтинг</b> сериалов, добавленных в Мои сериалы</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistTitle\">Буду смотреть</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistDescription\">Скрыть <b>описание</b> сериалов, добавленных в Хочу посмотреть</string>\n  <string name=\"textSettingsSpoilersShowsRatingsWatchlistDescription\">Скрыть <b>рейтинг</b> сериалов, добавленных в Хочу посмотреть</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenTitle\">Скрытое</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenDescription\">Скрыть <b>описание</b> сериалов, добавленных в Скрытое</string>\n  <string name=\"textSettingsSpoilersShowsRatingsHiddenDescription\">Скрыть <b>рейтинг</b> сериалов, добавленных в Скрытое</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedTitle\">Не в коллекции</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedDescription\">Скрыть <b>описание</b> сериалов, которых нет в Коллекции</string>\n  <string name=\"textSettingsSpoilersShowsRatingsNotCollectedDescription\">Скрыть <b>рейтинг</b> сериалов, которых нет в Коллекции</string>\n  <string name=\"textSettingsSpoilersMovies\">Фильмы</string>\n  <string name=\"textSettingsSpoilersMoviesSummary\">Управление настройками спойлеров для фильмов.</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyTitle\">Мои фильмы</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyDescription\">Скрыть <b>описание</b> фильмов, добавленных в Мои фильмы</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsMyDescription\">Скрыть <b>рейтинг</b> фильмов, добавленных в Мои фильмы</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistTitle\">Буду смотреть</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistDescription\">Скрыть <b>описание</b> фильмов, добавленных в Хочу посмотреть</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsWatchlistDescription\">Скрыть <b>рейтинг</b> фильмов, добавленных в Хочу посмотреть</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenTitle\">Скрытое</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenDescription\">Скрыть <b>описание</b> фильмов, добавленных в Скрытое</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsHiddenDescription\">Скрыть <b>рейтинг</b> фильмов, добавленных в Скрытое</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedTitle\">Не в коллекции</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedDescription\">Скрыть <b>описание</b> фильмов, которых нет в Коллекции</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsNotCollectedDescription\">Скрыть <b>рейтинг</b> фильмов, которых нет в Коллекции</string>\n  <string name=\"textSettingsSpoilersEpisodes\">Эпизоды</string>\n  <string name=\"textSettingsSpoilersEpisodesSummary\">Управление настройками спойлеров для серий.</string>\n  <string name=\"textSettingsSpoilersEpisodesImage\">Скрыть <b>изображения</b> непросмотренных эпизодов.</string>\n  <string name=\"textSettingsSpoilersEpisodesTitle\">Скрыть <b>название</b> из непросмотренных эпизодов.</string>\n  <string name=\"textSettingsSpoilersEpisodesDescription\">Скрыть <b>описание</b> непросмотренных эпизодов.</string>\n  <string name=\"textSettingsSpoilersEpisodesRating\">Скрыть <b>рейтинг </b> непросмотренных эпизодов.</string>\n  <string name=\"textSettingsSpoilersTapToRevealTitle\">Нажмите, чтобы показать</string>\n  <string name=\"textSettingsSpoilersTapToRevealDescription\">Нажмите на скрытый контент, чтобы временно его отобразить.</string>\n  <string name=\"textNextEpisodeLastWatched\">Последняя просмотренная серия</string>\n  <string name=\"textNextEpisodeOldest\">Ранее непросмотренная серия</string>\n  <string name=\"textThemeDark\">Темная</string>\n  <string name=\"textThemeLight\">Светлая</string>\n  <string name=\"textThemeSystem\">Системная</string>\n  <string name=\"textTransparency100\">Непрозрачная</string>\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"settingsPremiumAdMargin\">24dp</dimen>\n  <dimen name=\"settingsValueMargin\">24dp</dimen>\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values-sw600dp/styles.xml",
    "content": "<resources>\n\n  <style name=\"Settings\" />\n\n  <style name=\"Settings.Category\" parent=\"Settings\">\n    <item name=\"android:layout_width\">wrap_content</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:paddingStart\">@dimen/spaceBig</item>\n    <item name=\"android:paddingEnd\">@dimen/spaceBig</item>\n    <item name=\"android:layout_marginTop\">@dimen/spaceBig</item>\n    <item name=\"android:textColor\">?attr/colorAccent</item>\n    <item name=\"android:textSize\">14sp</item>\n    <item name=\"android:textStyle\">bold</item>\n  </style>\n\n  <style name=\"Settings.ItemLayout\" parent=\"Settings\">\n    <item name=\"android:layout_width\">0dp</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:paddingStart\">@dimen/spaceBig</item>\n    <item name=\"android:paddingEnd\">@dimen/spaceBig</item>\n    <item name=\"android:paddingTop\">14dp</item>\n    <item name=\"android:paddingBottom\">14dp</item>\n    <item name=\"android:orientation\">vertical</item>\n    <item name=\"android:background\">?android:attr/selectableItemBackground</item>\n  </style>\n\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Başarılı bir şekilde yetkilendirdiniz.</string>\n  <string name=\"textTraktLogoutSuccess\">Çıkış yaptınız.</string>\n  <string name=\"textImagesCacheCleared\">Görüntü önbelleği temizlendi.</string>\n  <string name=\"textTurnOff\">Kapat</string>\n  <string name=\"textSettings\">Ayarlar</string>\n  <string name=\"textSettingsTrakt\">Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Trakt.tv Yetkilendirmesi</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignIn\">Trakt.tv hesabınızda oturum açmak için tıklayın.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOut\">Giriş yapıldı. Çıkış yapmak için tıklayın.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOutUser\">%1$s olarak giriş yapıldı.\\nÇıkış yapmak için tıklayın.</string>\n  <string name=\"textSettingsTraktSyncTitle\">Trakt.tv Eşitlemesi</string>\n  <string name=\"textSettingsTraktSyncSummary\">Trakt.tv ve Showly arasında verileri eşitleyin.</string>\n  <string name=\"textSettingsTraktQuickSyncTitle\">Trakt.tv Anlık Karşıya Yükleme</string>\n  <string name=\"textSettingsTraktQuickSyncSummary\">Verileri, uygulamaya ekledikten sonra anında Trakt.tv hizmetine yükleyin.</string>\n  <string name=\"textSettingsTraktQuickRemoveTitle\">Trakt.tv Verisini Sil</string>\n  <string name=\"textSettingsTraktQuickRemoveSummary\">Verilerin ygulamadan silinince Trakt.tv hesabınızdan da silinmesini önerir.</string>\n  <string name=\"textSettingsTraktQuickRateTitle\">Trakt.tv Hızlı Oy Verme</string>\n  <string name=\"textSettingsTraktQuickRateSummary\">Bölümleri ve filmleri \\\"İlerleme\\\" ekranında izlendi olarak işaretlediğinizde oy verin.</string>\n  <string name=\"textSettingsGeneral\">Genel ve Deneyim</string>\n  <string name=\"textSettingsRecentShowsTitle\">Son Eklenenler Miktarı</string>\n  <string name=\"textSettingsRecentShowsSummary\">\\'Koleksiyon\\'da görüntülenecek en son ögeler sayısı.</string>\n  <string name=\"textSettingsUpcomingSectionTitle\">Yaklaşan Bölümü</string>\n  <string name=\"textSettingsUpcomingSectionSummary\">\\'İlerleme\\' sekmesinde \\'Yaklaşan\\' bölümünü etkinleştir/devre dışı bırak.</string>\n  <string name=\"textSettingsMyShowsSectionsTitle\">Dizilerim Bölümleri</string>\n  <string name=\"textSettingsMyShowsSectionSummary\">\\'Dizilerim\\' sekmesindeki bölümleri etkinleştir/devre dışı bırak.</string>\n  <string name=\"textSettingsMyMoviesSectionsTitle\">Filmlerim Bölümleri</string>\n  <string name=\"textSettingsMyMoviesSectionSummary\">\\'Filmlerim\\' sekmesindeki bölümleri etkinleştir/devre dışı bırak.</string>\n  <string name=\"textSettingsThemeTitle\">Tema</string>\n  <string name=\"textSettingsThemeSummary\">Ana temayı seçin.</string>\n  <string name=\"textSettingsThemeWidgetsTitle\">Tema</string>\n  <string name=\"textSettingsThemeWidgetsSummary\">Widget\\'ların temasını seçin.</string>\n  <string name=\"textSettingsWidgetsTransparencyTitle\">Arka Plan</string>\n  <string name=\"textSettingsWidgetsTransparencySummary\">Widget\\'ların arka plan şeffaflık düzeyini ayarlayın.</string>\n  <string name=\"textSettingsMyShowsCountryTitle\">Bölge</string>\n  <string name=\"textSettingsMyShowsCountrySummary\">Akış hizmetleri verileriyle kullanılacak bölgeyi seçin.</string>\n  <string name=\"textSettingsMyShowsLanguageTitle\">Dil</string>\n  <string name=\"textSettingsMyShowsLanguageSummary\">Mevcut olduğunda başlıklar ve açıklamalar tercüme edilecektir. Uygulama yeniden başlatılacaktır.</string>\n  <string name=\"textSettingsDateFormatTitle\">Tarih/Saat Biçimi</string>\n  <string name=\"textSettingsIncludeSpecialsTitle\">Özel Sezonlar</string>\n  <string name=\"textSettingsIncludeSpecialsSummary\">Dizinin ayrıntılar ekranında \\'Özel\\' sezonları görüntüleyin.</string>\n  <string name=\"textSettingsNotifications\">Bildirimler</string>\n  <string name=\"textSettingsPushNotificationsTitle\">Anlık Bildirimler</string>\n  <string name=\"textSettingsShowsNotificationsSummary\">Takip ettiğiniz dizilerin yeni bölümleri/sezonları ve yeni filmler hakkında bildirimler alın.</string>\n  <string name=\"textSettingsShowsNotificationsRationale\">Lütfen uygulamaya bildirim göstermesi için izin verin\\n\\nBunu şimdi yapmak ister misiniz?</string>\n  <string name=\"textSettingsMisc\">Çeşitli</string>\n  <string name=\"textSettingsContactDevsTitle\">Geliştiriciler İle İletişime Geç</string>\n  <string name=\"textSettingsContactDevsSummary\">Bir sorununuz mu var? GitHub\\'da bir sayı açmak için buraya tıklayın.</string>\n  <string name=\"textSettingsDeleteCacheTitle\">Görüntü Önbelleğini Temizle</string>\n  <string name=\"textSettingsDeleteCacheSummary\">Yerel görüntü önbelleğini temizlemek için dokunun. Bu, dizi görüntülerinin doğru yüklenmediğinde yardımcı olabilir.</string>\n  <string name=\"textSettingsQuickSyncConfirmationTitle\">Otomatik Eşitlemeyi Devre Dışı Bırak</string>\n  <string name=\"textSettingsQuickSyncConfirmationMessage\">\\nOtomatik Eşitleme zaten açık gibi görünüyor.\\n\\nAnlık eşitlemeyi etkinleştirmeden önce bu özelliği kapatmak ister misiniz (önerilir)?\\n</string>\n  <string name=\"textSettingsLogoutTitle\">Trakt.tv Çıkış Yap</string>\n  <string name=\"textSettingsLogoutMessage\">\\nOturumu kapatmak istediğinizden emin misiniz?\\n</string>\n  <string name=\"textSettingsMoviesEnabledTitle\">Filmler</string>\n  <string name=\"textSettingsMoviesEnabledSummary\">Film desteğini etkinleştirin/devre dışı bırakın. Uygulama yeniden başlatılacaktır.</string>\n  <string name=\"textSettingsNewsEnabledTitle\">Haberler</string>\n  <string name=\"textSettingsNewsEnabledSummary\">Haberler bölümünü etkinleştir/devre dışı bırak. Uygulama tekrar başlatılacaktır.</string>\n  <string name=\"textSettingsWidgets\">Widget’lar</string>\n  <string name=\"textSettingsWidgetsLabelsTitle\">Başlıklar</string>\n  <string name=\"textSettingsWidgetsLabelsSummary\">Yarı saydam widget başlıklarını görüntüleyin.</string>\n  <string name=\"textSettingsStreamingsDisclaimer\">Akış hizmetleri verileri tarafından sağlanır</string>\n  <string name=\"textSettingsStreamingServicesTitle\">Yayın Servisleri</string>\n  <string name=\"textSettingsStreamingServicesMessage\">Yayın hizmetleri verilerini görüntüleyin.</string>\n  <string name=\"textSettingsProgressNextEpisodeTitle\">Sonraki Bölüm</string>\n  <string name=\"textSettingsProgressNextEpisodeMessage\">Sonraki bölümü hesaplamakta şunu kullan:</string>\n  <string name=\"textSettingsSpoilers\">Spoiler</string>\n  <string name=\"textSettingsSpoilersShows\">Diziler</string>\n  <string name=\"textSettingsSpoilersShowsSummary\">Diziler için spoiler ayarlarını yönet.</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyTitle\">Dizilerim</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyDescription\">Dizilerim sekmesine eklenen diziler için <b>açıklamayı</b> gizle</string>\n  <string name=\"textSettingsSpoilersShowsRatingsMyDescription\">Dizilerim sekmesine eklenen diziler için <b>derecelendirmeyi</b> gizle</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistTitle\">İstek Listesi</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistDescription\">İzleme listesine eklenen diziler için <b>açıklamayı</b> gizle</string>\n  <string name=\"textSettingsSpoilersShowsRatingsWatchlistDescription\">İzleme listesine eklenen diziler için <b>derecelendirmeyi</b> gizle</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenTitle\">Gizlenmiş</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenDescription\">Gizli bölümüne eklenen diziler için <b>açıklamayı</b> gizle</string>\n  <string name=\"textSettingsSpoilersShowsRatingsHiddenDescription\">Gizli bölümüne eklenen diziler için <b>derecelendirmeyi</b> gizle</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedTitle\">Listelerinizde Olmayanlar</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedDescription\">Listelerinizde olmayan diziler için <b>açıklamayı</b> gizle</string>\n  <string name=\"textSettingsSpoilersShowsRatingsNotCollectedDescription\">Listelerinizde olmayan diziler için <b>derecelendirmeyi</b> gizle</string>\n  <string name=\"textSettingsSpoilersMovies\">Filmler</string>\n  <string name=\"textSettingsSpoilersMoviesSummary\">Filmler için spoiler ayarlarını yönet.</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyTitle\">Filmlerim</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyDescription\">Filmlerim sekmesine eklenen diziler için <b>açıklamayı</b> gizle</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsMyDescription\">Filmlerim sekmesine eklenen diziler için <b>derecelendirmeyi</b> gizle</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistTitle\">İstek Listesi</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistDescription\">İzleme listesine eklenen filmler için <b>açıklamayı</b> gizle</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsWatchlistDescription\">İzleme listesine eklenen filmler için <b>derecelendirmeyi</b> gizle</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenTitle\">Gizlenmiş</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenDescription\">Gizli bölümüne eklenen filmler için <b>açıklamayı</b> gizle</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsHiddenDescription\">Gizli bölümüne eklenen filmler için <b>derecelendirmeyi</b> gizle</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedTitle\">Listelerinizde Olmayanlar</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedDescription\">Listelerinizde olmayan filmler için <b>açıklamayı</b> gizle</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsNotCollectedDescription\">Listelerinizde olmayan filmler için <b>derecelendirmeyi</b> gizle</string>\n  <string name=\"textSettingsSpoilersEpisodes\">Bölüm</string>\n  <string name=\"textSettingsSpoilersEpisodesSummary\">Bölümler için spoiler ayarlarını yönet.</string>\n  <string name=\"textSettingsSpoilersEpisodesImage\">Seyredilmemiş bölümlerin <b>görüntüsünü</b> saklayın.</string>\n  <string name=\"textSettingsSpoilersEpisodesTitle\">Seyredilmemiş bölümlerin <b>başlığını</b> saklayın.</string>\n  <string name=\"textSettingsSpoilersEpisodesDescription\">Seyredilmemiş bölümlerin <b>açıklamasını</b> saklayın.</string>\n  <string name=\"textSettingsSpoilersEpisodesRating\">Seyredilmemiş bölümlerin <b>değerlendirmesini</b> saklayın.</string>\n  <string name=\"textSettingsSpoilersTapToRevealTitle\">Göstermek için Dokunun</string>\n  <string name=\"textSettingsSpoilersTapToRevealDescription\">Geçici olarak göstermek için gizlenen içeriğe dokunun.</string>\n  <string name=\"textNextEpisodeLastWatched\">İzlenen son bölüm</string>\n  <string name=\"textNextEpisodeOldest\">İzlenmeyen en eski bölüm</string>\n  <string name=\"textThemeDark\">Karanlık</string>\n  <string name=\"textThemeLight\">Aydınlık</string>\n  <string name=\"textThemeSystem\">Sistem</string>\n  <string name=\"textTransparency100\">Mat</string>\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Ви успішно авторизовані.</string>\n  <string name=\"textTraktLogoutSuccess\">Ви вийшли з облікового запису.</string>\n  <string name=\"textImagesCacheCleared\">Кеш зображень очищено.</string>\n  <string name=\"textTurnOff\">Вимкнути</string>\n  <string name=\"textSettings\">Налаштування</string>\n  <string name=\"textSettingsTrakt\">Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Авторизація Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignIn\">Натисніть для входу до Вашого облікового запису Trakt.tv.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOut\">Ви увійшли. Натисніть, щоб вийти.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOutUser\">Ви увійшли як %1$s. Натисніть, щоб вийти.</string>\n  <string name=\"textSettingsTraktSyncTitle\">Синхронізація Trakt.tv</string>\n  <string name=\"textSettingsTraktSyncSummary\">Синхронізація даних між Trakt.tv і Showly.</string>\n  <string name=\"textSettingsTraktQuickSyncTitle\">Миттєве вивантаження Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickSyncSummary\">Миттєво вивантажуйте дані на Trakt.tv після додавання їх в додатку.</string>\n  <string name=\"textSettingsTraktQuickRemoveTitle\">Видалення даних з Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickRemoveSummary\">Пропонувати видалити дані з вашого облікового запису Trakt.tv після видалення їх у додатку.</string>\n  <string name=\"textSettingsTraktQuickRateTitle\">Швидка оцінка Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickRateSummary\">Оцінюйте серії та фільми, коли ви позначаєте їх як переглянуті на екрані \\\"Прогрес\\\".</string>\n  <string name=\"textSettingsGeneral\">Загальні &amp; UX</string>\n  <string name=\"textSettingsRecentShowsTitle\">Кількість нещодавно доданих</string>\n  <string name=\"textSettingsRecentShowsSummary\">Кількість нещодавно доданих елементів для показу в розділі \\'Колекція\\'.</string>\n  <string name=\"textSettingsUpcomingSectionTitle\">Розділ Незабаром</string>\n  <string name=\"textSettingsUpcomingSectionSummary\">Увімкнути/вимкнути розділ \\'Незабаром\\' у вкладці \\'Прогрес\\'.</string>\n  <string name=\"textSettingsMyShowsSectionsTitle\">Мої Серіали - Розділи</string>\n  <string name=\"textSettingsMyShowsSectionSummary\">Увімкнути/вимкнути розділи на вкладці \\'Мої Серіали\\'.</string>\n  <string name=\"textSettingsMyMoviesSectionsTitle\">Мої Фільми - Розділи</string>\n  <string name=\"textSettingsMyMoviesSectionSummary\">Увімкнути/вимкнути розділи на вкладці \\'Мої Фільми\\'.</string>\n  <string name=\"textSettingsThemeTitle\">Тема</string>\n  <string name=\"textSettingsThemeSummary\">Оберіть основну тему.</string>\n  <string name=\"textSettingsThemeWidgetsTitle\">Тема</string>\n  <string name=\"textSettingsThemeWidgetsSummary\">Оберіть тему віджетів.</string>\n  <string name=\"textSettingsWidgetsTransparencyTitle\">Фон</string>\n  <string name=\"textSettingsWidgetsTransparencySummary\">Встановити рівень прозорості фону віджетів.</string>\n  <string name=\"textSettingsMyShowsCountryTitle\">Регіон</string>\n  <string name=\"textSettingsMyShowsCountrySummary\">Виберіть регіон для використання з даними стрімінгових сервісів.</string>\n  <string name=\"textSettingsMyShowsLanguageTitle\">Мова</string>\n  <string name=\"textSettingsMyShowsLanguageSummary\">Описи та заголовки будуть перекладені, якщо вони доступні. Додаток перезавантажиться.</string>\n  <string name=\"textSettingsDateFormatTitle\">Формат дати та часу</string>\n  <string name=\"textSettingsIncludeSpecialsTitle\">Спеціальні сезони</string>\n  <string name=\"textSettingsIncludeSpecialsSummary\">Показувати \\'Спеціальні\\' сезони на екрані інформації про серіал.</string>\n  <string name=\"textSettingsNotifications\">Сповіщення</string>\n  <string name=\"textSettingsPushNotificationsTitle\">Push сповіщення</string>\n  <string name=\"textSettingsShowsNotificationsSummary\">Отримувати сповіщення про нові фільми, серії, сезони, за якими ви стежите.</string>\n  <string name=\"textSettingsShowsNotificationsRationale\">Будь ласка, надайте додатку дозвіл на показ сповіщень.\\n\\nВи хотіли б зробити це зараз?</string>\n  <string name=\"textSettingsMisc\">Інше</string>\n  <string name=\"textSettingsContactDevsTitle\">Зв\\'язатися з розробниками</string>\n  <string name=\"textSettingsContactDevsSummary\">Є проблема? Натисніть, щоб надіслати нам листа (showlyapp@gmail.com).</string>\n  <string name=\"textSettingsRateAppTitle\">Залишити відгук</string>\n  <string name=\"textSettingsRateAppSummary\">Подобається Showly? Залиште свій відгук у Play Store. Дякуємо!</string>\n  <string name=\"textSettingsDeleteCacheTitle\">Очистити кеш зображень</string>\n  <string name=\"textSettingsDeleteCacheSummary\">Натисніть, щоб очистити кеш локальних зображень. Це може допомогти, якщо зображення серіалів не завантажуються коректно.</string>\n  <string name=\"textSettingsQuickSyncConfirmationTitle\">Вимкнути автоматичну синхронізацію</string>\n  <string name=\"textSettingsQuickSyncConfirmationMessage\">\\nСхоже, що автоматична синхронізація вже ввімкнена.\\n\\nБажаєте вимкнути її перед увімкненням миттєвої синхронізації (рекомендовано)?\\n</string>\n  <string name=\"textSettingsLogoutTitle\">Вихід з Trakt.tv</string>\n  <string name=\"textSettingsLogoutMessage\">\\nВи дійсно бажаєте вийти?\\n</string>\n  <string name=\"textSettingsMoviesEnabledTitle\">Фільми</string>\n  <string name=\"textSettingsMoviesEnabledSummary\">Увімкнути/вимкнути підтримку фільмів. Додаток перезавантажиться.</string>\n  <string name=\"textSettingsNewsEnabledTitle\">Новини</string>\n  <string name=\"textSettingsNewsEnabledSummary\">Увімкнути/вимкнути розділ новин. Додаток перезавантажиться.</string>\n  <string name=\"textSettingsWidgets\">Віджети</string>\n  <string name=\"textSettingsWidgetsLabelsTitle\">Назви</string>\n  <string name=\"textSettingsWidgetsLabelsSummary\">Показувати напівпрозорі назви віджетів.</string>\n  <string name=\"textSettingsStreamingsDisclaimer\">Дані стримінгових сервісів надаються</string>\n  <string name=\"textSettingsStreamingServicesTitle\">Стрімінгові сервіси</string>\n  <string name=\"textSettingsStreamingServicesMessage\">Показувати дані стрімінгових сервісів.</string>\n  <string name=\"textSettingsProgressNextEpisodeTitle\">Наступна серія</string>\n  <string name=\"textSettingsProgressNextEpisodeMessage\">Розрахувати наступну серію за допомогою:</string>\n  <string name=\"textSettingsTabletColumnsTitle\">Кількість стовпців у списках</string>\n  <string name=\"textSettingsTabletColumnsSummary\">Виберіть бажану кількість стовпців для списків.</string>\n  <string name=\"textSettingsSpoilers\">Спойлери</string>\n  <string name=\"textSettingsSpoilersShows\">Серіали</string>\n  <string name=\"textSettingsSpoilersShowsSummary\">Керування налаштуваннями спойлерів для серіалів.</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyTitle\">Мої Серіали</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyDescription\">Приховати <b>опис</b> серіалів, доданих в Мої Серіали</string>\n  <string name=\"textSettingsSpoilersShowsRatingsMyDescription\">Приховати <b>оцінку</b> серіалів, доданих в Мої Серіали</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistTitle\">На потім</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistDescription\">Приховати <b>опис</b> серіалів, доданих в На потім</string>\n  <string name=\"textSettingsSpoilersShowsRatingsWatchlistDescription\">Приховати <b>оцінку</b> серіалів, доданих в На потім</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenTitle\">Приховане</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenDescription\">Приховати <b>опис</b> серіалів, доданих в Приховане</string>\n  <string name=\"textSettingsSpoilersShowsRatingsHiddenDescription\">Приховати <b>оцінку</b> серіалів, доданих в Приховане</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedTitle\">Не в Колекції</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedDescription\">Приховати <b>опис</b> серіалів, які не додані до вашої Колекції</string>\n  <string name=\"textSettingsSpoilersShowsRatingsNotCollectedDescription\">Приховати <b>оцінку</b> серіалів, які не додані до вашої Колекції</string>\n  <string name=\"textSettingsSpoilersMovies\">Фільми</string>\n  <string name=\"textSettingsSpoilersMoviesSummary\">Керування налаштуваннями спойлерів для фільмів.</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyTitle\">Мої Фільми</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyDescription\">Приховати <b>опис</b> фільмів, доданих в Мої Фільми</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsMyDescription\">Приховати <b>оцінку</b> фільмів, доданих в Мої Фільми</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistTitle\">На потім</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistDescription\">Приховати <b>опис</b> фільмів, доданих в На потім</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsWatchlistDescription\">Приховати <b>оцінку</b> фільмів, доданих в На потім</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenTitle\">Приховане</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenDescription\">Приховати <b>опис</b> фільмів, доданих в Приховане</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsHiddenDescription\">Приховати <b>оцінку</b> фільмів, доданих в Приховане</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedTitle\">Не в Колекції</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedDescription\">Приховати <b>опис</b> фільмів, які не додані до вашої Колекції</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsNotCollectedDescription\">Приховати <b>оцінку</b> фільмів, які не додані до вашої Колекції</string>\n  <string name=\"textSettingsSpoilersEpisodes\">Серії</string>\n  <string name=\"textSettingsSpoilersEpisodesSummary\">Керування налаштуваннями спойлерів для серій.</string>\n  <string name=\"textSettingsSpoilersEpisodesImage\">Приховати <b>зображення</b> непереглянутих серій.</string>\n  <string name=\"textSettingsSpoilersEpisodesTitle\">Приховати <b>назву</b> непереглянутих серій.</string>\n  <string name=\"textSettingsSpoilersEpisodesDescription\">Приховати <b>опис</b> непереглянутих серій.</string>\n  <string name=\"textSettingsSpoilersEpisodesRating\">Приховати <b>оцінку</b> непереглянутих серій.</string>\n  <string name=\"textSettingsSpoilersTapToRevealTitle\">Показувати після дотику</string>\n  <string name=\"textSettingsSpoilersTapToRevealDescription\">Торкніться прихованого вмісту, щоб тимчасово показати його.</string>\n  <string name=\"textNextEpisodeLastWatched\">Остання переглянута серія</string>\n  <string name=\"textNextEpisodeOldest\">Найстаріша непереглянута серія</string>\n  <string name=\"textThemeDark\">Темна</string>\n  <string name=\"textThemeLight\">Світла</string>\n  <string name=\"textThemeSystem\">Системна</string>\n  <string name=\"textTransparency100\">Непрозорий</string>\n</resources>\n"
  },
  {
    "path": "ui-settings/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Bạn đã được ủy quyền thành công.</string>\n  <string name=\"textTraktLogoutSuccess\">Bạn đã đăng xuất.</string>\n  <string name=\"textImagesCacheCleared\">Bộ nhớ đệm hình ảnh đã được dọn dẹp.</string>\n  <string name=\"textTurnOff\">Tắt</string>\n\n  <string name=\"textSettings\">Thiết đặt</string>\n  <string name=\"textSettingsTrakt\">Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Ủy quyền của Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignIn\">Nhấp để đăng nhập vào tài khoản Trakt.tv của bạn.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOut\">Đã đăng nhập. Nhấp để đăng xuất.</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOutUser\">Đã đăng nhập bằng %1$s. Bấm để đăng xuất.</string>\n  <string name=\"textSettingsTraktSyncTitle\">Đồng bộ hóa Trakt.tv</string>\n  <string name=\"textSettingsTraktSyncSummary\">Đồng bộ dữ liệu giữa Trakt.tv và Showly.</string>\n  <string name=\"textSettingsTraktQuickSyncTitle\">Tải lên tức thì Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickSyncSummary\">Tải dữ liệu ngay lập tức lên Trakt.tv sau khi bạn thêm dữ liệu đó vào ứng dụng.</string>\n  <string name=\"textSettingsTraktQuickRemoveTitle\">Xóa dữ liệu Trakt.tv</string>\n  <string name=\"textSettingsTraktQuickRemoveSummary\">Đề nghị xóa dữ liệu khỏi tài khoản Trakt.tv của bạn sau khi bạn xóa dữ liệu đó trong ứng dụng.</string>\n  <string name=\"textSettingsTraktQuickRateTitle\">Trakt.tv Xếp hạng Nhanh</string>\n  <string name=\"textSettingsTraktQuickRateSummary\">Xếp hạng các tập và phim bất cứ khi nào bạn đánh dấu chúng là đã xem trên màn hình \\'Tiến độ\\'.</string>\n  <string name=\"textSettingsGeneral\">Tổng quan &amp; UX</string>\n  <string name=\"textSettingsRecentShowsTitle\">Số lượng được thêm gần đây</string>\n  <string name=\"textSettingsRecentShowsSummary\">Số mục gần đây sẽ hiển thị trong \\'Bộ sưu tập\\'.</string>\n  <string name=\"textSettingsUpcomingSectionTitle\">Mục sắp tới</string>\n  <string name=\"textSettingsUpcomingSectionSummary\">Bật/tắt phần \\'Sắp tới\\' trong tab \\'Tiến độ\\'.</string>\n  <string name=\"textSettingsMyShowsSectionsTitle\">Mục chương trình của tôi</string>\n  <string name=\"textSettingsMyShowsSectionSummary\">Bật/tắt các mục trong tab \\'Chương trình của tôi\\'.</string>\n  <string name=\"textSettingsMyMoviesSectionsTitle\">Mục phim của tôi</string>\n  <string name=\"textSettingsMyMoviesSectionSummary\">Bật/tắt các mục trong tab \\'Phim của tôi\\'.</string>\n  <string name=\"textSettingsThemeTitle\">Chủ đề</string>\n  <string name=\"textSettingsThemeSummary\">Chọn chủ đề chính.</string>\n  <string name=\"textSettingsThemeWidgetsTitle\">Chủ đề</string>\n  <string name=\"textSettingsThemeWidgetsSummary\">Chọn chủ đề tiện ích.</string>\n  <string name=\"textSettingsWidgetsTransparencyTitle\">Nền</string>\n  <string name=\"textSettingsWidgetsTransparencySummary\">Đặt mức độ trong suốt của nền tiện ích.</string>\n  <string name=\"textSettingsMyShowsCountryTitle\">Khu vực</string>\n  <string name=\"textSettingsMyShowsCountrySummary\">Chọn vùng để sử dụng với dữ liệu dịch vụ phát trực tuyến.</string>\n  <string name=\"textSettingsMyShowsLanguageTitle\">Ngôn ngữ</string>\n  <string name=\"textSettingsMyShowsLanguageSummary\">Mô tả và tiêu đề sẽ được dịch khi có sẵn. Ứng dụng sẽ khởi động lại.</string>\n  <string name=\"textSettingsDateFormatTitle\">Định dạng ngày/giờ</string>\n  <string name=\"textSettingsIncludeSpecialsTitle\">Các mùa đặc biệt</string>\n  <string name=\"textSettingsIncludeSpecialsSummary\">Hiển thị các mùa \\'Đặc biệt\\' trong màn hình chi tiết của chương trình.</string>\n  <string name=\"textSettingsNotifications\">Thông báo</string>\n  <string name=\"textSettingsPushNotificationsTitle\">Thông báo đẩy</string>\n  <string name=\"textSettingsShowsNotificationsSummary\">Nhận thông báo về phim, tập, mùa mới của chương trình bạn đang theo dõi.</string>\n  <string name=\"textSettingsShowsNotificationsRationale\">Vui lòng cấp cho ứng dụng quyền hiển thị thông báo.\\n\\nBạn có muốn thực hiện việc đó ngay bây giờ không?</string>\n  <string name=\"textSettingsMisc\">Linh tinh</string>\n  <string name=\"textSettingsContactDevsTitle\">Liên hệ với nhà phát triển</string>\n  <string name=\"textSettingsContactDevsSummary\">Tìm thấy một vấn đề? Vui lòng mở một vấn đề trên GitHub.</string>\n  <string name=\"textSettingsDeleteCacheTitle\">Dọn dẹp bộ nhớ đệm hình ảnh</string>\n  <string name=\"textSettingsDeleteCacheSummary\">Nhấp để dọn dẹp bộ nhớ đệm hình ảnh cục bộ. Điều này có thể hữu ích nếu hiển thị hình ảnh không được tải chính xác.</string>\n  <string name=\"textSettingsQuickSyncConfirmationTitle\">Tắt đồng bộ hóa tự động</string>\n  <string name=\"textSettingsQuickSyncConfirmationMessage\">\\nCó vẻ như tính năng Đồng bộ hóa tự động đã được bật.\\n\\nBạn có muốn tắt tính năng này trước khi bật đồng bộ hóa tức thì (được khuyên) không?\\n</string>\n  <string name=\"textSettingsLogoutTitle\">Đăng xuất Trakt.tv</string>\n  <string name=\"textSettingsLogoutMessage\">\\nBạn có chắc chắn muốn đăng xuất không?\\n</string>\n  <string name=\"textSettingsMoviesEnabledTitle\">Phim</string>\n  <string name=\"textSettingsMoviesEnabledSummary\">Bật/tắt hỗ trợ phim. Ứng dụng sẽ khởi động lại.</string>\n  <string name=\"textSettingsNewsEnabledTitle\">Tin tức</string>\n  <string name=\"textSettingsNewsEnabledSummary\">Bật/tắt mục tin tức. Ứng dụng sẽ khởi động lại.</string>\n  <string name=\"textSettingsWidgets\">Tiện ích</string>\n  <string name=\"textSettingsWidgetsLabelsTitle\">Tiêu đề</string>\n  <string name=\"textSettingsWidgetsLabelsSummary\">Hiển thị tiêu đề tiện ích mờ.</string>\n  <string name=\"textSettingsStreamingsDisclaimer\">Dữ liệu dịch vụ phát trực tuyến được cung cấp bởi</string>\n  <string name=\"textSettingsStreamingServicesTitle\">Dịch vụ phát trực tuyến</string>\n  <string name=\"textSettingsStreamingServicesMessage\">Hiển thị dữ liệu dịch vụ phát trực tuyến.</string>\n  <string name=\"textSettingsProgressNextEpisodeTitle\">Tập tiếp theo</string>\n  <string name=\"textSettingsProgressNextEpisodeMessage\">Tính tập tiếp theo bằng cách sử dụng:</string>\n  <string name=\"textSettingsTabletColumnsTitle\">Số cột danh sách</string>\n  <string name=\"textSettingsTabletColumnsSummary\">Chọn số cột ưa thích cho danh sách.</string>\n\n  <string name=\"textSettingsSpoilers\">Tiết lộ nội dung</string>\n\n  <string name=\"textSettingsSpoilersShows\">Chương trình</string>\n  <string name=\"textSettingsSpoilersShowsSummary\">Quản lý thiết đặt tiết lộ nội dung cho chương trình.</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyTitle\">Chương trình của tôi</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsMyDescription\">Ẩn <b>mô tả</b> những chương trình được thêm vào Chương trình của tôi</string>\n  <string name=\"textSettingsSpoilersShowsRatingsMyDescription\">Ẩn <b>xếp hạng</b> những chương trình được thêm vào Chương trình của tôi</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistTitle\">Danh sách theo dõi</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsWatchlistDescription\">Ẩn <b>mô tả</b> những chương trình được thêm vào Danh sách theo dõi</string>\n  <string name=\"textSettingsSpoilersShowsRatingsWatchlistDescription\">Ẩn <b>xếp hạng</b> những chương trình được thêm vào Danh sách theo dõi</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenTitle\">Ẩn</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsHiddenDescription\">Ẩn <b>mô tả</b> những chương trình được thêm vào Ẩn</string>\n  <string name=\"textSettingsSpoilersShowsRatingsHiddenDescription\">Ẩn <b>xếp hạng</b> những chương trình được thêm vào Ẩn</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedTitle\">Không được thu thập</string>\n  <string name=\"textSettingsSpoilersShowsOverviewsNotCollectedDescription\">Ẩn <b>mô tả</b> những chương trình không có trong Bộ sưu tập của bạn</string>\n  <string name=\"textSettingsSpoilersShowsRatingsNotCollectedDescription\">Ẩn <b>xếp hạng</b> những chương trình không có trong Bộ sưu tập của bạn</string>\n\n  <string name=\"textSettingsSpoilersMovies\">Phim</string>\n  <string name=\"textSettingsSpoilersMoviesSummary\">Quản lý thiết đặt tiết lộ nội dung cho phim.</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyTitle\">Phim của tôi</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsMyDescription\">Ẩn <b>mô tả</b> những phim được thêm vào Phim của tôi</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsMyDescription\">Ẩn <b>xếp hạng</b> những phim được thêm vào Phim của tôi</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistTitle\">Danh sách theo dõi</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsWatchlistDescription\">Ẩn <b>mô tả</b> những phim được thêm vào Danh sách theo dõi</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsWatchlistDescription\">Ẩn <b>xếp hạng</b> những phim được thêm vào Danh sách theo dõi</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenTitle\">Ẩn</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsHiddenDescription\">Ẩn <b>mô tả</b> những phim được thêm vào Ẩn</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsHiddenDescription\">Ẩn <b>xếp hạng</b> những phim được thêm vào Ẩn</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedTitle\">Không được thu thập</string>\n  <string name=\"textSettingsSpoilersMoviesOverviewsNotCollectedDescription\">Ẩn <b>mô tả</b> những phim không có trong Bộ sưu tập của bạn</string>\n  <string name=\"textSettingsSpoilersMoviesRatingsNotCollectedDescription\">Ẩn <b>xếp hạng</b> những phim không có trong Bộ sưu tập của bạn</string>\n\n  <string name=\"textSettingsSpoilersEpisodes\">Các tập</string>\n  <string name=\"textSettingsSpoilersEpisodesSummary\">Quản lý thiết đặt tiết lộ nội dung cho các tập.</string>\n  <string name=\"textSettingsSpoilersEpisodesImage\">Ẩn <b>hình ảnh</b> những tập chưa xem.</string>\n  <string name=\"textSettingsSpoilersEpisodesTitle\">Ẩn <b>tiêu đề</b> những tập chưa xem.</string>\n  <string name=\"textSettingsSpoilersEpisodesDescription\">Ẩn <b>mô tả</b> những tập chưa xem.</string>\n  <string name=\"textSettingsSpoilersEpisodesRating\">Ẩn <b>xếp hạng</b> những tập chưa xem.</string>\n\n  <string name=\"textSettingsSpoilersTapToRevealTitle\">Nhấn để tiết lộ</string>\n  <string name=\"textSettingsSpoilersTapToRevealDescription\">Nhấn vào nội dung ẩn để tạm thời tiết lộ nó.</string>\n\n  <string name=\"textNextEpisodeLastWatched\">Tập được xem lần cuối</string>\n  <string name=\"textNextEpisodeOldest\">Tập chưa xem lâu đời nhất</string>\n\n  <string name=\"textThemeDark\">Tối</string>\n  <string name=\"textThemeLight\">Sáng</string>\n  <string name=\"textThemeSystem\">Hệ thống</string>\n  <string name=\"textTransparency0\" translatable=\"false\">100%</string>\n  <string name=\"textTransparency25\" translatable=\"false\">25%</string>\n  <string name=\"textTransparency50\" translatable=\"false\">50%</string>\n  <string name=\"textTransparency75\" translatable=\"false\">75%</string>\n  <string name=\"textTransparency100\">Đục</string>\n</resources>"
  },
  {
    "path": "ui-settings/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">您已经授权成功。</string>\n  <string name=\"textTraktLogoutSuccess\">您已经登出。</string>\n  <string name=\"textImagesCacheCleared\">图像缓存已清理。</string>\n  <string name=\"textTurnOff\">关闭</string>\n  <string name=\"textSettings\">设置</string>\n  <string name=\"textSettingsTrakt\">Trakt.tv</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Trakt.tv 授权</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignIn\">点击登录您的 Trakt.tv 账号。</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOut\">已登录。点击登出。</string>\n  <string name=\"textSettingsTraktAuthorizeSummarySignOutUser\">已以用户： %1$s 登录，点击登出。</string>\n  <string name=\"textSettingsTraktSyncTitle\">Trakt.tv 同步</string>\n  <string name=\"textSettingsTraktSyncSummary\">双向同步 Trakt.tv 和 Showly 的数据。</string>\n  <string name=\"textSettingsTraktQuickSyncTitle\">Trakt.tv 即时上传</string>\n  <string name=\"textSettingsTraktQuickSyncSummary\">在此应用中添加数据后，立即上传同步到 Trakt.tv。</string>\n  <string name=\"textSettingsTraktQuickRemoveTitle\">Trakt.tv 数据移除</string>\n  <string name=\"textSettingsTraktQuickRemoveSummary\">提供在您移除应用的数据后，同步移除 Trakt.tv 账号中相应数据的功能。</string>\n  <string name=\"textSettingsTraktQuickRateTitle\">Trakt.tv 快速评分</string>\n  <string name=\"textSettingsTraktQuickRateSummary\">每当您在「进度」页面上标记剧集和电影为已看时，对它们进行评分。</string>\n  <string name=\"textSettingsGeneral\">常规 &amp; 用户体验</string>\n  <string name=\"textSettingsRecentShowsTitle\">最近添加的数量</string>\n  <string name=\"textSettingsRecentShowsSummary\">「合集」中最近添加项目的展示数量。</string>\n  <string name=\"textSettingsUpcomingSectionTitle\">即将开播模块</string>\n  <string name=\"textSettingsUpcomingSectionSummary\">在「进度」选项卡中启用/禁用「即将开播」模块。</string>\n  <string name=\"textSettingsMyShowsSectionsTitle\">我的剧集模块</string>\n  <string name=\"textSettingsMyShowsSectionSummary\">在「我的剧集」选项卡中启用/禁用模块。</string>\n  <string name=\"textSettingsMyMoviesSectionsTitle\">我的电影模块</string>\n  <string name=\"textSettingsMyMoviesSectionSummary\">在「我的电影」选项卡中启用/禁用模块。</string>\n  <string name=\"textSettingsThemeTitle\">主题</string>\n  <string name=\"textSettingsThemeSummary\">选择首要主题。</string>\n  <string name=\"textSettingsThemeWidgetsTitle\">主题</string>\n  <string name=\"textSettingsThemeWidgetsSummary\">选择桌面小部件主题。</string>\n  <string name=\"textSettingsWidgetsTransparencyTitle\">背景</string>\n  <string name=\"textSettingsWidgetsTransparencySummary\">设置桌面小部件背景透明度。</string>\n  <string name=\"textSettingsMyShowsCountryTitle\">地区</string>\n  <string name=\"textSettingsMyShowsCountrySummary\">选择用于流媒体服务数据的地区。</string>\n  <string name=\"textSettingsMyShowsLanguageTitle\">语言</string>\n  <string name=\"textSettingsMyShowsLanguageSummary\">详情和标题将在可用时会被翻译。应用将重启。</string>\n  <string name=\"textSettingsDateFormatTitle\">日期 / 时间格式</string>\n  <string name=\"textSettingsIncludeSpecialsTitle\">特别季</string>\n  <string name=\"textSettingsIncludeSpecialsSummary\">在节目详细页面上显示 「特别」季。</string>\n  <string name=\"textSettingsNotifications\">通知</string>\n  <string name=\"textSettingsPushNotificationsTitle\">通知推送</string>\n  <string name=\"textSettingsShowsNotificationsSummary\">接到关于您正在关注电影、剧集的更新通知。</string>\n  <string name=\"textSettingsMisc\">杂项</string>\n  <string name=\"textSettingsContactDevsTitle\">联系开发者</string>\n  <string name=\"textSettingsContactDevsSummary\">遇到了问题？点击后向我们发送邮件 (showlyapp@gmail.com)。</string>\n  <string name=\"textSettingsRateAppTitle\">提供反馈</string>\n  <string name=\"textSettingsRateAppSummary\">喜欢 Showly 吗？请在 PlayStore 上给我们留下评论。谢谢！</string>\n  <string name=\"textSettingsDeleteCacheTitle\">清理图片缓存</string>\n  <string name=\"textSettingsDeleteCacheSummary\">点击清除本地图片缓存。如果显示图片未正确加载，这可能会有帮助。</string>\n  <string name=\"textSettingsQuickSyncConfirmationTitle\">禁用自动同步</string>\n  <string name=\"textSettingsQuickSyncConfirmationMessage\">\\n似乎自动同步已经开启。\\n\\n您想要在启用即时同步之前将其关闭吗（推荐）？\\n</string>\n  <string name=\"textSettingsLogoutTitle\">登出 Trakt.tv</string>\n  <string name=\"textSettingsLogoutMessage\">\\n您确定要退出吗？\\n</string>\n  <string name=\"textSettingsMoviesEnabledTitle\">电影</string>\n  <string name=\"textSettingsMoviesEnabledSummary\">启用/禁用电影模块。应用会重启。</string>\n  <string name=\"textSettingsNewsEnabledTitle\">新闻</string>\n  <string name=\"textSettingsNewsEnabledSummary\">启用/禁用新闻模块。应用会重启。</string>\n  <string name=\"textSettingsWidgets\">桌面小部件</string>\n  <string name=\"textSettingsWidgetsLabelsTitle\">标题</string>\n  <string name=\"textSettingsWidgetsLabelsSummary\">显示半透明的桌面小部件标题。</string>\n  <string name=\"textSettingsStreamingsDisclaimer\">流媒体服务数据提供方</string>\n  <string name=\"textSettingsStreamingServicesTitle\">流媒体服务</string>\n  <string name=\"textSettingsStreamingServicesMessage\">显示流媒体服务数据。</string>\n  <string name=\"textSettingsProgressNextEpisodeTitle\">下一集</string>\n  <string name=\"textSettingsProgressNextEpisodeMessage\">使用该方式展示下一集：</string>\n  <string name=\"textNextEpisodeLastWatched\">最近观看的单集</string>\n  <string name=\"textNextEpisodeOldest\">最早未观看的单集</string>\n  <string name=\"textThemeDark\">暗色</string>\n  <string name=\"textThemeLight\">亮色</string>\n  <string name=\"textThemeSystem\">跟随系统</string>\n  <string name=\"textTransparency100\">不透明</string>\n</resources>\n"
  },
  {
    "path": "ui-settings/src/test/java/com/michaldrabik/ui_settings/ExampleUnitTest.kt",
    "content": "package com.michaldrabik.ui_settings\n\n// import org.junit.Test\n//\n// import org.junit.Assert.*\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// */\n// class ExampleUnitTest {\n//    @Test\n//    fun addition_isCorrect() {\n//        assertEquals(4, 2 + 2)\n//    }\n// }\n"
  },
  {
    "path": "ui-show/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-show/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-parcelize'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_show'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-local')\n  implementation project(':data-remote')\n  implementation project(':repository')\n  implementation project(':ui-base')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n  implementation project(':ui-episodes')\n  implementation project(':ui-comments')\n  implementation project(':ui-streamings')\n  implementation project(':ui-people')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-show/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n  <application android:theme=\"@style/AppTheme\" />\n\n</manifest>\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/ShowDetailsFragment.kt",
    "content": "package com.michaldrabik.ui_show\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT\nimport android.content.res.Configuration.ORIENTATION_PORTRAIT\nimport android.graphics.Typeface.BOLD\nimport android.graphics.Typeface.NORMAL\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup.MarginLayoutParams\nimport android.view.animation.DecelerateInterpolator\nimport androidx.activity.addCallback\nimport androidx.constraintlayout.widget.ConstraintSet\nimport androidx.core.os.bundleOf\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateMargins\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.transition.AutoTransition\nimport androidx.transition.TransitionManager\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade\nimport com.google.android.material.snackbar.Snackbar\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Config.IMAGE_FADE_DURATION_MS\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_base.Analytics\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.sheets.links.LinksBottomSheet\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Operation.REMOVE\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Operation.SAVE\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Type\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet\nimport com.michaldrabik.ui_base.utilities.SnackbarHost\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.copyToClipboard\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.openWebUrl\nimport com.michaldrabik.ui_base.utilities.extensions.requireLong\nimport com.michaldrabik.ui_base.utilities.extensions.screenHeight\nimport com.michaldrabik.ui_base.utilities.extensions.screenWidth\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_base.utilities.extensions.withSuccessListener\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_comments.fragment.CommentsFragment\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageFamily.SHOW\nimport com.michaldrabik.ui_model.ImageStatus.UNAVAILABLE\nimport com.michaldrabik.ui_model.ImageType.FANART\nimport com.michaldrabik.ui_model.PremiumFeature\nimport com.michaldrabik.ui_model.RatingState\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.Tip.SHOW_DETAILS_GALLERY\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_CUSTOM_IMAGE_CLEARED\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_FAMILY\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_ITEM\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SHOW_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_TYPE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_CUSTOM_IMAGE\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_MANAGE_LISTS\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_REMOVE_TRAKT\nimport com.michaldrabik.ui_show.ShowDetailsEvent.Finish\nimport com.michaldrabik.ui_show.ShowDetailsEvent.RemoveFromTrakt\nimport com.michaldrabik.ui_show.databinding.FragmentShowDetailsBinding\nimport com.michaldrabik.ui_show.views.AddToShowsButton\nimport dagger.hilt.android.AndroidEntryPoint\nimport timber.log.Timber\nimport java.util.Locale.ENGLISH\n\n@SuppressLint(\"SetTextI18n\", \"DefaultLocale\", \"SourceLockedOrientationActivity\")\n@AndroidEntryPoint\nclass ShowDetailsFragment : BaseFragment<ShowDetailsViewModel>(R.layout.fragment_show_details) {\n\n  override val navigationId = R.id.showDetailsFragment\n  val binding by viewBinding(FragmentShowDetailsBinding::bind)\n\n  override val viewModel by viewModels<ShowDetailsViewModel>()\n\n  private val showId by lazy { IdTrakt(requireLong(ARG_SHOW_ID)) }\n\n  private val imageHeight by lazy {\n    if (resources.configuration.orientation == ORIENTATION_PORTRAIT) screenHeight()\n    else screenWidth()\n  }\n  private val imageRatio by lazy { resources.getString(R.string.detailsImageRatio).toFloat() }\n  private val imagePadded by lazy { resources.getBoolean(R.bool.detailsImagePadded) }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    requireActivity().requestedOrientation = SCREEN_ORIENTATION_PORTRAIT\n    setupView()\n    setupStatusBar()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      { viewModel.messageFlow.collect { renderSnack(it) } },\n      doAfterLaunch = {\n        if (!isInitialized) {\n          viewModel.loadDetails(showId)\n          isInitialized = true\n        }\n        viewModel.loadPremium()\n      }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      hideNavigation()\n      showDetailsImageGuideline.setGuidelineBegin((imageHeight * imageRatio).toInt())\n      showDetailsBackArrow.onClick { requireActivity().onBackPressed() }\n      showDetailsImage.onClick {\n        val bundle = bundleOf(\n          ARG_SHOW_ID to showId.id,\n          ARG_FAMILY to SHOW,\n          ARG_TYPE to FANART\n        )\n        navigateToSafe(R.id.actionShowDetailsFragmentToArtGallery, bundle)\n        Analytics.logShowGalleryClick(showId.id)\n      }\n      showDetailsTipGallery.onClick {\n        it.gone()\n        showTip(SHOW_DETAILS_GALLERY)\n      }\n      showDetailsAddButton.run {\n        isEnabled = false\n        onAddMyShowsClickListener = { viewModel.addFollowedShow() }\n        onAddWatchlistClickListener = { viewModel.addWatchlistShow() }\n        onRemoveClickListener = { viewModel.removeFromFollowed() }\n      }\n      showDetailsManageListsLabel.onClick { openListsDialog() }\n      showDetailsHideLabel.onClick { viewModel.addHiddenShow() }\n      showDetailsTitle.onClick {\n        requireContext().copyToClipboard(showDetailsTitle.text.toString())\n        showSnack(MessageEvent.Info(R.string.textCopiedToClipboard))\n      }\n      showDetailsDescription.onLongClick {\n        val text = showDetailsDescription.text.toString()\n        if (text.count { it.toString() == SPOILERS_HIDE_SYMBOL } == 0) {\n          requireContext().copyToClipboard(text)\n          showSnack(MessageEvent.Info(R.string.textCopiedToClipboard))\n        }\n      }\n      showDetailsPremiumAd.onClick {\n        navigateToSafe(R.id.actionShowDetailsFragmentToPremium)\n      }\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      showDetailsBackArrow.doOnApplyWindowInsets { _, insets, _, _ ->\n        val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top\n        if (imagePadded) {\n          showDetailsMainLayout.updatePadding(top = inset)\n        } else {\n          (showDetailsShareButton.layoutParams as MarginLayoutParams)\n            .updateMargins(top = inset)\n        }\n        (showDetailsBackArrow.layoutParams as MarginLayoutParams).updateMargins(top = inset)\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is Finish -> requireActivity().onBackPressed()\n      is RemoveFromTrakt -> openRemoveTraktSheet(event)\n    }\n  }\n\n  private fun render(uiState: ShowDetailsUiState) {\n    uiState.run {\n      with(binding) {\n        show?.let { show ->\n          renderTitleDescription(show, translation, followedState, spoilers)\n          showDetailsStatus.text = getString(show.status.displayName)\n          val year = if (show.year > 0) String.format(ENGLISH, \"%d\", show.year) else \"\"\n          val country = if (show.country.isNotBlank()) String.format(ENGLISH, \"(%s)\", show.country) else \"\"\n          showDetailsExtraInfo.text = getString(\n            R.string.textShowExtraInfo,\n            show.network,\n            year,\n            country.uppercase(),\n            show.runtime.toString(),\n            getString(R.string.textMinutesShort),\n            renderGenres(show.genres)\n          )\n          showDetailsCommentsButton.visible()\n          showDetailsShareButton.run {\n            isEnabled = show.ids.imdb.id.isNotBlank()\n            alpha = if (isEnabled) 1.0F else 0.35F\n            onClick { openShareSheet(show) }\n          }\n          showDetailsTrailerButton.run {\n            isEnabled = show.trailer.isNotBlank()\n            alpha = if (isEnabled) 1.0F else 0.35F\n            onClick {\n              openWebUrl(show.trailer) ?: showSnack(MessageEvent.Info(R.string.errorCouldNotFindApp))\n              Analytics.logShowTrailerClick(show)\n            }\n          }\n          showDetailsCustomImagesButton.visibleIf(Config.SHOW_PREMIUM)\n          showDetailsCustomImagesButton.onClick { openCustomImagesSheet(show.traktId, meta?.isPremium) }\n          showDetailsLinksButton.onClick {\n            val args = LinksBottomSheet.createBundle(show)\n            navigateToSafe(R.id.actionShowDetailsFragmentToLinks, args)\n          }\n          showDetailsCommentsButton.onClick {\n            val bundle = CommentsFragment.createBundle(show)\n            navigateToSafe(R.id.actionShowDetailsFragmentToComments, bundle)\n          }\n          showDetailsAddButton.isEnabled = true\n          separator4.visible()\n        }\n        showLoading?.let {\n          showDetailsMainLayout.fadeIf(!it, hardware = true)\n          showDetailsMainProgress.visibleIf(it)\n        }\n        followedState?.let {\n          when {\n            it.isMyShows -> showDetailsAddButton.setState(AddToShowsButton.State.IN_MY_SHOWS, it.withAnimation)\n            it.isWatchlist -> showDetailsAddButton.setState(AddToShowsButton.State.IN_WATCHLIST, it.withAnimation)\n            it.isHidden -> showDetailsAddButton.setState(AddToShowsButton.State.IN_HIDDEN, it.withAnimation)\n            else -> showDetailsAddButton.setState(AddToShowsButton.State.ADD, it.withAnimation)\n          }\n          showDetailsHideLabel.visibleIf(!it.isHidden)\n        }\n        listsCount?.let {\n          val text =\n            if (it > 0) getString(R.string.textShowManageListsCount, it)\n            else getString(R.string.textShowManageLists)\n          showDetailsManageListsLabel.text = text\n        }\n        image?.let { renderImage(it) }\n        ratingState?.let { renderRating(it) }\n        meta?.isPremium.let {\n          showDetailsPremiumAd.visibleIf(it != true)\n        }\n      }\n    }\n  }\n\n  private fun renderTitleDescription(\n    show: Show,\n    translation: Translation?,\n    followedState: ShowDetailsUiState.FollowedState?,\n    spoilersSettings: SpoilersSettings?\n  ) {\n    with(binding) {\n      var title = show.title\n      var description = show.overview\n\n      if (translation?.title?.isNotBlank() == true) {\n        title = translation.title\n      }\n      if (translation?.overview?.isNotBlank() == true) {\n        description = translation.overview\n      }\n\n      if (followedState == null || spoilersSettings == null) {\n        showDetailsTitle.text = title\n        showDetailsDescription.text = description\n        return\n      }\n\n      val isMyShowHidden = spoilersSettings.isMyShowsHidden && followedState.isMyShows\n      val isWatchlistHidden = spoilersSettings.isWatchlistShowsHidden && followedState.isWatchlist\n      val isHiddenShowHidden = spoilersSettings.isHiddenShowsHidden && followedState.isHidden\n      val isNotCollectedHidden = spoilersSettings.isNotCollectedShowsHidden && (!followedState.isInCollection())\n\n      if (isMyShowHidden || isWatchlistHidden || isHiddenShowHidden || isNotCollectedHidden) {\n        showDetailsDescription.tag = description\n        description = SPOILERS_REGEX.replace(description, SPOILERS_HIDE_SYMBOL)\n\n        if (spoilersSettings.isTapToReveal) {\n          with(showDetailsDescription) {\n            onClick {\n              tag?.let { text = it.toString() }\n              enableFoldOnClick()\n            }\n          }\n        }\n      }\n\n      showDetailsTitle.text = title\n      showDetailsDescription.text = description\n    }\n  }\n\n  private fun renderGenres(genres: List<String>) =\n    genres\n      .take(2)\n      .mapNotNull { Genre.fromSlug(it) }\n      .joinToString(\", \") { getString(it.displayName) }\n\n  private fun renderRating(rating: RatingState) {\n    with(binding) {\n      showDetailsRateButton.visibleIf(rating.rateLoading == false, gone = false)\n      showDetailsRateProgress.visibleIf(rating.rateLoading == true)\n\n      showDetailsRateButton.text =\n        if (rating.hasRating()) \"${rating.userRating?.rating}/10\"\n        else getString(R.string.textRate)\n\n      val typeFace = if (rating.hasRating()) BOLD else NORMAL\n      showDetailsRateButton.setTypeface(null, typeFace)\n\n      showDetailsRateButton.onClick {\n        if (rating.rateAllowed == true) {\n          openRateDialog()\n        } else {\n          showSnack(MessageEvent.Info(R.string.textSignBeforeRate))\n        }\n      }\n    }\n  }\n\n  private fun renderImage(image: Image) {\n    with(binding) {\n      if (image.status == UNAVAILABLE) {\n        showDetailsImageProgress.gone()\n        showDetailsPlaceholder.visible()\n        showDetailsImage.isClickable = false\n        showDetailsImage.isEnabled = false\n        return\n      }\n      Glide.with(this@ShowDetailsFragment)\n        .load(image.fullFileUrl)\n        .transform(CenterCrop())\n        .transition(withCrossFade(IMAGE_FADE_DURATION_MS))\n        .withFailListener {\n          showDetailsImageProgress.gone()\n          showDetailsPlaceholder.visible()\n          showDetailsImage.isClickable = true\n          showDetailsImage.isEnabled = true\n        }\n        .withSuccessListener {\n          showDetailsImageProgress.gone()\n          showDetailsPlaceholder.gone()\n          showDetailsTipGallery.fadeIf(!isTipShown(SHOW_DETAILS_GALLERY))\n        }\n        .into(showDetailsImage)\n    }\n  }\n\n  private fun renderSnack(event: MessageEvent) {\n    if (event.textResId == R.string.errorMalformedShow) {\n      val host = (requireActivity() as SnackbarHost).provideSnackbarLayout()\n      val snack = host.showInfoSnackbar(getString(event.textResId), length = Snackbar.LENGTH_INDEFINITE) {\n        viewModel.removeMalformedShow(showId)\n      }\n      snackbars.add(snack)\n      return\n    }\n    showSnack(event)\n  }\n\n  private fun openRemoveTraktSheet(event: RemoveFromTrakt) {\n    setFragmentResultListener(REQUEST_REMOVE_TRAKT) { _, bundle ->\n      if (bundle.getBoolean(NavigationArgs.RESULT, false)) {\n        val text = resources.getString(R.string.textTraktSyncRemovedFromTrakt)\n        (requireActivity() as SnackbarHost).provideSnackbarLayout().showInfoSnackbar(text)\n\n        if (event.actionId == R.id.actionShowDetailsFragmentToRemoveTraktProgress) {\n          viewModel.refreshSeasons()\n        }\n      }\n    }\n    val args = RemoveTraktBottomSheet.createBundle(event.traktIds, event.mode)\n    navigateToSafe(event.actionId, args)\n  }\n\n  private fun openShareSheet(show: Show) {\n    val intent = Intent().apply {\n      val text = \"Hey! Check out ${show.title}:\\nhttps://trakt.tv/shows/${show.ids.slug.id}\\nhttps://www.imdb.com/title/${show.ids.imdb.id}\"\n      action = Intent.ACTION_SEND\n      putExtra(Intent.EXTRA_TEXT, text)\n      type = \"text/plain\"\n    }\n\n    val shareIntent = Intent.createChooser(intent, \"Share ${show.title}\")\n    startActivity(shareIntent)\n\n    Analytics.logShowShareClick(show)\n  }\n\n  private fun openRateDialog() {\n    setFragmentResultListener(NavigationArgs.REQUEST_RATING) { _, bundle ->\n      when (bundle.getParcelable<RatingsBottomSheet.Options.Operation>(NavigationArgs.RESULT)) {\n        SAVE -> renderSnack(MessageEvent.Info(R.string.textRateSaved))\n        REMOVE -> renderSnack(MessageEvent.Info(R.string.textRateRemoved))\n        else -> Timber.w(\"Unknown result\")\n      }\n      viewModel.loadUserRating()\n    }\n    val bundle = RatingsBottomSheet.createBundle(showId, Type.SHOW)\n    navigateToSafe(R.id.actionShowDetailsFragmentToRating, bundle)\n  }\n\n  private fun openListsDialog() {\n    if (findNavControl()?.currentDestination?.id != R.id.showDetailsFragment) {\n      return\n    }\n    setFragmentResultListener(REQUEST_MANAGE_LISTS) { _, _ -> viewModel.loadListsCount() }\n    val bundle = bundleOf(\n      ARG_ID to showId.id,\n      ARG_TYPE to Mode.SHOWS.type\n    )\n    navigateToSafe(R.id.actionShowDetailsFragmentToManageLists, bundle)\n  }\n\n  private fun openCustomImagesSheet(showId: Long, isPremium: Boolean?) {\n    if (isPremium == false) {\n      val args = bundleOf(ARG_ITEM to PremiumFeature.CUSTOM_IMAGES)\n      navigateToSafe(R.id.actionShowDetailsFragmentToPremium, args)\n      return\n    }\n\n    setFragmentResultListener(REQUEST_CUSTOM_IMAGE) { _, bundle ->\n      viewModel.loadBackgroundImage()\n      if (!bundle.getBoolean(ARG_CUSTOM_IMAGE_CLEARED)) {\n        openCustomImagesSheet(showId, true)\n      }\n    }\n\n    val bundle = bundleOf(\n      ARG_SHOW_ID to showId,\n      ARG_FAMILY to SHOW\n    )\n    navigateToSafe(R.id.actionShowDetailsFragmentToCustomImages, bundle)\n  }\n\n  fun showStreamingsView(animate: Boolean) {\n    with(binding) {\n      if (!animate) {\n        showDetailsStreamingsFragment.visible()\n        return\n      }\n      val animation = ConstraintSet().apply {\n        clone(showDetailsMainContent)\n        setVisibility(showDetailsStreamingsFragment.id, View.VISIBLE)\n      }\n      val transition = AutoTransition().apply {\n        interpolator = DecelerateInterpolator(1.5F)\n        duration = 200\n      }\n      TransitionManager.beginDelayedTransition(showDetailsMainContent, transition)\n      animation.applyTo(showDetailsMainContent)\n    }\n  }\n\n  override fun setupBackPressed() {\n    val dispatcher = requireActivity().onBackPressedDispatcher\n    dispatcher.addCallback(viewLifecycleOwner) {\n      isEnabled = false\n      findNavControl()?.popBackStack()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/ShowDetailsUiEvents.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_show\n\nimport androidx.annotation.IdRes\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_people.details.PersonDetailsArgs\n\nsealed class ShowDetailsEvent<T>(action: T) : Event<T>(action) {\n\n  data class OpenPersonSheet(\n    val show: Show,\n    val person: Person,\n    val personArgs: PersonDetailsArgs?,\n  ) : ShowDetailsEvent<Show>(show)\n\n  data class OpenPeopleSheet(\n    val show: Show,\n    val people: List<Person>,\n    val department: Person.Department,\n  ) : ShowDetailsEvent<Show>(show)\n\n  data class RemoveFromTrakt(\n    @IdRes val actionId: Int,\n    val mode: RemoveTraktBottomSheet.Mode,\n    val traktIds: List<IdTrakt>,\n  ) : ShowDetailsEvent<Int>(actionId)\n\n  data object RefreshSeasons : ShowDetailsEvent<Unit>(Unit)\n\n  data object Finish : ShowDetailsEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/ShowDetailsUiState.kt",
    "content": "package com.michaldrabik.ui_show\n\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.RatingState\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_show.helpers.ShowDetailsMeta\n\ndata class ShowDetailsUiState(\n  val show: Show? = null,\n  val showLoading: Boolean? = null,\n  val image: Image? = null,\n  val listsCount: Int? = null,\n  val followedState: FollowedState? = null,\n  val ratingState: RatingState? = null,\n  val translation: Translation? = null,\n  val meta: ShowDetailsMeta? = null,\n  val spoilers: SpoilersSettings? = null\n) {\n\n  data class FollowedState(\n    val isMyShows: Boolean,\n    val isWatchlist: Boolean,\n    val isHidden: Boolean,\n    val withAnimation: Boolean,\n  ) {\n\n    fun isInCollection() = isMyShows || isWatchlist || isHidden\n\n    companion object {\n      fun idle() = FollowedState(isMyShows = false, isWatchlist = false, isHidden = false, withAnimation = true)\n      fun inMyShows() = FollowedState(isMyShows = true, isWatchlist = false, isHidden = false, withAnimation = true)\n      fun inWatchlist() = FollowedState(isMyShows = false, isWatchlist = true, isHidden = false, withAnimation = true)\n      fun inHidden() = FollowedState(isMyShows = false, isWatchlist = false, isHidden = true, withAnimation = true)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/ShowDetailsViewModel.kt",
    "content": "package com.michaldrabik.ui_show\n\nimport android.annotation.SuppressLint\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError.CoroutineCancellation\nimport com.michaldrabik.common.errors.ShowlyError.ResourceNotFoundError\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.Analytics\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.combine\nimport com.michaldrabik.ui_base.utilities.extensions.launchDelayed\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType.FANART\nimport com.michaldrabik.ui_model.RatingState\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.TraktRating\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_show.ShowDetailsEvent.Finish\nimport com.michaldrabik.ui_show.ShowDetailsEvent.RemoveFromTrakt\nimport com.michaldrabik.ui_show.ShowDetailsUiState.FollowedState\nimport com.michaldrabik.ui_show.cases.ShowDetailsHiddenCase\nimport com.michaldrabik.ui_show.cases.ShowDetailsListsCase\nimport com.michaldrabik.ui_show.cases.ShowDetailsMainCase\nimport com.michaldrabik.ui_show.cases.ShowDetailsMyShowsCase\nimport com.michaldrabik.ui_show.cases.ShowDetailsTranslationCase\nimport com.michaldrabik.ui_show.cases.ShowDetailsWatchlistCase\nimport com.michaldrabik.ui_show.helpers.ShowDetailsMeta\nimport com.michaldrabik.ui_show.sections.ratings.cases.ShowDetailsRatingCase\nimport com.michaldrabik.ui_show.sections.seasons.helpers.SeasonsCache\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.asSharedFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@SuppressLint(\"StaticFieldLeak\")\n@HiltViewModel\nclass ShowDetailsViewModel @Inject constructor(\n  private val mainCase: ShowDetailsMainCase,\n  private val translationCase: ShowDetailsTranslationCase,\n  private val ratingsCase: ShowDetailsRatingCase,\n  private val watchlistCase: ShowDetailsWatchlistCase,\n  private val hiddenCase: ShowDetailsHiddenCase,\n  private val myShowsCase: ShowDetailsMyShowsCase,\n  private val listsCase: ShowDetailsListsCase,\n  private val settingsRepository: SettingsRepository,\n  private val userManager: UserTraktManager,\n  private val seasonsCache: SeasonsCache,\n  private val imagesProvider: ShowImagesProvider,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private lateinit var show: Show\n\n  private val _parentEvents = MutableSharedFlow<ShowDetailsEvent<*>>(extraBufferCapacity = 1)\n  val parentEvents = _parentEvents.asSharedFlow()\n\n  private val showState = MutableStateFlow<Show?>(null)\n  private val showLoadingState = MutableStateFlow<Boolean?>(null)\n  private val imageState = MutableStateFlow<Image?>(null)\n  private val followedState = MutableStateFlow<FollowedState?>(null)\n  private val ratingState = MutableStateFlow<RatingState?>(null)\n  private val translationState = MutableStateFlow<Translation?>(null)\n  private val listsCountState = MutableStateFlow(0)\n  private val spoilersState = MutableStateFlow<SpoilersSettings?>(null)\n  private val metaState = MutableStateFlow<ShowDetailsMeta?>(null)\n\n  val parentShowState = showState.asStateFlow()\n  val parentFollowedState = followedState.asStateFlow()\n\n  fun loadDetails(id: IdTrakt) {\n    viewModelScope.launch {\n      val progressJob = launchDelayed(700) {\n        showLoadingState.value = true\n      }\n      try {\n        show = mainCase.loadDetails(id)\n        Analytics.logShowDetailsDisplay(show)\n\n        val isSignedIn = userManager.isAuthorized()\n        val isMyShow = async { myShowsCase.isMyShows(show) }\n        val isWatchLater = async { watchlistCase.isWatchlist(show) }\n        val isArchived = async { hiddenCase.isHidden(show) }\n        val isFollowed = FollowedState(\n          isMyShows = isMyShow.await(),\n          isWatchlist = isWatchLater.await(),\n          isHidden = isArchived.await(),\n          withAnimation = false\n        )\n\n        progressJob.cancel()\n\n        showState.value = show\n        showLoadingState.value = false\n        followedState.value = isFollowed\n        ratingState.value = RatingState(rateAllowed = isSignedIn, rateLoading = false)\n        spoilersState.value = settingsRepository.spoilers.getAll()\n        metaState.value = ShowDetailsMeta(\n          isSignedIn = isSignedIn,\n          isPremium = settingsRepository.isPremium\n        )\n\n        loadBackgroundImage(show)\n        loadListsCount(show)\n        loadUserRating()\n        launch { loadTranslation(show) }\n      } catch (error: Throwable) {\n        Timber.e(error)\n        progressJob.cancel()\n        when (ErrorHelper.parse(error)) {\n          is CoroutineCancellation -> rethrowCancellation(error)\n          is ResourceNotFoundError -> {\n            // Malformed Trakt data or duplicate show.\n            messageChannel.send(MessageEvent.Info(R.string.errorMalformedShow))\n            Logger.record(error, \"ShowDetailsViewModel::loadDetails(${id.id})\")\n          }\n          else -> {\n            messageChannel.send(MessageEvent.Error(R.string.errorCouldNotLoadShow))\n            Logger.record(error, \"ShowDetailsViewModel::loadDetails(${id.id})\")\n          }\n        }\n      }\n    }\n  }\n\n  fun loadBackgroundImage(show: Show? = null) {\n    viewModelScope.launch {\n      try {\n        val backgroundImage = imagesProvider.loadRemoteImage(show ?: this@ShowDetailsViewModel.show, FANART)\n        imageState.value = backgroundImage\n      } catch (error: Throwable) {\n        imageState.value = Image.createUnavailable(FANART)\n        Timber.e(error)\n        rethrowCancellation(error)\n      }\n    }\n  }\n\n  private suspend fun loadTranslation(show: Show) {\n    try {\n      translationCase.loadTranslation(show)?.let {\n        translationState.value = it\n      }\n    } catch (error: Throwable) {\n      Timber.e(error)\n      rethrowCancellation(error)\n    }\n  }\n\n  fun loadListsCount(show: Show? = null) {\n    viewModelScope.launch {\n      val count = listsCase.getListsCount(show ?: this@ShowDetailsViewModel.show)\n      listsCountState.value = count\n    }\n  }\n\n  fun loadPremium() {\n    metaState.update { it?.copy(isPremium = settingsRepository.isPremium) }\n  }\n\n  fun loadUserRating() {\n    viewModelScope.launch {\n      val isSignedIn = userManager.isAuthorized()\n      if (!isSignedIn) return@launch\n      try {\n        ratingState.value = RatingState(rateLoading = true, rateAllowed = isSignedIn)\n        val rating = ratingsCase.loadRating(show)\n        ratingState.value = RatingState(rateLoading = false, rateAllowed = isSignedIn, userRating = rating ?: TraktRating.EMPTY)\n      } catch (error: Throwable) {\n        ratingState.value = RatingState(rateLoading = false, rateAllowed = isSignedIn)\n        rethrowCancellation(error)\n      }\n    }\n  }\n\n  fun addFollowedShow() {\n    viewModelScope.launch {\n      if (!checkSeasonsLoaded()) return@launch\n\n      val seasonItems = seasonsCache.loadSeasons(show.ids.trakt) ?: emptyList()\n      val seasons = seasonItems.map { it.season }\n      val episodes = seasonItems.flatMap { it.episodes.map { e -> e.episode } }\n\n      myShowsCase.addToMyShows(show, seasons, episodes)\n      followedState.value = FollowedState.inMyShows()\n      Analytics.logShowAddToMyShows(show)\n    }\n  }\n\n  fun addWatchlistShow() {\n    viewModelScope.launch {\n      if (!checkSeasonsLoaded()) return@launch\n\n      watchlistCase.addToWatchlist(show)\n      followedState.value = FollowedState.inWatchlist()\n      Analytics.logShowAddToWatchlistShows(show)\n    }\n  }\n\n  fun addHiddenShow() {\n    viewModelScope.launch {\n      if (!checkSeasonsLoaded()) return@launch\n\n      val areSeasonsLocal = seasonsCache.areSeasonsLocal(show.ids.trakt)\n      hiddenCase.addToHidden(show, removeLocalData = !areSeasonsLocal)\n      followedState.value = FollowedState.inHidden()\n      Analytics.logShowAddToArchive(show)\n    }\n  }\n\n  fun removeFromFollowed() {\n    viewModelScope.launch {\n      if (!checkSeasonsLoaded()) return@launch\n\n      val isMyShows = myShowsCase.isMyShows(show)\n      val isWatchlist = watchlistCase.isWatchlist(show)\n      val isArchived = hiddenCase.isHidden(show)\n      val areSeasonsLocal = seasonsCache.areSeasonsLocal(show.ids.trakt)\n\n      when {\n        isMyShows -> myShowsCase.removeFromMyShows(show, removeLocalData = !areSeasonsLocal)\n        isWatchlist -> watchlistCase.removeFromWatchlist(show)\n        isArchived -> hiddenCase.removeFromHidden(show)\n      }\n\n      val traktQuickRemoveEnabled = settingsRepository.load().traktQuickRemoveEnabled\n      val showRemoveTrakt = userManager.isAuthorized() && traktQuickRemoveEnabled && !areSeasonsLocal\n\n      val state = FollowedState.idle()\n      val ids = listOf(show.ids.trakt)\n      val mode = RemoveTraktBottomSheet.Mode.SHOW\n      when {\n        isMyShows -> {\n          followedState.value = state\n          if (showRemoveTrakt) {\n            eventChannel.send(RemoveFromTrakt(R.id.actionShowDetailsFragmentToRemoveTraktProgress, mode, ids))\n          }\n        }\n        isWatchlist -> {\n          followedState.value = state\n          if (showRemoveTrakt) {\n            eventChannel.send(RemoveFromTrakt(R.id.actionShowDetailsFragmentToRemoveTraktWatchlist, mode, ids))\n          }\n        }\n        isArchived -> {\n          followedState.value = state\n          if (showRemoveTrakt) {\n            eventChannel.send(RemoveFromTrakt(R.id.actionShowDetailsFragmentToRemoveTraktHidden, mode, ids))\n          }\n        }\n        else -> error(\"Unexpected show state.\")\n      }\n    }\n  }\n\n  fun removeMalformedShow(id: IdTrakt) {\n    viewModelScope.launch {\n      try {\n        mainCase.removeMalformedShow(id)\n      } catch (error: Throwable) {\n        Timber.e(error)\n        rethrowCancellation(error)\n      } finally {\n        eventChannel.send(Finish)\n      }\n    }\n  }\n\n  fun refreshSeasons() {\n    viewModelScope.launch {\n      _parentEvents.emit(ShowDetailsEvent.RefreshSeasons)\n    }\n  }\n\n  private suspend fun checkSeasonsLoaded(): Boolean {\n    if (!seasonsCache.hasSeasons(show.ids.trakt)) {\n      messageChannel.send(MessageEvent.Info(R.string.errorSeasonsNotLoaded))\n      return false\n    }\n    return true\n  }\n\n  override fun onCleared() {\n    if (this::show.isInitialized) {\n      seasonsCache.clear(show.ids.trakt)\n    }\n    super.onCleared()\n  }\n\n  val uiState = combine(\n    showState,\n    showLoadingState,\n    imageState,\n    followedState,\n    ratingState,\n    translationState,\n    listsCountState,\n    metaState,\n    spoilersState\n  ) { s1, s2, s3, s4, s5, s6, s7, s8, s9 ->\n    ShowDetailsUiState(\n      show = s1,\n      showLoading = s2,\n      image = s3,\n      followedState = s4,\n      ratingState = s5,\n      translation = s6,\n      listsCount = s7,\n      meta = s8,\n      spoilers = s9\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ShowDetailsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/cases/ShowDetailsHiddenCase.kt",
    "content": "package com.michaldrabik.ui_show.cases\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.Season\nimport com.michaldrabik.data_local.database.model.TraktSyncQueue\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsHiddenCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val localSource: LocalDataSource,\n  private val transactions: TransactionsProvider,\n  private val showsRepository: ShowsRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val quickSyncManager: QuickSyncManager,\n  private val announcementManager: AnnouncementManager,\n) {\n\n  suspend fun isHidden(show: Show) = withContext(dispatchers.IO) {\n    showsRepository.hiddenShows.exists(show.ids.trakt)\n  }\n\n  suspend fun addToHidden(show: Show, removeLocalData: Boolean) = withContext(dispatchers.IO) {\n    transactions.withTransaction {\n      showsRepository.hiddenShows.insert(show.ids.trakt)\n\n      if (removeLocalData) {\n        localSource.episodes.deleteAllUnwatchedForShow(show.traktId)\n        val seasons = localSource.seasons.getAllByShowId(show.traktId)\n        val episodes = localSource.episodes.getAllByShowId(show.traktId)\n        val toDelete = mutableListOf<Season>()\n        seasons.forEach { season ->\n          if (episodes.none { it.idSeason == season.idTrakt }) {\n            toDelete.add(season)\n          }\n        }\n        localSource.seasons.delete(toDelete)\n      }\n    }\n    pinnedItemsRepository.removePinnedItem(show)\n    announcementManager.refreshShowsAnnouncements()\n    quickSyncManager.scheduleHidden(show.traktId, Mode.SHOWS, TraktSyncQueue.Operation.ADD)\n  }\n\n  suspend fun removeFromHidden(show: Show) = withContext(dispatchers.IO) {\n    showsRepository.hiddenShows.delete(show.ids.trakt)\n    pinnedItemsRepository.removePinnedItem(show)\n    announcementManager.refreshShowsAnnouncements()\n    quickSyncManager.clearHiddenShows(listOf(show.traktId))\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/cases/ShowDetailsListsCase.kt",
    "content": "package com.michaldrabik.ui_show.cases\n\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.ListsRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsListsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val listsRepository: ListsRepository\n) {\n\n  suspend fun getListsCount(show: Show) = withContext(dispatchers.IO) {\n    listsRepository.loadListIdsForItem(IdTrakt(show.traktId), Mode.SHOWS.type).size\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/cases/ShowDetailsMainCase.kt",
    "content": "package com.michaldrabik.ui_show.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsMainCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val showsRepository: ShowsRepository,\n) {\n\n  suspend fun loadDetails(idTrakt: IdTrakt) = withContext(dispatchers.IO) {\n    showsRepository.detailsShow.load(idTrakt)\n  }\n\n  suspend fun removeMalformedShow(idTrakt: IdTrakt) = withContext(dispatchers.IO) {\n    with(showsRepository) {\n      myShows.delete(idTrakt)\n      watchlistShows.delete(idTrakt)\n      hiddenShows.delete(idTrakt)\n      detailsShow.delete(idTrakt)\n    }\n    Timber.d(\"Removing malformed show...\")\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/cases/ShowDetailsMyShowsCase.kt",
    "content": "package com.michaldrabik.ui_show.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.toMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.utilities.TransactionsProvider\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\nimport com.michaldrabik.data_local.database.model.Episode as EpisodeDb\nimport com.michaldrabik.data_local.database.model.Season as SeasonDb\n\n@ViewModelScoped\nclass ShowDetailsMyShowsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n  private val transactions: TransactionsProvider,\n  private val showsRepository: ShowsRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val announcementManager: AnnouncementManager\n) {\n\n  suspend fun getAllIds() = withContext(dispatchers.IO) {\n    val (myShows, watchlistShows) = awaitAll(\n      async { showsRepository.myShows.loadAllIds() },\n      async { showsRepository.watchlistShows.loadAllIds() }\n    )\n    Pair(myShows, watchlistShows)\n  }\n\n  suspend fun isMyShows(show: Show) = withContext(dispatchers.IO) {\n    showsRepository.myShows.exists(show.ids.trakt)\n  }\n\n  suspend fun addToMyShows(\n    show: Show,\n    seasons: List<Season>,\n    episodes: List<Episode>\n  ) = withContext(dispatchers.IO) {\n    transactions.withTransaction {\n      val localSeasons = localSource.seasons.getAllByShowId(show.traktId)\n      val localEpisodes = localSource.episodes.getAllByShowId(show.traktId)\n      val lastWatchedAt = localEpisodes.maxByOrNull { it.lastWatchedAt != null }?.lastWatchedAt?.toMillis() ?: 0L\n\n      showsRepository.myShows.insert(show.ids.trakt, lastWatchedAt)\n\n      val seasonsToAdd = mutableListOf<SeasonDb>()\n      val episodesToAdd = mutableListOf<EpisodeDb>()\n\n      seasons.forEach { season ->\n        if (localSeasons.none { it.idTrakt == season.ids.trakt.id }) {\n          seasonsToAdd.add(mappers.season.toDatabase(season, show.ids.trakt, false))\n        }\n      }\n      episodes.forEach { episode ->\n        if (localEpisodes.none { it.idTrakt == episode.ids.trakt.id }) {\n          val season = seasons.find { it.number == episode.season }!!\n          episodesToAdd.add(mappers.episode.toDatabase(episode, season, show.ids.trakt, false, null))\n        }\n      }\n\n      localSource.seasons.upsert(seasonsToAdd)\n      localSource.episodes.upsert(episodesToAdd)\n    }\n\n    pinnedItemsRepository.removePinnedItem(show)\n    announcementManager.refreshShowsAnnouncements()\n  }\n\n  suspend fun removeFromMyShows(show: Show, removeLocalData: Boolean) = withContext(dispatchers.IO) {\n    transactions.withTransaction {\n      showsRepository.myShows.delete(show.ids.trakt)\n\n      if (removeLocalData) {\n        localSource.episodes.deleteAllUnwatchedForShow(show.traktId)\n        val seasons = localSource.seasons.getAllByShowId(show.traktId)\n        val episodes = localSource.episodes.getAllByShowId(show.traktId)\n        val toDelete = mutableListOf<SeasonDb>()\n        seasons.forEach { season ->\n          if (episodes.none { it.idSeason == season.idTrakt }) {\n            toDelete.add(season)\n          }\n        }\n        localSource.seasons.delete(toDelete)\n      }\n\n      pinnedItemsRepository.removePinnedItem(show)\n      announcementManager.refreshShowsAnnouncements()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/cases/ShowDetailsTranslationCase.kt",
    "content": "package com.michaldrabik.ui_show.cases\n\nimport com.michaldrabik.common.Config.DEFAULT_LANGUAGE\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.Translation\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsTranslationCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val translationsRepository: TranslationsRepository\n) {\n\n  suspend fun loadTranslation(show: Show): Translation? =\n    withContext(dispatchers.IO) {\n      val language = translationsRepository.getLanguage()\n      if (language == DEFAULT_LANGUAGE) {\n        return@withContext null\n      }\n      translationsRepository.loadTranslation(show, language)\n    }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/cases/ShowDetailsWatchlistCase.kt",
    "content": "package com.michaldrabik.ui_show.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.PinnedItemsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsWatchlistCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val showsRepository: ShowsRepository,\n  private val pinnedItemsRepository: PinnedItemsRepository,\n  private val quickSyncManager: QuickSyncManager,\n  private val announcementManager: AnnouncementManager,\n) {\n\n  suspend fun isWatchlist(show: Show) = withContext(dispatchers.IO) {\n    showsRepository.watchlistShows.exists(show.ids.trakt)\n  }\n\n  suspend fun addToWatchlist(show: Show) = withContext(dispatchers.IO) {\n    showsRepository.watchlistShows.insert(show.ids.trakt)\n    pinnedItemsRepository.removePinnedItem(show)\n    announcementManager.refreshShowsAnnouncements()\n    quickSyncManager.scheduleShowsWatchlist(listOf(show.traktId))\n  }\n\n  suspend fun removeFromWatchlist(show: Show) = withContext(dispatchers.IO) {\n    showsRepository.watchlistShows.delete(show.ids.trakt)\n    pinnedItemsRepository.removePinnedItem(show)\n    announcementManager.refreshShowsAnnouncements()\n    quickSyncManager.clearWatchlistShows(listOf(show.traktId))\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/episodes/ShowDetailsEpisodesFragment.kt",
    "content": "package com.michaldrabik.ui_show.episodes\n\nimport android.os.Bundle\nimport android.os.Parcelable\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.WidgetsProvider\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Operation\nimport com.michaldrabik.ui_base.common.sheets.ratings.RatingsBottomSheet.Options.Type\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet\nimport com.michaldrabik.ui_base.utilities.SnackbarHost\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.setCheckedSilent\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_episodes.details.EpisodeDetailsBottomSheet\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.EpisodeBundle\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.databinding.FragmentShowDetailsEpisodesBinding\nimport com.michaldrabik.ui_show.episodes.ShowDetailsEpisodesEvent.Finish\nimport com.michaldrabik.ui_show.episodes.ShowDetailsEpisodesEvent.OpenEpisodeDetails\nimport com.michaldrabik.ui_show.episodes.ShowDetailsEpisodesEvent.OpenRateSeason\nimport com.michaldrabik.ui_show.episodes.ShowDetailsEpisodesEvent.RemoveFromTrakt\nimport com.michaldrabik.ui_show.episodes.ShowDetailsEpisodesEvent.RequestWidgetsUpdate\nimport com.michaldrabik.ui_show.episodes.recycler.EpisodesAdapter\nimport com.michaldrabik.ui_show.sections.seasons.recycler.SeasonListItem\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.parcelize.Parcelize\nimport timber.log.Timber\nimport java.util.Locale\n\n@AndroidEntryPoint\nclass ShowDetailsEpisodesFragment : BaseFragment<ShowDetailsEpisodesViewModel>(R.layout.fragment_show_details_episodes) {\n\n  companion object {\n    fun createBundle(\n      showId: IdTrakt,\n      seasonId: IdTrakt,\n    ): Bundle = bundleOf(\n      NavigationArgs.ARG_OPTIONS to Options(showId, seasonId)\n    )\n  }\n\n  override val navigationId = R.id.showDetailsEpisodesFragment\n  private val binding by viewBinding(FragmentShowDetailsEpisodesBinding::bind)\n\n  override val viewModel by viewModels<ShowDetailsEpisodesViewModel>()\n\n  private var episodesAdapter: EpisodesAdapter? = null\n  private var isLocked = true\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n\n    setupView()\n    setupStatusBar()\n    setupRecycler()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it as ShowDetailsEpisodesEvent<*>) } }\n    )\n  }\n\n  override fun onResume() {\n    super.onResume()\n    viewModel.launchRefreshWatchedEpisodes()\n  }\n\n  private fun setupView() {\n    with(binding) {\n      episodesBackArrow.onClick { findNavControl()?.popBackStack() }\n      episodesUnlockButton.onClick(safe = false) { toggleEpisodesLock() }\n      listOf(episodesSeasonRateButton, episodesSeasonMyStarIcon).onClick {\n        viewModel.openRateSeasonDialog()\n      }\n    }\n  }\n\n  private fun setupStatusBar() {\n    with(binding) {\n      episodesRoot.doOnApplyWindowInsets { _, insets, padding, _ ->\n        val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top\n        episodesRoot.updatePadding(top = padding.top + inset)\n      }\n    }\n  }\n\n  private fun setupRecycler() {\n    episodesAdapter = EpisodesAdapter(\n      itemClickListener = { episode: Episode, isWatched: Boolean ->\n        viewModel.openEpisodeDetails(episode, isWatched)\n      },\n      itemCheckedListener = { episode: Episode, isChecked: Boolean ->\n        viewModel.setEpisodeWatched(episode, isChecked)\n      }\n    )\n    binding.episodesRecycler.apply {\n      setHasFixedSize(true)\n      adapter = episodesAdapter\n      layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)\n      itemAnimator = null\n    }\n  }\n\n  private fun render(uiState: ShowDetailsEpisodesUiState) {\n    with(uiState) {\n      with(binding) {\n        season?.let {\n          episodesTitle.text =\n            if (it.season.isSpecial()) getString(R.string.textSpecials)\n            else String.format(Locale.ENGLISH, getString(R.string.textSeason), it.season.number)\n          episodesOverview.text = it.season.overview\n          episodesOverview.visibleIf(it.season.overview.isNotBlank())\n          episodesCheckbox.run {\n            isEnabled = it.episodes.all { ep -> ep.episode.hasAired(it.season) } || !isLocked\n            setCheckedSilent(it.isWatched) { _, isChecked ->\n              viewModel.setSeasonWatched(season, isChecked)\n            }\n            jumpDrawablesToCurrentState()\n          }\n          episodesUnlockButton.visibleIf(!it.season.isSpecial() && it.episodes.any { ep -> !ep.episode.hasAired(it.season) })\n\n          renderSeasonRating(season)\n        }\n        episodes?.let {\n          episodesAdapter?.setItems(it)\n          if (isInitialLoad == true) {\n            episodesRecycler.scheduleLayoutAnimation()\n          }\n        }\n      }\n    }\n  }\n\n  private fun renderSeasonRating(season: SeasonListItem) {\n    with(binding) {\n      val seasonRating = season.season.rating\n      episodesStarIcon.visibleIf(seasonRating > 0F)\n      episodesSeasonRating.visibleIf(seasonRating > 0F)\n      episodesSeasonRating.text = String.format(Locale.ENGLISH, \"%.1f\", seasonRating)\n\n      val ratingState = season.userRating\n      episodesSeasonRateButton.visibleIf(ratingState.rateAllowed == true && ratingState.userRating == null)\n      episodesSeasonMyStarIcon.visibleIf(ratingState.userRating != null)\n      episodesSeasonMyRating.visibleIf(ratingState.userRating != null)\n      ratingState.userRating?.let {\n        episodesSeasonMyStarIcon.isEnabled = ratingState.rateAllowed == true\n        episodesSeasonMyRating.text = String.format(Locale.ENGLISH, \"%d\", it.rating)\n      }\n    }\n  }\n\n  private fun handleEvent(event: ShowDetailsEpisodesEvent<*>) {\n    when (event) {\n      is RemoveFromTrakt -> openRemoveTraktSheet(event)\n      is OpenEpisodeDetails -> openEpisodeDetails(event.bundle, event.isWatched)\n      is OpenRateSeason -> openRateSeasonDialog(event.season)\n      is RequestWidgetsUpdate -> (requireAppContext() as WidgetsProvider).requestShowsWidgetsUpdate()\n      is Finish -> findNavControl()?.popBackStack()\n    }\n  }\n\n  private fun toggleEpisodesLock() {\n    isLocked = !isLocked\n\n    with(binding) {\n      episodesUnlockButton.setImageResource(if (isLocked) R.drawable.ic_locked else R.drawable.ic_unlocked)\n      episodesCheckbox.isEnabled = !isLocked\n    }\n    episodesAdapter?.toggleEpisodesLock()\n  }\n\n  private fun openEpisodeDetails(\n    episodeBundle: EpisodeBundle,\n    isWatched: Boolean,\n  ) {\n    val (episode, season, show) = episodeBundle\n    setFragmentResultListener(NavigationArgs.REQUEST_EPISODE_DETAILS) { _, bundle ->\n      when {\n        bundle.containsKey(NavigationArgs.ACTION_EPISODE_WATCHED) -> {\n          val watched = bundle.getBoolean(NavigationArgs.ACTION_EPISODE_WATCHED)\n          viewModel.setEpisodeWatched(episode, watched)\n        }\n        bundle.containsKey(NavigationArgs.ACTION_EPISODE_TAB_SELECTED) -> {\n          val selectedEpisode = bundle.getParcelable<Episode>(NavigationArgs.ACTION_EPISODE_TAB_SELECTED)!!\n          viewModel.openEpisodeDetails(selectedEpisode)\n        }\n        bundle.containsKey(NavigationArgs.ACTION_RATING_CHANGED) -> {\n          viewModel.loadEpisodesRating()\n        }\n      }\n    }\n\n    val bundle = EpisodeDetailsBottomSheet.createBundle(\n      ids = show.ids,\n      episode = episode,\n      seasonEpisodesIds = season.episodes.map { it.number },\n      isWatched = isWatched,\n      showButton = episode.hasAired(season),\n      showTabs = true\n    )\n    navigateToSafe(R.id.actionEpisodesFragmentToEpisodesDetails, bundle)\n  }\n\n  private fun openRemoveTraktSheet(event: RemoveFromTrakt) {\n    setFragmentResultListener(NavigationArgs.REQUEST_REMOVE_TRAKT) { _, bundle ->\n      if (bundle.getBoolean(NavigationArgs.RESULT, false)) {\n        val text = resources.getString(R.string.textTraktSyncRemovedFromTrakt)\n        (requireActivity() as SnackbarHost).provideSnackbarLayout().showInfoSnackbar(text)\n      }\n    }\n    val args = RemoveTraktBottomSheet.createBundle(event.traktIds, event.mode)\n    navigateToSafe(event.actionId, args)\n  }\n\n  private fun openRateSeasonDialog(season: Season) {\n    setFragmentResultListener(NavigationArgs.REQUEST_RATING) { _, bundle ->\n      when (bundle.getParcelable<Operation>(NavigationArgs.RESULT)) {\n        Operation.SAVE -> showSnack(MessageEvent.Info(R.string.textRateSaved))\n        Operation.REMOVE -> showSnack(MessageEvent.Info(R.string.textRateRemoved))\n        else -> Timber.w(\"Unknown result\")\n      }\n      viewModel.loadSeasonRating()\n    }\n    val bundle = RatingsBottomSheet.createBundle(season.ids.trakt, Type.SEASON)\n    navigateToSafe(R.id.actionEpisodesFragmentToRating, bundle)\n  }\n\n  override fun onDestroyView() {\n    episodesAdapter = null\n    super.onDestroyView()\n  }\n\n  @Parcelize\n  data class Options(\n    val showId: IdTrakt,\n    val seasonId: IdTrakt,\n  ) : Parcelable\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/episodes/ShowDetailsEpisodesUiEvents.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_show.episodes\n\nimport androidx.annotation.IdRes\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.EpisodeBundle\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Season\n\nsealed class ShowDetailsEpisodesEvent<T>(action: T) : Event<T>(action) {\n\n  data class OpenEpisodeDetails(\n    val bundle: EpisodeBundle,\n    val isWatched: Boolean\n  ) : ShowDetailsEpisodesEvent<EpisodeBundle>(bundle)\n\n  data class OpenRateSeason(\n    val season: Season,\n  ) : ShowDetailsEpisodesEvent<Season>(season)\n\n  data class RemoveFromTrakt(\n    @IdRes val actionId: Int,\n    val mode: RemoveTraktBottomSheet.Mode,\n    val traktIds: List<IdTrakt>\n  ) : ShowDetailsEpisodesEvent<Int>(actionId)\n\n  object RequestWidgetsUpdate : ShowDetailsEpisodesEvent<Unit>(Unit)\n\n  object Finish : ShowDetailsEpisodesEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/episodes/ShowDetailsEpisodesUiState.kt",
    "content": "package com.michaldrabik.ui_show.episodes\n\nimport com.michaldrabik.ui_show.episodes.recycler.EpisodeListItem\nimport com.michaldrabik.ui_show.sections.seasons.recycler.SeasonListItem\n\ndata class ShowDetailsEpisodesUiState(\n  val season: SeasonListItem? = null,\n  val episodes: List<EpisodeListItem>? = null,\n  val isInitialLoad: Boolean? = null\n)\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/episodes/ShowDetailsEpisodesViewModel.kt",
    "content": "package com.michaldrabik.ui_show.episodes\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.EpisodeBundle\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.Translation\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_OPTIONS\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.episodes.ShowDetailsEpisodesFragment.Options\nimport com.michaldrabik.ui_show.episodes.cases.EpisodesAnnouncementsCase\nimport com.michaldrabik.ui_show.episodes.cases.EpisodesLoadShowCase\nimport com.michaldrabik.ui_show.episodes.cases.EpisodesMarkWatchedCase\nimport com.michaldrabik.ui_show.episodes.cases.EpisodesRatingCase\nimport com.michaldrabik.ui_show.episodes.cases.EpisodesSetEpisodeWatchedCase\nimport com.michaldrabik.ui_show.episodes.cases.EpisodesSetEpisodeWatchedCase.Result\nimport com.michaldrabik.ui_show.episodes.cases.EpisodesSetSeasonWatchedCase\nimport com.michaldrabik.ui_show.episodes.cases.EpisodesSetSeasonWatchedCase.Result.REMOVE_FROM_TRAKT\nimport com.michaldrabik.ui_show.episodes.cases.EpisodesTranslationCase\nimport com.michaldrabik.ui_show.episodes.recycler.EpisodeListItem\nimport com.michaldrabik.ui_show.sections.seasons.helpers.SeasonsCache\nimport com.michaldrabik.ui_show.sections.seasons.recycler.SeasonListItem\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\nimport kotlin.properties.Delegates.notNull\n\n@HiltViewModel\nclass ShowDetailsEpisodesViewModel @Inject constructor(\n  savedStateHandle: SavedStateHandle,\n  private val loadShowCase: EpisodesLoadShowCase,\n  private val episodeWatchedCase: EpisodesSetEpisodeWatchedCase,\n  private val seasonWatchedCase: EpisodesSetSeasonWatchedCase,\n  private val ratingsCase: EpisodesRatingCase,\n  private val translationCase: EpisodesTranslationCase,\n  private val announcementsCase: EpisodesAnnouncementsCase,\n  private val markWatchedCase: EpisodesMarkWatchedCase,\n  private val seasonsCache: SeasonsCache,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private val seasonState = MutableStateFlow<SeasonListItem?>(null)\n  private val episodesState = MutableStateFlow<List<EpisodeListItem>?>(null)\n  private val initialLoadState = MutableStateFlow<Boolean?>(null)\n\n  private var show by notNull<Show>()\n\n  init {\n    val (showId, seasonId) = savedStateHandle.get<Options>(ARG_OPTIONS)!!\n    loadInitialData(showId, seasonId)\n  }\n\n  private fun loadInitialData(showId: IdTrakt, seasonId: IdTrakt) {\n    viewModelScope.launch {\n      this@ShowDetailsEpisodesViewModel.show = loadShowCase.loadDetails(showId)\n\n      val seasons = seasonsCache.loadSeasons(showId)\n      val season = seasons?.find { it.id == seasonId.id }\n      if (seasons == null || season == null) {\n        eventChannel.send(ShowDetailsEpisodesEvent.Finish)\n        return@launch\n      }\n      val ratingSeason = ratingsCase.loadRating(season.season)\n      seasonState.value = season.copy(\n        userRating = season.userRating.copy(ratingSeason)\n      )\n\n      delay(265) // Let enter transition animation complete peacefully.\n      episodesState.value = season.episodes\n      initialLoadState.value = true\n\n      loadEpisodesRating().join()\n      loadTranslations()\n    }\n  }\n\n  fun loadEpisodesRating(): Job =\n    viewModelScope.launch {\n      val seasonItem = seasonState.value\n      val episodeItems = episodesState.value ?: emptyList()\n\n      seasonItem?.let {\n        val updatedEpisodesItems = episodeItems.map { episodeItem ->\n          async {\n            val ratingEpisode = ratingsCase.loadRating(episodeItem.episode)\n            episodeItem.copy(myRating = ratingEpisode)\n          }\n        }.awaitAll()\n        val updatedSeasonItem = seasonItem.copy(\n          episodes = updatedEpisodesItems\n        )\n        seasonState.value = updatedSeasonItem\n        episodesState.value = updatedEpisodesItems\n        initialLoadState.value = false\n      }\n    }\n\n  fun loadSeasonRating() {\n    viewModelScope.launch {\n      val seasonItem = seasonState.value\n      seasonItem?.let {\n        val ratingSeason = ratingsCase.loadRating(seasonItem.season)\n        val updatedSeasonItem = seasonItem.copy(\n          userRating = seasonItem.userRating.copy(ratingSeason)\n        )\n        seasonState.value = updatedSeasonItem\n        initialLoadState.value = false\n      }\n    }\n  }\n\n  private suspend fun loadTranslations() {\n    try {\n      val season = seasonState.value?.season\n      val episodes = episodesState.value?.toMutableList() ?: mutableListOf()\n      val translations = translationCase.loadTranslations(season, show)\n\n      if (translations.isEmpty() || episodes.isEmpty()) {\n        return\n      }\n\n      translations.forEach { translation ->\n        val episode = episodes.find { it.id == translation.ids.trakt.id }\n        episode?.let { ep ->\n          if (translation.title.isNotBlank() || translation.overview.isNotBlank()) {\n            val t = Translation(translation.title, translation.overview, translation.language)\n            val withTranslation = ep.copy(translation = t)\n            episodes.findReplace(withTranslation) { it.id == withTranslation.id }\n          }\n        }\n      }\n\n      val updatedSeason = seasonState.value?.copy(episodes = episodes, updatedAt = nowUtcMillis())\n      updatedSeason?.let {\n        val marked = markWatchedCase.markWatchedEpisodes(show, updatedSeason)\n        seasonState.value = marked\n        episodesState.value = marked.episodes\n        initialLoadState.value = false\n      }\n    } catch (error: Throwable) {\n      Timber.e(error)\n      rethrowCancellation(error)\n    }\n  }\n\n  fun setEpisodeWatched(\n    episode: Episode,\n    isChecked: Boolean,\n  ) {\n    viewModelScope.launch {\n      seasonState.value?.let {\n        val bundle = EpisodeBundle(episode, it.season, show)\n        val result = episodeWatchedCase.setEpisodeWatched(bundle, isChecked)\n        if (result == Result.REMOVE_FROM_TRAKT) {\n          val event = ShowDetailsEpisodesEvent.RemoveFromTrakt(\n            actionId = R.id.actionEpisodesFragmentToRemoveTraktProgress,\n            mode = RemoveTraktBottomSheet.Mode.EPISODE,\n            traktIds = listOf(episode.ids.trakt)\n          )\n          eventChannel.send(event)\n        }\n        refreshWatchedEpisodes()\n        announcementsCase.refreshAnnouncements(show.ids.trakt)\n      }\n    }\n  }\n\n  fun setSeasonWatched(season: SeasonListItem, isChecked: Boolean) {\n    viewModelScope.launch {\n      val result = seasonWatchedCase.setSeasonWatched(show, season.season, isChecked)\n      if (result == REMOVE_FROM_TRAKT) {\n        val event = ShowDetailsEpisodesEvent.RemoveFromTrakt(\n          actionId = R.id.actionEpisodesFragmentToRemoveTraktProgress,\n          mode = RemoveTraktBottomSheet.Mode.EPISODE,\n          traktIds = season.season.episodes.map { it.ids.trakt }\n        )\n        eventChannel.send(event)\n      }\n      refreshWatchedEpisodes()\n      announcementsCase.refreshAnnouncements(show.ids.trakt)\n    }\n  }\n\n  fun openEpisodeDetails(episode: Episode) {\n    val currentEpisode = episodesState.value?.find { it.season.number == episode.season && it.episode.number == episode.number }!!\n    openEpisodeDetails(episode, currentEpisode.isWatched)\n  }\n\n  fun openEpisodeDetails(episode: Episode, isWatched: Boolean) {\n    val currentSeason = seasonState.value?.season\n    val currentEpisode = episodesState.value\n      ?.find { it.season.number == episode.season && it.episode.number == episode.number }\n\n    if (currentSeason == null || currentEpisode == null) {\n      return\n    }\n\n    viewModelScope.launch {\n      val episodeBundle = EpisodeBundle(\n        episode = currentEpisode.episode,\n        season = currentSeason,\n        show = show\n      )\n      eventChannel.send(ShowDetailsEpisodesEvent.OpenEpisodeDetails(episodeBundle, isWatched))\n    }\n  }\n\n  fun openRateSeasonDialog() {\n    viewModelScope.launch {\n      seasonState.value?.season?.let {\n        eventChannel.send(ShowDetailsEpisodesEvent.OpenRateSeason(it))\n      }\n    }\n  }\n\n  fun launchRefreshWatchedEpisodes() {\n    viewModelScope.launch {\n      refreshWatchedEpisodes()\n    }\n  }\n\n  private suspend fun refreshWatchedEpisodes() {\n    val season = seasonState.value?.copy()\n    season?.let {\n      val marked = markWatchedCase.markWatchedEpisodes(show, it)\n      seasonState.value = marked\n      episodesState.value = marked.episodes\n      initialLoadState.value = false\n      eventChannel.send(ShowDetailsEpisodesEvent.RequestWidgetsUpdate)\n    }\n  }\n\n  private fun refreshSeasonsCache() {\n    val cachedSeasons = seasonsCache.loadSeasons(show.ids.trakt)?.toMutableList()\n    val currentSeason = seasonState.value\n    val currentEpisodes = episodesState.value\n    val isSeasonLocal = seasonsCache.areSeasonsLocal(show.ids.trakt)\n\n    if (currentSeason != null && currentEpisodes != null) {\n      cachedSeasons?.find { it.id == currentSeason.id }?.let { season ->\n        val updated = currentSeason.copy(\n          episodes = currentEpisodes,\n          userRating = currentSeason.userRating\n        )\n        cachedSeasons.findReplace(updated) { it.id == season.id }\n        seasonsCache.setSeasons(show.ids.trakt, cachedSeasons, isSeasonLocal)\n      }\n    }\n  }\n\n  override fun onCleared() {\n    refreshSeasonsCache()\n    super.onCleared()\n  }\n\n  val uiState = combine(\n    seasonState,\n    episodesState,\n    initialLoadState\n  ) { s2, s3, s4 ->\n    ShowDetailsEpisodesUiState(\n      season = s2,\n      episodes = s3,\n      isInitialLoad = s4\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ShowDetailsEpisodesUiState()\n  )\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/episodes/cases/EpisodesAnnouncementsCase.kt",
    "content": "package com.michaldrabik.ui_show.episodes.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.notifications.AnnouncementManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass EpisodesAnnouncementsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val showsRepository: ShowsRepository,\n  private val announcementManager: AnnouncementManager,\n) {\n\n  suspend fun refreshAnnouncements(idTrakt: IdTrakt) = withContext(dispatchers.IO) {\n    val isMyShow = showsRepository.myShows.exists(idTrakt)\n    if (isMyShow) {\n      announcementManager.refreshShowsAnnouncements()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/episodes/cases/EpisodesLoadShowCase.kt",
    "content": "package com.michaldrabik.ui_show.episodes.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass EpisodesLoadShowCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val showsRepository: ShowsRepository,\n) {\n\n  suspend fun loadDetails(idTrakt: IdTrakt) = withContext(dispatchers.IO) {\n    showsRepository.detailsShow.load(idTrakt)\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/episodes/cases/EpisodesMarkWatchedCase.kt",
    "content": "package com.michaldrabik.ui_show.episodes.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.EpisodesManager\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_show.sections.seasons.recycler.SeasonListItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass EpisodesMarkWatchedCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val episodesManager: EpisodesManager,\n) {\n\n  suspend fun markWatchedEpisodes(\n    show: Show,\n    season: SeasonListItem\n  ): SeasonListItem = withContext(dispatchers.IO) {\n    val (watchedSeasonsIds, watchedEpisodesIds) = awaitAll(\n      async { episodesManager.getWatchedSeasonsIds(show) },\n      async { episodesManager.getWatchedEpisodesIds(show) }\n    )\n\n    val isSeasonWatched = watchedSeasonsIds.any { id -> id == season.id }\n    val episodes = season.episodes.map { episodeItem ->\n      val isEpisodeWatched = watchedEpisodesIds.any { id -> id == episodeItem.id }\n      episodeItem.copy(season = season.season, isWatched = isEpisodeWatched)\n    }\n\n    season.copy(episodes = episodes, isWatched = isSeasonWatched)\n  }\n\n  suspend fun markWatchedEpisodes(\n    show: Show,\n    seasonsList: List<SeasonListItem>?\n  ): List<SeasonListItem> =\n    withContext(dispatchers.IO) {\n      val items = mutableListOf<SeasonListItem>()\n\n      val (watchedSeasonsIds, watchedEpisodesIds) = awaitAll(\n        async { episodesManager.getWatchedSeasonsIds(show) },\n        async { episodesManager.getWatchedEpisodesIds(show) }\n      )\n\n      seasonsList?.forEach { item ->\n        val isSeasonWatched = watchedSeasonsIds.any { id -> id == item.id }\n        val episodes = item.episodes.map { episodeItem ->\n          val isEpisodeWatched = watchedEpisodesIds.any { id -> id == episodeItem.id }\n          episodeItem.copy(season = item.season, isWatched = isEpisodeWatched)\n        }\n        val updated = item.copy(episodes = episodes, isWatched = isSeasonWatched)\n        items.add(updated)\n      }\n\n      items\n    }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/episodes/cases/EpisodesRatingCase.kt",
    "content": "package com.michaldrabik.ui_show.episodes.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.Season\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass EpisodesRatingCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val ratingsRepository: RatingsRepository,\n) {\n\n  suspend fun loadRating(episode: Episode) = withContext(dispatchers.IO) {\n    ratingsRepository.shows.loadRating(episode)\n  }\n\n  suspend fun loadRating(season: Season) = withContext(dispatchers.IO) {\n    ratingsRepository.shows.loadRating(season)\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/episodes/cases/EpisodesSetEpisodeWatchedCase.kt",
    "content": "package com.michaldrabik.ui_show.episodes.cases\n\nimport com.michaldrabik.repository.EpisodesManager\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.EpisodeBundle\nimport com.michaldrabik.ui_show.sections.seasons.helpers.SeasonsCache\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass EpisodesSetEpisodeWatchedCase @Inject constructor(\n  private val showsRepository: ShowsRepository,\n  private val episodesManager: EpisodesManager,\n  private val quickSyncManager: QuickSyncManager,\n  private val userTraktManager: UserTraktManager,\n  private val seasonsCache: SeasonsCache,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun setEpisodeWatched(\n    episodeBundle: EpisodeBundle,\n    isChecked: Boolean\n  ): Result {\n    val (episode, _, show) = episodeBundle\n\n    val isMyShows = showsRepository.myShows.exists(show.ids.trakt)\n    val isWatchlist = showsRepository.watchlistShows.exists(show.ids.trakt)\n    val isHidden = showsRepository.hiddenShows.exists(show.ids.trakt)\n    val isCollection = isMyShows || isWatchlist || isHidden\n\n    when {\n      isChecked -> {\n        episodesManager.setEpisodeWatched(episodeBundle)\n        if (isMyShows) {\n          quickSyncManager.scheduleEpisodes(\n            episodesIds = listOf(episode.ids.trakt.id),\n            showId = show.traktId,\n            clearProgress = false\n          )\n        }\n        return Result.SUCCESS\n      }\n      else -> {\n        episodesManager.setEpisodeUnwatched(episodeBundle)\n        quickSyncManager.clearEpisodes(listOf(episode.ids.trakt.id))\n\n        val traktQuickRemoveEnabled = settingsRepository.load().traktQuickRemoveEnabled\n        val isSeasonLocal = seasonsCache.areSeasonsLocal(show.ids.trakt)\n\n        val showRemoveTrakt = userTraktManager.isAuthorized() && traktQuickRemoveEnabled && !isSeasonLocal && isCollection\n        if (showRemoveTrakt) {\n          return Result.REMOVE_FROM_TRAKT\n        }\n\n        return Result.SUCCESS\n      }\n    }\n  }\n\n  enum class Result {\n    SUCCESS,\n    REMOVE_FROM_TRAKT\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/episodes/cases/EpisodesSetSeasonWatchedCase.kt",
    "content": "package com.michaldrabik.ui_show.episodes.cases\n\nimport com.michaldrabik.repository.EpisodesManager\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.SeasonBundle\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_show.sections.seasons.helpers.SeasonsCache\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass EpisodesSetSeasonWatchedCase @Inject constructor(\n  private val showsRepository: ShowsRepository,\n  private val episodesManager: EpisodesManager,\n  private val quickSyncManager: QuickSyncManager,\n  private val userManager: UserTraktManager,\n  private val seasonsCache: SeasonsCache,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun setSeasonWatched(\n    show: Show,\n    season: Season,\n    isChecked: Boolean\n  ): Result {\n    val bundle = SeasonBundle(season, show)\n\n    val isMyShows = showsRepository.myShows.exists(show.ids.trakt)\n    val isWatchlist = showsRepository.watchlistShows.exists(show.ids.trakt)\n    val isHidden = showsRepository.hiddenShows.exists(show.ids.trakt)\n    val isCollection = isMyShows || isWatchlist || isHidden\n\n    when {\n      isChecked -> {\n        val episodesAdded = episodesManager.setSeasonWatched(bundle)\n        if (isMyShows) {\n          quickSyncManager.scheduleEpisodes(\n            showId = show.traktId,\n            episodesIds = episodesAdded.map { it.ids.trakt.id }\n          )\n        }\n        return Result.SUCCESS\n      }\n      else -> {\n        episodesManager.setSeasonUnwatched(bundle)\n        quickSyncManager.clearEpisodes(season.episodes.map { it.ids.trakt.id })\n\n        val traktQuickRemoveEnabled = settingsRepository.load().traktQuickRemoveEnabled\n        val isSeasonLocal = seasonsCache.areSeasonsLocal(show.ids.trakt)\n\n        val showRemoveTrakt = userManager.isAuthorized() && traktQuickRemoveEnabled && !isSeasonLocal && isCollection\n        if (showRemoveTrakt) {\n          return Result.REMOVE_FROM_TRAKT\n        }\n        return Result.SUCCESS\n      }\n    }\n  }\n\n  enum class Result {\n    SUCCESS,\n    REMOVE_FROM_TRAKT\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/episodes/cases/EpisodesTranslationCase.kt",
    "content": "package com.michaldrabik.ui_show.episodes.cases\n\nimport com.michaldrabik.common.Config.DEFAULT_LANGUAGE\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.SeasonTranslation\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass EpisodesTranslationCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val translationsRepository: TranslationsRepository\n) {\n\n  suspend fun loadTranslations(season: Season?, show: Show): List<SeasonTranslation> =\n    withContext(dispatchers.IO) {\n      if (season == null) {\n        return@withContext emptyList()\n      }\n\n      val language = translationsRepository.getLanguage()\n      if (language == DEFAULT_LANGUAGE) {\n        return@withContext emptyList()\n      }\n\n      translationsRepository.loadTranslations(season, show.ids.trakt, language)\n    }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/episodes/recycler/EpisodeListItem.kt",
    "content": "package com.michaldrabik.ui_show.episodes.recycler\n\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_model.TraktRating\nimport com.michaldrabik.ui_model.Translation\nimport java.time.format.DateTimeFormatter\n\ndata class EpisodeListItem(\n  val episode: Episode,\n  val season: Season,\n  val isWatched: Boolean,\n  val translation: Translation? = null,\n  val myRating: TraktRating? = null,\n  val dateFormat: DateTimeFormatter? = null,\n  val isLocked: Boolean = true,\n  val isAnime: Boolean = false,\n  val spoilers: SpoilersSettings\n) {\n\n  val id = episode.ids.trakt.id\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/episodes/recycler/EpisodeListItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_show.episodes.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass EpisodeListItemDiffCallback(\n  private val oldList: List<EpisodeListItem>,\n  private val newList: List<EpisodeListItem>\n) : DiffUtil.Callback() {\n\n  override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =\n    oldList[oldItemPosition].id == newList[newItemPosition].id\n\n  override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {\n    val (_, _, isWatched, translation, rating, _, isLocked, _, spoilers) = oldList[oldItemPosition]\n    val (_, _, isWatched2, translation2, rating2, _, isLocked2, _, spoilers2) = newList[newItemPosition]\n\n    return isWatched == isWatched2 &&\n      translation?.title == translation2?.title &&\n      rating == rating2 &&\n      isLocked == isLocked2 &&\n      spoilers == spoilers2\n  }\n\n  override fun getOldListSize() = oldList.size\n\n  override fun getNewListSize() = newList.size\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/episodes/recycler/EpisodeView.kt",
    "content": "package com.michaldrabik.ui_show.episodes.recycler\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.ui_base.utilities.extensions.addRipple\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.databinding.ViewEpisodeBinding\nimport java.util.Locale.ENGLISH\n\nclass EpisodeView : ConstraintLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewEpisodeBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    addRipple()\n  }\n\n  fun bind(\n    item: EpisodeListItem,\n    itemClickListener: (Episode, Boolean) -> Unit,\n    itemCheckedListener: (Episode, Boolean) -> Unit,\n    isLocked: Boolean\n  ) {\n    clear()\n    with(binding) {\n      bindTitle(item)\n\n      val hasAired = item.episode.hasAired(item.season) || item.season.isSpecial()\n      episodeCheckbox.isChecked = item.isWatched\n      episodeCheckbox.isEnabled = hasAired || !isLocked\n\n      val rating = String.format(ENGLISH, \"%.1f\", item.episode.rating)\n      episodeRating.visibleIf(item.episode.rating != 0F)\n      if (!item.isWatched && item.spoilers.isEpisodeRatingHidden) {\n        episodeRating.tag = rating\n        episodeRating.text = Config.SPOILERS_RATINGS_HIDE_SYMBOL\n        if (item.spoilers.isTapToReveal) {\n          with(episodeRating) {\n            onClick {\n              tag?.let { text = it.toString() }\n              isClickable = false\n            }\n          }\n        }\n      } else {\n        episodeRating.text = rating\n      }\n\n      item.myRating?.let {\n        episodeMyStarIcon.visible()\n        episodeMyRating.visible()\n        episodeMyRating.text = String.format(ENGLISH, \"%d\", item.myRating.rating)\n      }\n\n      if (!hasAired) {\n        val date = item.episode.firstAired?.toLocalZone()\n        val displayDate = date?.let { item.dateFormat?.format(it)?.capitalizeWords() } ?: context.getString(R.string.textTba)\n        episodeTitle.text = String.format(ENGLISH, context.getString(R.string.textEpisodeDate), item.episode.number, displayDate)\n      }\n\n      episodeCheckbox.setOnCheckedChangeListener { _, isChecked -> itemCheckedListener(item.episode, isChecked) }\n      onClick { itemClickListener(item.episode, item.isWatched) }\n    }\n  }\n\n  private fun bindTitle(item: EpisodeListItem) {\n    with(binding) {\n      val titleText = String.format(ENGLISH, context.getString(R.string.textEpisode), item.episode.number)\n        .plus(item.episode.numberAbs?.let { if (it > 0 && item.isAnime) \" ($it)\" else \"\" } ?: \"\")\n\n      var overviewText = when {\n        !item.translation?.title.isNullOrBlank() -> item.translation?.title\n        item.episode.title.isEmpty() -> context.getString(R.string.textTba)\n        item.episode.title == \"Episode ${item.episode.number}\" -> titleText\n        else -> item.episode.title\n      }\n\n      if (!item.isWatched && item.spoilers.isEpisodeTitleHidden) {\n        episodeOverview.tag = overviewText.toString()\n        overviewText = SPOILERS_REGEX.replace(overviewText.toString(), SPOILERS_HIDE_SYMBOL)\n\n        if (item.spoilers.isTapToReveal) {\n          episodeOverview.onClick { view ->\n            view.tag?.let { episodeOverview.text = it.toString() }\n            view.isClickable = false\n          }\n        }\n      }\n\n      episodeTitle.text = titleText\n      episodeOverview.text = overviewText\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      episodeCheckbox.setOnCheckedChangeListener(null)\n      episodeMyStarIcon.gone()\n      episodeMyRating.gone()\n    }\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/episodes/recycler/EpisodesAdapter.kt",
    "content": "package com.michaldrabik.ui_show.episodes.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_model.Episode\n\nclass EpisodesAdapter(\n  private val itemClickListener: (Episode, Boolean) -> Unit,\n  private val itemCheckedListener: (Episode, Boolean) -> Unit,\n) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n  private val items: MutableList<EpisodeListItem> = mutableListOf()\n  private var isLocked = true\n\n  fun setItems(newItems: List<EpisodeListItem>) {\n    val elements = newItems.map { it.copy(isLocked = isLocked) }\n    val diffCallback = EpisodeListItemDiffCallback(items, elements)\n    val diffResult = DiffUtil.calculateDiff(diffCallback)\n    this.items.apply {\n      clear()\n      addAll(elements)\n    }\n    diffResult.dispatchUpdatesTo(this)\n  }\n\n  fun clearItems() {\n    isLocked = true\n    items.clear()\n    notifyDataSetChanged()\n  }\n\n  fun toggleEpisodesLock() {\n    isLocked = !isLocked\n    setItems(items.toList())\n  }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    ViewHolderShow(EpisodeView(parent.context))\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    (holder.itemView as EpisodeView).bind(items[position], itemClickListener, itemCheckedListener, isLocked)\n  }\n\n  override fun getItemCount() = items.size\n\n  class ViewHolderShow(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/helpers/ShowDetailsMeta.kt",
    "content": "package com.michaldrabik.ui_show.helpers\n\ndata class ShowDetailsMeta(\n  val isSignedIn: Boolean,\n  val isPremium: Boolean\n)\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/quicksetup/QuickSetupAdapter.kt",
    "content": "package com.michaldrabik.ui_show.quicksetup\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_show.quicksetup.views.QuickSetupHeaderView\nimport com.michaldrabik.ui_show.quicksetup.views.QuickSetupItemView\n\nclass QuickSetupAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n  companion object {\n    private const val TYPE_HEADER = 0\n    private const val TYPE_ITEM = 1\n  }\n\n  var onItemClickListener: ((Episode, Boolean) -> Unit)? = null\n  private val asyncDiffer = AsyncListDiffer(this, QuickSetupItemDiffCallback())\n\n  fun setItems(newItems: List<QuickSetupListItem>) =\n    asyncDiffer.submitList(newItems)\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) {\n    TYPE_HEADER -> ViewHolder(QuickSetupHeaderView(parent.context))\n    TYPE_ITEM -> ViewHolder(QuickSetupItemView(parent.context))\n    else -> error(\"Unsupported view type\")\n  }\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    when (holder.itemViewType) {\n      TYPE_HEADER -> (holder.itemView as QuickSetupHeaderView).bind(item.season)\n      TYPE_ITEM -> (holder.itemView as QuickSetupItemView).bind(item.episode, item.isChecked, onItemClickListener)\n    }\n  }\n\n  override fun getItemViewType(position: Int) = when {\n    asyncDiffer.currentList[position].isHeader -> TYPE_HEADER\n    else -> TYPE_ITEM\n  }\n\n  override fun getItemCount() = asyncDiffer.currentList.size\n\n  fun getItems() = asyncDiffer.currentList\n\n  class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/quicksetup/QuickSetupItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_show.quicksetup\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass QuickSetupItemDiffCallback : DiffUtil.ItemCallback<QuickSetupListItem>() {\n\n  override fun areItemsTheSame(oldItem: QuickSetupListItem, newItem: QuickSetupListItem) =\n    oldItem.episode.ids.trakt == newItem.episode.ids.trakt &&\n      oldItem.season.ids.trakt == newItem.season.ids.trakt &&\n      oldItem.isHeader == newItem.isHeader\n\n  override fun areContentsTheSame(oldItem: QuickSetupListItem, newItem: QuickSetupListItem) =\n    oldItem.episode == newItem.episode &&\n      oldItem.season == newItem.season &&\n      oldItem.isHeader == newItem.isHeader &&\n      oldItem.isChecked == newItem.isChecked\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/quicksetup/QuickSetupListItem.kt",
    "content": "package com.michaldrabik.ui_show.quicksetup\n\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.Season\n\ndata class QuickSetupListItem(\n  val episode: Episode,\n  val season: Season,\n  val isHeader: Boolean = false,\n  val isChecked: Boolean = false\n)\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/quicksetup/QuickSetupView.kt",
    "content": "package com.michaldrabik.ui_show.quicksetup\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.widget.FrameLayout\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_show.databinding.ViewQuickSetupBinding\n\nclass QuickSetupView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewQuickSetupBinding.inflate(LayoutInflater.from(context), this)\n  private val quickSetupAdapter by lazy { QuickSetupAdapter() }\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n    setupRecycler()\n  }\n\n  private fun setupRecycler() {\n    binding.viewQuickSetupRecycler.apply {\n      setHasFixedSize(true)\n      adapter = quickSetupAdapter\n      layoutManager = LinearLayoutManager(context, VERTICAL, false)\n      itemAnimator = null\n    }\n    quickSetupAdapter.onItemClickListener = { episode, isChecked ->\n      onItemChecked(episode, isChecked)\n    }\n  }\n\n  fun bind(seasons: List<Season>) {\n    val items = mutableListOf<QuickSetupListItem>()\n    seasons\n      .filterNot { it.isSpecial() }\n      .sortedByDescending { it.number }\n      .forEach { season ->\n        season.episodes\n          .filter { it.hasAired(season) }\n          .sortedByDescending { it.number }\n          .forEachIndexed { index, episode ->\n            if (index == 0) {\n              items.add(QuickSetupListItem(episode, season, isHeader = true))\n            }\n            items.add(QuickSetupListItem(episode, season))\n          }\n      }\n    quickSetupAdapter.setItems(items)\n  }\n\n  fun getSelectedItem() =\n    quickSetupAdapter.getItems().firstOrNull { it.isChecked }\n\n  private fun onItemChecked(episode: Episode, isChecked: Boolean) {\n    val items = quickSetupAdapter.getItems()\n      .map { it.copy(isChecked = false) }\n      .toMutableList()\n\n    val item = items\n      .filterNot { it.isHeader }\n      .first { it.episode.ids.trakt == episode.ids.trakt }\n      .copy(isChecked = !isChecked)\n\n    items.findReplace(item) { it.episode.ids.trakt == episode.ids.trakt && !it.isHeader }\n\n    quickSetupAdapter.setItems(items.toList())\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/quicksetup/views/QuickSetupHeaderView.kt",
    "content": "package com.michaldrabik.ui_show.quicksetup.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.databinding.ViewQuickSetupHeaderBinding\nimport java.util.Locale\n\nclass QuickSetupHeaderView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewQuickSetupHeaderBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n  }\n\n  fun bind(season: Season) {\n    binding.viewQuickSetupHeaderTitle.text =\n      String.format(Locale.ENGLISH, context.getString(R.string.textSeason), season.number)\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/quicksetup/views/QuickSetupItemView.kt",
    "content": "package com.michaldrabik.ui_show.quicksetup.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.databinding.ViewQuickSetupItemBinding\nimport java.util.Locale.ENGLISH\n\nclass QuickSetupItemView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewQuickSetupItemBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n  }\n\n  fun bind(\n    item: Episode,\n    isChecked: Boolean,\n    onItemClickListener: ((Episode, Boolean) -> Unit)?\n  ) {\n    val titleColor = if (isChecked) R.attr.colorAccent else android.R.attr.textColorSecondary\n    val subTitleColor = if (isChecked) R.attr.colorAccent else android.R.attr.textColorPrimary\n\n    with(binding) {\n      viewQuickSetupItemRadio.isChecked = isChecked\n      viewQuickSetupItemTitle.run {\n        text = String.format(ENGLISH, context.getString(R.string.textEpisode), item.number)\n        setTextColor(context.colorFromAttr(titleColor))\n      }\n      viewQuickSetupItemSubtitle.run {\n        text = item.title\n        setTextColor(context.colorFromAttr(subTitleColor))\n      }\n    }\n\n    onClick { onItemClickListener?.invoke(item, isChecked) }\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/nextepisode/ShowDetailsNextEpisodeFragment.kt",
    "content": "package com.michaldrabik.ui_show.sections.nextepisode\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.common.Config.SPOILERS_HIDE_SYMBOL\nimport com.michaldrabik.common.Config.SPOILERS_REGEX\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_episodes.details.EpisodeDetailsBottomSheet\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.ShowDetailsFragment\nimport com.michaldrabik.ui_show.ShowDetailsViewModel\nimport com.michaldrabik.ui_show.databinding.FragmentShowDetailsNextEpisodeBinding\nimport com.michaldrabik.ui_show.sections.nextepisode.helpers.NextEpisodeBundle\nimport dagger.hilt.android.AndroidEntryPoint\nimport java.util.Locale\n\n@AndroidEntryPoint\nclass ShowDetailsNextEpisodeFragment : BaseFragment<ShowDetailsNextEpisodeViewModel>(R.layout.fragment_show_details_next_episode) {\n\n  override val navigationId = R.id.showDetailsFragment\n  private val binding by viewBinding(FragmentShowDetailsNextEpisodeBinding::bind)\n\n  private val parentViewModel by viewModels<ShowDetailsViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<ShowDetailsNextEpisodeViewModel>()\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    launchAndRepeatStarted(\n      { parentViewModel.parentShowState.collect { it?.let { viewModel.loadNextEpisode(it) } } },\n      { viewModel.uiState.collect { render(it) } }\n    )\n  }\n\n  private fun render(uiState: ShowDetailsNextEpisodeUiState) {\n    with(uiState) {\n      with(binding) {\n        nextEpisode?.let { episodeBundle ->\n          val episode = episodeBundle.nextEpisode.second\n\n          var episodeTitle = episode.title\n          if (!episodeBundle.isWatched && spoilersSettings?.isEpisodeTitleHidden == true) {\n            showDetailsEpisodeText.tag = episodeTitle\n            episodeTitle = SPOILERS_REGEX.replace(episodeTitle, SPOILERS_HIDE_SYMBOL)\n\n            if (spoilersSettings.isTapToReveal) {\n              showDetailsEpisodeText.onClick { view ->\n                view.tag?.let {\n                  showDetailsEpisodeText.text = String.format(\n                    Locale.ENGLISH,\n                    getString(R.string.textEpisodeTitle),\n                    episode.season,\n                    episode.number,\n                    it.toString()\n                  )\n                }\n                view.isClickable = false\n              }\n            }\n          }\n\n          showDetailsEpisodeText.text = String.format(\n            Locale.ENGLISH,\n            getString(R.string.textEpisodeTitle),\n            episode.season,\n            episode.number,\n            episodeTitle\n          )\n\n          episode.firstAired?.let { date ->\n            val displayDate = episodeBundle.dateFormat?.format(date.toLocalZone())?.capitalizeWords()\n            showDetailsEpisodeAirtime.visible()\n            showDetailsEpisodeAirtime.text = displayDate\n          }\n\n          showDetailsEpisodeRoot.onClick { openDetails(episodeBundle) }\n          (requireParentFragment() as ShowDetailsFragment)\n            .binding.showDetailsEpisodeFragment.fadeIn(withHardware = true)\n        }\n      }\n    }\n  }\n\n  private fun openDetails(episodeBundle: NextEpisodeBundle) {\n    val (show, episode) = episodeBundle.nextEpisode\n    val bundle = EpisodeDetailsBottomSheet.createBundle(\n      ids = show.ids,\n      episode = episode,\n      seasonEpisodesIds = null,\n      isWatched = episodeBundle.isWatched,\n      showButton = false,\n      showTabs = false\n    )\n    navigateToSafe(R.id.actionShowDetailsFragmentEpisodeDetails, bundle)\n  }\n\n  override fun setupBackPressed() = Unit\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/nextepisode/ShowDetailsNextEpisodeUiState.kt",
    "content": "package com.michaldrabik.ui_show.sections.nextepisode\n\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_show.sections.nextepisode.helpers.NextEpisodeBundle\n\ndata class ShowDetailsNextEpisodeUiState(\n  val nextEpisode: NextEpisodeBundle? = null,\n  val spoilersSettings: SpoilersSettings? = null\n)\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/nextepisode/ShowDetailsNextEpisodeViewModel.kt",
    "content": "package com.michaldrabik.ui_show.sections.nextepisode\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.SpoilersSettings\nimport com.michaldrabik.ui_show.sections.nextepisode.cases.ShowDetailsNextEpisodeCase\nimport com.michaldrabik.ui_show.sections.nextepisode.cases.ShowDetailsTranslationCase\nimport com.michaldrabik.ui_show.sections.nextepisode.cases.ShowDetailsWatchedCase\nimport com.michaldrabik.ui_show.sections.nextepisode.helpers.NextEpisodeBundle\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ShowDetailsNextEpisodeViewModel @Inject constructor(\n  private val nextEpisodeCase: ShowDetailsNextEpisodeCase,\n  private val translationCase: ShowDetailsTranslationCase,\n  private val watchedCase: ShowDetailsWatchedCase,\n  private val spoilersSettingsRepository: SettingsSpoilersRepository,\n  private val dateFormatProvider: DateFormatProvider,\n) : ViewModel() {\n\n  private lateinit var show: Show\n\n  private val nextEpisodeState = MutableStateFlow<NextEpisodeBundle?>(null)\n  private val spoilersState = MutableStateFlow<SpoilersSettings?>(null)\n\n  fun loadNextEpisode(show: Show) {\n    if (this::show.isInitialized) return\n    this.show = show\n    viewModelScope.launch {\n      try {\n        val dateFormat = dateFormatProvider.loadFullHourFormat()\n        val episode = nextEpisodeCase.loadNextEpisode(show.ids.trakt)\n        episode?.let {\n          val isWatched = watchedCase.isWatched(show, it)\n          val nextEpisode = NextEpisodeBundle(\n            nextEpisode = Pair(show, it),\n            dateFormat = dateFormat,\n            isWatched = isWatched\n          )\n          spoilersState.value = spoilersSettingsRepository.getAll()\n          nextEpisodeState.value = nextEpisode\n\n          val translation = translationCase.loadTranslation(episode, show)\n          if (translation?.title?.isNotBlank() == true) {\n            val translated = it.copy(title = translation.title)\n            val nextEpisodeTranslated = NextEpisodeBundle(\n              nextEpisode = Pair(show, translated),\n              dateFormat = dateFormat,\n              isWatched = isWatched\n            )\n            nextEpisodeState.value = nextEpisodeTranslated\n          }\n        }\n      } catch (error: Throwable) {\n        Logger.record(error, \"ShowDetailsViewModel::loadNextEpisode()\")\n        rethrowCancellation(error)\n      }\n    }\n  }\n\n  val uiState = combine(\n    nextEpisodeState,\n    spoilersState\n  ) { s1, s2 ->\n    ShowDetailsNextEpisodeUiState(\n      nextEpisode = s1,\n      spoilersSettings = s2\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ShowDetailsNextEpisodeUiState()\n  )\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/nextepisode/cases/ShowDetailsNextEpisodeCase.kt",
    "content": "package com.michaldrabik.ui_show.sections.nextepisode.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsNextEpisodeCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val remoteSource: RemoteDataSource,\n  private val mappers: Mappers,\n) {\n\n  suspend fun loadNextEpisode(traktId: IdTrakt): Episode? = withContext(dispatchers.IO) {\n    val episode = remoteSource.trakt.fetchNextEpisode(traktId.id) ?: return@withContext null\n    return@withContext mappers.episode.fromNetwork(episode)\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/nextepisode/cases/ShowDetailsTranslationCase.kt",
    "content": "package com.michaldrabik.ui_show.sections.nextepisode.cases\n\nimport com.michaldrabik.common.Config.DEFAULT_LANGUAGE\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.Translation\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsTranslationCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val translationsRepository: TranslationsRepository,\n) {\n\n  suspend fun loadTranslation(\n    episode: Episode,\n    show: Show,\n    onlyLocal: Boolean = false,\n  ): Translation? = withContext(dispatchers.IO) {\n    val language = translationsRepository.getLanguage()\n    if (language == DEFAULT_LANGUAGE) {\n      return@withContext null\n    }\n    translationsRepository.loadTranslation(episode, show.ids.trakt, language, onlyLocal)\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/nextepisode/cases/ShowDetailsWatchedCase.kt",
    "content": "package com.michaldrabik.ui_show.sections.nextepisode.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.data_local.sources.EpisodesLocalDataSource\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsWatchedCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val episodesLocalDataSource: EpisodesLocalDataSource,\n) {\n\n  suspend fun isWatched(\n    show: Show,\n    episode: Episode,\n  ): Boolean = withContext(dispatchers.IO) {\n    return@withContext episodesLocalDataSource.isEpisodeWatched(\n      showTraktId = show.traktId,\n      episodeTraktId = episode.ids.trakt.id\n    )\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/nextepisode/helpers/NextEpisodeBundle.kt",
    "content": "package com.michaldrabik.ui_show.sections.nextepisode.helpers\n\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.Show\nimport java.time.format.DateTimeFormatter\n\ndata class NextEpisodeBundle(\n  val nextEpisode: Pair<Show, Episode>,\n  val isWatched: Boolean,\n  val dateFormat: DateTimeFormatter? = null,\n)\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/people/ShowDetailsPeopleFragment.kt",
    "content": "package com.michaldrabik.ui_show.sections.people\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.TextView\nimport androidx.fragment.app.clearFragmentResultListener\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.extensions.addDivider\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.trimWithSuffix\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_model.Person.Department\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_PERSON\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_PERSON_ARGS\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_DETAILS\nimport com.michaldrabik.ui_people.details.PersonDetailsArgs\nimport com.michaldrabik.ui_people.details.PersonDetailsBottomSheet\nimport com.michaldrabik.ui_people.list.PeopleListBottomSheet\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.ShowDetailsEvent.OpenPeopleSheet\nimport com.michaldrabik.ui_show.ShowDetailsEvent.OpenPersonSheet\nimport com.michaldrabik.ui_show.ShowDetailsFragment\nimport com.michaldrabik.ui_show.ShowDetailsViewModel\nimport com.michaldrabik.ui_show.databinding.FragmentShowDetailsPeopleBinding\nimport com.michaldrabik.ui_show.sections.people.recycler.ActorsAdapter\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass ShowDetailsPeopleFragment : BaseFragment<ShowDetailsPeopleViewModel>(R.layout.fragment_show_details_people) {\n\n  override val navigationId = R.id.showDetailsFragment\n  private val binding by viewBinding(FragmentShowDetailsPeopleBinding::bind)\n\n  private val parentViewModel by viewModels<ShowDetailsViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<ShowDetailsPeopleViewModel>()\n\n  private var actorsAdapter: ActorsAdapter? = null\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    launchAndRepeatStarted(\n      { parentViewModel.parentShowState.collect { it?.let { viewModel.loadPeople(it) } } },\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      doAfterLaunch = { viewModel.loadLastPerson() }\n    )\n  }\n\n  private fun setupView() {\n    actorsAdapter = ActorsAdapter().apply {\n      itemClickListener = { viewModel.loadPersonDetails(it) }\n    }\n    binding.showDetailsActorsRecycler.apply {\n      setHasFixedSize(true)\n      adapter = actorsAdapter\n      layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)\n      addDivider(R.drawable.divider_horizontal_list, LinearLayoutManager.HORIZONTAL)\n    }\n  }\n\n  private fun openPersonSheet(show: Show, person: Person, personArgs: PersonDetailsArgs?) {\n    handleSheetResult()\n    val bundle = PersonDetailsBottomSheet.createBundle(person, show.ids.trakt, personArgs)\n    (requireParentFragment() as BaseFragment<*>)\n      .navigateToSafe(R.id.actionShowDetailsFragmentToPerson, bundle)\n  }\n\n  private fun openPeopleSheet(event: OpenPeopleSheet) {\n    val (show, people, department) = event\n\n    if (people.isEmpty()) return\n    if (people.size == 1) {\n      viewModel.loadPersonDetails(people.first())\n      return\n    }\n\n    handleSheetResult()\n\n    val title = (requireParentFragment() as ShowDetailsFragment).binding.showDetailsTitle.text.toString()\n    val bundle = PeopleListBottomSheet.createBundle(show.ids.trakt, title, Mode.SHOWS, department)\n    navigateToSafe(R.id.actionShowDetailsFragmentToPeopleList, bundle)\n  }\n\n  private fun render(uiState: ShowDetailsPeopleUiState) {\n    with(uiState) {\n      actors?.let {\n        if (actorsAdapter?.itemCount != 0) return@let\n        actorsAdapter?.setItems(it)\n        binding.showDetailsActorsRecycler.visibleIf(actors.isNotEmpty(), gone = false)\n        binding.showDetailsActorsEmptyView.visibleIf(actors.isEmpty())\n      }\n      crew?.let { renderCrew(it) }\n      isLoading.let {\n        binding.showDetailsActorsProgress.visibleIf(it)\n      }\n    }\n  }\n\n  private fun renderCrew(crew: Map<Department, List<Person>>) {\n\n    fun renderPeople(\n      labelView: View,\n      valueView: TextView,\n      people: List<Person>,\n      department: Department,\n    ) {\n      labelView.visibleIf(people.isNotEmpty())\n      valueView.visibleIf(people.isNotEmpty())\n      valueView.text = people\n        .take(2)\n        .joinToString(\"\\n\") { it.name.trimWithSuffix(20, \"…\") }\n        .plus(if (people.size > 2) \"\\n…\" else \"\")\n      valueView.onClick { viewModel.loadPeopleList(people, department) }\n    }\n\n    if (!crew.containsKey(Department.DIRECTING)) {\n      return\n    }\n\n    val directors = crew[Department.DIRECTING] ?: emptyList()\n    val writers = crew[Department.WRITING] ?: emptyList()\n    val sound = crew[Department.SOUND] ?: emptyList()\n\n    with(binding) {\n      renderPeople(showDetailsDirectingLabel, showDetailsDirectingValue, directors, Department.DIRECTING)\n      renderPeople(showDetailsWritingLabel, showDetailsWritingValue, writers, Department.WRITING)\n      renderPeople(showDetailsMusicLabel, showDetailsMusicValue, sound, Department.SOUND)\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      is OpenPersonSheet -> openPersonSheet(event.show, event.person, event.personArgs)\n      is OpenPeopleSheet -> openPeopleSheet(event)\n    }\n  }\n\n  @Suppress(\"DEPRECATION\")\n  private fun handleSheetResult() {\n    requireParentFragment()\n      .setFragmentResultListener(REQUEST_DETAILS) { _, bundle ->\n        val person = bundle.getParcelable<Person>(ARG_PERSON)\n        val personArgs = bundle.getParcelable<PersonDetailsArgs>(ARG_PERSON_ARGS)\n        person?.let {\n          viewModel.saveLastPerson(it, personArgs)\n          bundle.clear()\n        }\n        requireParentFragment().clearFragmentResultListener(REQUEST_DETAILS)\n      }\n  }\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    actorsAdapter = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/people/ShowDetailsPeopleUiState.kt",
    "content": "package com.michaldrabik.ui_show.sections.people\n\nimport com.michaldrabik.ui_model.Person\n\ndata class ShowDetailsPeopleUiState(\n  val isLoading: Boolean = true,\n  val actors: List<Person>? = null,\n  val crew: Map<Person.Department, List<Person>>? = null,\n)\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/people/ShowDetailsPeopleViewModel.kt",
    "content": "package com.michaldrabik.ui_show.sections.people\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_model.Person.Department\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_people.details.PersonDetailsArgs\nimport com.michaldrabik.ui_show.ShowDetailsEvent\nimport com.michaldrabik.ui_show.sections.people.cases.ShowDetailsPeopleCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ShowDetailsPeopleViewModel @Inject constructor(\n  private val actorsCase: ShowDetailsPeopleCase,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private lateinit var show: Show\n\n  private var lastOpenedPerson: Person? = null\n  private var lastOpenedPersonArgs: PersonDetailsArgs? = null\n\n  private val loadingState = MutableStateFlow(true)\n  private val actorsState = MutableStateFlow<List<Person>?>(null)\n  private val crewState = MutableStateFlow<Map<Department, List<Person>>?>(null)\n\n  fun loadPeople(show: Show) {\n    if (this::show.isInitialized) return\n    this.show = show\n\n    viewModelScope.launch {\n      try {\n        val people = actorsCase.loadPeople(show)\n\n        val actors = people.getOrDefault(Department.ACTING, emptyList())\n        val crew = people.filter { it.key !in arrayOf(Department.ACTING, Department.UNKNOWN) }\n\n        loadingState.value = false\n        actorsState.value = actors\n        crewState.value = crew\n\n        actorsCase.preloadDetails(actors)\n      } catch (error: Throwable) {\n        loadingState.value = false\n        actorsState.value = emptyList()\n        crewState.value = emptyMap()\n        rethrowCancellation(error)\n      }\n    }\n    Timber.d(\"Loading people...\")\n  }\n\n  fun loadPersonDetails(\n    person: Person,\n    personArgs: PersonDetailsArgs? = null,\n  ) {\n    viewModelScope.launch {\n      eventChannel.send(ShowDetailsEvent.OpenPersonSheet(show, person, personArgs))\n    }\n  }\n\n  fun loadPeopleList(people: List<Person>, department: Department) {\n    viewModelScope.launch {\n      eventChannel.send(ShowDetailsEvent.OpenPeopleSheet(show, people, department))\n    }\n  }\n\n  fun loadLastPerson() {\n    lastOpenedPerson?.let {\n      loadPersonDetails(it, lastOpenedPersonArgs)\n      lastOpenedPerson = null\n      lastOpenedPersonArgs = null\n    }\n  }\n\n  fun saveLastPerson(\n    person: Person,\n    personArgs: PersonDetailsArgs?,\n  ) {\n    lastOpenedPerson = person\n    lastOpenedPersonArgs = personArgs\n  }\n\n  val uiState = combine(\n    loadingState,\n    actorsState,\n    crewState\n  ) { s1, s2, s3 ->\n    ShowDetailsPeopleUiState(\n      isLoading = s1,\n      actors = s2,\n      crew = s3,\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ShowDetailsPeopleUiState()\n  )\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/people/cases/ShowDetailsPeopleCase.kt",
    "content": "package com.michaldrabik.ui_show.sections.people.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.PeopleRepository\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.CoroutineExceptionHandler\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.supervisorScope\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsPeopleCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val peopleRepository: PeopleRepository\n) {\n\n  suspend fun loadPeople(show: Show) = withContext(dispatchers.IO) {\n    peopleRepository.loadAllForShow(show.ids)\n  }\n\n  suspend fun preloadDetails(people: List<Person>) =\n    supervisorScope {\n      val errorHandler = CoroutineExceptionHandler { _, _ -> Timber.d(\"Failed to preload details.\") }\n      people.take(5).forEach {\n        launch(errorHandler) {\n          withContext(dispatchers.IO) {\n            peopleRepository.loadDetails(it)\n          }\n        }\n      }\n    }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/people/recycler/ActorView.kt",
    "content": "package com.michaldrabik.ui_show.sections.people.recycler\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.engine.DiskCacheStrategy.DATA\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade\nimport com.michaldrabik.common.Config.IMAGE_FADE_DURATION_MS\nimport com.michaldrabik.common.Config.TMDB_IMAGE_BASE_ACTOR_URL\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.withFailListener\nimport com.michaldrabik.ui_model.Person\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.databinding.ViewActorBinding\n\nclass ActorView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewActorBinding.inflate(LayoutInflater.from(context), this)\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.actorTileCorner) }\n\n  init {\n    layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)\n    clipChildren = false\n  }\n\n  fun bind(item: Person, clickListener: (Person) -> Unit) {\n    clear()\n    tag = item.ids.tmdb.id\n    onClick { clickListener(item) }\n    binding.actorName.text = item.name.split(\" \").joinToString(\"\\n\")\n    loadImage(item)\n  }\n\n  private fun loadImage(person: Person) {\n    with(binding) {\n      if (person.imagePath.isNullOrBlank()) {\n        actorPlaceholder.visible()\n        actorImage.gone()\n        return\n      }\n\n      Glide.with(this@ActorView)\n        .load(\"$TMDB_IMAGE_BASE_ACTOR_URL${person.imagePath}\")\n        .diskCacheStrategy(DATA)\n        .transform(CenterCrop(), RoundedCorners(cornerRadius))\n        .transition(withCrossFade(IMAGE_FADE_DURATION_MS))\n        .withFailListener {\n          actorPlaceholder.visible()\n          actorImage.gone()\n        }\n        .into(actorImage)\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      actorPlaceholder.gone()\n      actorImage.visible()\n      Glide.with(this@ActorView).clear(actorImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/people/recycler/ActorsAdapter.kt",
    "content": "package com.michaldrabik.ui_show.sections.people.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_model.Person\n\nclass ActorsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n  private val items: MutableList<Person> = mutableListOf()\n\n  var itemClickListener: (Person) -> Unit = {}\n\n  fun setItems(items: List<Person>) {\n    this.items.apply {\n      clear()\n      addAll(items)\n    }\n    notifyDataSetChanged()\n  }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    ViewHolderShow(ActorView(parent.context))\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    (holder.itemView as ActorView).bind(items[position], itemClickListener)\n  }\n\n  override fun getItemCount() = items.size\n\n  class ViewHolderShow(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/ratings/ShowDetailsRatingsFragment.kt",
    "content": "package com.michaldrabik.ui_show.sections.ratings\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.viewModels\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.AppCountry\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.openImdbUrl\nimport com.michaldrabik.ui_base.utilities.extensions.openWebUrl\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.IdImdb\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.ShowDetailsViewModel\nimport com.michaldrabik.ui_show.databinding.FragmentShowDetailsRatingsBinding\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass ShowDetailsRatingsFragment : BaseFragment<ShowDetailsRatingsViewModel>(R.layout.fragment_show_details_ratings) {\n\n  override val navigationId = R.id.showDetailsFragment\n  private val binding by viewBinding(FragmentShowDetailsRatingsBinding::bind)\n\n  private val parentViewModel by viewModels<ShowDetailsViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<ShowDetailsRatingsViewModel>()\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    launchAndRepeatStarted(\n      { parentViewModel.parentShowState.collect { it?.let { viewModel.loadRatings(it) } } },\n      { parentViewModel.parentFollowedState.collect { it?.let { viewModel.refreshRatings() } } },\n      { viewModel.uiState.collect { render(it) } }\n    )\n  }\n\n  private fun render(uiState: ShowDetailsRatingsUiState) {\n    with(uiState) {\n      with(binding) {\n        ratings?.let {\n          if (showDetailsRatings.isBound() && !isRefreshingRatings) {\n            return\n          }\n          showDetailsRatings.bind(ratings)\n          show?.let {\n            showDetailsRatings.onTraktClick = { openLink(ShowLink.TRAKT, show.traktId.toString()) }\n            showDetailsRatings.onImdbClick = { openLink(ShowLink.IMDB, show.ids.imdb.id) }\n            showDetailsRatings.onMetaClick = { openLink(ShowLink.METACRITIC, show.title) }\n            showDetailsRatings.onRottenClick = {\n              val url = it.rottenTomatoesUrl\n              if (!url.isNullOrBlank()) {\n                openWebUrl(url) ?: openLink(ShowLink.ROTTEN, \"${show.title} ${show.year}\")\n              } else {\n                openLink(ShowLink.ROTTEN, \"${show.title} ${show.year}\")\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  private fun openLink(\n    link: ShowLink,\n    id: String,\n    country: AppCountry = AppCountry.UNITED_STATES,\n  ) {\n    if (link == ShowLink.IMDB) {\n      openImdbUrl(IdImdb(id)) ?: showSnack(MessageEvent.Info(R.string.errorCouldNotFindApp))\n    } else {\n      openWebUrl(link.getUri(id, country)) ?: showSnack(MessageEvent.Info(R.string.errorCouldNotFindApp))\n    }\n  }\n\n  override fun setupBackPressed() = Unit\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/ratings/ShowDetailsRatingsUiState.kt",
    "content": "package com.michaldrabik.ui_show.sections.ratings\n\nimport com.michaldrabik.ui_model.Ratings\nimport com.michaldrabik.ui_model.Show\n\ndata class ShowDetailsRatingsUiState(\n  val show: Show? = null,\n  val ratings: Ratings? = null,\n  val isRefreshingRatings: Boolean = false\n)\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/ratings/ShowDetailsRatingsViewModel.kt",
    "content": "package com.michaldrabik.ui_show.sections.ratings\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_model.Ratings\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_show.sections.ratings.cases.ShowDetailsRatingCase\nimport com.michaldrabik.ui_show.sections.ratings.cases.ShowDetailsRatingSpoilersCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport java.util.Locale\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ShowDetailsRatingsViewModel @Inject constructor(\n  private val ratingsCase: ShowDetailsRatingCase,\n  private val ratingsSpoilersCase: ShowDetailsRatingSpoilersCase,\n) : ViewModel() {\n\n  private lateinit var show: Show\n\n  private val showState = MutableStateFlow<Show?>(null)\n  private val ratingsState = MutableStateFlow<Ratings?>(null)\n  private val isRefreshingRatingsState = MutableStateFlow(false)\n\n  fun loadRatings(show: Show) {\n    if (this::show.isInitialized) return\n    this.show = show\n\n    viewModelScope.launch {\n      showState.value = show\n\n      val traktRatings = Ratings(\n        trakt = Ratings.Value(String.format(Locale.ENGLISH, \"%.1f\", show.rating), false),\n        imdb = Ratings.Value(null, true),\n        metascore = Ratings.Value(null, true),\n        rottenTomatoes = Ratings.Value(null, true)\n      )\n\n      try {\n        isRefreshingRatingsState.value = false\n        ratingsState.value = ratingsSpoilersCase.hideSpoilerRatings(show, traktRatings)\n        val ratings = ratingsCase.loadExternalRatings(show)\n        ratingsState.value = ratingsSpoilersCase.hideSpoilerRatings(show, ratings)\n      } catch (error: Throwable) {\n        ratingsState.value = ratingsSpoilersCase.hideSpoilerRatings(show, traktRatings)\n        rethrowCancellation(error)\n      }\n    }\n  }\n\n  fun refreshRatings() {\n    val show = showState.value\n    val ratings = ratingsState.value\n    viewModelScope.launch {\n      if (show != null && ratings != null) {\n        isRefreshingRatingsState.value = true\n        ratingsState.value = ratingsSpoilersCase.hideSpoilerRatings(show, ratings)\n      }\n    }\n  }\n\n  val uiState = combine(\n    showState,\n    ratingsState,\n    isRefreshingRatingsState\n  ) { s1, s2, s3 ->\n    ShowDetailsRatingsUiState(\n      show = s1,\n      ratings = s2,\n      isRefreshingRatings = s3\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ShowDetailsRatingsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/ratings/ShowLink.kt",
    "content": "package com.michaldrabik.ui_show.sections.ratings\n\nimport android.net.Uri\nimport com.michaldrabik.ui_base.common.AppCountry\n\nenum class ShowLink {\n  IMDB,\n  TRAKT,\n  TVDB,\n  TMDB,\n  METACRITIC,\n  ROTTEN,\n  JUST_WATCH;\n\n  fun getUri(\n    id: String,\n    country: AppCountry,\n  ) = when (this) {\n    IMDB -> \"https://www.imdb.com/title/$id\"\n    TRAKT -> \"https://trakt.tv/search/trakt/$id?id_type=show\"\n    TVDB -> \"https://www.thetvdb.com/?id=$id&tab=series\"\n    TMDB -> \"https://www.themoviedb.org/tv/$id\"\n    METACRITIC -> \"https://www.metacritic.com/search/$id?category=1\"\n    ROTTEN -> \"https://www.rottentomatoes.com/search?search=$id\"\n    JUST_WATCH -> \"https://www.justwatch.com/${country.code}/${country.justWatchQuery}?content_type=show&q=${Uri.encode(id)}\"\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/ratings/cases/ShowDetailsRatingCase.kt",
    "content": "package com.michaldrabik.ui_show.sections.ratings.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsRatingCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val ratingsRepository: RatingsRepository,\n) {\n\n  suspend fun loadRating(show: Show) = withContext(dispatchers.IO) {\n    ratingsRepository.shows.loadRatings(listOf(show)).firstOrNull()\n  }\n\n  suspend fun loadExternalRatings(show: Show) = withContext(dispatchers.IO) {\n    ratingsRepository.shows.external.loadRatings(show)\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/ratings/cases/ShowDetailsRatingSpoilersCase.kt",
    "content": "package com.michaldrabik.ui_show.sections.ratings.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.settings.SettingsSpoilersRepository\nimport com.michaldrabik.ui_model.Ratings\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_show.ShowDetailsUiState.FollowedState\nimport com.michaldrabik.ui_show.cases.ShowDetailsHiddenCase\nimport com.michaldrabik.ui_show.cases.ShowDetailsMyShowsCase\nimport com.michaldrabik.ui_show.cases.ShowDetailsWatchlistCase\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsRatingSpoilersCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val watchlistCase: ShowDetailsWatchlistCase,\n  private val hiddenCase: ShowDetailsHiddenCase,\n  private val myShowsCase: ShowDetailsMyShowsCase,\n  private val settingsSpoilersRepository: SettingsSpoilersRepository,\n) {\n\n  suspend fun hideSpoilerRatings(\n    show: Show,\n    ratings: Ratings,\n  ): Ratings = withContext(dispatchers.IO) {\n    val spoilers = settingsSpoilersRepository.getAll()\n\n    val isMy = async { myShowsCase.isMyShows(show) }\n    val isWatchlist = async { watchlistCase.isWatchlist(show) }\n    val isHidden = async { hiddenCase.isHidden(show) }\n\n    val state = FollowedState(\n      isMyShows = isMy.await(),\n      isWatchlist = isWatchlist.await(),\n      isHidden = isHidden.await(),\n      withAnimation = false\n    )\n\n    val isMyHidden = spoilers.isMyShowsRatingsHidden && state.isMyShows\n    val isWatchlistHidden = spoilers.isWatchlistShowsRatingsHidden && state.isWatchlist\n    val isHiddenHidden = spoilers.isHiddenShowsRatingsHidden && state.isHidden\n    val isNotCollectedHidden = spoilers.isNotCollectedShowsRatingsHidden && !state.isInCollection()\n\n    return@withContext ratings.copy(\n      isHidden = isMyHidden || isWatchlistHidden || isHiddenHidden || isNotCollectedHidden,\n      isTapToReveal = settingsSpoilersRepository.isTapToReveal\n    )\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/related/ShowDetailsRelatedFragment.kt",
    "content": "package com.michaldrabik.ui_show.sections.related\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.clearFragmentResultListener\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView.HORIZONTAL\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.sheets.context_menu.ContextMenuBottomSheet\nimport com.michaldrabik.ui_base.utilities.extensions.addDivider\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SHOW_ID\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.REQUEST_ITEM_MENU\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.ShowDetailsViewModel\nimport com.michaldrabik.ui_show.databinding.FragmentShowDetailsRelatedBinding\nimport com.michaldrabik.ui_show.sections.related.recycler.RelatedListItem\nimport com.michaldrabik.ui_show.sections.related.recycler.RelatedShowAdapter\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass ShowDetailsRelatedFragment : BaseFragment<ShowDetailsRelatedViewModel>(R.layout.fragment_show_details_related) {\n\n  override val navigationId = R.id.showDetailsFragment\n  private val binding by viewBinding(FragmentShowDetailsRelatedBinding::bind)\n\n  private val parentViewModel by viewModels<ShowDetailsViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<ShowDetailsRelatedViewModel>()\n\n  private var relatedAdapter: RelatedShowAdapter? = null\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    launchAndRepeatStarted(\n      { parentViewModel.parentShowState.collect { it?.let { viewModel.initRelatedShows(it) } } },\n      { viewModel.uiState.collect { render(it) } }\n    )\n  }\n\n  private fun setupView() {\n    relatedAdapter = RelatedShowAdapter(\n      itemClickListener = ::openDetails,\n      itemLongClickListener = ::openContextMenu,\n      missingImageListener = viewModel::loadMissingImage\n    )\n    binding.showDetailsRelatedRecycler.apply {\n      setHasFixedSize(true)\n      adapter = relatedAdapter\n      layoutManager = LinearLayoutManager(requireContext(), HORIZONTAL, false)\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      addDivider(R.drawable.divider_horizontal_list, HORIZONTAL)\n    }\n  }\n\n  private fun openDetails(item: RelatedListItem) {\n    val bundle = Bundle().apply { putLong(ARG_SHOW_ID, item.show.traktId) }\n    navigateTo(R.id.actionShowDetailsFragmentToSelf, bundle)\n  }\n\n  private fun openContextMenu(item: RelatedListItem) {\n    requireParentFragment()\n      .setFragmentResultListener(REQUEST_ITEM_MENU) { requestKey, _ ->\n        if (requestKey == REQUEST_ITEM_MENU) {\n          viewModel.loadRelatedShows()\n        }\n        requireParentFragment().clearFragmentResultListener(REQUEST_ITEM_MENU)\n      }\n\n    val bundle = ContextMenuBottomSheet.createBundle(item.show.ids.trakt)\n    navigateTo(R.id.actionShowDetailsFragmentToContext, bundle)\n  }\n\n  private fun render(uiState: ShowDetailsRelatedUiState) {\n    with(uiState) {\n      with(binding) {\n        relatedShows?.let {\n          relatedAdapter?.setItems(it)\n          showDetailsRelatedRecycler.visibleIf(it.isNotEmpty())\n          showDetailsRelatedLabel.fadeIf(it.isNotEmpty(), hardware = true)\n        }\n        isLoading.let {\n          showDetailsRelatedProgress.visibleIf(it)\n        }\n      }\n    }\n  }\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    relatedAdapter = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/related/ShowDetailsRelatedUiState.kt",
    "content": "package com.michaldrabik.ui_show.sections.related\n\nimport com.michaldrabik.ui_show.sections.related.recycler.RelatedListItem\n\ndata class ShowDetailsRelatedUiState(\n  val isLoading: Boolean = true,\n  val relatedShows: List<RelatedListItem>? = null,\n)\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/related/ShowDetailsRelatedViewModel.kt",
    "content": "package com.michaldrabik.ui_show.sections.related\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.findReplace\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_show.cases.ShowDetailsMyShowsCase\nimport com.michaldrabik.ui_show.sections.related.cases.ShowDetailsRelatedCase\nimport com.michaldrabik.ui_show.sections.related.recycler.RelatedListItem\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ShowDetailsRelatedViewModel @Inject constructor(\n  private val relatedCase: ShowDetailsRelatedCase,\n  private val myShowsCase: ShowDetailsMyShowsCase,\n  private val imagesProvider: ShowImagesProvider,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private lateinit var show: Show\n\n  private val loadingState = MutableStateFlow(true)\n  private val relatedItemsState = MutableStateFlow<List<RelatedListItem>?>(null)\n\n  fun initRelatedShows(show: Show) {\n    if (this::show.isInitialized) return\n    this.show = show\n    loadRelatedShows()\n  }\n\n  fun loadRelatedShows() {\n    if (!this::show.isInitialized) return\n    viewModelScope.launch {\n      try {\n        val (myShows, watchlistShows) = myShowsCase.getAllIds()\n        val related = relatedCase.loadRelatedShows(show).map {\n          val image = imagesProvider.findCachedImage(it, ImageType.POSTER)\n          RelatedListItem(\n            show = it,\n            image = image,\n            isFollowed = it.traktId in myShows,\n            isWatchlist = it.traktId in watchlistShows\n          )\n        }\n        relatedItemsState.value = related\n      } catch (error: Throwable) {\n        relatedItemsState.value = emptyList()\n        Timber.e(error)\n        rethrowCancellation(error)\n      } finally {\n        loadingState.value = false\n      }\n    }\n    Timber.d(\"Loading related shows...\")\n  }\n\n  fun loadMissingImage(item: RelatedListItem, force: Boolean) {\n\n    fun updateItem(new: RelatedListItem) {\n      val currentItems = uiState.value.relatedShows?.toMutableList()\n      currentItems?.findReplace(new) { it isSameAs new }\n      relatedItemsState.value = currentItems\n    }\n\n    viewModelScope.launch {\n      updateItem(item.copy(isLoading = true))\n      try {\n        val image = imagesProvider.loadRemoteImage(item.show, item.image.type, force)\n        updateItem(item.copy(isLoading = false, image = image))\n      } catch (t: Throwable) {\n        updateItem(item.copy(isLoading = false, image = Image.createUnavailable(item.image.type)))\n      }\n    }\n  }\n\n  val uiState = combine(\n    loadingState,\n    relatedItemsState\n  ) { s1, s2 ->\n    ShowDetailsRelatedUiState(\n      isLoading = s1,\n      relatedShows = s2\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ShowDetailsRelatedUiState()\n  )\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/related/cases/ShowDetailsRelatedCase.kt",
    "content": "package com.michaldrabik.ui_show.sections.related.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsRelatedCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val showsRepository: ShowsRepository\n) {\n\n  suspend fun loadRelatedShows(show: Show): List<Show> = withContext(dispatchers.IO) {\n    val archivedShowsIds = showsRepository.hiddenShows.loadAllIds()\n    showsRepository.relatedShows.loadAll(show, archivedShowsIds.size)\n      .filter { it.traktId !in archivedShowsIds }\n      .sortedWith(compareBy({ it.votes }, { it.rating }))\n      .reversed()\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/related/recycler/RelatedItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_show.sections.related.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass RelatedItemDiffCallback : DiffUtil.ItemCallback<RelatedListItem>() {\n\n  override fun areItemsTheSame(oldItem: RelatedListItem, newItem: RelatedListItem) =\n    oldItem.show.ids.trakt == newItem.show.ids.trakt\n\n  override fun areContentsTheSame(oldItem: RelatedListItem, newItem: RelatedListItem) =\n    oldItem.image == newItem.image &&\n      oldItem.isLoading == newItem.isLoading &&\n      oldItem.isFollowed == newItem.isFollowed &&\n      oldItem.isWatchlist == newItem.isWatchlist\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/related/recycler/RelatedListItem.kt",
    "content": "package com.michaldrabik.ui_show.sections.related.recycler\n\nimport com.michaldrabik.ui_base.common.ListItem\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Show\n\ndata class RelatedListItem(\n  override val show: Show,\n  override val image: Image,\n  override var isLoading: Boolean = false,\n  val isFollowed: Boolean = false,\n  val isWatchlist: Boolean = false\n) : ListItem\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/related/recycler/RelatedShowAdapter.kt",
    "content": "package com.michaldrabik.ui_show.sections.related.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseAdapter\n\nclass RelatedShowAdapter(\n  private val itemClickListener: (RelatedListItem) -> Unit,\n  private val itemLongClickListener: (RelatedListItem) -> Unit,\n  private val missingImageListener: (RelatedListItem, Boolean) -> Unit,\n) : BaseAdapter<RelatedListItem>() {\n\n  override val asyncDiffer = AsyncListDiffer(this, RelatedItemDiffCallback())\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    ViewHolderShow(\n      RelatedShowView(parent.context).apply {\n        itemClickListener = this@RelatedShowAdapter.itemClickListener\n        itemLongClickListener = this@RelatedShowAdapter.itemLongClickListener\n        missingImageListener = this@RelatedShowAdapter.missingImageListener\n      }\n    )\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    (holder.itemView as RelatedShowView).bind(item)\n  }\n\n  class ViewHolderShow(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/related/recycler/RelatedShowView.kt",
    "content": "package com.michaldrabik.ui_show.sections.related.recycler\n\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport androidx.core.content.ContextCompat\nimport androidx.core.widget.ImageViewCompat\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.onLongClick\nimport com.michaldrabik.ui_base.utilities.extensions.visible\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.ImageStatus.AVAILABLE\nimport com.michaldrabik.ui_model.ImageStatus.UNAVAILABLE\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.databinding.ViewRelatedShowBinding\n\nclass RelatedShowView : ShowView<RelatedListItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewRelatedShowBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)\n    clipChildren = false\n    onClick { itemClickListener?.invoke(item) }\n    onLongClick { itemLongClickListener?.invoke(item) }\n  }\n\n  private val colorAccent by lazy { ContextCompat.getColor(context, R.color.colorAccent) }\n  private val colorGray by lazy { ContextCompat.getColor(context, R.color.colorGrayLight) }\n\n  override val imageView: ImageView = binding.relatedImage\n  override val placeholderView: ImageView = binding.relatedPlaceholder\n\n  private lateinit var item: RelatedListItem\n\n  override fun bind(item: RelatedListItem) {\n    clear()\n    this.item = item\n    with(binding) {\n      relatedTitle.text = item.show.title\n      relatedBadge.visibleIf(item.isFollowed || item.isWatchlist)\n      val color = if (item.isFollowed) colorAccent else colorGray\n      ImageViewCompat.setImageTintList(relatedBadge, ColorStateList.valueOf(color))\n    }\n    loadImage(item)\n  }\n\n  override fun loadImage(item: RelatedListItem) {\n    if (item.image.status == UNAVAILABLE) {\n      binding.relatedTitle.visible()\n    }\n    super.loadImage(item)\n  }\n\n  override fun onImageLoadFail(item: RelatedListItem) {\n    super.onImageLoadFail(item)\n    if (item.image.status == AVAILABLE) {\n      binding.relatedTitle.visible()\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      relatedTitle.text = \"\"\n      relatedPlaceholder.gone()\n      relatedTitle.gone()\n      Glide.with(this@RelatedShowView).clear(relatedImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/seasons/ShowDetailsSeasonsFragment.kt",
    "content": "package com.michaldrabik.ui_show.sections.seasons\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.content.ContextCompat\nimport androidx.fragment.app.setFragmentResultListener\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.GridLayoutManager\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.WidgetsProvider\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet\nimport com.michaldrabik.ui_base.utilities.SnackbarHost\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.navigateToSafe\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.showInfoSnackbar\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_navigation.java.NavigationArgs\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.ShowDetailsViewModel\nimport com.michaldrabik.ui_show.databinding.FragmentShowDetailsSeasonsBinding\nimport com.michaldrabik.ui_show.episodes.ShowDetailsEpisodesFragment\nimport com.michaldrabik.ui_show.quicksetup.QuickSetupView\nimport com.michaldrabik.ui_show.sections.seasons.ShowDetailsSeasonsEvent.RequestWidgetsUpdate\nimport com.michaldrabik.ui_show.sections.seasons.recycler.SeasonListItem\nimport com.michaldrabik.ui_show.sections.seasons.recycler.SeasonsAdapter\nimport com.michaldrabik.ui_show.sections.seasons.recycler.helpers.SeasonsGridItemDecoration\nimport com.michaldrabik.ui_show.sections.seasons.recycler.helpers.SeasonsLayoutManagerProvider\nimport dagger.hilt.android.AndroidEntryPoint\nimport java.time.Duration\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ShowDetailsSeasonsFragment : BaseFragment<ShowDetailsSeasonsViewModel>(R.layout.fragment_show_details_seasons) {\n\n  @Inject lateinit var settings: SettingsViewModeRepository\n\n  override val navigationId = R.id.showDetailsFragment\n  private val binding by viewBinding(FragmentShowDetailsSeasonsBinding::bind)\n\n  private val parentViewModel by viewModels<ShowDetailsViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<ShowDetailsSeasonsViewModel>()\n\n  private var seasonsAdapter: SeasonsAdapter? = null\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n\n    launchAndRepeatStarted(\n      { parentViewModel.parentEvents.collect { viewModel.handleEvent(it) } },\n      { parentViewModel.parentShowState.collect { it?.let { viewModel.loadSeasons(it) } } },\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it as ShowDetailsSeasonsEvent<*>) } }\n    )\n  }\n\n  override fun onResume() {\n    viewModel.refreshSeasons()\n    super.onResume()\n  }\n\n  private fun setupView() {\n    seasonsAdapter = SeasonsAdapter(\n      itemClickListener = { viewModel.openSeasonEpisodes(it) },\n      itemCheckedListener = { item: SeasonListItem, isChecked: Boolean -> viewModel.setSeasonWatched(item.season, isChecked) }\n    )\n    binding.showDetailsSeasonsRecycler.apply {\n      adapter = seasonsAdapter\n      layoutManager = SeasonsLayoutManagerProvider.provideLayoutManger(requireContext(), settings)\n      itemAnimator = null\n      if (layoutManager is GridLayoutManager) {\n        addItemDecoration(SeasonsGridItemDecoration(requireContext(), R.dimen.spaceBig))\n      }\n    }\n  }\n\n  private fun render(uiState: ShowDetailsSeasonsUiState) {\n    with(uiState) {\n      seasons?.let {\n        renderSeasons(it)\n        renderRuntimeLeft(it)\n      }\n    }\n  }\n\n  private fun renderSeasons(seasonsItems: List<SeasonListItem>) {\n    with(binding) {\n      seasonsAdapter?.setItems(seasonsItems)\n      showDetailsSeasonsProgress.gone()\n      showDetailsSeasonsEmptyView.visibleIf(seasonsItems.isEmpty())\n      showDetailsSeasonsRecycler.fadeIf(seasonsItems.isNotEmpty(), hardware = true)\n      showDetailsSeasonsLabel.fadeIf(seasonsItems.isNotEmpty(), hardware = true)\n      showDetailsQuickProgress.fadeIf(seasonsItems.isNotEmpty(), hardware = true)\n      showDetailsQuickProgress.onClick {\n        if (seasonsItems.any { !it.season.isSpecial() }) {\n          openQuickSetupDialog(seasonsItems.map { it.season })\n        } else {\n          showSnack(MessageEvent.Info(R.string.textSeasonsEmpty))\n        }\n      }\n    }\n  }\n\n  private fun renderRuntimeLeft(seasonsItems: List<SeasonListItem>) {\n    val runtimeLeft = seasonsItems\n      .filter { !it.season.isSpecial() }\n      .flatMap { it.episodes }\n      .filterNot { it.isWatched }\n      .sumOf { it.episode.runtime }\n      .toLong()\n\n    val duration = Duration.ofMinutes(runtimeLeft)\n    val hours = duration.toHours()\n    val minutes = duration.minusHours(hours).toMinutes()\n\n    val runtimeText = when {\n      hours <= 0 -> getString(R.string.textRuntimeLeftMinutes, minutes.toString())\n      else -> getString(R.string.textRuntimeLeftHours, hours.toString(), minutes.toString())\n    }\n    with(binding) {\n      showDetailsRuntimeLeft.text = runtimeText\n      showDetailsRuntimeLeft.fadeIf(seasonsItems.isNotEmpty() && runtimeLeft > 0, hardware = true)\n    }\n  }\n\n  private fun handleEvent(event: ShowDetailsSeasonsEvent<*>) {\n    when (event) {\n      is ShowDetailsSeasonsEvent.RemoveFromTrakt ->\n        openRemoveTraktSheet(event)\n      is ShowDetailsSeasonsEvent.OpenSeasonEpisodes -> {\n        val bundle = ShowDetailsEpisodesFragment.createBundle(event.showId, event.seasonId)\n        navigateToSafe(R.id.actionShowDetailsFragmentToEpisodes, bundle)\n      }\n      is RequestWidgetsUpdate -> {\n        (requireAppContext() as WidgetsProvider).requestShowsWidgetsUpdate()\n      }\n    }\n  }\n\n  private fun openQuickSetupDialog(seasons: List<Season>) {\n    val context = requireContext()\n    val view = QuickSetupView(context).apply {\n      bind(seasons)\n    }\n    MaterialAlertDialogBuilder(context, R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(context, R.drawable.bg_dialog))\n      .setView(view)\n      .setPositiveButton(R.string.textSelect) { _, _ ->\n        viewModel.setQuickProgress(view.getSelectedItem())\n      }\n      .setNegativeButton(R.string.textCancel) { _, _ -> }\n      .show()\n  }\n\n  private fun openRemoveTraktSheet(event: ShowDetailsSeasonsEvent.RemoveFromTrakt) {\n    requireParentFragment().setFragmentResultListener(NavigationArgs.REQUEST_REMOVE_TRAKT) { _, bundle ->\n      if (bundle.getBoolean(NavigationArgs.RESULT, false)) {\n        val text = resources.getString(R.string.textTraktSyncRemovedFromTrakt)\n        (requireActivity() as SnackbarHost).provideSnackbarLayout().showInfoSnackbar(text)\n\n        if (event.actionId == R.id.actionShowDetailsFragmentToRemoveTraktProgress) {\n          viewModel.refreshSeasons()\n        }\n      }\n    }\n    val args = RemoveTraktBottomSheet.createBundle(event.traktIds, event.mode)\n    navigateToSafe(event.actionId, args)\n  }\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    seasonsAdapter = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/seasons/ShowDetailsSeasonsUiEvents.kt",
    "content": "// ktlint-disable filename\npackage com.michaldrabik.ui_show.sections.seasons\n\nimport androidx.annotation.IdRes\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_model.IdTrakt\n\nsealed class ShowDetailsSeasonsEvent<T>(action: T) : Event<T>(action) {\n\n  data class OpenSeasonEpisodes(\n    val showId: IdTrakt,\n    val seasonId: IdTrakt\n  ) : ShowDetailsSeasonsEvent<IdTrakt>(showId)\n\n  data class RemoveFromTrakt(\n    @IdRes val actionId: Int,\n    val mode: RemoveTraktBottomSheet.Mode,\n    val traktIds: List<IdTrakt>\n  ) : ShowDetailsSeasonsEvent<Int>(actionId)\n\n  object RequestWidgetsUpdate : ShowDetailsSeasonsEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/seasons/ShowDetailsSeasonsUiState.kt",
    "content": "package com.michaldrabik.ui_show.sections.seasons\n\nimport com.michaldrabik.ui_show.sections.seasons.recycler.SeasonListItem\n\ndata class ShowDetailsSeasonsUiState(\n  val isLoading: Boolean = true,\n  val seasons: List<SeasonListItem>? = null,\n)\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/seasons/ShowDetailsSeasonsViewModel.kt",
    "content": "package com.michaldrabik.ui_show.sections.seasons\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.ui_base.Analytics\nimport com.michaldrabik.ui_base.common.sheets.remove_trakt.RemoveTraktBottomSheet.Mode\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.ShowDetailsEvent\nimport com.michaldrabik.ui_show.episodes.cases.EpisodesMarkWatchedCase\nimport com.michaldrabik.ui_show.quicksetup.QuickSetupListItem\nimport com.michaldrabik.ui_show.sections.seasons.ShowDetailsSeasonsEvent.RequestWidgetsUpdate\nimport com.michaldrabik.ui_show.sections.seasons.cases.ShowDetailsLoadSeasonsCase\nimport com.michaldrabik.ui_show.sections.seasons.cases.ShowDetailsQuickProgressCase\nimport com.michaldrabik.ui_show.sections.seasons.cases.ShowDetailsWatchedSeasonCase\nimport com.michaldrabik.ui_show.sections.seasons.cases.ShowDetailsWatchedSeasonCase.Result\nimport com.michaldrabik.ui_show.sections.seasons.helpers.SeasonsCache\nimport com.michaldrabik.ui_show.sections.seasons.recycler.SeasonListItem\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ShowDetailsSeasonsViewModel @Inject constructor(\n  private val loadSeasonsCase: ShowDetailsLoadSeasonsCase,\n  private val quickProgressCase: ShowDetailsQuickProgressCase,\n  private val watchedSeasonCase: ShowDetailsWatchedSeasonCase,\n  private val markWatchedCase: EpisodesMarkWatchedCase,\n  private val seasonsCache: SeasonsCache,\n) : ViewModel(), ChannelsDelegate by DefaultChannelsDelegate() {\n\n  private lateinit var show: Show\n\n  private val loadingState = MutableStateFlow(true)\n  private val seasonsState = MutableStateFlow<List<SeasonListItem>?>(null)\n\n  private var areSeasonsLocal = false\n\n  fun handleEvent(event: ShowDetailsEvent<*>) {\n    when (event) {\n      is ShowDetailsEvent.RefreshSeasons -> refreshSeasons()\n      else -> Unit\n    }\n  }\n\n  fun loadSeasons(show: Show) {\n    if (this::show.isInitialized) return\n    this.show = show\n    viewModelScope.launch {\n      try {\n        val (seasons, isLocal) = loadSeasonsCase.loadSeasons(show)\n        areSeasonsLocal = isLocal\n        val calculated = markWatchedCase.markWatchedEpisodes(show, seasons)\n        updateSeasons(calculated)\n      } catch (error: Throwable) {\n        updateSeasons(emptyList())\n      }\n    }\n  }\n\n  fun setSeasonWatched(\n    season: Season,\n    isChecked: Boolean\n  ) {\n    viewModelScope.launch {\n      val result = watchedSeasonCase.setSeasonWatched(\n        show = show,\n        season = season,\n        isChecked = isChecked,\n        isLocal = areSeasonsLocal\n      )\n      if (result == Result.REMOVE_FROM_TRAKT) {\n        val ids = season.episodes.map { it.ids.trakt }\n        val event = ShowDetailsSeasonsEvent.RemoveFromTrakt(R.id.actionShowDetailsFragmentToRemoveTraktProgress, Mode.EPISODE, ids)\n        eventChannel.send(event)\n      }\n      refreshSeasons()\n    }\n  }\n\n  fun setQuickProgress(item: QuickSetupListItem?) {\n    viewModelScope.launch {\n      if (item == null || !checkSeasonsLoaded()) {\n        return@launch\n      }\n\n      val seasonItems = seasonsState.value?.toList() ?: emptyList()\n      quickProgressCase.setQuickProgress(item, seasonItems, show)\n      refreshSeasons()\n\n      messageChannel.send(MessageEvent.Info(R.string.textShowQuickProgressDone))\n      Analytics.logShowQuickProgress(show)\n    }\n  }\n\n  fun openSeasonEpisodes(season: SeasonListItem) {\n    viewModelScope.launch {\n      seasonsCache.setSeasons(show.ids.trakt, seasonsState.value ?: emptyList(), areSeasonsLocal)\n      val event = ShowDetailsSeasonsEvent.OpenSeasonEpisodes(show.ids.trakt, season.season.ids.trakt)\n      eventChannel.send(event)\n    }\n  }\n\n  fun refreshSeasons() {\n    if (!this::show.isInitialized || seasonsState.value == null) {\n      return\n    }\n    viewModelScope.launch {\n      val seasonItems = seasonsState.value?.toList() ?: emptyList()\n      val calculated = markWatchedCase.markWatchedEpisodes(show, seasonItems)\n      updateSeasons(calculated)\n    }\n  }\n\n  private suspend fun checkSeasonsLoaded(): Boolean {\n    if (seasonsState.value == null) {\n      messageChannel.send(MessageEvent.Info(R.string.errorSeasonsNotLoaded))\n      return false\n    }\n    return true\n  }\n\n  private suspend fun updateSeasons(seasons: List<SeasonListItem>) {\n    seasonsState.value = seasons\n    seasonsCache.setSeasons(show.ids.trakt, seasons, areSeasonsLocal)\n    eventChannel.send(RequestWidgetsUpdate)\n  }\n\n  val uiState = combine(\n    loadingState,\n    seasonsState\n  ) { s1, s2 ->\n    ShowDetailsSeasonsUiState(\n      isLoading = s1,\n      seasons = s2\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ShowDetailsSeasonsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/seasons/cases/ShowDetailsLoadSeasonsCase.kt",
    "content": "package com.michaldrabik.ui_show.sections.seasons.cases\n\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.nowUtcMillis\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_remote.RemoteDataSource\nimport com.michaldrabik.repository.EpisodesManager\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_base.network.NetworkStatusProvider\nimport com.michaldrabik.ui_model.RatingState\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_show.episodes.recycler.EpisodeListItem\nimport com.michaldrabik.ui_show.sections.seasons.helpers.SeasonsBundle\nimport com.michaldrabik.ui_show.sections.seasons.recycler.SeasonListItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsLoadSeasonsCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val remoteSource: RemoteDataSource,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n  private val showsRepository: ShowsRepository,\n  private val settingsRepository: SettingsRepository,\n  private val ratingsRepository: RatingsRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val episodesManager: EpisodesManager,\n  private val userManager: UserTraktManager,\n  private val dateFormatProvider: DateFormatProvider,\n  private val networkStatusProvider: NetworkStatusProvider\n) {\n\n  suspend fun loadSeasons(show: Show): SeasonsBundle =\n    withContext(dispatchers.IO) {\n      val showSpecialSeasons = settingsRepository.load().specialSeasonsEnabled\n      try {\n        if (!networkStatusProvider.isOnline()) {\n          loadLocalSeasons(show, showSpecialSeasons)\n        }\n\n        val remoteSeasons = remoteSource.trakt.fetchSeasons(show.traktId)\n          .map { mappers.season.fromNetwork(it) }\n          .filter { it.episodes.isNotEmpty() }\n          .filter { if (!showSpecialSeasons) !it.isSpecial() else true }\n\n        val isFollowed = showsRepository.myShows.load(show.ids.trakt) != null\n        if (isFollowed) {\n          episodesManager.invalidateSeasons(show, remoteSeasons)\n        }\n\n        val seasonsItems = mapToSeasonItems(remoteSeasons, show)\n        SeasonsBundle(seasonsItems, isLocal = false)\n      } catch (error: Throwable) {\n        loadLocalSeasons(show, showSpecialSeasons)\n      }\n    }\n\n  private suspend fun loadLocalSeasons(show: Show, showSpecials: Boolean): SeasonsBundle {\n    val localEpisodes = localSource.episodes.getAllByShowId(show.traktId)\n    val localSeasons = localSource.seasons.getAllByShowId(show.traktId).map { season ->\n      val seasonEpisodes = localEpisodes.filter { ep -> ep.idSeason == season.idTrakt }\n      mappers.season.fromDatabase(season, seasonEpisodes)\n    }\n      .filter { it.episodes.isNotEmpty() }\n      .filter { if (!showSpecials) !it.isSpecial() else true }\n\n    val seasonsItems = mapToSeasonItems(localSeasons, show)\n    return SeasonsBundle(seasonsItems, isLocal = true)\n  }\n\n  private suspend fun mapToSeasonItems(remoteSeasons: List<Season>, show: Show) = coroutineScope {\n    val isSignedIn = userManager.isAuthorized()\n    val format = dateFormatProvider.loadFullHourFormat()\n    val seasonsRatings = ratingsRepository.shows.loadRatingsSeasons(remoteSeasons)\n    val spoilers = settingsRepository.spoilers.getAll()\n    remoteSeasons\n      .map {\n        val userRating = RatingState(\n          userRating = seasonsRatings.find { rating -> rating.idTrakt == it.ids.trakt },\n          rateAllowed = isSignedIn,\n        )\n        val episodes = it.episodes.map { episode ->\n          async {\n            val rating = ratingsRepository.shows.loadRating(episode)\n            val translation = translationsRepository.loadTranslation(episode, show.ids.trakt, onlyLocal = true)\n            EpisodeListItem(\n              episode = episode,\n              season = it,\n              isWatched = false,\n              translation = translation,\n              myRating = rating,\n              dateFormat = format,\n              isAnime = show.isAnime,\n              spoilers = spoilers\n            )\n          }\n        }.awaitAll()\n        SeasonListItem(\n          show = show,\n          season = it,\n          episodes = episodes,\n          isWatched = false,\n          userRating = userRating,\n          updatedAt = nowUtcMillis()\n        )\n      }\n      .sortedByDescending { it.season.number }\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/seasons/cases/ShowDetailsQuickProgressCase.kt",
    "content": "package com.michaldrabik.ui_show.sections.seasons.cases\n\nimport com.michaldrabik.repository.EpisodesManager\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.EpisodeBundle\nimport com.michaldrabik.ui_model.SeasonBundle\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_show.quicksetup.QuickSetupListItem\nimport com.michaldrabik.ui_show.sections.seasons.recycler.SeasonListItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.coroutineScope\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsQuickProgressCase @Inject constructor(\n  private val showsRepository: ShowsRepository,\n  private val episodesManager: EpisodesManager,\n  private val quickSyncManager: QuickSyncManager,\n) {\n\n  suspend fun setQuickProgress(\n    selectedItem: QuickSetupListItem,\n    seasonsItems: List<SeasonListItem>,\n    show: Show\n  ) = coroutineScope {\n    val isMyShows = async { showsRepository.myShows.exists(show.ids.trakt) }\n    val isWatchlist = async { showsRepository.watchlistShows.exists(show.ids.trakt) }\n    val isHidden = async { showsRepository.hiddenShows.exists(show.ids.trakt) }\n\n    val isCollection = isMyShows.await() || isWatchlist.await() || isHidden.await()\n    val episodesAdded = mutableListOf<Episode>()\n\n    episodesManager.setAllUnwatched(show.ids.trakt, skipSpecials = true)\n    val seasons = seasonsItems.map { it.season }\n    seasons\n      .filter { !it.isSpecial() && it.number < selectedItem.season.number }\n      .forEach { season ->\n        val bundle = SeasonBundle(season, show)\n        episodesManager.setSeasonWatched(bundle).apply {\n          episodesAdded.addAll(this)\n        }\n      }\n\n    val season = seasons.find { it.number == selectedItem.season.number }\n    season?.episodes\n      ?.filter { it.number <= selectedItem.episode.number }\n      ?.forEach { episode ->\n        val bundle = EpisodeBundle(episode, season, show)\n        episodesManager.setEpisodeWatched(bundle)\n        episodesAdded.add(episode)\n      }\n\n    if (isCollection) {\n      val episodesIds = episodesAdded.map { it.ids.trakt.id }\n      quickSyncManager.clearEpisodes()\n      quickSyncManager.scheduleEpisodes(\n        episodesIds = episodesIds,\n        showId = show.traktId,\n        clearProgress = true\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/seasons/cases/ShowDetailsWatchedSeasonCase.kt",
    "content": "package com.michaldrabik.ui_show.sections.seasons.cases\n\nimport com.michaldrabik.repository.EpisodesManager\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.SeasonBundle\nimport com.michaldrabik.ui_model.Show\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsWatchedSeasonCase @Inject constructor(\n  private val showsRepository: ShowsRepository,\n  private val settingsRepository: SettingsRepository,\n  private val episodesManager: EpisodesManager,\n  private val userManager: UserTraktManager,\n  private val quickSyncManager: QuickSyncManager,\n) {\n\n  suspend fun setSeasonWatched(\n    show: Show,\n    season: Season,\n    isChecked: Boolean,\n    isLocal: Boolean\n  ): Result {\n    val bundle = SeasonBundle(season, show)\n\n    val isMyShows = showsRepository.myShows.exists(show.ids.trakt)\n    val isWatchlist = showsRepository.watchlistShows.exists(show.ids.trakt)\n    val isHidden = showsRepository.hiddenShows.exists(show.ids.trakt)\n    val isCollection = isMyShows || isWatchlist || isHidden\n\n    when {\n      isChecked -> {\n        val episodesAdded = episodesManager.setSeasonWatched(bundle)\n        if (isMyShows) {\n          quickSyncManager.scheduleEpisodes(\n            showId = show.traktId,\n            episodesIds = episodesAdded.map { it.ids.trakt.id }\n          )\n        }\n        return Result.SUCCESS\n      }\n      else -> {\n        episodesManager.setSeasonUnwatched(bundle)\n        quickSyncManager.clearEpisodes(season.episodes.map { it.ids.trakt.id })\n\n        val traktQuickRemoveEnabled = settingsRepository.load().traktQuickRemoveEnabled\n        val showRemoveTrakt = userManager.isAuthorized() && traktQuickRemoveEnabled && !isLocal && isCollection\n        if (showRemoveTrakt) {\n          return Result.REMOVE_FROM_TRAKT\n        }\n        return Result.SUCCESS\n      }\n    }\n  }\n\n  enum class Result {\n    SUCCESS,\n    REMOVE_FROM_TRAKT\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/seasons/helpers/SeasonsBundle.kt",
    "content": "package com.michaldrabik.ui_show.sections.seasons.helpers\n\nimport com.michaldrabik.ui_show.sections.seasons.recycler.SeasonListItem\n\ndata class SeasonsBundle(\n  val seasons: List<SeasonListItem>?,\n  val isLocal: Boolean\n)\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/seasons/helpers/SeasonsCache.kt",
    "content": "package com.michaldrabik.ui_show.sections.seasons.helpers\n\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_show.sections.seasons.recycler.SeasonListItem\nimport java.util.Collections\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * Helper memory cache of seasons for a current show details.\n */\n@Singleton\nclass SeasonsCache @Inject constructor() {\n\n  private val seasonsCache = Collections.synchronizedMap(mutableMapOf<IdTrakt, SeasonsBundle?>())\n\n  fun setSeasons(\n    showId: IdTrakt,\n    seasons: List<SeasonListItem>,\n    areSeasonsLocal: Boolean\n  ) {\n    seasonsCache[showId] = SeasonsBundle(seasons.toList(), areSeasonsLocal)\n  }\n\n  fun loadSeasons(showId: IdTrakt): List<SeasonListItem>? =\n    seasonsCache[showId]?.seasons\n\n  fun hasSeasons(showId: IdTrakt): Boolean =\n    seasonsCache[showId]?.seasons != null\n\n  fun areSeasonsLocal(showId: IdTrakt): Boolean =\n    seasonsCache[showId]?.isLocal ?: false\n\n  fun clear(showId: IdTrakt) {\n    seasonsCache.remove(showId)\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/seasons/recycler/SeasonListItem.kt",
    "content": "package com.michaldrabik.ui_show.sections.seasons.recycler\n\nimport com.michaldrabik.ui_model.RatingState\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_show.episodes.recycler.EpisodeListItem\n\ndata class SeasonListItem(\n  val show: Show,\n  val season: Season,\n  val episodes: List<EpisodeListItem>,\n  val isWatched: Boolean,\n  val userRating: RatingState,\n  val updatedAt: Long,\n) {\n\n  val id = season.ids.trakt.id\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/seasons/recycler/SeasonListItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_show.sections.seasons.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass SeasonListItemDiffCallback(\n  private val oldList: List<SeasonListItem>,\n  private val newList: List<SeasonListItem>\n) : DiffUtil.Callback() {\n\n  override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {\n    return oldList[oldItemPosition].id == newList[newItemPosition].id\n  }\n\n  override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {\n    val (_, _, episodes, isWatched) = oldList[oldItemPosition]\n    val (_, _, episodes2, isWatched2) = newList[newItemPosition]\n\n    if (episodes.size != episodes2.size) return isWatched == isWatched2\n\n    var areEpisodesTheSame = true\n    episodes.forEach { e1 ->\n      val e2 = episodes2.firstOrNull { it.id == e1.id }\n      e2?.let {\n        if (e1.isWatched != e2.isWatched || e1.myRating != e2.myRating) {\n          areEpisodesTheSame = false\n          return@forEach\n        }\n      }\n    }\n\n    return isWatched == isWatched2 && areEpisodesTheSame\n  }\n\n  override fun getOldListSize() = oldList.size\n\n  override fun getNewListSize() = newList.size\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/seasons/recycler/SeasonView.kt",
    "content": "package com.michaldrabik.ui_show.sections.seasons.recycler\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport androidx.core.widget.ImageViewCompat\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.expandTouch\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.databinding.ViewSeasonBinding\nimport java.util.Locale.ENGLISH\nimport kotlin.math.floor\n\nclass SeasonView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewSeasonBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    clipChildren = false\n    clipToPadding = false\n    post { binding.seasonViewCheckbox.expandTouch() }\n  }\n\n  @SuppressLint(\"SetTextI18n\")\n  fun bind(\n    item: SeasonListItem,\n    clickListener: (SeasonListItem) -> Unit,\n    itemCheckedListener: (SeasonListItem, Boolean) -> Unit,\n  ) {\n    clear()\n    setOnClickListener { clickListener(item) }\n    with(binding) {\n      seasonViewTitle.text =\n        if (item.season.isSpecial()) context.getString(R.string.textSpecials)\n        else String.format(ENGLISH, context.getString(R.string.textSeason), item.season.number)\n\n      val progressCount = item.episodes.count { it.isWatched }\n      val episodesCount = item.episodes.size\n      var percent = 0\n      if (episodesCount != 0) {\n        percent = floor((progressCount.toFloat() / episodesCount.toFloat()) * 100F).toInt()\n      }\n\n      seasonViewProgress.max = item.season.episodeCount\n      seasonViewProgress.setProgressCompat(item.episodes.count { it.isWatched }, false)\n      seasonViewProgressText.text = String.format(ENGLISH, \"%d/%d (%d%%)\", progressCount, item.episodes.size, percent)\n\n      seasonViewCheckbox.isChecked = item.isWatched\n      seasonViewCheckbox.isEnabled = item.episodes.all { it.episode.hasAired(item.season) } || item.isWatched\n\n      val color = context.colorFromAttr(if (item.isWatched) android.R.attr.colorAccent else android.R.attr.textColorPrimary)\n      seasonViewTitle.setTextColor(color)\n      seasonViewProgressText.setTextColor(color)\n      ImageViewCompat.setImageTintList(seasonViewArrow, ColorStateList.valueOf(color))\n\n      seasonViewCheckbox.setOnCheckedChangeListener { _, isChecked ->\n        itemCheckedListener(item, isChecked)\n      }\n    }\n  }\n\n  private fun clear() {\n    with(binding) {\n      seasonViewTitle.text = \"\"\n      seasonViewProgress.progress = 0\n      seasonViewProgress.max = 0\n      seasonViewCheckbox.setOnCheckedChangeListener(null)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/seasons/recycler/SeasonsAdapter.kt",
    "content": "package com.michaldrabik.ui_show.sections.seasons.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.RecyclerView\n\nclass SeasonsAdapter(\n  private val itemClickListener: (SeasonListItem) -> Unit,\n  private val itemCheckedListener: (SeasonListItem, Boolean) -> Unit\n) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n  private val items: MutableList<SeasonListItem> = mutableListOf()\n\n  fun setItems(newItems: List<SeasonListItem>) {\n    val diffCallback = SeasonListItemDiffCallback(items, newItems)\n    val diffResult = DiffUtil.calculateDiff(diffCallback)\n    this.items.apply {\n      clear()\n      addAll(newItems)\n    }\n    diffResult.dispatchUpdatesTo(this)\n  }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    ViewHolderShow(SeasonView(parent.context))\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    (holder.itemView as SeasonView).bind(items[position], itemClickListener, itemCheckedListener)\n  }\n\n  override fun getItemCount() = items.size\n\n  class ViewHolderShow(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/seasons/recycler/helpers/SeasonsGridItemDecoration.kt",
    "content": "package com.michaldrabik.ui_show.sections.seasons.recycler.helpers\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.annotation.DimenRes\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ItemDecoration\n\nclass SeasonsGridItemDecoration : ItemDecoration {\n\n  private var spacing: Int\n  private var halfSpacing: Int\n\n  constructor(\n    context: Context,\n    @DimenRes spacingDimen: Int,\n  ) {\n    this.spacing = context.resources.getDimensionPixelSize(spacingDimen)\n    this.halfSpacing = spacing / 2\n  }\n\n  override fun getItemOffsets(\n    outRect: Rect,\n    view: View,\n    parent: RecyclerView,\n    state: RecyclerView.State,\n  ) {\n    val totalSpan = (parent.layoutManager as GridLayoutManager).spanCount\n\n    val position = parent.getChildAdapterPosition(view)\n    val column = position % totalSpan\n\n    outRect.left = spacing * column / totalSpan\n    outRect.right = spacing * ((totalSpan - 1) - column) / totalSpan\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/seasons/recycler/helpers/SeasonsLayoutManagerProvider.kt",
    "content": "package com.michaldrabik.ui_show.sections.seasons.recycler.helpers\n\nimport android.content.Context\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.VERTICAL\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.repository.settings.SettingsViewModeRepository\nimport com.michaldrabik.ui_base.utilities.extensions.isTablet\n\ninternal object SeasonsLayoutManagerProvider {\n\n  fun provideLayoutManger(\n    context: Context,\n    settings: SettingsViewModeRepository,\n  ): RecyclerView.LayoutManager {\n    return if (context.isTablet()) {\n      GridLayoutManager(context, settings.tabletGridSpanSize)\n    } else {\n      LinearLayoutManager(context, VERTICAL, false)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/streamings/ShowDetailsStreamingsFragment.kt",
    "content": "package com.michaldrabik.ui_show.sections.streamings\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.extensions.addDivider\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.ShowDetailsFragment\nimport com.michaldrabik.ui_show.ShowDetailsViewModel\nimport com.michaldrabik.ui_show.databinding.FragmentShowDetailsStreamingsBinding\nimport com.michaldrabik.ui_streamings.recycler.StreamingAdapter\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass ShowDetailsStreamingsFragment : BaseFragment<ShowDetailsStreamingsViewModel>(R.layout.fragment_show_details_streamings) {\n\n  override val navigationId = R.id.showDetailsFragment\n  private val binding by viewBinding(FragmentShowDetailsStreamingsBinding::bind)\n\n  private val parentViewModel by viewModels<ShowDetailsViewModel>({ requireParentFragment() })\n  override val viewModel by viewModels<ShowDetailsStreamingsViewModel>()\n\n  private var streamingAdapter: StreamingAdapter? = null\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    launchAndRepeatStarted(\n      { parentViewModel.parentShowState.collect { it?.let { viewModel.loadStreamings(it) } } },\n      { viewModel.uiState.collect { render(it) } }\n    )\n  }\n\n  private fun setupView() {\n    streamingAdapter = StreamingAdapter()\n    binding.showDetailsStreamingsRecycler.apply {\n      setHasFixedSize(true)\n      adapter = streamingAdapter\n      layoutManager = LinearLayoutManager(requireContext(), HORIZONTAL, false)\n      addDivider(R.drawable.divider_horizontal_list, HORIZONTAL)\n    }\n  }\n\n  private fun render(uiState: ShowDetailsStreamingsUiState) {\n    with(uiState) {\n      streamings?.let {\n        if (streamingAdapter?.itemCount != 0) return@let\n        val (items, isLocal) = it\n        streamingAdapter?.setItems(items)\n        if (items.isNotEmpty()) {\n          (requireParentFragment() as ShowDetailsFragment).showStreamingsView(animate = !isLocal)\n        }\n      }\n    }\n  }\n\n  override fun setupBackPressed() = Unit\n\n  override fun onDestroyView() {\n    streamingAdapter = null\n    super.onDestroyView()\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/streamings/ShowDetailsStreamingsUiState.kt",
    "content": "package com.michaldrabik.ui_show.sections.streamings\n\nimport com.michaldrabik.ui_model.StreamingService\n\ndata class ShowDetailsStreamingsUiState(\n  val streamings: StreamingsState? = null,\n) {\n\n  data class StreamingsState(\n    val streamings: List<StreamingService>,\n    val isLocal: Boolean,\n  )\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/streamings/ShowDetailsStreamingsViewModel.kt",
    "content": "package com.michaldrabik.ui_show.sections.streamings\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_show.sections.streamings.ShowDetailsStreamingsUiState.StreamingsState\nimport com.michaldrabik.ui_show.sections.streamings.cases.ShowDetailsStreamingCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ShowDetailsStreamingsViewModel @Inject constructor(\n  private val streamingCase: ShowDetailsStreamingCase,\n) : ViewModel() {\n\n  private lateinit var show: Show\n\n  private val streamingsState = MutableStateFlow<StreamingsState?>(null)\n  private val loadingState = MutableStateFlow(false)\n\n  fun loadStreamings(show: Show) {\n    if (this::show.isInitialized) return\n    this.show = show\n    viewModelScope.launch {\n      loadingState.value = true\n      try {\n        val localStreamings = streamingCase.getLocalStreamingServices(show)\n        streamingsState.value = StreamingsState(localStreamings, isLocal = true)\n\n        val remoteStreamings = streamingCase.loadStreamingServices(show)\n        streamingsState.value = StreamingsState(remoteStreamings, isLocal = false)\n      } catch (error: Throwable) {\n        streamingsState.value = StreamingsState(emptyList(), isLocal = false)\n        rethrowCancellation(error)\n      } finally {\n        loadingState.value = false\n      }\n    }\n  }\n\n  val uiState = combine(\n    streamingsState,\n    loadingState\n  ) { s1, _ ->\n    ShowDetailsStreamingsUiState(\n      streamings = s1\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = ShowDetailsStreamingsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/sections/streamings/cases/ShowDetailsStreamingCase.kt",
    "content": "package com.michaldrabik.ui_show.sections.streamings.cases\n\nimport com.michaldrabik.common.ConfigVariant\nimport com.michaldrabik.common.dispatchers.CoroutineDispatchers\nimport com.michaldrabik.common.extensions.nowUtc\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.repository.shows.ShowStreamingsRepository\nimport com.michaldrabik.ui_base.common.AppCountry\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.StreamingService\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass ShowDetailsStreamingCase @Inject constructor(\n  private val dispatchers: CoroutineDispatchers,\n  private val streamingsRepository: ShowStreamingsRepository,\n  private val settingsRepository: SettingsRepository,\n) {\n\n  suspend fun getLocalStreamingServices(show: Show): List<StreamingService> =\n    withContext(dispatchers.IO) {\n      if (!settingsRepository.streamingsEnabled) {\n        return@withContext emptyList()\n      }\n      val country = AppCountry.fromCode(settingsRepository.country)\n      val localData = streamingsRepository.getLocalStreamings(show, country.code)\n      return@withContext localData.first\n    }\n\n  suspend fun loadStreamingServices(show: Show): List<StreamingService> =\n    withContext(dispatchers.IO) {\n      if (!settingsRepository.streamingsEnabled) {\n        return@withContext emptyList()\n      }\n      val country = AppCountry.fromCode(settingsRepository.country)\n      val (localItems, timestamp) = streamingsRepository.getLocalStreamings(show, country.code)\n      if (timestamp != null && timestamp.plusSeconds(ConfigVariant.STREAMINGS_CACHE_DURATION / 1000).isAfter(nowUtc())) {\n        return@withContext localItems\n      }\n      streamingsRepository.loadRemoteStreamings(show, country.code)\n    }\n}\n"
  },
  {
    "path": "ui-show/src/main/java/com/michaldrabik/ui_show/views/AddToShowsButton.kt",
    "content": "package com.michaldrabik.ui_show.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport androidx.core.view.isVisible\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.colorStateListFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIn\nimport com.michaldrabik.ui_base.utilities.extensions.fadeOut\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_show.R\nimport com.michaldrabik.ui_show.databinding.ViewAddToShowsButtonBinding\n\nclass AddToShowsButton : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewAddToShowsButtonBinding.inflate(LayoutInflater.from(context), this)\n\n  var onAddMyShowsClickListener: (() -> Unit)? = null\n  var onAddWatchlistClickListener: (() -> Unit)? = null\n  var onRemoveClickListener: (() -> Unit)? = null\n\n  private var state: State = State.ADD\n  private var isAnimating = false\n\n  init {\n    with(binding) {\n      addToMyShowsButton.onClick {\n        if (!isAnimating) onAddMyShowsClickListener?.invoke()\n      }\n      watchlistButton.onClick {\n        if (!isAnimating) onAddWatchlistClickListener?.invoke()\n      }\n      addedToButton.onClick {\n        if (!isAnimating) onRemoveClickListener?.invoke()\n      }\n    }\n  }\n\n  fun setState(state: State, animate: Boolean = false) {\n    if (state == this.state) return\n    this.state = state\n\n    val duration = if (animate) 175L else 0\n    val startDelay = if (animate) 200L else 0\n    if (animate) isAnimating = true\n\n    with(binding) {\n      when (state) {\n        State.ADD -> {\n          addedToButton.fadeOut(duration, withHardware = true)\n          addToMyShowsButton.fadeIn(duration, startDelay = startDelay, withHardware = true)\n          watchlistButton.fadeIn(duration, startDelay = startDelay, withHardware = true) { isAnimating = false }\n        }\n        State.IN_MY_SHOWS -> {\n          val color = context.colorFromAttr(R.attr.colorAccent)\n          val colorState = context.colorStateListFromAttr(R.attr.colorAccent)\n\n          addToMyShowsButton.fadeOut(duration, withHardware = true)\n          watchlistButton.fadeOut(duration, withHardware = true)\n          addedToButton.run {\n            setIconResource(R.drawable.ic_bookmark_full)\n            setText(R.string.textInMyShows)\n            setTextColor(color)\n            iconTint = colorState\n            strokeColor = colorState\n            rippleColor = colorState\n            fadeIn(duration, startDelay = startDelay, withHardware = true) { isAnimating = false }\n          }\n        }\n        State.IN_WATCHLIST -> {\n          val color = context.colorFromAttr(android.R.attr.textColorSecondary)\n          val colorState = context.colorStateListFromAttr(android.R.attr.textColorSecondary)\n\n          addToMyShowsButton.fadeOut(duration, withHardware = true)\n          watchlistButton.fadeOut(duration, withHardware = true)\n          addedToButton.run {\n            setIconResource(R.drawable.ic_bookmark_full)\n            setText(R.string.textInWatchlist)\n            setTextColor(color)\n            iconTint = colorState\n            strokeColor = colorState\n            rippleColor = colorState\n            fadeIn(duration, startDelay = startDelay, withHardware = true) { isAnimating = false }\n          }\n        }\n        State.IN_HIDDEN -> {\n          val delay = if (addToMyShowsButton.isVisible) startDelay else 0\n          addToMyShowsButton.fadeOut(duration, withHardware = true)\n          watchlistButton.fadeOut(duration, withHardware = true)\n          with(addedToButton) {\n            fadeOut(duration, withHardware = true) {\n              val color = context.colorFromAttr(android.R.attr.textColorSecondary)\n              val colorState = context.colorStateListFromAttr(android.R.attr.textColorSecondary)\n              setIconResource(R.drawable.ic_eye_no)\n              setText(R.string.textInHidden)\n              setTextColor(color)\n              iconTint = colorState\n              strokeColor = colorState\n              rippleColor = colorState\n              fadeIn(duration, startDelay = delay, withHardware = true) { isAnimating = false }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  override fun setEnabled(enabled: Boolean) {\n    with(binding) {\n      addToMyShowsButton.isEnabled = enabled\n      addToMyShowsButton.isClickable = enabled\n      watchlistButton.isEnabled = enabled\n      watchlistButton.isClickable = enabled\n      addedToButton.isEnabled = enabled\n      addedToButton.isClickable = enabled\n    }\n  }\n\n  enum class State {\n    ADD,\n    IN_MY_SHOWS,\n    IN_WATCHLIST,\n    IN_HIDDEN\n  }\n}\n"
  },
  {
    "path": "ui-show/src/main/res/drawable/divider_horizontal_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <size\n    android:width=\"@dimen/dividerHorizontalList\"\n    android:height=\"1dp\"\n    />\n</shape>"
  },
  {
    "path": "ui-show/src/main/res/drawable/ic_arrow_right.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M8.59,16.34l4.58,-4.59 -4.58,-4.59L10,5.75l6,6 -6,6z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-show/src/main/res/drawable/ic_locked.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z\"/>\n</vector>\n"
  },
  {
    "path": "ui-show/src/main/res/drawable/ic_quick_setup.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24.0\"\n  android:viewportHeight=\"24.0\"\n  >\n  <path\n    android:fillColor=\"#FF000000\"\n    android:pathData=\"M14,10L2,10v2h12v-2zM14,6L2,6v2h12L14,6zM2,16h8v-2L2,14v2zM21.5,11.5L23,13l-6.99,7 -4.51,-4.5L13,14l3.01,3 5.49,-5.5z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-show/src/main/res/drawable/ic_unlocked.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h1.9c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z\"/>\n</vector>\n"
  },
  {
    "path": "ui-show/src/main/res/drawable-ar/ic_arrow_right.xml",
    "content": "<vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"24dp\"\n  android:height=\"24dp\"\n  android:tint=\"#FFFFFF\"\n  android:viewportWidth=\"24\"\n  android:viewportHeight=\"24\"\n  >\n  <path\n    android:fillColor=\"@android:color/white\"\n    android:pathData=\"M15.41,16.59L10.83,12l4.58,-4.59L14,6l-6,6 6,6 1.41,-1.41z\"\n    />\n</vector>\n"
  },
  {
    "path": "ui-show/src/main/res/layout/fragment_show_details.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  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/showDetailsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.core.widget.NestedScrollView\n    android:id=\"@+id/showDetailsMainLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:scrollbars=\"none\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    >\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:id=\"@+id/showDetailsMainContent\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:clipChildren=\"false\"\n      android:clipToPadding=\"false\"\n      android:descendantFocusability=\"blocksDescendants\"\n      android:paddingBottom=\"@dimen/spaceNormal\"\n      >\n\n      <androidx.constraintlayout.widget.Guideline\n        android:id=\"@+id/showDetailsImageGuideline\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:orientation=\"horizontal\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator1\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:layout_constraintGuide_begin=\"230dp\"\n        />\n\n      <ImageView\n        android:id=\"@+id/showDetailsImage\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsImageGuideline\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n      <ImageView\n        android:id=\"@+id/showDetailsPlaceholder\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"?attr/colorPlaceholderBackground\"\n        android:padding=\"70dp\"\n        android:src=\"@drawable/ic_television\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsImageGuideline\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:tint=\"?attr/colorPlaceholderIcon\"\n        tools:visibility=\"visible\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsTitle\"\n        style=\"@style/ImageTitle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"@dimen/spaceNormal\"\n        android:gravity=\"start\"\n        android:maxLines=\"2\"\n        android:paddingStart=\"@dimen/spaceNormal\"\n        android:paddingEnd=\"@dimen/spaceNormal\"\n        android:textAlignment=\"viewStart\"\n        android:textSize=\"25sp\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsImageGuideline\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        tools:text=\"Game Of Thrones\"\n        />\n\n      <ImageView\n        android:id=\"@+id/showDetailsShareButton\"\n        android:layout_width=\"@dimen/backArrowSize\"\n        android:layout_height=\"@dimen/backArrowSize\"\n        android:background=\"?android:attr/selectableItemBackground\"\n        android:padding=\"@dimen/backArrowPadding\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:srcCompat=\"@drawable/ic_share\"\n        />\n\n      <View\n        android:id=\"@+id/separator1\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:background=\"?attr/colorSeparator\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsExtraInfo\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsImageGuideline\"\n        />\n\n      <ProgressBar\n        android:id=\"@+id/showDetailsImageProgress\"\n        style=\"@style/ProgressBar.Dark\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/showDetailsImage\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/showDetailsImage\"\n        />\n\n      <com.michaldrabik.ui_base.common.views.tips.TipView\n        android:id=\"@+id/showDetailsTipGallery\"\n        android:layout_width=\"@dimen/tutorialTipSize\"\n        android:layout_height=\"@dimen/tutorialTipSize\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/showDetailsImage\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/showDetailsImage\"\n        tools:visibility=\"visible\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsExtraInfo\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginTop=\"@dimen/spaceMedium\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:ellipsize=\"end\"\n        android:gravity=\"start\"\n        android:maxLines=\"1\"\n        android:textAlignment=\"viewStart\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"14sp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsStatus\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator1\"\n        tools:text=\"Netflix 2019 (PL) | 60 min | Drama, SF\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsStatus\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceSmall\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"13sp\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsAddButton\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/showDetailsExtraInfo\"\n        tools:text=\"Returning Series\"\n        />\n\n      <com.michaldrabik.ui_show.views.AddToShowsButton\n        android:id=\"@+id/showDetailsAddButton\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsManageListsLabel\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsStatus\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsManageListsLabel\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginTop=\"@dimen/spaceMedium\"\n        android:layout_marginBottom=\"18dp\"\n        android:drawablePadding=\"@dimen/spaceSmall\"\n        android:fontFamily=\"sans-serif-medium\"\n        android:gravity=\"start|center_vertical\"\n        android:text=\"@string/textShowManageLists\"\n        android:textAlignment=\"viewStart\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"13sp\"\n        app:drawableStartCompat=\"@drawable/ic_lists\"\n        app:drawableTint=\"?android:attr/textColorPrimary\"\n        app:layout_constraintBottom_toTopOf=\"@+id/separator5\"\n        app:layout_constraintEnd_toStartOf=\"@id/showDetailsHideLabel\"\n        app:layout_constraintHorizontal_bias=\"0\"\n        app:layout_constraintHorizontal_chainStyle=\"packed\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsAddButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsHideLabel\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"20dp\"\n        android:drawablePadding=\"6dp\"\n        android:fontFamily=\"sans-serif-medium\"\n        android:gravity=\"start|center_vertical\"\n        android:text=\"@string/textHide\"\n        android:textAlignment=\"viewStart\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"13sp\"\n        app:drawableStartCompat=\"@drawable/ic_eye_no\"\n        app:drawableTint=\"?android:attr/textColorPrimary\"\n        app:layout_constraintBottom_toBottomOf=\"@id/showDetailsManageListsLabel\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/showDetailsManageListsLabel\"\n        app:layout_constraintTop_toTopOf=\"@id/showDetailsManageListsLabel\"\n        />\n\n      <View\n        android:id=\"@+id/separator5\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:background=\"?attr/colorSeparator\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsRatingsFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsManageListsLabel\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/showDetailsRatingsFragment\"\n        android:name=\"com.michaldrabik.ui_show.sections.ratings.ShowDetailsRatingsFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceTiny\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceNormal\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsStreamingsFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/separator5\"\n        tools:layout=\"@layout/fragment_show_details_ratings\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/showDetailsStreamingsFragment\"\n        android:name=\"com.michaldrabik.ui_show.sections.streamings.ShowDetailsStreamingsFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsDescription\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsRatingsFragment\"\n        tools:layout_height=\"40dp\"\n        tools:visibility=\"visible\"\n        />\n\n      <com.michaldrabik.ui_base.common.views.FoldableTextView\n        android:id=\"@+id/showDetailsDescription\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceTiny\"\n        android:ellipsize=\"end\"\n        android:gravity=\"start\"\n        android:textAlignment=\"viewStart\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"14sp\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsActorsFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsStreamingsFragment\"\n        app:layout_goneMarginBottom=\"@dimen/spaceHuge\"\n        app:layout_goneMarginTop=\"0dp\"\n        tools:targetApi=\"o\"\n        tools:text=\"Description\"\n        />\n\n      <View\n        android:id=\"@+id/separator2\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginTop=\"14dp\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceMedium\"\n        android:background=\"?attr/colorSeparator\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsEpisodeFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.0\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsActorsFragment\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/showDetailsActorsFragment\"\n        android:name=\"com.michaldrabik.ui_show.sections.people.ShowDetailsPeopleFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceMedium\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator2\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsDescription\"\n        tools:layout_height=\"100dp\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/showDetailsEpisodeFragment\"\n        android:name=\"com.michaldrabik.ui_show.sections.nextepisode.ShowDetailsNextEpisodeFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"@dimen/episodeNextViewHeight\"\n        android:layout_marginLeft=\"@dimen/spaceNormal\"\n        android:layout_marginTop=\"@dimen/spaceTiny\"\n        android:layout_marginRight=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceMedium\"\n        android:visibility=\"gone\"\n        app:layout_constrainedHeight=\"false\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsSeasonsFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator2\"\n        tools:layout=\"@layout/fragment_show_details_next_episode\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/showDetailsSeasonsFragment\"\n        android:name=\"com.michaldrabik.ui_show.sections.seasons.ShowDetailsSeasonsFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceTiny\"\n        app:layout_constrainedHeight=\"false\"\n        app:layout_constraintBottom_toBottomOf=\"@id/separator3\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsEpisodeFragment\"\n        app:layout_goneMarginTop=\"0dp\"\n        tools:layout=\"@layout/fragment_show_details_seasons\"\n        />\n\n      <View\n        android:id=\"@+id/separator3\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginTop=\"@dimen/spaceTiny\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:layout_marginBottom=\"@dimen/spaceNormal\"\n        android:background=\"?attr/colorSeparator\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsCommentsButton\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsSeasonsFragment\"\n        app:layout_goneMarginBottom=\"@dimen/spaceMedium\"\n        app:layout_goneMarginTop=\"@dimen/spaceBig\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsTrailerButton\"\n        style=\"@style/ShowDetails.ExtraButton\"\n        android:text=\"@string/textTrailer\"\n        app:drawableTopCompat=\"@drawable/ic_play\"\n        app:layout_constraintBottom_toBottomOf=\"@id/showDetailsCommentsButton\"\n        app:layout_constraintEnd_toStartOf=\"@+id/showDetailsRateButton\"\n        app:layout_constraintHorizontal_chainStyle=\"spread\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@id/showDetailsCommentsButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsLinksButton\"\n        style=\"@style/ShowDetails.ExtraButton\"\n        android:text=\"@string/textLink\"\n        app:drawableTopCompat=\"@drawable/ic_link\"\n        app:layout_constraintBottom_toBottomOf=\"@id/showDetailsCommentsButton\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@+id/showDetailsCommentsButton\"\n        app:layout_constraintTop_toTopOf=\"@id/showDetailsCommentsButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsRateButton\"\n        style=\"@style/ShowDetails.ExtraButton\"\n        android:text=\"@string/textRate\"\n        app:drawableTopCompat=\"@drawable/ic_star\"\n        app:layout_constraintBottom_toBottomOf=\"@id/showDetailsCommentsButton\"\n        app:layout_constraintEnd_toStartOf=\"@+id/showDetailsCommentsButton\"\n        app:layout_constraintStart_toEndOf=\"@id/showDetailsTrailerButton\"\n        app:layout_constraintTop_toTopOf=\"@id/showDetailsCommentsButton\"\n        />\n\n      <ProgressBar\n        android:id=\"@+id/showDetailsRateProgress\"\n        style=\"@style/ProgressBar.Dark\"\n        android:layout_width=\"30dp\"\n        android:layout_height=\"30dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"@id/showDetailsRateButton\"\n        app:layout_constraintEnd_toEndOf=\"@id/showDetailsRateButton\"\n        app:layout_constraintStart_toStartOf=\"@id/showDetailsRateButton\"\n        app:layout_constraintTop_toTopOf=\"@id/showDetailsRateButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsCommentsButton\"\n        style=\"@style/ShowDetails.ExtraButton\"\n        android:text=\"@string/textComments2\"\n        app:drawableTopCompat=\"@drawable/ic_comment\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator4\"\n        app:layout_constraintEnd_toStartOf=\"@+id/showDetailsLinksButton\"\n        app:layout_constraintStart_toEndOf=\"@+id/showDetailsRateButton\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator3\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/showDetailsRelatedFragment\"\n        android:name=\"com.michaldrabik.ui_show.sections.related.ShowDetailsRelatedFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator4\"\n        tools:layout=\"@layout/fragment_show_details_related\"\n        />\n\n      <View\n        android:id=\"@+id/separator4\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:background=\"?attr/colorSeparator\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsRelatedFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.0\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsCommentsButton\"\n        app:layout_goneMarginBottom=\"@dimen/spaceBig\"\n        app:layout_goneMarginTop=\"54dp\"\n        tools:visibility=\"visible\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsCustomImagesButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:drawablePadding=\"@dimen/spaceTiny\"\n        android:fontFamily=\"sans-serif-medium\"\n        android:gravity=\"center_vertical\"\n        android:text=\"@string/textSetCustomImages\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"13sp\"\n        android:visibility=\"gone\"\n        app:drawableStartCompat=\"@drawable/ic_custom_image\"\n        app:drawableTint=\"?android:attr/textColorPrimary\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsPremiumAd\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsRelatedFragment\"\n        app:layout_goneMarginTop=\"0dp\"\n        tools:visibility=\"visible\"\n        />\n\n      <com.michaldrabik.ui_base.common.views.PremiumAdView\n        android:id=\"@+id/showDetailsPremiumAd\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"100dp\"\n        android:layout_marginStart=\"@dimen/spaceNormal\"\n        android:layout_marginTop=\"18dp\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsCustomImagesButton\"\n        />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n  </androidx.core.widget.NestedScrollView>\n\n  <ProgressBar\n    android:id=\"@+id/showDetailsMainProgress\"\n    style=\"@style/ProgressBar.Accent\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:visibility=\"gone\"\n    />\n\n  <ImageView\n    android:id=\"@+id/showDetailsBackArrow\"\n    android:layout_width=\"@dimen/backArrowSize\"\n    android:layout_height=\"@dimen/backArrowSize\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:padding=\"@dimen/backArrowPadding\"\n    app:srcCompat=\"@drawable/ic_arrow_back\"\n    />\n\n</FrameLayout>\n"
  },
  {
    "path": "ui-show/src/main/res/layout/fragment_show_details_episodes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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/episodesRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  >\n\n  <TextView\n    android:id=\"@+id/episodesTitle\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"@dimen/backArrowSize\"\n    android:gravity=\"center_vertical\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"18sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintStart_toEndOf=\"@id/episodesBackArrow\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:text=\"Season 2\"\n    />\n\n  <com.google.android.material.checkbox.MaterialCheckBox\n    android:id=\"@+id/episodesCheckbox\"\n    style=\"@style/ShowlyCheckbox\"\n    android:layout_width=\"32dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/showDetailsMarginHorizontal\"\n    app:layout_constraintBottom_toTopOf=\"@id/episodesOverview\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <ImageView\n    android:id=\"@+id/episodesUnlockButton\"\n    android:layout_width=\"24dp\"\n    android:layout_height=\"24dp\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    app:layout_constraintBottom_toTopOf=\"@id/episodesOverview\"\n    app:layout_constraintEnd_toStartOf=\"@id/episodesCheckbox\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_goneMarginEnd=\"@dimen/spaceNormal\"\n    app:srcCompat=\"@drawable/ic_locked\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n  <ImageView\n    android:id=\"@+id/episodesStarIcon\"\n    android:layout_width=\"20dp\"\n    android:layout_height=\"0dp\"\n    app:layout_constraintBottom_toBottomOf=\"@id/episodesSeasonRating\"\n    app:layout_constraintEnd_toStartOf=\"@id/episodesSeasonRating\"\n    app:layout_constraintTop_toTopOf=\"@id/episodesSeasonRating\"\n    app:srcCompat=\"@drawable/ic_star\"\n    app:tint=\"?attr/colorAccent\"\n    />\n\n  <TextView\n    android:id=\"@+id/episodesSeasonRating\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:gravity=\"end\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    app:layout_constraintBottom_toTopOf=\"@id/episodesOverview\"\n    app:layout_constraintEnd_toStartOf=\"@id/episodesUnlockButton\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_goneMarginEnd=\"@dimen/spaceMedium\"\n    tools:text=\"7.6\"\n    />\n\n  <ImageView\n    android:id=\"@+id/episodesSeasonMyStarIcon\"\n    android:layout_width=\"20dp\"\n    android:layout_height=\"0dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/episodesSeasonMyRating\"\n    app:layout_constraintEnd_toStartOf=\"@id/episodesSeasonMyRating\"\n    app:layout_constraintTop_toTopOf=\"@id/episodesSeasonMyRating\"\n    app:srcCompat=\"@drawable/ic_star\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/episodesSeasonRateButton\"\n    style=\"@style/RoundTextButton\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/spaceTiny\"\n    android:gravity=\"center\"\n    android:minWidth=\"64dp\"\n    android:text=\"@string/textRate\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/episodesOverview\"\n    app:layout_constraintEnd_toStartOf=\"@id/episodesSeasonMyStarIcon\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:rippleColor=\"?android:attr/textColorPrimary\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/episodesSeasonMyRating\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/spaceTiny\"\n    android:gravity=\"end\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/episodesOverview\"\n    app:layout_constraintEnd_toStartOf=\"@id/episodesStarIcon\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_goneMarginEnd=\"@dimen/spaceMedium\"\n    tools:text=\"7.6\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.michaldrabik.ui_base.common.views.FoldableTextView\n    android:id=\"@+id/episodesOverview\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceSmall\"\n    android:ellipsize=\"end\"\n    android:gravity=\"center_vertical\"\n    android:paddingLeft=\"@dimen/showDetailsMarginHorizontal\"\n    android:paddingRight=\"@dimen/showDetailsMarginHorizontal\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"14sp\"\n    app:layout_constraintBottom_toTopOf=\"@id/episodesSeparator\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/episodesTitle\"\n    app:layout_goneMarginBottom=\"0dp\"\n    tools:targetApi=\"o\"\n    tools:text=\"@tools:sample/lorem/random\"\n    />\n\n  <View\n    android:id=\"@+id/episodesSeparator\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"1dp\"\n    android:layout_marginStart=\"@dimen/showDetailsMarginHorizontal\"\n    android:layout_marginEnd=\"@dimen/showDetailsMarginHorizontal\"\n    android:background=\"?attr/colorSeparator\"\n    app:layout_constraintBottom_toTopOf=\"@id/episodesRecycler\"\n    app:layout_constraintTop_toBottomOf=\"@id/episodesOverview\"\n    />\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/episodesRecycler\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"0dp\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:layoutAnimation=\"@anim/anim_recycler_fall_down_fast\"\n    android:nestedScrollingEnabled=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingTop=\"@dimen/spaceSmall\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/episodesSeparator\"\n    />\n\n  <ImageView\n    android:id=\"@+id/episodesBackArrow\"\n    android:layout_width=\"@dimen/backArrowSize\"\n    android:layout_height=\"@dimen/backArrowSize\"\n    android:layout_marginStart=\"@dimen/showDetailsBackArrowMargin\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:padding=\"@dimen/backArrowPadding\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:srcCompat=\"@drawable/ic_arrow_back\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-show/src/main/res/layout/fragment_show_details_next_episode.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n  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/showDetailsEpisodeRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  app:cardBackgroundColor=\"?attr/colorCardBackground\"\n  app:cardCornerRadius=\"6dp\"\n  app:cardElevation=\"@dimen/elevationSmall\"\n  app:strokeWidth=\"0dp\"\n  >\n\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceMedium\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:layout_marginBottom=\"@dimen/spaceSmall\"\n    android:orientation=\"vertical\"\n    >\n\n    <LinearLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center_vertical\"\n      android:orientation=\"horizontal\"\n      >\n\n      <ImageView\n        android:id=\"@+id/showDetailsEpisodeIcon\"\n        android:layout_width=\"20dp\"\n        android:layout_height=\"20dp\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_marginEnd=\"@dimen/spaceTiny\"\n        app:srcCompat=\"@drawable/ic_clock\"\n        app:tint=\"?attr/colorAccent\"\n        />\n\n      <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"center\"\n        android:text=\"@string/textNextEpisode\"\n        android:textColor=\"?attr/colorAccent\"\n        android:textSize=\"14sp\"\n        android:textStyle=\"bold\"\n        />\n\n    </LinearLayout>\n\n    <TextView\n      android:id=\"@+id/showDetailsEpisodeText\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"match_parent\"\n      android:layout_marginTop=\"@dimen/spaceMicro\"\n      android:ellipsize=\"marquee\"\n      android:gravity=\"start\"\n      android:singleLine=\"true\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <TextView\n      android:id=\"@+id/showDetailsEpisodeAirtime\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"match_parent\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      android:visibility=\"gone\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Airs in 3 days 2 hours\"\n      tools:visibility=\"visible\"\n      />\n\n  </LinearLayout>\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "ui-show/src/main/res/layout/fragment_show_details_people.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/showDetailsActorsRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:minHeight=\"@dimen/actorTileImageHeight\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:visibility=\"invisible\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/showDetailsActorsProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    app:layout_constraintBottom_toBottomOf=\"@id/showDetailsActorsRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsActorsEmptyView\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    android:gravity=\"center\"\n    android:text=\"@string/textActorsEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/showDetailsActorsRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsDirectingLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"13dp\"\n    android:gravity=\"center_vertical\"\n    android:text=\"@string/textDirecting\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"11sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/showDetailsDirectingValue\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/showDetailsActorsRecycler\"\n    app:layout_goneMarginTop=\"@dimen/spaceHuge\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsDirectingValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:minWidth=\"50dp\"\n    android:text=\"-\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"11sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintStart_toStartOf=\"@id/showDetailsDirectingLabel\"\n    app:layout_constraintTop_toBottomOf=\"@id/showDetailsDirectingLabel\"\n    tools:ignore=\"HardcodedText\"\n    tools:text=\"Steven\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsWritingLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:text=\"@string/textWriting\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"11sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/showDetailsDirectingLabel\"\n    app:layout_constraintStart_toEndOf=\"@id/showDetailsDirectingValue\"\n    app:layout_constraintTop_toTopOf=\"@id/showDetailsDirectingLabel\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsWritingValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:minWidth=\"50dp\"\n    android:text=\"-\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"11sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintStart_toStartOf=\"@id/showDetailsWritingLabel\"\n    app:layout_constraintTop_toBottomOf=\"@id/showDetailsWritingLabel\"\n    tools:ignore=\"HardcodedText\"\n    tools:text=\"Steven Spilberg\\nTest\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsMusicLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"0dp\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:text=\"@string/textMusic\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"11sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/showDetailsDirectingLabel\"\n    app:layout_constraintStart_toEndOf=\"@id/showDetailsWritingValue\"\n    app:layout_constraintTop_toTopOf=\"@id/showDetailsDirectingLabel\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsMusicValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:text=\"-\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"11sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"@id/showDetailsMusicLabel\"\n    app:layout_constraintTop_toBottomOf=\"@id/showDetailsMusicLabel\"\n    tools:ignore=\"HardcodedText\"\n    tools:text=\"Steven Spilberg\"\n    tools:visibility=\"visible\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-show/src/main/res/layout/fragment_show_details_ratings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipToPadding=\"false\"\n  >\n\n  <com.michaldrabik.ui_base.common.views.RatingsStripView\n    android:id=\"@+id/showDetailsRatings\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    />\n\n</FrameLayout>\n"
  },
  {
    "path": "ui-show/src/main/res/layout/fragment_show_details_related.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  tools:layout_marginTop=\"-0dp\"\n  >\n\n  <TextView\n    android:id=\"@+id/showDetailsRelatedLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:text=\"@string/textYouMayAlsoLike\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"16sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintBottom_toTopOf=\"@id/showDetailsRelatedRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"0\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/showDetailsRelatedRecycler\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"0dp\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginBottom=\"@dimen/spaceNormal\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:visibility=\"invisible\"\n    app:layout_constrainedHeight=\"true\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHeight_min=\"@dimen/relatedShowHeight\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/showDetailsRelatedLabel\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/showDetailsRelatedProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    app:layout_constraintBottom_toBottomOf=\"@id/showDetailsRelatedRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/showDetailsRelatedRecycler\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-show/src/main/res/layout/fragment_show_details_seasons.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <TextView\n    android:id=\"@+id/showDetailsSeasonsLabel\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/textSeasons\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"16sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintBottom_toTopOf=\"@id/showDetailsSeasonsRecycler\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_goneMarginTop=\"@dimen/spaceMicro\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsRuntimeLeft\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"0dp\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:gravity=\"center_vertical\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"13sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/showDetailsSeasonsLabel\"\n    app:layout_constraintEnd_toStartOf=\"@id/showDetailsQuickProgress\"\n    app:layout_constraintHorizontal_bias=\"1\"\n    app:layout_constraintStart_toEndOf=\"@id/showDetailsSeasonsLabel\"\n    app:layout_constraintTop_toTopOf=\"@id/showDetailsSeasonsLabel\"\n    app:layout_constraintWidth_default=\"wrap\"\n    tools:text=\"~14h 12min of watching\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/showDetailsQuickProgress\"\n    android:layout_width=\"25dp\"\n    android:layout_height=\"25dp\"\n    android:layout_gravity=\"center_vertical|end\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n    android:src=\"@drawable/ic_quick_setup\"\n    android:translationY=\"1dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/showDetailsSeasonsLabel\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"1\"\n    app:layout_constraintStart_toEndOf=\"@id/showDetailsSeasonsLabel\"\n    app:layout_constraintTop_toTopOf=\"@id/showDetailsSeasonsLabel\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    tools:visibility=\"visible\"\n    />\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/showDetailsSeasonsRecycler\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:nestedScrollingEnabled=\"false\"\n    android:overScrollMode=\"never\"\n    android:visibility=\"invisible\"\n    app:layout_constrainedHeight=\"true\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHeight_min=\"48dp\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/showDetailsSeasonsLabel\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/showDetailsSeasonsProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsSeasonsEmptyView\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:layout_marginBottom=\"@dimen/spaceBig\"\n    android:text=\"@string/textSeasonsEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-show/src/main/res/layout/fragment_show_details_streamings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  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=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/showDetailsStreamingsRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/streamingTileHeight\"\n    android:layout_marginTop=\"@dimen/spaceMicro\"\n    android:layout_marginBottom=\"@dimen/spaceNormal\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/showDetailsMarginHorizontal\"\n    android:paddingEnd=\"@dimen/showDetailsMarginHorizontal\"\n    app:layout_constraintBottom_toTopOf=\"@id/showDetailsDescription\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@+id/showDetailsRatings\"\n    />\n\n</FrameLayout>\n"
  },
  {
    "path": "ui-show/src/main/res/layout/view_actor.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"wrap_content\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/actorRoot\"\n    android:layout_width=\"@dimen/actorTileImageWidth\"\n    android:layout_height=\"@dimen/actorTileImageHeight\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/actorImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      />\n\n    <ImageView\n      android:id=\"@+id/actorPlaceholder\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"@dimen/actorTilePlaceholder\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_person_outline\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/actorName\"\n      style=\"@style/ImageTitle\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"bottom|start\"\n      android:layout_marginStart=\"5dp\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:layout_marginBottom=\"3dp\"\n      android:gravity=\"start\"\n      android:maxLines=\"2\"\n      android:textAlignment=\"viewStart\"\n      android:textSize=\"@dimen/actorTileTextSize\"\n      android:translationZ=\"10dp\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Erin Moriarty\"\n      />\n\n  </FrameLayout>\n\n</merge>"
  },
  {
    "path": "ui-show/src/main/res/layout/view_add_to_shows_button.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:gravity=\"center\"\n    android:orientation=\"horizontal\"\n    >\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/addToMyShowsButton\"\n      style=\"@style/RoundOutlinedButton\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:layout_weight=\"2\"\n      android:gravity=\"center\"\n      android:includeFontPadding=\"false\"\n      android:text=\"@string/textAddToMyShows\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"@dimen/addToButtonTextSize\"\n      app:rippleColor=\"?android:attr/textColorPrimary\"\n      app:strokeColor=\"?android:attr/textColorPrimary\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/watchlistButton\"\n      style=\"@style/RoundOutlinedButton\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:layout_weight=\"1\"\n      android:gravity=\"center\"\n      android:maxLines=\"2\"\n      android:padding=\"0dp\"\n      android:text=\"@string/textWatchlist\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"@dimen/addToButtonTextSize\"\n      app:rippleColor=\"?android:attr/textColorSecondary\"\n      app:strokeWidth=\"0dp\"\n      />\n\n  </LinearLayout>\n\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    >\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/addedToButton\"\n      style=\"@style/RoundOutlinedButton\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_weight=\"1\"\n      android:gravity=\"center\"\n      android:includeFontPadding=\"false\"\n      android:text=\"@string/textInMyShows\"\n      android:textColor=\"?attr/colorAccent\"\n      android:textSize=\"@dimen/addToButtonTextSize\"\n      android:visibility=\"gone\"\n      app:icon=\"@drawable/ic_bookmark_full\"\n      app:iconGravity=\"textStart\"\n      app:iconPadding=\"@dimen/spaceTiny\"\n      app:iconTint=\"?attr/colorAccent\"\n      app:rippleColor=\"?attr/colorAccent\"\n      app:strokeColor=\"?attr/colorAccent\"\n      tools:visibility=\"visible\"\n      />\n\n  </LinearLayout>\n\n</merge>\n"
  },
  {
    "path": "ui-show/src/main/res/layout/view_episode.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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/episodeRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  >\n\n  <TextView\n    android:id=\"@+id/episodeTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/showDetailsMarginHorizontal\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:ellipsize=\"end\"\n    android:gravity=\"center_vertical|start\"\n    android:maxLines=\"1\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    app:layout_constraintEnd_toStartOf=\"@id/episodeMyStarIcon\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:text=\"Episode 1\"\n    />\n\n  <TextView\n    android:id=\"@+id/episodeOverview\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/showDetailsMarginHorizontal\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:layout_marginBottom=\"@dimen/spaceSmall\"\n    android:ellipsize=\"end\"\n    android:gravity=\"center_vertical|start\"\n    android:maxLines=\"2\"\n    android:textAlignment=\"viewStart\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"16sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toStartOf=\"@id/episodeMyStarIcon\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/episodeTitle\"\n    tools:text=\"@tools:sample/lorem/random\"\n    />\n\n  <ImageView\n    android:id=\"@+id/episodeStarIcon\"\n    android:layout_width=\"20dp\"\n    android:layout_height=\"0dp\"\n    app:layout_constraintBottom_toBottomOf=\"@id/episodeRating\"\n    app:layout_constraintEnd_toStartOf=\"@id/episodeRating\"\n    app:layout_constraintTop_toTopOf=\"@id/episodeRating\"\n    app:srcCompat=\"@drawable/ic_star\"\n    app:tint=\"?attr/colorAccent\"\n    />\n\n  <TextView\n    android:id=\"@+id/episodeRating\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:gravity=\"end\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toStartOf=\"@id/episodeCheckbox\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_constraintVertical_chainStyle=\"packed\"\n    tools:text=\"7.6\"\n    />\n\n  <ImageView\n    android:id=\"@+id/episodeMyStarIcon\"\n    android:layout_width=\"20dp\"\n    android:layout_height=\"0dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/episodeMyRating\"\n    app:layout_constraintEnd_toStartOf=\"@id/episodeMyRating\"\n    app:layout_constraintTop_toTopOf=\"@id/episodeMyRating\"\n    app:srcCompat=\"@drawable/ic_star\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/episodeMyRating\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/spaceTiny\"\n    android:gravity=\"end\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"12sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toStartOf=\"@id/episodeStarIcon\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:text=\"7.6\"\n    tools:visibility=\"visible\"\n    />\n\n  <com.google.android.material.checkbox.MaterialCheckBox\n    android:id=\"@+id/episodeCheckbox\"\n    style=\"@style/ShowlyCheckbox\"\n    android:layout_width=\"32dp\"\n    android:layout_height=\"@dimen/episodeViewHeight\"\n    android:layout_marginEnd=\"@dimen/showDetailsMarginHorizontal\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n</merge>\n"
  },
  {
    "path": "ui-show/src/main/res/layout/view_quick_setup.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:minHeight=\"800dp\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewQuickSetupTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_margin=\"@dimen/spaceNormal\"\n      android:text=\"@string/textShowQuickProgressTitle\"\n      android:textAllCaps=\"true\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"18sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewQuickSetupSubTitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginTop=\"@dimen/spaceMicro\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:text=\"@string/textShowQuickProgressSubTitle\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewQuickSetupTitle\"\n      />\n\n    <androidx.recyclerview.widget.RecyclerView\n      android:id=\"@+id/viewQuickSetupRecycler\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceNormal\"\n      android:layout_marginTop=\"@dimen/spaceMedium\"\n      android:layout_marginEnd=\"@dimen/spaceNormal\"\n      android:overScrollMode=\"never\"\n      android:paddingBottom=\"80dp\"\n      android:scrollbarStyle=\"outsideOverlay\"\n      android:scrollbars=\"vertical\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewQuickSetupSubTitle\"\n      app:layout_constraintVertical_bias=\"0\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-show/src/main/res/layout/view_quick_setup_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <TextView\n    android:id=\"@+id/viewQuickSetupHeaderTitle\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginBottom=\"@dimen/spaceNormal\"\n    android:gravity=\"center\"\n    android:text=\"@string/textSeason\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?attr/colorAccent\"\n    android:textSize=\"18sp\"\n    android:textStyle=\"bold\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-show/src/main/res/layout/view_quick_setup_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewQuickSetupItemTitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      android:ellipsize=\"end\"\n      android:gravity=\"center_vertical|start\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewQuickSetupItemRadio\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:text=\"Episode 1\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewQuickSetupItemSubtitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:ellipsize=\"end\"\n      android:gravity=\"center_vertical|start\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"16sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewQuickSetupItemRadio\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewQuickSetupItemTitle\"\n      tools:text=\"@tools:sample/lorem\"\n      />\n\n    <com.google.android.material.radiobutton.MaterialRadioButton\n      android:id=\"@+id/viewQuickSetupItemRadio\"\n      android:layout_width=\"30dp\"\n      android:layout_height=\"wrap_content\"\n      android:clickable=\"false\"\n      android:ellipsize=\"end\"\n      android:maxLines=\"1\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      app:buttonTint=\"@color/selector_main_checkbox\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-show/src/main/res/layout/view_related_show.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"wrap_content\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <FrameLayout\n    android:id=\"@+id/relatedRoot\"\n    android:layout_width=\"@dimen/relatedShowWidth\"\n    android:layout_height=\"@dimen/relatedShowHeight\"\n    android:foreground=\"@drawable/bg_media_view_ripple\"\n    >\n\n    <ImageView\n      android:id=\"@+id/relatedImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      />\n\n    <ImageView\n      android:id=\"@+id/relatedPlaceholder\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"@dimen/relatedTileShowPlaceholder\"\n      android:visibility=\"gone\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/relatedTitle\"\n      style=\"@style/ImageTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"bottom|start\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:layout_marginBottom=\"@dimen/spaceMicro\"\n      android:maxLines=\"1\"\n      android:textSize=\"@dimen/relatedShowTextSize\"\n      android:translationZ=\"10dp\"\n      android:visibility=\"gone\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Erin Moriarty\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/relatedBadge\"\n      style=\"@style/Badge\"\n      android:layout_width=\"22dp\"\n      android:layout_height=\"22dp\"\n      android:layout_marginEnd=\"@dimen/spaceMicro\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:translationY=\"-3dp\"\n      app:srcCompat=\"@drawable/ic_bookmark_full\"\n      tools:visibility=\"visible\"\n      />\n\n  </FrameLayout>\n\n</merge>"
  },
  {
    "path": "ui-show/src/main/res/layout/view_season.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/seasonViewRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/seasonViewHeight\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:paddingStart=\"10dp\"\n    android:paddingTop=\"6dp\"\n    android:paddingEnd=\"10dp\"\n    android:paddingBottom=\"6dp\"\n    >\n\n    <com.google.android.material.checkbox.MaterialCheckBox\n      android:id=\"@+id/seasonViewCheckbox\"\n      style=\"@style/ShowlyCheckbox\"\n      android:layout_width=\"30dp\"\n      android:layout_height=\"0dp\"\n      android:gravity=\"center\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <TextView\n      android:id=\"@+id/seasonViewTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:gravity=\"center_vertical\"\n      android:minWidth=\"80dp\"\n      android:paddingBottom=\"@dimen/spaceMicro\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/seasonViewCheckbox\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:text=\"Season 00\"\n      />\n\n    <ImageView\n      android:id=\"@+id/seasonViewArrow\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/seasonViewProgressText\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_arrow_right\"\n      />\n\n    <TextView\n      android:id=\"@+id/seasonViewProgressText\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"center\"\n      android:minWidth=\"110dp\"\n      android:paddingStart=\"@dimen/spaceSmall\"\n      android:paddingEnd=\"@dimen/spaceSmall\"\n      android:paddingBottom=\"@dimen/spaceMicro\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/seasonViewArrow\"\n      app:layout_constraintStart_toEndOf=\"@id/seasonViewProgress\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:text=\"20/20 (100%)\"\n      />\n\n    <com.google.android.material.progressindicator.LinearProgressIndicator\n      android:id=\"@+id/seasonViewProgress\"\n      style=\"@style/Widget.MaterialComponents.LinearProgressIndicator\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/seasonViewProgressMargin\"\n      app:indicatorColor=\"?attr/colorAccent\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/seasonViewProgressText\"\n      app:layout_constraintStart_toEndOf=\"@id/seasonViewTitle\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:trackColor=\"?attr/colorProgressTrack\"\n      app:trackCornerRadius=\"4dp\"\n      app:trackThickness=\"4dp\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n</merge>\n"
  },
  {
    "path": "ui-show/src/main/res/layout-sw600dp/fragment_show_details.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  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/showDetailsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.core.widget.NestedScrollView\n    android:id=\"@+id/showDetailsMainLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:scrollbars=\"none\"\n    android:visibility=\"gone\"\n    tools:visibility=\"visible\"\n    >\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:id=\"@+id/showDetailsMainContent\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:clipChildren=\"false\"\n      android:clipToPadding=\"false\"\n      android:descendantFocusability=\"blocksDescendants\"\n      android:paddingBottom=\"@dimen/spaceBig\"\n      >\n\n      <androidx.constraintlayout.widget.Guideline\n        android:id=\"@+id/showDetailsImageGuideline\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:orientation=\"horizontal\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator1\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:layout_constraintGuide_begin=\"230dp\"\n        />\n\n      <androidx.constraintlayout.widget.Guideline\n        android:id=\"@+id/showDetailsGuidelineVertical\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:orientation=\"vertical\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintGuide_percent=\"0.7\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        />\n\n      <ImageView\n        android:id=\"@+id/showDetailsImage\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsImageGuideline\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n      <ImageView\n        android:id=\"@+id/showDetailsPlaceholder\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"?attr/colorPlaceholderBackground\"\n        android:padding=\"70dp\"\n        android:src=\"@drawable/ic_television\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsImageGuideline\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:tint=\"?attr/colorPlaceholderIcon\"\n        tools:visibility=\"visible\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsTitle\"\n        style=\"@style/ImageTitle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"@dimen/spaceNormal\"\n        android:gravity=\"start\"\n        android:maxLines=\"2\"\n        android:paddingStart=\"@dimen/spaceBig\"\n        android:paddingEnd=\"@dimen/spaceBig\"\n        android:textAlignment=\"viewStart\"\n        android:textSize=\"28sp\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsImageGuideline\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        tools:text=\"Game Of Thrones\"\n        />\n\n      <ImageView\n        android:id=\"@+id/showDetailsShareButton\"\n        android:layout_width=\"@dimen/backArrowSize\"\n        android:layout_height=\"@dimen/backArrowSize\"\n        android:layout_marginEnd=\"@dimen/spaceSmall\"\n        android:background=\"?android:attr/selectableItemBackground\"\n        android:padding=\"@dimen/backArrowPadding\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:srcCompat=\"@drawable/ic_share\"\n        />\n\n      <View\n        android:id=\"@+id/separator1\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:background=\"?attr/colorSeparator\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsExtraInfo\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsImageGuideline\"\n        />\n\n      <ProgressBar\n        android:id=\"@+id/showDetailsImageProgress\"\n        style=\"@style/ProgressBar.Dark\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/showDetailsImage\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/showDetailsImage\"\n        />\n\n      <com.michaldrabik.ui_base.common.views.tips.TipView\n        android:id=\"@+id/showDetailsTipGallery\"\n        android:layout_width=\"@dimen/tutorialTipSize\"\n        android:layout_height=\"@dimen/tutorialTipSize\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/showDetailsImage\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/showDetailsImage\"\n        tools:visibility=\"visible\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsExtraInfo\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginTop=\"20dp\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        android:ellipsize=\"end\"\n        android:gravity=\"start\"\n        android:maxLines=\"1\"\n        android:textAlignment=\"viewStart\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"15sp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsStatus\"\n        app:layout_constraintEnd_toStartOf=\"@id/showDetailsGuidelineVertical\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator1\"\n        tools:text=\"Netflix 2019 (PL) | 60 min | Drama, SF\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsStatus\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginBottom=\"@dimen/spaceSmall\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"14sp\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsAddButton\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/showDetailsExtraInfo\"\n        tools:text=\"Returning Series\"\n        />\n\n      <com.michaldrabik.ui_show.views.AddToShowsButton\n        android:id=\"@+id/showDetailsAddButton\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginEnd=\"@dimen/spaceNormal\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsManageListsLabel\"\n        app:layout_constraintEnd_toStartOf=\"@id/showDetailsGuidelineVertical\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsStatus\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsManageListsLabel\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginTop=\"@dimen/spaceMedium\"\n        android:layout_marginBottom=\"18dp\"\n        android:drawablePadding=\"@dimen/spaceSmall\"\n        android:fontFamily=\"sans-serif-medium\"\n        android:gravity=\"start|center_vertical\"\n        android:text=\"@string/textShowManageLists\"\n        android:textAlignment=\"viewStart\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"14sp\"\n        app:drawableStartCompat=\"@drawable/ic_lists\"\n        app:drawableTint=\"?android:attr/textColorPrimary\"\n        app:layout_constraintBottom_toTopOf=\"@+id/separator5\"\n        app:layout_constraintEnd_toStartOf=\"@id/showDetailsHideLabel\"\n        app:layout_constraintHorizontal_bias=\"0\"\n        app:layout_constraintHorizontal_chainStyle=\"packed\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsAddButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsHideLabel\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"20dp\"\n        android:drawablePadding=\"6dp\"\n        android:fontFamily=\"sans-serif-medium\"\n        android:gravity=\"start|center_vertical\"\n        android:text=\"@string/textHide\"\n        android:textAlignment=\"viewStart\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"14sp\"\n        app:drawableStartCompat=\"@drawable/ic_eye_no\"\n        app:drawableTint=\"?android:attr/textColorPrimary\"\n        app:layout_constraintBottom_toBottomOf=\"@id/showDetailsManageListsLabel\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/showDetailsManageListsLabel\"\n        app:layout_constraintTop_toTopOf=\"@id/showDetailsManageListsLabel\"\n        />\n\n      <View\n        android:id=\"@+id/separator5\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginEnd=\"@dimen/spaceBig\"\n        android:background=\"?attr/colorSeparator\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsStreamingsFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsManageListsLabel\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/showDetailsRatingsFragment\"\n        android:name=\"com.michaldrabik.ui_show.sections.ratings.ShowDetailsRatingsFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"@dimen/spaceNormal\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator5\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/showDetailsGuidelineVertical\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator1\"\n        tools:layout=\"@layout/fragment_show_details_ratings\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/showDetailsStreamingsFragment\"\n        android:name=\"com.michaldrabik.ui_show.sections.streamings.ShowDetailsStreamingsFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsDescription\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator5\"\n        tools:layout_height=\"40dp\"\n        tools:visibility=\"visible\"\n        />\n\n      <com.michaldrabik.ui_base.common.views.FoldableTextView\n        android:id=\"@+id/showDetailsDescription\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginEnd=\"@dimen/spaceBig\"\n        android:layout_marginBottom=\"@dimen/spaceTiny\"\n        android:ellipsize=\"end\"\n        android:gravity=\"start\"\n        android:textAlignment=\"viewStart\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"15sp\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsActorsFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsStreamingsFragment\"\n        app:layout_goneMarginBottom=\"@dimen/spaceHuge\"\n        app:layout_goneMarginTop=\"@dimen/spaceNormal\"\n        tools:targetApi=\"o\"\n        tools:text=\"Description\"\n        />\n\n      <View\n        android:id=\"@+id/separator2\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginTop=\"14dp\"\n        android:layout_marginEnd=\"@dimen/spaceBig\"\n        android:layout_marginBottom=\"@dimen/spaceMedium\"\n        android:background=\"?attr/colorSeparator\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsEpisodeFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.0\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsActorsFragment\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/showDetailsActorsFragment\"\n        android:name=\"com.michaldrabik.ui_show.sections.people.ShowDetailsPeopleFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceMedium\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator2\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsDescription\"\n        tools:layout_height=\"100dp\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/showDetailsEpisodeFragment\"\n        android:name=\"com.michaldrabik.ui_show.sections.nextepisode.ShowDetailsNextEpisodeFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"@dimen/episodeNextViewHeight\"\n        android:layout_marginLeft=\"@dimen/spaceBig\"\n        android:layout_marginTop=\"@dimen/spaceTiny\"\n        android:layout_marginRight=\"@dimen/spaceBig\"\n        android:layout_marginBottom=\"@dimen/spaceMedium\"\n        android:visibility=\"gone\"\n        app:layout_constrainedHeight=\"false\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsSeasonsFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator2\"\n        tools:layout=\"@layout/fragment_show_details_next_episode\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/showDetailsSeasonsFragment\"\n        android:name=\"com.michaldrabik.ui_show.sections.seasons.ShowDetailsSeasonsFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceTiny\"\n        app:layout_constrainedHeight=\"false\"\n        app:layout_constraintBottom_toBottomOf=\"@id/separator3\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsEpisodeFragment\"\n        app:layout_goneMarginTop=\"0dp\"\n        tools:layout=\"@layout/fragment_show_details_seasons\"\n        />\n\n      <View\n        android:id=\"@+id/separator3\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginTop=\"@dimen/spaceSmall\"\n        android:layout_marginEnd=\"@dimen/spaceBig\"\n        android:layout_marginBottom=\"@dimen/spaceNormal\"\n        android:background=\"?attr/colorSeparator\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsCommentsButton\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsSeasonsFragment\"\n        app:layout_goneMarginBottom=\"@dimen/spaceMedium\"\n        app:layout_goneMarginTop=\"@dimen/spaceBig\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsTrailerButton\"\n        style=\"@style/ShowDetails.ExtraButton\"\n        android:text=\"@string/textTrailer\"\n        app:drawableTopCompat=\"@drawable/ic_play\"\n        app:layout_constraintBottom_toBottomOf=\"@id/showDetailsCommentsButton\"\n        app:layout_constraintEnd_toStartOf=\"@+id/showDetailsRateButton\"\n        app:layout_constraintHorizontal_chainStyle=\"spread\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@id/showDetailsCommentsButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsLinksButton\"\n        style=\"@style/ShowDetails.ExtraButton\"\n        android:text=\"@string/textLink\"\n        app:drawableTopCompat=\"@drawable/ic_link\"\n        app:layout_constraintBottom_toBottomOf=\"@id/showDetailsCommentsButton\"\n        app:layout_constraintEnd_toStartOf=\"@id/showDetailsCustomImagesButton\"\n        app:layout_constraintStart_toEndOf=\"@+id/showDetailsCommentsButton\"\n        app:layout_constraintTop_toTopOf=\"@id/showDetailsCommentsButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsRateButton\"\n        style=\"@style/ShowDetails.ExtraButton\"\n        android:text=\"@string/textRate\"\n        app:drawableTopCompat=\"@drawable/ic_star\"\n        app:layout_constraintBottom_toBottomOf=\"@id/showDetailsCommentsButton\"\n        app:layout_constraintEnd_toStartOf=\"@+id/showDetailsCommentsButton\"\n        app:layout_constraintStart_toEndOf=\"@id/showDetailsTrailerButton\"\n        app:layout_constraintTop_toTopOf=\"@id/showDetailsCommentsButton\"\n        />\n\n      <ProgressBar\n        android:id=\"@+id/showDetailsRateProgress\"\n        style=\"@style/ProgressBar.Dark\"\n        android:layout_width=\"30dp\"\n        android:layout_height=\"30dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"@id/showDetailsRateButton\"\n        app:layout_constraintEnd_toEndOf=\"@id/showDetailsRateButton\"\n        app:layout_constraintStart_toStartOf=\"@id/showDetailsRateButton\"\n        app:layout_constraintTop_toTopOf=\"@id/showDetailsRateButton\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsCommentsButton\"\n        style=\"@style/ShowDetails.ExtraButton\"\n        android:text=\"@string/textComments2\"\n        app:drawableTopCompat=\"@drawable/ic_comment\"\n        app:layout_constraintBottom_toTopOf=\"@id/separator4\"\n        app:layout_constraintEnd_toStartOf=\"@+id/showDetailsLinksButton\"\n        app:layout_constraintStart_toEndOf=\"@+id/showDetailsRateButton\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator3\"\n        />\n\n      <TextView\n        android:id=\"@+id/showDetailsCustomImagesButton\"\n        style=\"@style/ShowDetails.ExtraButton\"\n        android:text=\"@string/textSetCustomImages\"\n        app:drawableTopCompat=\"@drawable/ic_custom_image\"\n        app:layout_constraintBottom_toBottomOf=\"@id/showDetailsCommentsButton\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@+id/showDetailsLinksButton\"\n        app:layout_constraintTop_toTopOf=\"@id/showDetailsCommentsButton\"\n        />\n\n      <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/showDetailsRelatedFragment\"\n        android:name=\"com.michaldrabik.ui_show.sections.related.ShowDetailsRelatedFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/separator4\"\n        tools:layout=\"@layout/fragment_show_details_related\"\n        />\n\n      <View\n        android:id=\"@+id/separator4\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"1dp\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceBig\"\n        android:background=\"?attr/colorSeparator\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toTopOf=\"@id/showDetailsRelatedFragment\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.0\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsCommentsButton\"\n        app:layout_goneMarginBottom=\"@dimen/spaceBig\"\n        app:layout_goneMarginTop=\"54dp\"\n        tools:visibility=\"visible\"\n        />\n\n      <com.michaldrabik.ui_base.common.views.PremiumAdView\n        android:id=\"@+id/showDetailsPremiumAd\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"100dp\"\n        android:layout_marginStart=\"@dimen/spaceBig\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceBig\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/showDetailsRelatedFragment\"\n        />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n  </androidx.core.widget.NestedScrollView>\n\n  <ProgressBar\n    android:id=\"@+id/showDetailsMainProgress\"\n    style=\"@style/ProgressBar.Accent\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:visibility=\"gone\"\n    />\n\n  <ImageView\n    android:id=\"@+id/showDetailsBackArrow\"\n    android:layout_width=\"@dimen/backArrowSize\"\n    android:layout_height=\"@dimen/backArrowSize\"\n    android:layout_marginStart=\"@dimen/spaceSmall\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:padding=\"@dimen/backArrowPadding\"\n    app:srcCompat=\"@drawable/ic_arrow_back\"\n    />\n\n</FrameLayout>\n"
  },
  {
    "path": "ui-show/src/main/res/layout-sw600dp/fragment_show_details_next_episode.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n  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/showDetailsEpisodeRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  app:cardBackgroundColor=\"?attr/colorCardBackground\"\n  app:cardCornerRadius=\"8dp\"\n  app:cardElevation=\"@dimen/elevationSmall\"\n  app:strokeWidth=\"0dp\"\n  >\n\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginStart=\"@dimen/spaceMedium\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:layout_marginBottom=\"@dimen/spaceSmall\"\n    android:orientation=\"vertical\"\n    >\n\n    <LinearLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center_vertical\"\n      android:orientation=\"horizontal\"\n      >\n\n      <ImageView\n        android:id=\"@+id/showDetailsEpisodeIcon\"\n        android:layout_width=\"22dp\"\n        android:layout_height=\"22dp\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_marginEnd=\"@dimen/spaceTiny\"\n        app:srcCompat=\"@drawable/ic_clock\"\n        app:tint=\"?attr/colorAccent\"\n        />\n\n      <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"center\"\n        android:text=\"@string/textNextEpisode\"\n        android:textColor=\"?attr/colorAccent\"\n        android:textSize=\"16sp\"\n        android:textStyle=\"bold\"\n        />\n\n    </LinearLayout>\n\n    <TextView\n      android:id=\"@+id/showDetailsEpisodeText\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"match_parent\"\n      android:layout_marginTop=\"@dimen/spaceMicro\"\n      android:ellipsize=\"marquee\"\n      android:gravity=\"start\"\n      android:singleLine=\"true\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"15sp\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <TextView\n      android:id=\"@+id/showDetailsEpisodeAirtime\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"match_parent\"\n      android:layout_marginTop=\"2dp\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"13sp\"\n      android:visibility=\"gone\"\n      tools:ignore=\"SmallSp\"\n      tools:text=\"Airs in 3 days 2 hours\"\n      tools:visibility=\"visible\"\n      />\n\n  </LinearLayout>\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "ui-show/src/main/res/layout-sw600dp/fragment_show_details_people.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/showDetailsActorsRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:minHeight=\"@dimen/actorTileImageHeight\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/spaceBig\"\n    android:paddingEnd=\"@dimen/spaceBig\"\n    android:visibility=\"invisible\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/showDetailsActorsProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"@dimen/spaceNormal\"\n    app:layout_constraintBottom_toBottomOf=\"@id/showDetailsActorsRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsActorsEmptyView\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"@dimen/spaceBig\"\n    android:gravity=\"center\"\n    android:text=\"@string/textActorsEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"15sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/showDetailsActorsRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsDirectingLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginTop=\"13dp\"\n    android:gravity=\"center_vertical\"\n    android:text=\"@string/textDirecting\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"13sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/showDetailsDirectingValue\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/showDetailsActorsRecycler\"\n    app:layout_goneMarginTop=\"@dimen/spaceHuge\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsDirectingValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:minWidth=\"80dp\"\n    android:text=\"-\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"13sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintStart_toStartOf=\"@id/showDetailsDirectingLabel\"\n    app:layout_constraintTop_toBottomOf=\"@id/showDetailsDirectingLabel\"\n    tools:ignore=\"HardcodedText\"\n    tools:text=\"Steven\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsWritingLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:text=\"@string/textWriting\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"13sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/showDetailsDirectingLabel\"\n    app:layout_constraintStart_toEndOf=\"@id/showDetailsDirectingValue\"\n    app:layout_constraintTop_toTopOf=\"@id/showDetailsDirectingLabel\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsWritingValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:minWidth=\"80dp\"\n    android:text=\"-\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"13sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintStart_toStartOf=\"@id/showDetailsWritingLabel\"\n    app:layout_constraintTop_toBottomOf=\"@id/showDetailsWritingLabel\"\n    tools:ignore=\"HardcodedText\"\n    tools:text=\"Steven Spilberg\\nTest\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsMusicLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"0dp\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:text=\"@string/textMusic\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"13sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/showDetailsDirectingLabel\"\n    app:layout_constraintStart_toEndOf=\"@id/showDetailsWritingValue\"\n    app:layout_constraintTop_toTopOf=\"@id/showDetailsDirectingLabel\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsMusicValue\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:text=\"-\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"13sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"@id/showDetailsMusicLabel\"\n    app:layout_constraintTop_toBottomOf=\"@id/showDetailsMusicLabel\"\n    tools:ignore=\"HardcodedText\"\n    tools:text=\"Steven Spilberg\"\n    tools:visibility=\"visible\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-show/src/main/res/layout-sw600dp/fragment_show_details_related.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  tools:layout_marginTop=\"-0dp\"\n  >\n\n  <TextView\n    android:id=\"@+id/showDetailsRelatedLabel\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:text=\"@string/textYouMayAlsoLike\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"18sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintBottom_toTopOf=\"@id/showDetailsRelatedRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"0\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/showDetailsRelatedRecycler\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"0dp\"\n    android:layout_marginTop=\"@dimen/spaceMedium\"\n    android:layout_marginBottom=\"@dimen/spaceNormal\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"@dimen/spaceBig\"\n    android:paddingEnd=\"@dimen/spaceBig\"\n    android:visibility=\"invisible\"\n    app:layout_constrainedHeight=\"true\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHeight_min=\"@dimen/relatedShowHeight\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/showDetailsRelatedLabel\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/showDetailsRelatedProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    app:layout_constraintBottom_toBottomOf=\"@id/showDetailsRelatedRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"@id/showDetailsRelatedRecycler\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-show/src/main/res/layout-sw600dp/fragment_show_details_seasons.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  >\n\n  <TextView\n    android:id=\"@+id/showDetailsSeasonsLabel\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceBig\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/textSeasons\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"18sp\"\n    android:textStyle=\"bold\"\n    app:layout_constraintBottom_toTopOf=\"@id/showDetailsSeasonsRecycler\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_goneMarginTop=\"@dimen/spaceMicro\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsRuntimeLeft\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"0dp\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:gravity=\"center_vertical\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"15sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/showDetailsSeasonsLabel\"\n    app:layout_constraintEnd_toStartOf=\"@id/showDetailsQuickProgress\"\n    app:layout_constraintHorizontal_bias=\"1\"\n    app:layout_constraintStart_toEndOf=\"@id/showDetailsSeasonsLabel\"\n    app:layout_constraintTop_toTopOf=\"@id/showDetailsSeasonsLabel\"\n    app:layout_constraintWidth_default=\"wrap\"\n    tools:text=\"~14h 12min of watching\"\n    tools:visibility=\"visible\"\n    />\n\n  <ImageView\n    android:id=\"@+id/showDetailsQuickProgress\"\n    android:layout_width=\"28dp\"\n    android:layout_height=\"28dp\"\n    android:layout_gravity=\"center_vertical|end\"\n    android:layout_marginEnd=\"@dimen/spaceBig\"\n    android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n    android:src=\"@drawable/ic_quick_setup\"\n    android:translationY=\"1dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"@id/showDetailsSeasonsLabel\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHorizontal_bias=\"1\"\n    app:layout_constraintStart_toEndOf=\"@id/showDetailsSeasonsLabel\"\n    app:layout_constraintTop_toTopOf=\"@id/showDetailsSeasonsLabel\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    tools:visibility=\"visible\"\n    />\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/showDetailsSeasonsRecycler\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:nestedScrollingEnabled=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingStart=\"18dp\"\n    android:paddingTop=\"@dimen/spaceTiny\"\n    android:paddingEnd=\"@dimen/spaceBig\"\n    android:visibility=\"invisible\"\n    app:layout_constrainedHeight=\"true\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintHeight_min=\"48dp\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/showDetailsSeasonsLabel\"\n    tools:visibility=\"visible\"\n    />\n\n  <ProgressBar\n    android:id=\"@+id/showDetailsSeasonsProgress\"\n    style=\"@style/ProgressBar.Dark\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"30dp\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    />\n\n  <TextView\n    android:id=\"@+id/showDetailsSeasonsEmptyView\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginStart=\"@dimen/spaceNormal\"\n    android:layout_marginTop=\"@dimen/spaceNormal\"\n    android:layout_marginEnd=\"@dimen/spaceNormal\"\n    android:layout_marginBottom=\"@dimen/spaceBig\"\n    android:text=\"@string/textSeasonsEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"15sp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    tools:visibility=\"visible\"\n    />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "ui-show/src/main/res/layout-sw600dp/view_season.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/seasonViewRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/seasonViewHeight\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:paddingTop=\"@dimen/spaceMedium\"\n    android:paddingBottom=\"@dimen/spaceMedium\"\n    >\n\n    <com.google.android.material.checkbox.MaterialCheckBox\n      android:id=\"@+id/seasonViewCheckbox\"\n      style=\"@style/ShowlyCheckbox\"\n      android:layout_width=\"30dp\"\n      android:layout_height=\"0dp\"\n      android:gravity=\"center\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <TextView\n      android:id=\"@+id/seasonViewTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:gravity=\"center_vertical\"\n      android:minWidth=\"80dp\"\n      android:paddingBottom=\"@dimen/spaceMicro\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"16sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/seasonViewCheckbox\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:text=\"Season 00\"\n      />\n\n    <ImageView\n      android:id=\"@+id/seasonViewArrow\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/seasonViewProgressText\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_arrow_right\"\n      />\n\n    <TextView\n      android:id=\"@+id/seasonViewProgressText\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"end\"\n      android:paddingBottom=\"@dimen/spaceMicro\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"16sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/seasonViewArrow\"\n      app:layout_constraintStart_toEndOf=\"@id/seasonViewProgress\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:text=\"20/20 (100%)\"\n      />\n\n    <com.google.android.material.progressindicator.LinearProgressIndicator\n      android:id=\"@+id/seasonViewProgress\"\n      style=\"@style/Widget.MaterialComponents.LinearProgressIndicator\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/seasonViewProgressMargin\"\n      android:layout_marginEnd=\"20dp\"\n      app:indicatorColor=\"?attr/colorAccent\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/seasonViewProgressText\"\n      app:layout_constraintStart_toEndOf=\"@id/seasonViewTitle\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:trackColor=\"?attr/colorProgressTrack\"\n      app:trackCornerRadius=\"4dp\"\n      app:trackThickness=\"4dp\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n</merge>\n"
  },
  {
    "path": "ui-show/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"addToButtonTextSize\">14sp</dimen>\n  <dimen name=\"dividerHorizontalList\">8dp</dimen>\n  <dimen name=\"showDetailsExtraButtonTextSize\">12sp</dimen>\n\n  <dimen name=\"actorTileCorner\">4dp</dimen>\n  <dimen name=\"actorTilePlaceholder\">20dp</dimen>\n  <dimen name=\"actorTileImageHeight\">120dp</dimen>\n  <dimen name=\"actorTileImageWidth\">80dp</dimen>\n  <dimen name=\"actorTileTextSize\">11sp</dimen>\n\n  <dimen name=\"relatedShowHeight\">120dp</dimen>\n  <dimen name=\"relatedShowWidth\">80dp</dimen>\n  <dimen name=\"relatedShowTextSize\">12sp</dimen>\n\n  <dimen name=\"seasonViewHeight\">44dp</dimen>\n  <dimen name=\"seasonViewProgressMargin\">0dp</dimen>\n  <dimen name=\"episodeViewHeight\">42dp</dimen>\n  <dimen name=\"episodeNextViewHeight\">74dp</dimen>\n\n  <dimen name=\"showDetailsMarginHorizontal\">16dp</dimen>\n  <dimen name=\"showDetailsBackArrowMargin\">0dp</dimen>\n</resources>"
  },
  {
    "path": "ui-show/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textNextEpisode\">Next Episode:</string>\n  <string name=\"textInMyShows\">Added to my shows</string>\n  <string name=\"textInWatchlist\">Added to watchlist</string>\n  <string name=\"textInHidden\">Hidden</string>\n  <string name=\"textShowExtraInfo\">%s %s %s | %s %s | %s</string>\n\n  <string name=\"textShowQuickProgressTitle\">Quick Progress</string>\n  <string name=\"textShowQuickProgressSubTitle\">Select last seen episode to quickly bring your progress up to date.</string>\n  <string name=\"textShowQuickProgressDone\">Your progress has been updated.</string>\n\n  <string name=\"textEpisodeDate\">Episode %1$d | %2$s</string>\n  <string name=\"textEpisodeTitle\">S.%02d E.%02d - \\'%s\\'</string>\n  <string name=\"textTraktSyncRemovedFromTrakt\">Trakt.tv synced successfully.</string>\n  <string name=\"textActorsEmpty\">Actors are not available</string>\n  <string name=\"textSeasons\">Seasons:</string>\n  <string name=\"textSeasonsEmpty\">Seasons not available.</string>\n  <string name=\"textTrailer\">Trailer</string>\n  <string name=\"textYouMayAlsoLike\">You may also like</string>\n  <string name=\"textRuntimeLeftHours\">~%sh %sm remaining</string>\n  <string name=\"textRuntimeLeftMinutes\">~%sm remaining</string>\n\n  <string name=\"textShowManageLists\">Manage Lists</string>\n  <string name=\"textShowManageListsCount\">Manage Lists (%d)</string>\n</resources>"
  },
  {
    "path": "ui-show/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <!-- Show Details -->\n\n  <style name=\"ShowDetails\" />\n\n  <style name=\"ShowDetails.ExtraButton\" parent=\"ShowDetails\">\n    <item name=\"android:layout_width\">wrap_content</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:drawablePadding\">@dimen/spaceTiny</item>\n    <item name=\"android:gravity\">center</item>\n    <item name=\"android:textAllCaps\">true</item>\n    <item name=\"android:maxLines\">1</item>\n    <item name=\"drawableTint\">?android:attr/textColorPrimary</item>\n    <item name=\"android:textColor\">?android:attr/textColorPrimary</item>\n    <item name=\"android:textSize\">@dimen/showDetailsExtraButtonTextSize</item>\n    <item name=\"android:background\">?android:attr/selectableItemBackgroundBorderless</item>\n    <item name=\"layout_constraintHorizontal_chainStyle\">spread_inside</item>\n  </style>\n\n</resources>"
  },
  {
    "path": "ui-show/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textNextEpisode\">الحلقة التالية:</string>\n  <string name=\"textInMyShows\">تمت الإضافة إلى مسلسلاتي</string>\n  <string name=\"textInWatchlist\">تمت الإضافة إلى قائمة المشاهدة</string>\n  <string name=\"textInHidden\">المخفية</string>\n  <string name=\"textShowExtraInfo\">%3$s %2$s %1$s | %6$s | %4$s %5$s</string>\n  <string name=\"textShowQuickProgressTitle\">مستوى التقدم السريع</string>\n  <string name=\"textShowQuickProgressSubTitle\">إختر آخر حلقة قمت بمتابعتها لتحديث قائمة مستوى التقدم بسرعة.</string>\n  <string name=\"textShowQuickProgressDone\">تم تحديث مستوى تقدمك.</string>\n  <string name=\"textEpisodeDate\">الحلقة %1$d | %2$s</string>\n  <string name=\"textEpisodeTitle\">الموسم %02d الحلقة %02d - %s</string>\n  <string name=\"textTraktSyncRemovedFromTrakt\">إكتملت مزامنة Trakt.tv بنجاح.</string>\n  <string name=\"textActorsEmpty\">قائمة الممثلين غير متاحة</string>\n  <string name=\"textSeasons\">المواسم:</string>\n  <string name=\"textSeasonsEmpty\">المواسم غير متوفرة.</string>\n  <string name=\"textTrailer\">التريلر</string>\n  <string name=\"textYouMayAlsoLike\">قد تعجبك أيضاً</string>\n  <string name=\"textRuntimeLeftHours\">المتبقي ~%sh و %sm</string>\n  <string name=\"textRuntimeLeftMinutes\">المتبقي ~%sm</string>\n  <string name=\"textShowManageLists\">إدارة القوائم</string>\n  <string name=\"textShowManageListsCount\">إدارة القوائم (%d)</string>\n</resources>\n"
  },
  {
    "path": "ui-show/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textNextEpisode\">Nächste Folge:</string>\n  <string name=\"textInMyShows\">In Meine Serien</string>\n  <string name=\"textInWatchlist\">Im Watchlist</string>\n  <string name=\"textInHidden\">Versteckt</string>\n  <string name=\"textShowExtraInfo\">%s %s %s | %s %s | %s</string>\n  <string name=\"textShowQuickProgressTitle\">Schneller Fortschritt</string>\n  <string name=\"textShowQuickProgressSubTitle\">Wähle deine zuletzt gesehene Episode aus, um deinen Fortschritt schnell auf den neuesten Stand zu bringen.</string>\n  <string name=\"textShowQuickProgressDone\">Dein Fortschritt wurde aktualisiert.</string>\n  <string name=\"textEpisodeDate\">Folge %1$d | %2$s</string>\n  <string name=\"textEpisodeTitle\">S.%02d E.%02d - \\'%s\\'</string>\n  <string name=\"textTraktSyncRemovedFromTrakt\">Trakt.tv Synchronisation erfolgreich.</string>\n  <string name=\"textActorsEmpty\">Schauspieler sind nicht verfügbar.</string>\n  <string name=\"textSeasons\">Staffel:</string>\n  <string name=\"textSeasonsEmpty\">Staffel nicht verfügbar.</string>\n  <string name=\"textTrailer\">Trailer</string>\n  <string name=\"textYouMayAlsoLike\">Vielleicht mögen Sie auch</string>\n  <string name=\"textRuntimeLeftHours\">Noch ~%sh %sm</string>\n  <string name=\"textRuntimeLeftMinutes\">Noch ~%sm</string>\n  <string name=\"textShowManageLists\">Listen verwalten</string>\n  <string name=\"textShowManageListsCount\">Listen verwalten (%d)</string>\n</resources>\n"
  },
  {
    "path": "ui-show/src/main/res/values-es/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"seasonViewProgressMargin\">12dp</dimen>\n</resources>"
  },
  {
    "path": "ui-show/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textNextEpisode\">Siguiente Episodio:</string>\n  <string name=\"textInMyShows\">Añadido a mis series</string>\n  <string name=\"textInWatchlist\">Añadido a pendientes</string>\n  <string name=\"textInHidden\">Ocultos</string>\n  <string name=\"textShowExtraInfo\">%s %s %s | %s %s | %s</string>\n  <string name=\"textShowQuickProgressTitle\">Progreso Rápido</string>\n  <string name=\"textShowQuickProgressSubTitle\">Selecciona el último episodio visto para actualizar rápidamente tu progreso.</string>\n  <string name=\"textShowQuickProgressDone\">Tu progreso se ha actualizado.</string>\n  <string name=\"textEpisodeDate\">Episodio %1$d | %2$s</string>\n  <string name=\"textEpisodeTitle\">T.%02d E.%02d - \\'%s\\'</string>\n  <string name=\"textTraktSyncRemovedFromTrakt\">Trakt.tv se sincronizó correctamente.</string>\n  <string name=\"textActorsEmpty\">El elenco no está disponible</string>\n  <string name=\"textSeasons\">Temporadas:</string>\n  <string name=\"textSeasonsEmpty\">Temporadas no disponibles.</string>\n  <string name=\"textTrailer\">Tráiler</string>\n  <string name=\"textYouMayAlsoLike\">También podría gustarte</string>\n  <string name=\"textRuntimeLeftHours\">~%sh %sm restantes</string>\n  <string name=\"textRuntimeLeftMinutes\">~%sm restantes</string>\n  <string name=\"textShowManageLists\">Gestionar Listas</string>\n  <string name=\"textShowManageListsCount\">Gestionar Listas (%d)</string>\n</resources>\n"
  },
  {
    "path": "ui-show/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textNextEpisode\">Seuraava jakso:</string>\n  <string name=\"textInMyShows\">Lisätty omiin sarjoihin</string>\n  <string name=\"textInWatchlist\">Lisätty katselulistalle</string>\n  <string name=\"textInHidden\">Piilotetut</string>\n  <string name=\"textShowExtraInfo\">%s %s %s | %s %s | %s</string>\n  <string name=\"textShowQuickProgressTitle\">Pikakatselutila</string>\n  <string name=\"textShowQuickProgressSubTitle\">Valitse viimeksi katsottu jakso saattaaksesi katselutilasi nopeasti ajan tasalle.</string>\n  <string name=\"textShowQuickProgressDone\">Katselutilasi päivitettiin.</string>\n  <string name=\"textEpisodeDate\">Jakso %1$d | %2$s</string>\n  <string name=\"textEpisodeTitle\">K.%02d J.%02d - \\'%s\\'</string>\n  <string name=\"textTraktSyncRemovedFromTrakt\">Trakt.tv synkronoitiin.</string>\n  <string name=\"textActorsEmpty\">Näyttelijät eivät ole saatavilla</string>\n  <string name=\"textSeasons\">Kaudet:</string>\n  <string name=\"textSeasonsEmpty\">Tuotantokausia ei ole.</string>\n  <string name=\"textTrailer\">Traileri</string>\n  <string name=\"textYouMayAlsoLike\">Saattaisit pitää myös näistä</string>\n  <string name=\"textRuntimeLeftHours\">~%s h %s m jäljellä</string>\n  <string name=\"textRuntimeLeftMinutes\">~%s m jäljellä</string>\n  <string name=\"textShowManageLists\">Hallitse listoja</string>\n  <string name=\"textShowManageListsCount\">Hallitse listoja (%d)</string>\n</resources>\n"
  },
  {
    "path": "ui-show/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textNextEpisode\">Prochain épisode :</string>\n  <string name=\"textInMyShows\">Ajouté à mes séries</string>\n  <string name=\"textInWatchlist\">Ajouté à la Watchlist</string>\n  <string name=\"textInHidden\">Masqué</string>\n  <string name=\"textShowExtraInfo\">%s %s %s | %s %s | %s</string>\n  <string name=\"textShowQuickProgressTitle\">Progression Rapide</string>\n  <string name=\"textShowQuickProgressSubTitle\">Sélectionnez le dernier épisode vu pour mettre à jour rapidement votre progression.</string>\n  <string name=\"textShowQuickProgressDone\">Votre progression a été mise à jour.</string>\n  <string name=\"textEpisodeDate\">Épisode %1$d | %2$s</string>\n  <string name=\"textEpisodeTitle\">S.%02d E.%02d - \\'%s\\'</string>\n  <string name=\"textTraktSyncRemovedFromTrakt\">Succès de la synchronisation avec Track.tv.</string>\n  <string name=\"textActorsEmpty\">Acteurs non disponibles</string>\n  <string name=\"textSeasons\">Saisons :</string>\n  <string name=\"textSeasonsEmpty\">Saisons non disponibles.</string>\n  <string name=\"textTrailer\">Bande\\nAnnonce</string>\n  <string name=\"textYouMayAlsoLike\">Vous pourriez aussi aimer</string>\n  <string name=\"textRuntimeLeftHours\">~%sh %sm restantes</string>\n  <string name=\"textRuntimeLeftMinutes\">~%sm restantes</string>\n  <string name=\"textShowManageLists\">Gérer les listes</string>\n  <string name=\"textShowManageListsCount\">Gérer les listes (%d)</string>\n</resources>\n"
  },
  {
    "path": "ui-show/src/main/res/values-fr/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <style name=\"ShowDetails.ExtraButton\" parent=\"ShowDetails\">\n    <item name=\"android:layout_width\">wrap_content</item>\n    <item name=\"android:layout_height\">wrap_content</item>\n    <item name=\"android:drawablePadding\">@dimen/spaceTiny</item>\n    <item name=\"android:gravity\">center</item>\n    <item name=\"android:textAllCaps\">true</item>\n    <item name=\"android:maxLines\">2</item>\n    <item name=\"android:textColor\">@color/colorWhite</item>\n    <item name=\"android:textSize\">12sp</item>\n    <item name=\"android:background\">?android:attr/selectableItemBackgroundBorderless</item>\n    <item name=\"layout_constraintHorizontal_chainStyle\">spread_inside</item>\n  </style>\n\n</resources>"
  },
  {
    "path": "ui-show/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textNextEpisode\">Prossimo episodio:</string>\n  <string name=\"textInMyShows\">Aggiunto ai miei show</string>\n  <string name=\"textInWatchlist\">Aggiunta alla lista da vedere</string>\n  <string name=\"textInHidden\">Nascosti</string>\n  <string name=\"textShowExtraInfo\">%s %s %s | %s %s | %s</string>\n  <string name=\"textShowQuickProgressTitle\">Progresso rapido</string>\n  <string name=\"textShowQuickProgressSubTitle\">Seleziona l\\'ultimo episodio che hai visto per aggiornare velocemente i tuoi progressi.</string>\n  <string name=\"textShowQuickProgressDone\">I tuoi progressi sono stati aggiornati.</string>\n  <string name=\"textEpisodeDate\">Episodio %1$d | %2$s</string>\n  <string name=\"textEpisodeTitle\">S.%02d E.%02d - \\'%s\\'</string>\n  <string name=\"textTraktSyncRemovedFromTrakt\">Trakt.tv sincronizzato correttamente.</string>\n  <string name=\"textActorsEmpty\">Attori non disponibili</string>\n  <string name=\"textSeasons\">Stagioni:</string>\n  <string name=\"textSeasonsEmpty\">Stagioni non disponibili.</string>\n  <string name=\"textTrailer\">Trailer</string>\n  <string name=\"textYouMayAlsoLike\">Potrebbe piacerti anche</string>\n  <string name=\"textRuntimeLeftHours\">~%sh %sm rimasti</string>\n  <string name=\"textRuntimeLeftMinutes\">~%sm rimasti</string>\n  <string name=\"textShowManageLists\">Gestisci liste</string>\n  <string name=\"textShowManageListsCount\">Gestisci liste (%d)</string>\n</resources>\n"
  },
  {
    "path": "ui-show/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textNextEpisode\">Następny Odcinek:</string>\n  <string name=\"textInMyShows\">W moich serialach</string>\n  <string name=\"textInWatchlist\">Na później</string>\n  <string name=\"textInHidden\">Ukryte</string>\n  <string name=\"textShowExtraInfo\">%s %s %s | %s %s | %s</string>\n  <string name=\"textShowQuickProgressTitle\">Szybki Postęp</string>\n  <string name=\"textShowQuickProgressSubTitle\">Wybierz ostatni obejrzany odcinek aby zaktualizować swój postęp.</string>\n  <string name=\"textShowQuickProgressDone\">Postęp został zaktualizowany.</string>\n  <string name=\"textEpisodeDate\">Odcinek %1$d | %2$s</string>\n  <string name=\"textEpisodeTitle\">S.%02d E.%02d - \\'%s\\'</string>\n  <string name=\"textTraktSyncRemovedFromTrakt\">Trakt.tv zostało zaktualizowane.</string>\n  <string name=\"textActorsEmpty\">Obsada niedostępna</string>\n  <string name=\"textSeasons\">Sezony:</string>\n  <string name=\"textSeasonsEmpty\">Sezony niedostępne</string>\n  <string name=\"textTrailer\">Trailer</string>\n  <string name=\"textYouMayAlsoLike\">Zobacz również</string>\n  <string name=\"textRuntimeLeftHours\">~%sg %sm do obejrzenia</string>\n  <string name=\"textRuntimeLeftMinutes\">~%sm do obejrzenia</string>\n  <string name=\"textShowManageLists\">Zarządzaj listami</string>\n  <string name=\"textShowManageListsCount\">Zarządzaj listami (%d)</string>\n</resources>\n"
  },
  {
    "path": "ui-show/src/main/res/values-pt/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"seasonViewProgressMargin\">12dp</dimen>\n</resources>"
  },
  {
    "path": "ui-show/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textNextEpisode\">Próximo Episódio:</string>\n  <string name=\"textInMyShows\">Adicionado às minhas séries</string>\n  <string name=\"textInWatchlist\">Adicionar à Interesses</string>\n  <string name=\"textInHidden\">Oculto</string>\n  <string name=\"textShowExtraInfo\">%s %s %s | %s %s | %s</string>\n  <string name=\"textShowQuickProgressTitle\">Progresso rápido</string>\n  <string name=\"textShowQuickProgressSubTitle\">Selecione o último episódio visto para atualizar rapidamente seu progresso.</string>\n  <string name=\"textShowQuickProgressDone\">Seu progresso foi atualizado.</string>\n  <string name=\"textEpisodeDate\">Episódio %1$d | %2$s</string>\n  <string name=\"textEpisodeTitle\">S.%02d E.%02d - \\'%s\\'</string>\n  <string name=\"textTraktSyncRemovedFromTrakt\">Trakt.tv sincronizado com sucesso.</string>\n  <string name=\"textActorsEmpty\">Atores não estão disponíveis</string>\n  <string name=\"textSeasons\">Temporadas:</string>\n  <string name=\"textSeasonsEmpty\">Temporadas não disponíveis.</string>\n  <string name=\"textTrailer\">Trailer</string>\n  <string name=\"textYouMayAlsoLike\">Você também pode gostar</string>\n  <string name=\"textRuntimeLeftHours\">~%sh %sm restantes</string>\n  <string name=\"textRuntimeLeftMinutes\">%sm restando</string>\n  <string name=\"textShowManageLists\">Gerenciar Listas</string>\n  <string name=\"textShowManageListsCount\">Gerenciar Listas (%d)</string>\n</resources>\n"
  },
  {
    "path": "ui-show/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textNextEpisode\">Следующий эпизод:</string>\n  <string name=\"textInMyShows\">Добавлено в мои сериалы</string>\n  <string name=\"textInWatchlist\">Добавлено в Буду смотреть</string>\n  <string name=\"textInHidden\">Скрытое</string>\n  <string name=\"textShowExtraInfo\">%s %s %s | %s %s | %s</string>\n  <string name=\"textShowQuickProgressTitle\">Быстрый прогресс</string>\n  <string name=\"textShowQuickProgressSubTitle\">Выберите последний просмотренный эпизод, чтобы быстро отметить эпизоды.</string>\n  <string name=\"textShowQuickProgressDone\">Ваш прогресс был обновлен.</string>\n  <string name=\"textEpisodeDate\">Эпизод %1$d | %2$s</string>\n  <string name=\"textEpisodeTitle\">S.%02d E.%02d - \\'%s\\'</string>\n  <string name=\"textTraktSyncRemovedFromTrakt\">Trakt.tv успешно синхронизирован.</string>\n  <string name=\"textActorsEmpty\">Актеры недоступны</string>\n  <string name=\"textSeasons\">Сезоны:</string>\n  <string name=\"textSeasonsEmpty\">Сезоны недоступны.</string>\n  <string name=\"textTrailer\">Трейлер</string>\n  <string name=\"textYouMayAlsoLike\">Вам также может понравиться</string>\n  <string name=\"textRuntimeLeftHours\">~%sч %sм осталось</string>\n  <string name=\"textRuntimeLeftMinutes\">~%sм осталось</string>\n  <string name=\"textShowManageLists\">Управление списками</string>\n  <string name=\"textShowManageListsCount\">Управление списками (%d)</string>\n</resources>\n"
  },
  {
    "path": "ui-show/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"addToButtonTextSize\">15sp</dimen>\n  <dimen name=\"showDetailsExtraButtonTextSize\">13sp</dimen>\n  <dimen name=\"dividerHorizontalList\">12dp</dimen>\n\n  <dimen name=\"actorTileCorner\">6dp</dimen>\n  <dimen name=\"actorTileImageHeight\">140dp</dimen>\n  <dimen name=\"actorTileImageWidth\">93dp</dimen>\n  <dimen name=\"actorTileTextSize\">12sp</dimen>\n\n  <dimen name=\"relatedShowHeight\">140dp</dimen>\n  <dimen name=\"relatedShowWidth\">93dp</dimen>\n  <dimen name=\"relatedShowTextSize\">12sp</dimen>\n\n  <dimen name=\"seasonViewProgressMargin\">8dp</dimen>\n  <dimen name=\"episodeNextViewHeight\">88dp</dimen>\n\n  <dimen name=\"showDetailsMarginHorizontal\">24dp</dimen>\n  <dimen name=\"showDetailsBackArrowMargin\">8dp</dimen>\n</resources>"
  },
  {
    "path": "ui-show/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textNextEpisode\">Sonraki Bölüm:</string>\n  <string name=\"textInMyShows\">Dizilerime eklendi</string>\n  <string name=\"textInWatchlist\">İstek listesine eklendi</string>\n  <string name=\"textInHidden\">Gizlenmiş</string>\n  <string name=\"textShowExtraInfo\">%s %s %s | %s %s | %s</string>\n  <string name=\"textShowQuickProgressTitle\">Hızlı İlerleme</string>\n  <string name=\"textShowQuickProgressSubTitle\">İlerlemenizi hızlı bir şekilde güncellemek için en son izlediğiniz bölümü seçin.</string>\n  <string name=\"textShowQuickProgressDone\">İlerlemeniz güncellendi.</string>\n  <string name=\"textEpisodeDate\">Bölüm %1$d | %2$s</string>\n  <string name=\"textEpisodeTitle\">S.%02d B.%02d - \\'%s\\'</string>\n  <string name=\"textTraktSyncRemovedFromTrakt\">Trakt.tv eşitlemesi başarılı.</string>\n  <string name=\"textActorsEmpty\">Oyuncular mevcut değil</string>\n  <string name=\"textSeasons\">Sezonlar:</string>\n  <string name=\"textSeasonsEmpty\">Sezonlar mevcut değil.</string>\n  <string name=\"textTrailer\">Fragman</string>\n  <string name=\"textYouMayAlsoLike\">Bunlar da hoşunuza gidebilir</string>\n  <string name=\"textRuntimeLeftHours\">~%s sa %s dk kaldı</string>\n  <string name=\"textRuntimeLeftMinutes\">~%s dk kaldı</string>\n  <string name=\"textShowManageLists\">Listeleri Yönet</string>\n  <string name=\"textShowManageListsCount\">Listeleri Yönet (%d)</string>\n</resources>\n"
  },
  {
    "path": "ui-show/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textNextEpisode\">Наступна серія:</string>\n  <string name=\"textInMyShows\">Додано до моїх серіалів</string>\n  <string name=\"textInWatchlist\">Додано в \\'На потім\\'</string>\n  <string name=\"textInHidden\">Приховане</string>\n  <string name=\"textShowExtraInfo\">%s %s %s | %s %s | %s</string>\n  <string name=\"textShowQuickProgressTitle\">Швидкий прогрес</string>\n  <string name=\"textShowQuickProgressSubTitle\">Виберіть останню переглянуту серію, щоб швидко відмітити попередні.</string>\n  <string name=\"textShowQuickProgressDone\">Ваш прогрес було оновлено.</string>\n  <string name=\"textEpisodeDate\">Серія %1$d | %2$s</string>\n  <string name=\"textEpisodeTitle\">S.%02d E.%02d - \\'%s\\'</string>\n  <string name=\"textTraktSyncRemovedFromTrakt\">Trakt.tv успішно синхронізовано.</string>\n  <string name=\"textActorsEmpty\">Актори недоступні</string>\n  <string name=\"textSeasons\">Сезони:</string>\n  <string name=\"textSeasonsEmpty\">Сезони недоступні.</string>\n  <string name=\"textTrailer\">Трейлер</string>\n  <string name=\"textYouMayAlsoLike\">Вам також може сподобатись</string>\n  <string name=\"textRuntimeLeftHours\">~%sг %sхв залишилось</string>\n  <string name=\"textRuntimeLeftMinutes\">~%sхв залишилось</string>\n  <string name=\"textShowManageLists\">Керування списками</string>\n  <string name=\"textShowManageListsCount\">Керування списками (%d)</string>\n</resources>\n"
  },
  {
    "path": "ui-show/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textNextEpisode\">Tập tiếp theo:</string>\n  <string name=\"textInMyShows\">Đã thêm vào chương trình của tôi</string>\n  <string name=\"textInWatchlist\">Đã thêm vào danh sách theo dõi</string>\n  <string name=\"textInHidden\">Ẩn</string>\n  <string name=\"textShowExtraInfo\">|%s %s %s | %s %s | %s</string>\n\n  <string name=\"textShowQuickProgressTitle\">Tiến độ nhanh</string>\n  <string name=\"textShowQuickProgressSubTitle\">Chọn tập đã xem lần cuối để nhanh chóng cập nhật tiến độ của bạn.</string>\n  <string name=\"textShowQuickProgressDone\">Tiến độ của bạn đã được cập nhật.</string>\n\n  <string name=\"textEpisodeDate\">Tập %1$d | %2$s</string>\n  <string name=\"textEpisodeTitle\">M.%02d T.%02d - \\'%s\\'</string>\n  <string name=\"textTraktSyncRemovedFromTrakt\">Trakt.tv đã được đồng bộ hóa thành công.</string>\n  <string name=\"textActorsEmpty\">Diễn viên không có sẵn</string>\n  <string name=\"textSeasons\">Các mùa:</string>\n  <string name=\"textSeasonsEmpty\">Các mùa không có sẵn.</string>\n  <string name=\"textTrailer\">Đoạn giới thiệu</string>\n  <string name=\"textYouMayAlsoLike\">Bạn cũng có thể thích</string>\n  <string name=\"textRuntimeLeftHours\">~%sh %sm còn lại</string>\n  <string name=\"textRuntimeLeftMinutes\">~%sm còn lại</string>\n\n  <string name=\"textShowManageLists\">Quản lý các danh sách</string>\n  <string name=\"textShowManageListsCount\">Quản lý các danh sách (%d)</string>\n</resources>\n"
  },
  {
    "path": "ui-show/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textNextEpisode\">下一集：</string>\n  <string name=\"textInMyShows\">已加入我的剧集</string>\n  <string name=\"textInWatchlist\">已加入待看列表</string>\n  <string name=\"textInHidden\">已隐藏</string>\n  <string name=\"textShowExtraInfo\">%s %s %s | %s %s | %s</string>\n  <string name=\"textShowQuickProgressTitle\">快速设置进度</string>\n  <string name=\"textShowQuickProgressSubTitle\">选择最后观看的单集，来快速更新您的进度至此。</string>\n  <string name=\"textShowQuickProgressDone\">您的进度已经更新。</string>\n  <string name=\"textEpisodeDate\">集数 %1$d | %2$s</string>\n  <string name=\"textEpisodeTitle\">%02d 季 %02d 集 | \\'%s\\'</string>\n  <string name=\"textTraktSyncRemovedFromTrakt\">Trakt.tv 同步成功。</string>\n  <string name=\"textActorsEmpty\">暂无演职人员信息</string>\n  <string name=\"textSeasons\">季：</string>\n  <string name=\"textSeasonsEmpty\">暂无剧季信息</string>\n  <string name=\"textTrailer\">预告片</string>\n  <string name=\"textYouMayAlsoLike\">您还可能喜欢</string>\n  <string name=\"textRuntimeLeftHours\">剩余约 %s 小时 %s 分钟</string>\n  <string name=\"textRuntimeLeftMinutes\">剩余约 %s 分钟</string>\n  <string name=\"textShowManageLists\">管理列表</string>\n  <string name=\"textShowManageListsCount\">管理列表 (%d)</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-statistics/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_statistics'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-local')\n  implementation project(':ui-base')\n  implementation project(':repository')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  testImplementation project(':common-test')\n  testImplementation libs.bundles.testing\n  androidTestImplementation libs.android.test.runner\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-statistics/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/StatisticsFragment.kt",
    "content": "package com.michaldrabik.ui_statistics\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_SHOW_ID\nimport com.michaldrabik.ui_statistics.databinding.FragmentStatisticsBinding\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.launch\n\n@AndroidEntryPoint\nclass StatisticsFragment : BaseFragment<StatisticsViewModel>(R.layout.fragment_statistics) {\n\n  override val viewModel by viewModels<StatisticsViewModel>()\n\n  private val binding by viewBinding(FragmentStatisticsBinding::bind)\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupStatusBar()\n\n    viewLifecycleOwner.lifecycleScope.launch {\n      repeatOnLifecycle(Lifecycle.State.STARTED) {\n        with(viewModel) {\n          launch { uiState.collect { render(it) } }\n          if (!isInitialized) {\n            loadData()\n            isInitialized = true\n          }\n          loadRatings()\n        }\n      }\n    }\n  }\n\n  private fun setupView() {\n    with(binding) {\n      statisticsToolbar.setNavigationOnClickListener { activity?.onBackPressed() }\n      statisticsMostWatchedShows.run {\n        onLoadMoreClickListener = { addLimit -> viewModel.loadData(addLimit) }\n        onShowClickListener = {\n          openShowDetails(it.traktId)\n        }\n      }\n      statisticsRatings.onShowClickListener = {\n        openShowDetails(it.show.traktId)\n      }\n    }\n  }\n\n  private fun setupStatusBar() {\n    binding.statisticsRoot.doOnApplyWindowInsets { view, insets, padding, _ ->\n      val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top\n      view.updatePadding(top = padding.top + inset)\n    }\n  }\n\n  private fun render(uiState: StatisticsUiState) {\n    uiState.run {\n      with(binding) {\n        statisticsMostWatchedShows.bind(mostWatchedShows ?: emptyList(), mostWatchedTotalCount ?: 0)\n        statisticsTotalTimeSpent.bind(totalTimeSpentMinutes ?: 0)\n        statisticsTotalEpisodes.bind(totalWatchedEpisodes ?: 0, totalWatchedEpisodesShows ?: 0)\n        statisticsTopGenres.bind(topGenres ?: emptyList())\n        statisticsRatings.bind(ratings ?: emptyList())\n\n        ratings?.let { statisticsRatings.visibleIf(it.isNotEmpty()) }\n        mostWatchedShows?.let {\n          statisticsContent.fadeIf(it.isNotEmpty())\n          statisticsEmptyView.rootLayout.fadeIf(it.isEmpty())\n        }\n      }\n    }\n  }\n\n  private fun openShowDetails(traktId: Long) {\n    val bundle = bundleOf(ARG_SHOW_ID to traktId)\n    navigateTo(R.id.actionStatisticsFragmentToShowDetailsFragment, bundle)\n  }\n}\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/StatisticsUiState.kt",
    "content": "package com.michaldrabik.ui_statistics\n\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_statistics.views.mostWatched.StatisticsMostWatchedItem\nimport com.michaldrabik.ui_statistics.views.ratings.recycler.StatisticsRatingItem\n\ndata class StatisticsUiState(\n  val mostWatchedShows: List<StatisticsMostWatchedItem>? = null,\n  val mostWatchedTotalCount: Int? = null,\n  val totalTimeSpentMinutes: Int? = null,\n  val totalWatchedEpisodes: Int? = null,\n  val totalWatchedEpisodesShows: Int? = null,\n  val topGenres: List<Genre>? = null,\n  val ratings: List<StatisticsRatingItem>? = null,\n)\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/StatisticsViewModel.kt",
    "content": "package com.michaldrabik.ui_statistics\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.data_local.database.model.Episode\nimport com.michaldrabik.data_local.database.model.Season\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.combine\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType.POSTER\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_statistics.cases.StatisticsLoadRatingsCase\nimport com.michaldrabik.ui_statistics.views.mostWatched.StatisticsMostWatchedItem\nimport com.michaldrabik.ui_statistics.views.ratings.recycler.StatisticsRatingItem\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass StatisticsViewModel @Inject constructor(\n  private val ratingsCase: StatisticsLoadRatingsCase,\n  private val showsRepository: ShowsRepository,\n  private val translationsRepository: TranslationsRepository,\n  private val imagesProvider: ShowImagesProvider,\n  private val localSource: LocalDataSource,\n  private val mappers: Mappers,\n) : ViewModel() {\n\n  private val mostWatchedShowsState = MutableStateFlow<List<StatisticsMostWatchedItem>?>(null)\n  private val mostWatchedTotalCountState = MutableStateFlow<Int?>(null)\n  private val totalTimeSpentMinutesState = MutableStateFlow<Int?>(null)\n  private val totalWatchedEpisodesState = MutableStateFlow<Int?>(null)\n  private val totalWatchedEpisodesShowsState = MutableStateFlow<Int?>(null)\n  private val topGenresState = MutableStateFlow<List<Genre>?>(null)\n  private val ratingsState = MutableStateFlow<List<StatisticsRatingItem>?>(null)\n\n  private var takeLimit = 5\n\n  fun loadData(limit: Int = 0, initialDelay: Long = 150L) {\n    takeLimit += limit\n    viewModelScope.launch {\n      val language = translationsRepository.getLanguage()\n\n      val myShows = showsRepository.myShows.loadAll()\n      val watchlistShows = showsRepository.watchlistShows.loadAll() // Add shows from watchlist\n      val hiddenShows = showsRepository.hiddenShows.loadAll()\n\n      val shows = (myShows + watchlistShows + hiddenShows).distinctBy { it.traktId }\n      val showsIds = shows.map { it.traktId }\n\n      val episodes = batchEpisodes(showsIds)\n      val seasons = batchSeasons(showsIds)\n\n      val genres = extractTopGenres(shows)\n      val mostWatchedShows = shows\n        .map { show ->\n          val translation = loadTranslation(language, show)\n          StatisticsMostWatchedItem(\n            show = shows.first { it.traktId == show.traktId },\n            seasonsCount = seasons.filter { it.idShowTrakt == show.traktId }.count().toLong(),\n            episodes = episodes\n              .filter { it.idShowTrakt == show.traktId }\n              .map { mappers.episode.fromDatabase(it) },\n            image = Image.createUnknown(POSTER),\n            translation = translation\n          )\n        }\n        .sortedByDescending { item -> item.episodes.sumOf { it.runtime } }\n        .take(takeLimit)\n        .map {\n          it.copy(image = imagesProvider.findCachedImage(it.show, POSTER))\n        }\n\n      delay(initialDelay) // Let transition finish peacefully.\n\n      mostWatchedShowsState.value = mostWatchedShows\n      mostWatchedTotalCountState.value = showsIds.size\n      totalTimeSpentMinutesState.value = episodes.sumOf { it.runtime }\n      totalWatchedEpisodesState.value = episodes.count()\n      totalWatchedEpisodesShowsState.value = episodes.distinctBy { it.idShowTrakt }.count()\n      topGenresState.value = genres\n    }\n  }\n\n  fun loadRatings() {\n    viewModelScope.launch {\n      try {\n        ratingsState.value = ratingsCase.loadRatings()\n      } catch (t: Throwable) {\n        ratingsState.value = emptyList()\n      }\n    }\n  }\n\n  private suspend fun batchEpisodes(\n    showsIds: List<Long>,\n    allEpisodes: MutableList<Episode> = mutableListOf(),\n  ): List<Episode> {\n    viewModelScope.ensureActive()\n\n    val batch = showsIds.take(500)\n    if (batch.isEmpty()) return allEpisodes\n\n    val episodes = localSource.episodes.getAllWatchedForShows(batch)\n    allEpisodes.addAll(episodes)\n\n    return batchEpisodes(showsIds.filter { it !in batch }, allEpisodes)\n  }\n\n  private suspend fun batchSeasons(\n    showsIds: List<Long>,\n    allSeasons: MutableList<Season> = mutableListOf(),\n  ): List<Season> {\n    viewModelScope.ensureActive()\n\n    val batch = showsIds.take(500)\n    if (batch.isEmpty()) return allSeasons\n\n    val seasons = localSource.seasons.getAllWatchedForShows(batch)\n    allSeasons.addAll(seasons)\n\n    return batchSeasons(showsIds.filter { it !in batch }, allSeasons)\n  }\n\n  private fun extractTopGenres(shows: List<Show>) =\n    shows\n      .flatMap { it.genres }\n      .asSequence()\n      .filter { it.isNotBlank() }\n      .distinct()\n      .map { genre -> Pair(Genre.fromSlug(genre), shows.count { genre in it.genres }) }\n      .sortedByDescending { it.second }\n      .map { it.first }\n      .toList()\n      .filterNotNull()\n\n  private suspend fun loadTranslation(language: String, show: Show) =\n    if (language == Config.DEFAULT_LANGUAGE) null\n    else translationsRepository.loadTranslation(show, language, true)\n\n  val uiState = combine(\n    mostWatchedShowsState,\n    mostWatchedTotalCountState,\n    totalTimeSpentMinutesState,\n    totalWatchedEpisodesState,\n    totalWatchedEpisodesShowsState,\n    topGenresState,\n    ratingsState\n  ) { s1, s2, s3, s4, s5, s6, s7 ->\n    StatisticsUiState(\n      mostWatchedShows = s1,\n      mostWatchedTotalCount = s2,\n      totalTimeSpentMinutes = s3,\n      totalWatchedEpisodes = s4,\n      totalWatchedEpisodesShows = s5,\n      topGenres = s6,\n      ratings = s7\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = StatisticsUiState()\n  )\n}\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/cases/StatisticsLoadRatingsCase.kt",
    "content": "package com.michaldrabik.ui_statistics.cases\n\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_statistics.views.ratings.recycler.StatisticsRatingItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass StatisticsLoadRatingsCase @Inject constructor(\n  private val userTraktManager: UserTraktManager,\n  private val showsRepository: ShowsRepository,\n  private val ratingsRepository: RatingsRepository,\n  private val imagesProvider: ShowImagesProvider,\n) {\n\n  companion object {\n    private const val LIMIT = 25\n  }\n\n  suspend fun loadRatings(): List<StatisticsRatingItem> {\n    if (!userTraktManager.isAuthorized()) {\n      return emptyList()\n    }\n\n    val ratings = ratingsRepository.shows.loadShowsRatings()\n\n    val ratingsIds = ratings.map { it.idTrakt }\n    val myShows = showsRepository.myShows.loadAll(ratingsIds)\n\n    return ratings\n      .filter { rating -> myShows.any { it.traktId == rating.idTrakt.id } }\n      .take(LIMIT)\n      .map { rating ->\n        val show = myShows.first { it.traktId == rating.idTrakt.id }\n        StatisticsRatingItem(\n          isLoading = false,\n          show = show,\n          image = imagesProvider.findCachedImage(show, ImageType.POSTER),\n          rating = rating\n        )\n      }.sortedByDescending { it.rating.ratedAt }\n  }\n}\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/views/StatisticsTopGenresView.kt",
    "content": "package com.michaldrabik.ui_statistics.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport com.google.android.material.card.MaterialCardView\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_statistics.R\nimport com.michaldrabik.ui_statistics.databinding.ViewStatisticsCardTopGenreBinding\n\n@SuppressLint(\"SetTextI18n\")\nclass StatisticsTopGenresView : MaterialCardView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewStatisticsCardTopGenreBinding.inflate(LayoutInflater.from(context), this)\n\n  private var topGenres = emptyList<Genre>()\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n    setCardBackgroundColor(context.colorFromAttr(R.attr.colorCardBackground))\n    cardElevation = context.dimenToPx(R.dimen.elevationSmall).toFloat()\n    strokeWidth = 0\n    onClick {\n      showGenres(10)\n      isClickable = false\n      binding.viewTopGenresSubValue.text = context.getString(R.string.textStatisticsTopGenreSubValue2)\n    }\n  }\n\n  fun bind(genres: List<Genre>) {\n    topGenres = genres.toList()\n    showGenres(3)\n  }\n\n  private fun showGenres(limit: Int) {\n    binding.viewTopGenresValue.text = topGenres\n      .take(limit)\n      .joinToString(\"\\n\") {\n        context.getString(it.displayName)\n      }\n  }\n}\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/views/StatisticsTotalEpisodesView.kt",
    "content": "package com.michaldrabik.ui_statistics.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport com.google.android.material.card.MaterialCardView\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_statistics.R\nimport com.michaldrabik.ui_statistics.databinding.ViewStatisticsCardTotalEpisodesBinding\nimport java.text.NumberFormat\nimport java.util.Locale\n\n@SuppressLint(\"SetTextI18n\")\nclass StatisticsTotalEpisodesView : MaterialCardView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewStatisticsCardTotalEpisodesBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n    setCardBackgroundColor(context.colorFromAttr(R.attr.colorCardBackground))\n    cardElevation = context.dimenToPx(R.dimen.elevationSmall).toFloat()\n    strokeWidth = 0\n  }\n\n  fun bind(episodesCount: Int, episodesShowsCount: Int) {\n    val formatter = NumberFormat.getNumberInstance(Locale.ENGLISH)\n    with(binding) {\n      viewTotalEpisodesValue.text = context.getString(\n        R.string.textStatisticsTotalEpisodesCount,\n        formatter.format(episodesCount)\n      )\n      viewTotalEpisodesSubValue.text = context.getString(\n        R.string.textStatisticsTotalEpisodesShowsCount,\n        formatter.format(episodesShowsCount)\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/views/StatisticsTotalTimeSpentView.kt",
    "content": "package com.michaldrabik.ui_statistics.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport com.google.android.material.card.MaterialCardView\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_statistics.R\nimport com.michaldrabik.ui_statistics.databinding.ViewStatisticsCardTotalTimeBinding\nimport java.text.NumberFormat\nimport java.util.Locale.ENGLISH\nimport java.util.concurrent.TimeUnit\n\n@SuppressLint(\"SetTextI18n\")\nclass StatisticsTotalTimeSpentView : MaterialCardView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewStatisticsCardTotalTimeBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n    setCardBackgroundColor(context.colorFromAttr(R.attr.colorCardBackground))\n    cardElevation = context.dimenToPx(R.dimen.elevationSmall).toFloat()\n    strokeWidth = 0\n  }\n\n  fun bind(timeMinutes: Int) {\n    val formatter = NumberFormat.getNumberInstance(ENGLISH)\n\n    val hours = TimeUnit.MINUTES.toHours(timeMinutes.toLong())\n    val days = TimeUnit.HOURS.toDays(hours)\n\n    with(binding) {\n      viewTotalTimeSpentHoursValue.text = context.getString(R.string.textStatisticsTotalTimeSpentHours, formatter.format(hours))\n      viewTotalTimeSpentMinutesValue.text = context.getString(R.string.textStatisticsTotalTimeSpentMinutes, formatter.format(timeMinutes))\n      viewTotalTimeSpentSubValue.text = context.getString(R.string.textStatisticsTotalTimeSpentDays, formatter.format(days))\n    }\n  }\n}\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/views/mostWatched/StatisticsMostWatchedItem.kt",
    "content": "package com.michaldrabik.ui_statistics.views.mostWatched\n\nimport com.michaldrabik.ui_base.common.ListItem\nimport com.michaldrabik.ui_model.Episode\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.Translation\n\ndata class StatisticsMostWatchedItem(\n  override val show: Show,\n  val episodes: List<Episode>,\n  val seasonsCount: Long,\n  override val image: Image,\n  override val isLoading: Boolean = false,\n  val translation: Translation? = null\n) : ListItem\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/views/mostWatched/StatisticsMostWatchedItemView.kt",
    "content": "package com.michaldrabik.ui_statistics.views.mostWatched\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_statistics.databinding.ViewStatisticsMostWatchedItemBinding\n\n@SuppressLint(\"SetTextI18n\")\nclass StatisticsMostWatchedItemView : ShowView<StatisticsMostWatchedItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewStatisticsMostWatchedItemBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    binding.viewMostWatchedItem.onClick { itemClickListener?.invoke(item) }\n  }\n\n  override val imageView: ImageView = binding.viewMostWatchedItemImage\n  override val placeholderView: ImageView = binding.viewMostWatchedItemPlaceholder\n\n  private lateinit var item: StatisticsMostWatchedItem\n\n  override fun bind(item: StatisticsMostWatchedItem) {\n    this.item = item\n    clear()\n\n    with(binding) {\n      viewMostWatchedItemTitle.text =\n        if (item.translation?.title.isNullOrBlank()) item.show.title\n        else item.translation?.title\n      viewMostWatchedItemHoursValue.text = \"${item.episodes.sumOf { it.runtime } / 60}\"\n      viewMostWatchedItemEpisodesValue.text = \"${item.episodes.size}\"\n      viewMostWatchedItemSeasonsValue.text = \"${item.seasonsCount}\"\n    }\n\n    loadImage(item)\n  }\n\n  private fun clear() {\n    Glide.with(this).clear(binding.viewMostWatchedItemImage)\n  }\n}\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/views/mostWatched/StatisticsMostWatchedShowsView.kt",
    "content": "package com.michaldrabik.ui_statistics.views.mostWatched\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.michaldrabik.ui_base.utilities.extensions.addDivider\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_statistics.R\nimport com.michaldrabik.ui_statistics.databinding.ViewStatisticsCardMostWatchedShowsBinding\nimport com.michaldrabik.ui_statistics.views.mostWatched.recycler.MostWatchedAdapter\n\n@SuppressLint(\"SetTextI18n\")\nclass StatisticsMostWatchedShowsView : ConstraintLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewStatisticsCardMostWatchedShowsBinding.inflate(LayoutInflater.from(context), this)\n\n  private var adapter: MostWatchedAdapter? = null\n  private var layoutManager: LinearLayoutManager? = null\n\n  var onLoadMoreClickListener: ((Int) -> Unit)? = null\n  var onShowClickListener: ((Show) -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    setupRecycler()\n  }\n\n  private fun setupRecycler() {\n    layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)\n    adapter = MostWatchedAdapter(\n      itemClickListener = { onShowClickListener?.invoke(it.show) }\n    )\n    binding.viewMostWatchedShowsRecycler.apply {\n      adapter = this@StatisticsMostWatchedShowsView.adapter\n      layoutManager = this@StatisticsMostWatchedShowsView.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      addDivider(R.drawable.divider_statistics_most_watched)\n    }\n  }\n\n  fun bind(\n    items: List<StatisticsMostWatchedItem>,\n    totalCount: Int\n  ) {\n    adapter?.setItems(items)\n    binding.viewMostWatchedShowsMoreButton.run {\n      visibleIf(items.size < totalCount)\n      onClick { onLoadMoreClickListener?.invoke(10) }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/views/mostWatched/recycler/MostWatchedAdapter.kt",
    "content": "package com.michaldrabik.ui_statistics.views.mostWatched.recycler\n\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseAdapter\nimport com.michaldrabik.ui_statistics.views.mostWatched.StatisticsMostWatchedItem\nimport com.michaldrabik.ui_statistics.views.mostWatched.StatisticsMostWatchedItemView\n\nclass MostWatchedAdapter(\n  private val itemClickListener: (StatisticsMostWatchedItem) -> Unit,\n) : BaseAdapter<StatisticsMostWatchedItem>() {\n\n  override val asyncDiffer = AsyncListDiffer(this, MostWatchedItemDiffCallback())\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    BaseViewHolder(\n      StatisticsMostWatchedItemView(parent.context).apply {\n        itemClickListener = this@MostWatchedAdapter.itemClickListener\n      }\n    )\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    (holder.itemView as StatisticsMostWatchedItemView).bind(item)\n  }\n}\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/views/mostWatched/recycler/MostWatchedItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_statistics.views.mostWatched.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\nimport com.michaldrabik.ui_statistics.views.mostWatched.StatisticsMostWatchedItem\n\nclass MostWatchedItemDiffCallback : DiffUtil.ItemCallback<StatisticsMostWatchedItem>() {\n\n  override fun areItemsTheSame(oldItem: StatisticsMostWatchedItem, newItem: StatisticsMostWatchedItem) =\n    oldItem.show.ids.trakt == newItem.show.ids.trakt\n\n  override fun areContentsTheSame(oldItem: StatisticsMostWatchedItem, newItem: StatisticsMostWatchedItem) =\n    oldItem.image == newItem.image &&\n      oldItem.isLoading == newItem.isLoading &&\n      oldItem.seasonsCount == newItem.seasonsCount &&\n      oldItem.episodes.size == newItem.episodes.size &&\n      oldItem.translation == newItem.translation\n}\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/views/ratings/StatisticsRateItemView.kt",
    "content": "package com.michaldrabik.ui_statistics.views.ratings\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.ui_base.common.views.ShowView\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_statistics.R\nimport com.michaldrabik.ui_statistics.databinding.ViewStatisticsRateItemBinding\nimport com.michaldrabik.ui_statistics.views.ratings.recycler.StatisticsRatingItem\n\nclass StatisticsRateItemView : ShowView<StatisticsRatingItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewStatisticsRateItemBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    val width = context.dimenToPx(R.dimen.statisticsRatingItemWidth)\n    layoutParams = LayoutParams(width, WRAP_CONTENT)\n    clipChildren = false\n    binding.viewRateItemImageLayout.onClick { itemClickListener?.invoke(item) }\n  }\n\n  override val imageView: ImageView = binding.viewRateItemImage\n  override val placeholderView: ImageView = binding.viewRateItemPlaceholder\n\n  private lateinit var item: StatisticsRatingItem\n\n  override fun bind(item: StatisticsRatingItem) {\n    this.item = item\n    clear()\n    with(binding) {\n      viewRateItemTitle.text = item.show.title\n      viewRateItemRating.text = \"${item.rating.rating}\"\n    }\n    loadImage(item)\n  }\n\n  private fun clear() {\n    with(binding) {\n      viewRateItemTitle.gone()\n      viewRateItemPlaceholder.gone()\n      Glide.with(this@StatisticsRateItemView).clear(viewRateItemImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/views/ratings/StatisticsRatingsView.kt",
    "content": "package com.michaldrabik.ui_statistics.views.ratings\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.google.android.material.card.MaterialCardView\nimport com.michaldrabik.ui_base.common.ListItem\nimport com.michaldrabik.ui_base.utilities.extensions.addDivider\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_statistics.R\nimport com.michaldrabik.ui_statistics.databinding.ViewStatisticsCardRatingsBinding\nimport com.michaldrabik.ui_statistics.views.ratings.recycler.StatisticsRatingItem\nimport com.michaldrabik.ui_statistics.views.ratings.recycler.StatisticsRatingsAdapter\n\n@SuppressLint(\"SetTextI18n\")\nclass StatisticsRatingsView : MaterialCardView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewStatisticsCardRatingsBinding.inflate(LayoutInflater.from(context), this)\n\n  private lateinit var adapter: StatisticsRatingsAdapter\n  private val layoutManager by lazy { LinearLayoutManager(context, HORIZONTAL, false) }\n\n  var onShowClickListener: ((ListItem) -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n    clipToPadding = false\n    clipChildren = false\n    cardElevation = context.dimenToPx(R.dimen.elevationSmall).toFloat()\n    strokeWidth = 0\n    setCardBackgroundColor(context.colorFromAttr(R.attr.colorCardBackground))\n    setupRecycler()\n  }\n\n  private fun setupRecycler() {\n    adapter = StatisticsRatingsAdapter(\n      itemClickListener = { onShowClickListener?.invoke(it) }\n    )\n    binding.viewRatingsRecycler.apply {\n      setHasFixedSize(true)\n      adapter = this@StatisticsRatingsView.adapter\n      layoutManager = this@StatisticsRatingsView.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      addDivider(R.drawable.divider_statistics_ratings, HORIZONTAL)\n    }\n  }\n\n  fun bind(items: List<StatisticsRatingItem>) {\n    adapter.setItems(items)\n  }\n}\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/views/ratings/recycler/StatisticsRatingItem.kt",
    "content": "package com.michaldrabik.ui_statistics.views.ratings.recycler\n\nimport com.michaldrabik.ui_base.common.ListItem\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.TraktRating\n\ndata class StatisticsRatingItem(\n  override val show: Show,\n  override val image: Image,\n  override val isLoading: Boolean,\n  val rating: TraktRating\n) : ListItem\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/views/ratings/recycler/StatisticsRatingsAdapter.kt",
    "content": "package com.michaldrabik.ui_statistics.views.ratings.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseAdapter\nimport com.michaldrabik.ui_statistics.views.ratings.StatisticsRateItemView\n\nclass StatisticsRatingsAdapter(\n  private val itemClickListener: (StatisticsRatingItem) -> Unit,\n) : BaseAdapter<StatisticsRatingItem>() {\n\n  override val asyncDiffer = AsyncListDiffer(this, StatisticsRatingsDiffCallback())\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    ViewHolderShow(\n      StatisticsRateItemView(parent.context).apply {\n        itemClickListener = this@StatisticsRatingsAdapter.itemClickListener\n      }\n    )\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    (holder.itemView as StatisticsRateItemView).bind(item)\n  }\n\n  class ViewHolderShow(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-statistics/src/main/java/com/michaldrabik/ui_statistics/views/ratings/recycler/StatisticsRatingsDiffCallback.kt",
    "content": "package com.michaldrabik.ui_statistics.views.ratings.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass StatisticsRatingsDiffCallback : DiffUtil.ItemCallback<StatisticsRatingItem>() {\n\n  override fun areItemsTheSame(oldItem: StatisticsRatingItem, newItem: StatisticsRatingItem) =\n    oldItem.show.ids.trakt == newItem.show.ids.trakt\n\n  override fun areContentsTheSame(oldItem: StatisticsRatingItem, newItem: StatisticsRatingItem) =\n    oldItem.rating.rating == newItem.rating.rating &&\n      oldItem.image == newItem.image\n}\n"
  },
  {
    "path": "ui-statistics/src/main/res/drawable/divider_statistics_most_watched.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<inset\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:insetTop=\"@dimen/spaceSmall\"\n  android:insetBottom=\"@dimen/spaceSmall\"\n  >\n  <shape>\n    <size android:height=\"1dp\" />\n    <solid android:color=\"?attr/colorSeparator\" />\n  </shape>\n</inset>"
  },
  {
    "path": "ui-statistics/src/main/res/drawable/divider_statistics_ratings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <size\n    android:width=\"8dp\"\n    android:height=\"8dp\"\n    />\n</shape>"
  },
  {
    "path": "ui-statistics/src/main/res/layout/fragment_statistics.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView\n  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/statisticsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  android:fillViewport=\"true\"\n  android:overScrollMode=\"never\"\n  android:paddingBottom=\"@dimen/spaceSmall\"\n  android:scrollbars=\"none\"\n  tools:background=\"@color/colorBackground\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/statisticsCoordinator\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    >\n\n    <com.google.android.material.appbar.MaterialToolbar\n      android:id=\"@+id/statisticsToolbar\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:background=\"?android:windowBackground\"\n      android:elevation=\"@dimen/elevationNormal\"\n      app:contentInsetStartWithNavigation=\"0dp\"\n      app:layout_constraintBottom_toTopOf=\"@id/statisticsTotalTimeSpent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:navigationIcon=\"@drawable/ic_arrow_back\"\n      app:subtitleTextAppearance=\"@style/ToolbarSubtitleAppearance\"\n      app:title=\"@string/textStatistics\"\n      />\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:id=\"@+id/statisticsContent\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:clipChildren=\"false\"\n      android:clipToPadding=\"false\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      >\n\n      <com.michaldrabik.ui_statistics.views.StatisticsTotalTimeSpentView\n        android:id=\"@+id/statisticsTotalTimeSpent\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceMedium\"\n        android:layout_marginTop=\"@dimen/statisticsTopPadding\"\n        android:layout_marginEnd=\"@dimen/spaceMedium\"\n        app:layout_constraintBottom_toTopOf=\"@id/statisticsTotalEpisodes\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_bias=\"0\"\n        app:layout_constraintVertical_chainStyle=\"packed\"\n        />\n\n      <com.michaldrabik.ui_statistics.views.StatisticsTotalEpisodesView\n        android:id=\"@+id/statisticsTotalEpisodes\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceMedium\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceMedium\"\n        app:layout_constraintBottom_toTopOf=\"@id/statisticsTopGenres\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/statisticsTotalTimeSpent\"\n        />\n\n      <com.michaldrabik.ui_statistics.views.StatisticsTopGenresView\n        android:id=\"@+id/statisticsTopGenres\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceMedium\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceMedium\"\n        app:layout_constraintBottom_toTopOf=\"@+id/statisticsRatings\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/statisticsTotalEpisodes\"\n        />\n\n      <com.michaldrabik.ui_statistics.views.ratings.StatisticsRatingsView\n        android:id=\"@+id/statisticsRatings\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceMedium\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceMedium\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toTopOf=\"@+id/statisticsMostWatchedShows\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/statisticsTopGenres\"\n        tools:visibility=\"visible\"\n        />\n\n      <com.michaldrabik.ui_statistics.views.mostWatched.StatisticsMostWatchedShowsView\n        android:id=\"@+id/statisticsMostWatchedShows\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceMedium\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceMedium\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/statisticsRatings\"\n        />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <include\n      android:id=\"@+id/statisticsEmptyView\"\n      layout=\"@layout/layout_statistics_empty\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center\"\n      android:layout_marginStart=\"@dimen/spaceBig\"\n      android:layout_marginEnd=\"@dimen/spaceBig\"\n      android:visibility=\"gone\"\n      />\n\n  </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</androidx.core.widget.NestedScrollView>"
  },
  {
    "path": "ui-statistics/src/main/res/layout/layout_statistics_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/rootLayout\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  tools:background=\"@color/colorBackground\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/textStatistics\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textStatisticsEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-statistics/src/main/res/layout/view_statistics_card_most_watched_shows.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:background=\"@color/colorBackground\"\n  tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <TextView\n    android:id=\"@+id/viewMostWatchedShowsTitle\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textStatisticsMostWatchedShows\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"@dimen/statisticsHeaderSize\"\n    android:textStyle=\"bold\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewMostWatchedShowsRecycler\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_constraintVertical_bias=\"0\"\n    app:layout_constraintVertical_chainStyle=\"packed\"\n    />\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/viewMostWatchedShowsRecycler\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipToPadding=\"false\"\n    android:nestedScrollingEnabled=\"false\"\n    android:overScrollMode=\"never\"\n    android:paddingTop=\"@dimen/spaceMedium\"\n    app:layout_constrainedHeight=\"true\"\n    app:layout_constraintBottom_toTopOf=\"@id/viewMostWatchedShowsMoreButton\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewMostWatchedShowsTitle\"\n    app:layout_goneMarginBottom=\"@dimen/spaceMedium\"\n    />\n\n  <com.google.android.material.button.MaterialButton\n    android:id=\"@+id/viewMostWatchedShowsMoreButton\"\n    style=\"@style/RoundTextButton\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"bottom|center_horizontal\"\n    android:text=\"@string/textStatisticsShowMore\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    app:layout_constraintBottom_toBottomOf=\"parent\"\n    app:layout_constraintEnd_toEndOf=\"parent\"\n    app:layout_constraintStart_toStartOf=\"parent\"\n    app:layout_constraintTop_toBottomOf=\"@id/viewMostWatchedShowsRecycler\"\n    app:rippleColor=\"?android:attr/textColorSecondary\"\n    />\n\n</merge>"
  },
  {
    "path": "ui-statistics/src/main/res/layout/view_statistics_card_ratings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  app:cardBackgroundColor=\"?attr/colorCardBackground\"\n  tools:cardBackgroundColor=\"@color/colorBackground\"\n  tools:parentTag=\"com.google.android.material.card.MaterialCardView\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <LinearLayout\n    android:id=\"@+id/viewRatingsContent\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:orientation=\"vertical\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewRatingsTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:layout_marginTop=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      android:layout_marginBottom=\"@dimen/spaceMedium\"\n      android:drawablePadding=\"@dimen/spaceSmall\"\n      android:gravity=\"center_vertical\"\n      android:text=\"@string/textStatisticsRatings\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"@dimen/statisticsHeaderSize\"\n      android:textStyle=\"bold\"\n      app:drawableStartCompat=\"@drawable/ic_star\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      />\n\n    <androidx.recyclerview.widget.RecyclerView\n      android:id=\"@+id/viewRatingsRecycler\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:clipChildren=\"false\"\n      android:clipToPadding=\"false\"\n      android:nestedScrollingEnabled=\"false\"\n      android:overScrollMode=\"never\"\n      android:paddingStart=\"@dimen/spaceMedium\"\n      android:paddingEnd=\"@dimen/spaceMedium\"\n      />\n\n  </LinearLayout>\n\n</merge>"
  },
  {
    "path": "ui-statistics/src/main/res/layout/view_statistics_card_top_genre.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  app:cardBackgroundColor=\"?attr/colorCardBackground\"\n  tools:parentTag=\"com.google.android.material.card.MaterialCardView\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/viewTopGenresContent\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingStart=\"@dimen/spaceMedium\"\n    android:paddingTop=\"@dimen/spaceNormal\"\n    android:paddingEnd=\"@dimen/spaceSmall\"\n    android:paddingBottom=\"@dimen/spaceNormal\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewTopGenresTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:drawablePadding=\"@dimen/spaceSmall\"\n      android:gravity=\"center_vertical\"\n      android:text=\"@string/textStatisticsTopGenre\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"@dimen/statisticsHeaderSize\"\n      android:textStyle=\"bold\"\n      app:drawableStartCompat=\"@drawable/ic_stars_round\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:drawableStart=\"@drawable/ic_stars_round\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewTopGenresValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      android:lineSpacingExtra=\"2dp\"\n      android:textColor=\"?attr/colorAccent\"\n      android:textSize=\"24sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewTopGenresTitle\"\n      tools:text=\"Drama\\nComedy\\nThriller\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewTopGenresSubValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceTiny\"\n      android:text=\"@string/textStatisticsTopGenreSubValue\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewTopGenresValue\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-statistics/src/main/res/layout/view_statistics_card_total_episodes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  app:cardBackgroundColor=\"?attr/colorCardBackground\"\n  tools:background=\"@color/colorBackground\"\n  tools:parentTag=\"com.google.android.material.card.MaterialCardView\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <LinearLayout\n    android:id=\"@+id/viewTotalEpisodesContent\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:paddingStart=\"@dimen/spaceMedium\"\n    android:paddingTop=\"@dimen/spaceNormal\"\n    android:paddingEnd=\"@dimen/spaceMedium\"\n    android:paddingBottom=\"@dimen/spaceNormal\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewTotalEpisodesTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:drawablePadding=\"@dimen/spaceSmall\"\n      android:gravity=\"center_vertical\"\n      android:text=\"@string/textStatisticsTotalEpisodes\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"@dimen/statisticsHeaderSize\"\n      android:textStyle=\"bold\"\n      app:drawableStartCompat=\"@drawable/ic_eye\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      tools:drawableStart=\"@drawable/ic_eye\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewTotalEpisodesValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:textColor=\"?attr/colorAccent\"\n      android:textSize=\"24sp\"\n      android:textStyle=\"bold\"\n      tools:text=\"298 episodes\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewTotalEpisodesSubValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"14sp\"\n      tools:text=\"among 23 shows.\"\n      />\n\n  </LinearLayout>\n\n</merge>"
  },
  {
    "path": "ui-statistics/src/main/res/layout/view_statistics_card_total_time.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  app:cardBackgroundColor=\"?attr/colorCardBackground\"\n  app:cardElevation=\"@dimen/elevationSmall\"\n  tools:parentTag=\"com.google.android.material.card.MaterialCardView\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <LinearLayout\n    android:id=\"@+id/viewTotalTimeSpentContent\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:paddingStart=\"@dimen/spaceMedium\"\n    android:paddingTop=\"@dimen/spaceNormal\"\n    android:paddingEnd=\"@dimen/spaceMedium\"\n    android:paddingBottom=\"@dimen/spaceNormal\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewTotalTimeSpentTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:drawablePadding=\"@dimen/spaceSmall\"\n      android:gravity=\"center_vertical\"\n      android:text=\"@string/textStatisticsTotalTimeSpent\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"@dimen/statisticsHeaderSize\"\n      android:textStyle=\"bold\"\n      app:drawableStartCompat=\"@drawable/ic_clock\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      tools:drawableStart=\"@drawable/ic_clock\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewTotalTimeSpentMinutesValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:textColor=\"?attr/colorAccent\"\n      android:textSize=\"24sp\"\n      android:textStyle=\"bold\"\n      tools:text=\"1000000 minutes\"\n      />\n\n    <TextView\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:text=\"@string/textStatisticsTotalTimeItIs\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"14sp\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewTotalTimeSpentHoursValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:textColor=\"?attr/colorAccent\"\n      android:textSize=\"24sp\"\n      android:textStyle=\"bold\"\n      tools:text=\"1 000 000 hours\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewTotalTimeSpentSubValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"14sp\"\n      tools:text=\"which is about 342 days and 23 hours.\"\n      />\n\n  </LinearLayout>\n\n</merge>"
  },
  {
    "path": "ui-statistics/src/main/res/layout/view_statistics_most_watched_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:background=\"@color/colorBackground\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/viewMostWatchedItem\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:minHeight=\"@dimen/statisticsMostWatchedItemImageHeight\"\n    >\n\n    <ImageView\n      android:id=\"@+id/viewMostWatchedItemImage\"\n      android:layout_width=\"@dimen/statisticsMostWatchedItemImageWidth\"\n      android:layout_height=\"0dp\"\n      android:background=\"@drawable/bg_media_view_elevation\"\n      android:elevation=\"@dimen/elevationSmall\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <ImageView\n      android:id=\"@+id/viewMostWatchedItemPlaceholder\"\n      android:layout_width=\"@dimen/statisticsMostWatchedItemImageWidth\"\n      android:layout_height=\"0dp\"\n      android:layout_gravity=\"center\"\n      android:background=\"@drawable/bg_media_view_placeholder\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:padding=\"20dp\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_television\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:visibility=\"visible\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/viewMostWatchedItemProgress\"\n      style=\"@style/ProgressBar.Dark\"\n      android:layout_width=\"28dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"center\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"@id/viewMostWatchedItemImage\"\n      app:layout_constraintStart_toStartOf=\"@id/viewMostWatchedItemImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMostWatchedItemTitle\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      android:ellipsize=\"end\"\n      android:gravity=\"center\"\n      android:maxLines=\"1\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"20sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewMostWatchedItemSeparator1\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewMostWatchedItemImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:text=\"Game of Thrones\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMostWatchedItemHoursValue\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"0dp\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:ellipsize=\"end\"\n      android:gravity=\"center|bottom\"\n      android:maxLines=\"1\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"22sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewMostWatchedItemHoursLabel\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewMostWatchedItemSeparator1\"\n      app:layout_constraintStart_toEndOf=\"@id/viewMostWatchedItemImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewMostWatchedItemTitle\"\n      tools:text=\"999\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMostWatchedItemHoursLabel\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"0dp\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:ellipsize=\"end\"\n      android:gravity=\"center|top\"\n      android:maxLines=\"1\"\n      android:text=\"@string/textStatisticsHours\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewMostWatchedItemSeparator1\"\n      app:layout_constraintStart_toEndOf=\"@id/viewMostWatchedItemImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewMostWatchedItemHoursValue\"\n      />\n\n    <View\n      android:id=\"@+id/viewMostWatchedItemSeparator1\"\n      android:layout_width=\"1dp\"\n      android:layout_height=\"0dp\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:background=\"?attr/colorAccent\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewMostWatchedItemSeasonsValue\"\n      app:layout_constraintStart_toEndOf=\"@id/viewMostWatchedItemHoursLabel\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewMostWatchedItemTitle\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMostWatchedItemSeasonsValue\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"0dp\"\n      android:ellipsize=\"end\"\n      android:gravity=\"center|bottom\"\n      android:maxLines=\"1\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"22sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewMostWatchedItemSeasonsLabel\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewMostWatchedItemSeparator2\"\n      app:layout_constraintStart_toEndOf=\"@id/viewMostWatchedItemSeparator1\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewMostWatchedItemTitle\"\n      tools:text=\"11\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMostWatchedItemSeasonsLabel\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"0dp\"\n      android:ellipsize=\"end\"\n      android:gravity=\"center|top\"\n      android:maxLines=\"1\"\n      android:text=\"@string/textStatisticsSeasons\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewMostWatchedItemSeparator2\"\n      app:layout_constraintStart_toEndOf=\"@id/viewMostWatchedItemSeparator1\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewMostWatchedItemSeasonsValue\"\n      />\n\n    <View\n      android:id=\"@+id/viewMostWatchedItemSeparator2\"\n      android:layout_width=\"1dp\"\n      android:layout_height=\"0dp\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:background=\"?attr/colorAccent\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewMostWatchedItemEpisodesValue\"\n      app:layout_constraintStart_toEndOf=\"@id/viewMostWatchedItemSeasonsLabel\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewMostWatchedItemTitle\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMostWatchedItemEpisodesValue\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"0dp\"\n      android:ellipsize=\"end\"\n      android:gravity=\"center|bottom\"\n      android:maxLines=\"1\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"22sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewMostWatchedItemEpisodesLabel\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewMostWatchedItemSeparator2\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewMostWatchedItemTitle\"\n      tools:text=\"11\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMostWatchedItemEpisodesLabel\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"0dp\"\n      android:ellipsize=\"end\"\n      android:gravity=\"center|top\"\n      android:maxLines=\"1\"\n      android:text=\"@string/textStatisticsEpisodes\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toEndOf=\"@id/viewMostWatchedItemSeparator2\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewMostWatchedItemEpisodesValue\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-statistics/src/main/res/layout/view_statistics_rate_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:background=\"@color/colorBackground\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    >\n\n    <FrameLayout\n      android:id=\"@+id/viewRateItemImageLayout\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"@dimen/statisticsRatingItemHeight\"\n      android:background=\"@drawable/bg_media_view_elevation_card\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:foreground=\"?attr/selectableItemBackground\"\n      >\n\n      <ImageView\n        android:id=\"@+id/viewRateItemImage\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        />\n\n      <ImageView\n        android:id=\"@+id/viewRateItemPlaceholder\"\n        android:layout_width=\"@dimen/showTilePlaceholder\"\n        android:layout_height=\"@dimen/showTilePlaceholder\"\n        android:layout_gravity=\"center\"\n        android:visibility=\"gone\"\n        app:srcCompat=\"@drawable/ic_television\"\n        app:tint=\"?attr/colorPlaceholderIcon\"\n        tools:visibility=\"visible\"\n        />\n\n      <TextView\n        android:id=\"@+id/viewRateItemTitle\"\n        style=\"@style/ImageTitle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom|start\"\n        android:layout_margin=\"@dimen/spaceSmall\"\n        android:maxLines=\"1\"\n        android:textSize=\"12sp\"\n        android:visibility=\"gone\"\n        tools:text=\"Game Of Thrones\"\n        tools:visibility=\"visible\"\n        />\n\n    </FrameLayout>\n\n    <TextView\n      android:id=\"@+id/viewRateItemRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"bottom|center\"\n      android:layout_marginTop=\"@dimen/spaceTiny\"\n      android:drawablePadding=\"@dimen/spaceMicro\"\n      android:gravity=\"center\"\n      android:maxLines=\"1\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"18sp\"\n      android:textStyle=\"bold\"\n      app:drawableStartCompat=\"@drawable/ic_star\"\n      app:drawableTint=\"?attr/colorAccent\"\n      tools:text=\"8\"\n      />\n\n  </LinearLayout>\n\n</merge>"
  },
  {
    "path": "ui-statistics/src/main/res/layout-sw600dp/fragment_statistics.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView\n  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/statisticsRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  android:fillViewport=\"true\"\n  android:overScrollMode=\"never\"\n  android:paddingBottom=\"@dimen/spaceSmall\"\n  android:scrollbars=\"none\"\n  tools:background=\"@color/colorBackground\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/statisticsCoordinator\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    >\n\n    <com.google.android.material.appbar.MaterialToolbar\n      android:id=\"@+id/statisticsToolbar\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:background=\"?android:windowBackground\"\n      android:elevation=\"@dimen/elevationNormal\"\n      app:contentInsetStartWithNavigation=\"0dp\"\n      app:layout_constraintBottom_toTopOf=\"@id/statisticsTotalTimeSpent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:navigationIcon=\"@drawable/ic_arrow_back\"\n      app:subtitleTextAppearance=\"@style/ToolbarSubtitleAppearance\"\n      app:title=\"@string/textStatistics\"\n      />\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:id=\"@+id/statisticsContent\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:clipChildren=\"false\"\n      android:clipToPadding=\"false\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      >\n\n      <com.michaldrabik.ui_statistics.views.StatisticsTotalTimeSpentView\n        android:id=\"@+id/statisticsTotalTimeSpent\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n        android:layout_marginTop=\"@dimen/statisticsTopPadding\"\n        android:layout_marginEnd=\"@dimen/spaceMedium\"\n        app:layout_constraintEnd_toStartOf=\"@id/statisticsTopGenres\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n      <com.michaldrabik.ui_statistics.views.StatisticsTopGenresView\n        android:id=\"@+id/statisticsTopGenres\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceMedium\"\n        android:layout_marginTop=\"@dimen/statisticsTopPadding\"\n        android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/statisticsTotalTimeSpent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n      <com.michaldrabik.ui_statistics.views.StatisticsTotalEpisodesView\n        android:id=\"@+id/statisticsTotalEpisodes\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n        android:layout_marginTop=\"@dimen/spaceBig\"\n        android:layout_marginEnd=\"@dimen/spaceMedium\"\n        app:layout_constraintEnd_toStartOf=\"@id/statisticsRatings\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/statisticsTotalTimeSpent\"\n        app:layout_goneMarginEnd=\"@dimen/screenMarginHorizontal\"\n        />\n\n      <com.michaldrabik.ui_statistics.views.ratings.StatisticsRatingsView\n        android:id=\"@+id/statisticsRatings\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceMedium\"\n        android:layout_marginTop=\"@dimen/spaceBig\"\n        android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n        android:visibility=\"gone\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/statisticsTotalEpisodes\"\n        app:layout_constraintTop_toBottomOf=\"@id/statisticsTopGenres\"\n        tools:visibility=\"visible\"\n        />\n\n      <androidx.constraintlayout.widget.Barrier\n        android:id=\"@+id/barrier1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:barrierDirection=\"bottom\"\n        app:constraint_referenced_ids=\"statisticsTotalEpisodes, statisticsRatings\"\n        />\n\n      <com.michaldrabik.ui_statistics.views.mostWatched.StatisticsMostWatchedShowsView\n        android:id=\"@+id/statisticsMostWatchedShows\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n        android:layout_marginTop=\"@dimen/spaceBig\"\n        android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n        android:layout_marginBottom=\"@dimen/spaceMedium\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/barrier1\"\n        app:layout_constraintVertical_bias=\"0\"\n        />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <include\n      android:id=\"@+id/statisticsEmptyView\"\n      layout=\"@layout/layout_statistics_empty\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center\"\n      android:layout_marginStart=\"@dimen/spaceBig\"\n      android:layout_marginEnd=\"@dimen/spaceBig\"\n      android:visibility=\"gone\"\n      />\n\n  </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</androidx.core.widget.NestedScrollView>"
  },
  {
    "path": "ui-statistics/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"statisticsHeaderSize\">22sp</dimen>\n\n  <dimen name=\"statisticsTopPadding\">60dp</dimen>\n  <dimen name=\"statisticsMostWatchedItemImageHeight\">100dp</dimen>\n  <dimen name=\"statisticsMostWatchedItemImageWidth\">67dp</dimen>\n  <dimen name=\"statisticsRatingItemHeight\">100dp</dimen>\n  <dimen name=\"statisticsRatingItemWidth\">67dp</dimen>\n</resources>"
  },
  {
    "path": "ui-statistics/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <string name=\"textStatistics\">Statistics</string>\n  <string name=\"textStatisticsEmpty\">At least one show must be present in <b>My Shows</b>\\nbefore statistics can be displayed.</string>\n  <string name=\"textStatisticsMostWatchedShows\">Most Viewed Shows</string>\n  <string name=\"textStatisticsTotalTimeSpent\">Total Time Spent Watching</string>\n  <string name=\"textStatisticsTotalTimeItIs\">and it is</string>\n  <string name=\"textStatisticsTotalTimeSpentHours\">%s hours</string>\n  <string name=\"textStatisticsTotalTimeSpentMinutes\">%s minutes</string>\n  <string name=\"textStatisticsTotalTimeSpentDays\">which is about %s days.</string>\n  <string name=\"textStatisticsTotalEpisodes\">Total Episodes Watched</string>\n  <string name=\"textStatisticsTotalEpisodesCount\">%s episodes</string>\n  <string name=\"textStatisticsTotalEpisodesShowsCount\">spread across %s shows.</string>\n  <string name=\"textStatisticsTopGenre\">Top Genres</string>\n  <string name=\"textStatisticsTopGenreSubValue\">seem to be your cup of tea. Tap to see more.</string>\n  <string name=\"textStatisticsTopGenreSubValue2\">seem to be your cup of tea.</string>\n  <string name=\"textStatisticsShowMore\">Show More</string>\n  <string name=\"textStatisticsRatings\">Recently Rated</string>\n  <string name=\"textStatisticsHours\">Hours</string>\n  <string name=\"textStatisticsEpisodes\">Episodes</string>\n  <string name=\"textStatisticsSeasons\">Seasons</string>\n\n</resources>"
  },
  {
    "path": "ui-statistics/src/main/res/values/styles.xml",
    "content": "<resources>\n\n  <style name=\"ToolbarSubtitleAppearance\" parent=\"@style/TextAppearance.Widget.AppCompat.Toolbar.Subtitle\">\n    <item name=\"android:textSize\">12sp</item>\n  </style>\n\n</resources>\n"
  },
  {
    "path": "ui-statistics/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatistics\">الإحصائيات</string>\n  <string name=\"textStatisticsEmpty\">أضف مسلسل واحد على الأقل في قائمة <b>مسلسلاتي</b>\\nلعرض قائمة الإحصائيات.</string>\n  <string name=\"textStatisticsMostWatchedShows\">المسلسلات الأكثر مشاهدة</string>\n  <string name=\"textStatisticsTotalTimeSpent\">إجمالي الوقت المنقضي في المشاهدة</string>\n  <string name=\"textStatisticsTotalTimeItIs\">يساوي</string>\n  <string name=\"textStatisticsTotalTimeSpentHours\">%s ساعات</string>\n  <string name=\"textStatisticsTotalTimeSpentMinutes\">%s دقائق</string>\n  <string name=\"textStatisticsTotalTimeSpentDays\">وهو حوالي %s أيام.</string>\n  <string name=\"textStatisticsTotalEpisodes\">إجمالي الحلقات التي تمت مشاهدتها</string>\n  <string name=\"textStatisticsTotalEpisodesCount\">%s حلقة</string>\n  <string name=\"textStatisticsTotalEpisodesShowsCount\">مقسمة على %s مسلسلات</string>\n  <string name=\"textStatisticsTopGenre\">أعلى التصنيفات</string>\n  <string name=\"textStatisticsTopGenreSubValue\">يبدو أنهم تصنيفاتك المفضلة. انقر للمزيد.</string>\n  <string name=\"textStatisticsTopGenreSubValue2\">يبدو أنهم تصنيفاتك المفضلة.</string>\n  <string name=\"textStatisticsShowMore\">عرض المزيد</string>\n  <string name=\"textStatisticsRatings\">تم تقييمه مؤخراً</string>\n  <string name=\"textStatisticsHours\">ساعات</string>\n  <string name=\"textStatisticsEpisodes\">حلقات</string>\n  <string name=\"textStatisticsSeasons\">مواسم</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatistics\">Statistik</string>\n  <string name=\"textStatisticsEmpty\">Es muss mindestens eine <b>Serie</b>\\n vorhanden sein, bevor deine Statistik angezeigt werden kann.</string>\n  <string name=\"textStatisticsMostWatchedShows\">Meist geschaute Serie</string>\n  <string name=\"textStatisticsTotalTimeSpent\">Gesamtzeit</string>\n  <string name=\"textStatisticsTotalTimeItIs\">und es ist</string>\n  <string name=\"textStatisticsTotalTimeSpentHours\">%s Stunden</string>\n  <string name=\"textStatisticsTotalTimeSpentMinutes\">%s Minuten</string>\n  <string name=\"textStatisticsTotalTimeSpentDays\">welches ist etwa %s Tage.</string>\n  <string name=\"textStatisticsTotalEpisodes\">Insgesamt geguckte Folgen</string>\n  <string name=\"textStatisticsTotalEpisodesCount\">%s Folgen</string>\n  <string name=\"textStatisticsTotalEpisodesShowsCount\">über %s Serien verteilt.</string>\n  <string name=\"textStatisticsTopGenre\">Top Genre</string>\n  <string name=\"textStatisticsTopGenreSubValue\">Scheint eine Tasse Tee zu sein. Tippe hier für mehr Informationen.</string>\n  <string name=\"textStatisticsTopGenreSubValue2\">Scheint eine Tasse Tee zu sein.</string>\n  <string name=\"textStatisticsShowMore\">Mehr</string>\n  <string name=\"textStatisticsRatings\">Zuletzt Bewertet</string>\n  <string name=\"textStatisticsHours\">Stunden</string>\n  <string name=\"textStatisticsEpisodes\">Folgen</string>\n  <string name=\"textStatisticsSeasons\">Staffel</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatistics\">Estadísticas</string>\n  <string name=\"textStatisticsEmpty\">Al menos una serie debe estar presente en <b>Mis Series</b>\\nantes de que se pueda mostrar las estadísticas.</string>\n  <string name=\"textStatisticsMostWatchedShows\">Series Más Vistas</string>\n  <string name=\"textStatisticsTotalTimeSpent\">Tiempo Total Utilizado Viendo</string>\n  <string name=\"textStatisticsTotalTimeItIs\">y es</string>\n  <string name=\"textStatisticsTotalTimeSpentHours\">%s horas</string>\n  <string name=\"textStatisticsTotalTimeSpentMinutes\">%s minutos</string>\n  <string name=\"textStatisticsTotalTimeSpentDays\">que es alrededor de %s días.</string>\n  <string name=\"textStatisticsTotalEpisodes\">Episodios Totales Vistos</string>\n  <string name=\"textStatisticsTotalEpisodesCount\">%s episodios</string>\n  <string name=\"textStatisticsTotalEpisodesShowsCount\">distribuidos en %s series.</string>\n  <string name=\"textStatisticsTopGenre\">Géneros Principales</string>\n  <string name=\"textStatisticsTopGenreSubValue\">parece ser santo de tu devoción. Toca para ver más.</string>\n  <string name=\"textStatisticsTopGenreSubValue2\">parece ser santo de tu devoción.</string>\n  <string name=\"textStatisticsShowMore\">Ver Más</string>\n  <string name=\"textStatisticsRatings\">Calificado Recientemente</string>\n  <string name=\"textStatisticsHours\">Horas</string>\n  <string name=\"textStatisticsEpisodes\">Episodios</string>\n  <string name=\"textStatisticsSeasons\">Temporadas</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatistics\">Tilastot</string>\n  <string name=\"textStatisticsEmpty\"><b>Omissa sarjoissa</b> on oltava ainakin\\nyksi sarja ennen kuin tilastoja voidaan näyttää.</string>\n  <string name=\"textStatisticsMostWatchedShows\">Katsotuimmat sarjat</string>\n  <string name=\"textStatisticsTotalTimeSpent\">Kokonaiskatseluaika on</string>\n  <string name=\"textStatisticsTotalTimeItIs\">joka on</string>\n  <string name=\"textStatisticsTotalTimeSpentHours\">%s tuntia</string>\n  <string name=\"textStatisticsTotalTimeSpentMinutes\">%s minuuttia</string>\n  <string name=\"textStatisticsTotalTimeSpentDays\">eli noin %s päivää.</string>\n  <string name=\"textStatisticsTotalEpisodes\">Jaksoja katsottu</string>\n  <string name=\"textStatisticsTotalEpisodesCount\">%s jaksoa</string>\n  <string name=\"textStatisticsTotalEpisodesShowsCount\">kattaen %s sarjaa.</string>\n  <string name=\"textStatisticsTopGenre\">Suosituimmat tyylilajit</string>\n  <string name=\"textStatisticsTopGenreSubValue\">näyttävät olevan juttusi. Napauta nähdäksesi lisää.</string>\n  <string name=\"textStatisticsTopGenreSubValue2\">näyttävät olevan juttusi.</string>\n  <string name=\"textStatisticsShowMore\">Näytä lisää</string>\n  <string name=\"textStatisticsRatings\">Viimeksi arvioidut</string>\n  <string name=\"textStatisticsHours\">tuntia</string>\n  <string name=\"textStatisticsEpisodes\">jaksoa</string>\n  <string name=\"textStatisticsSeasons\">kautta</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatistics\">Statistiques</string>\n  <string name=\"textStatisticsEmpty\">Au moins une série doit être présente dans <b>Mes Séries</b>\\navant que les statistiques puissent être affichées.</string>\n  <string name=\"textStatisticsMostWatchedShows\">Séries les plus vues</string>\n  <string name=\"textStatisticsTotalTimeSpent\">Temps Total Passé à Visionner</string>\n  <string name=\"textStatisticsTotalTimeItIs\">ce qui correspond à</string>\n  <string name=\"textStatisticsTotalTimeSpentHours\">%s heures</string>\n  <string name=\"textStatisticsTotalTimeSpentMinutes\">%s minutes</string>\n  <string name=\"textStatisticsTotalTimeSpentDays\">ce qui équivaut à environ %s jours.</string>\n  <string name=\"textStatisticsTotalEpisodes\">Nombre Total d\\'Épisodes Vus</string>\n  <string name=\"textStatisticsTotalEpisodesCount\">%s épisodes</string>\n  <string name=\"textStatisticsTotalEpisodesShowsCount\">répartis sur %s séries.</string>\n  <string name=\"textStatisticsTopGenre\">Genres Principaux</string>\n  <string name=\"textStatisticsTopGenreSubValue\">semblent être votre tasse de thé. Appuyez pour en voir plus.</string>\n  <string name=\"textStatisticsTopGenreSubValue2\">semblent être votre tasse de thé.</string>\n  <string name=\"textStatisticsShowMore\">Afficher Plus</string>\n  <string name=\"textStatisticsRatings\">Évaluations Récentes</string>\n  <string name=\"textStatisticsHours\">Heures</string>\n  <string name=\"textStatisticsEpisodes\">Épisodes</string>\n  <string name=\"textStatisticsSeasons\">Saisons</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatistics\">Statistiche</string>\n  <string name=\"textStatisticsEmpty\">Deve essere presente almeno uno show nella pagina <b>I miei show</b>\\nper visualizzare le statistiche.</string>\n  <string name=\"textStatisticsMostWatchedShows\">Show più visti</string>\n  <string name=\"textStatisticsTotalTimeSpent\">Tempo Totale Serie TV</string>\n  <string name=\"textStatisticsTotalTimeItIs\">ovvero</string>\n  <string name=\"textStatisticsTotalTimeSpentHours\">%s ore</string>\n  <string name=\"textStatisticsTotalTimeSpentMinutes\">%s minuti</string>\n  <string name=\"textStatisticsTotalTimeSpentDays\">che sono approssimativamente %s giorni.</string>\n  <string name=\"textStatisticsTotalEpisodes\">Episodi visti in totale</string>\n  <string name=\"textStatisticsTotalEpisodesCount\">%s episodi</string>\n  <string name=\"textStatisticsTotalEpisodesShowsCount\">suddivisi fra %s show.</string>\n  <string name=\"textStatisticsTopGenre\">Generi di Punta</string>\n  <string name=\"textStatisticsTopGenreSubValue\">sembrano fatti apposta per te. Tocca per vederne altri.</string>\n  <string name=\"textStatisticsTopGenreSubValue2\">sembrano fatti apposta per te.</string>\n  <string name=\"textStatisticsShowMore\">Mostra di più</string>\n  <string name=\"textStatisticsRatings\">Valutati di Recente</string>\n  <string name=\"textStatisticsHours\">Ore</string>\n  <string name=\"textStatisticsEpisodes\">Episodi</string>\n  <string name=\"textStatisticsSeasons\">Stagioni</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics/src/main/res/values-notnight/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"statisticsTopPadding\">70dp</dimen>\n</resources>"
  },
  {
    "path": "ui-statistics/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatistics\">Statystyki</string>\n  <string name=\"textStatisticsEmpty\">Dodaj przynajmniej jeden serial do <b>Kolekcji</b> aby zobaczyć statystyki</string>\n  <string name=\"textStatisticsMostWatchedShows\">Najwięcej Obejrzanych</string>\n  <string name=\"textStatisticsTotalTimeSpent\">Całkowity Czas</string>\n  <string name=\"textStatisticsTotalTimeItIs\">czyli</string>\n  <string name=\"textStatisticsTotalTimeSpentHours\">%s godzin</string>\n  <string name=\"textStatisticsTotalTimeSpentMinutes\">%s minut</string>\n  <string name=\"textStatisticsTotalTimeSpentDays\">co daje około %s dni.</string>\n  <string name=\"textStatisticsTotalEpisodes\">Całkowita Ilość Odcinków</string>\n  <string name=\"textStatisticsTotalEpisodesCount\">%s odcinków</string>\n  <string name=\"textStatisticsTotalEpisodesShowsCount\">w %s serialach.</string>\n  <string name=\"textStatisticsTopGenre\">Ulubione Gatunki</string>\n  <string name=\"textStatisticsTopGenreSubValue\">zdają się być ulubionymi. Kliknij po więcej.</string>\n  <string name=\"textStatisticsTopGenreSubValue2\">zdają się być ulubionymi.</string>\n  <string name=\"textStatisticsShowMore\">Pokaż Więcej</string>\n  <string name=\"textStatisticsRatings\">Niedawno Ocenione</string>\n  <string name=\"textStatisticsHours\">Godziny</string>\n  <string name=\"textStatisticsEpisodes\">Odcinki</string>\n  <string name=\"textStatisticsSeasons\">Sezony</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatistics\">Estatísticas</string>\n  <string name=\"textStatisticsEmpty\">Pelo menos uma série deve estar presente na aba <b>Minhas Séries</b>\\nantes que as estatísticas possam ser exibidas.</string>\n  <string name=\"textStatisticsMostWatchedShows\">Séries mais assistidas</string>\n  <string name=\"textStatisticsTotalTimeSpent\">Tempo total gasto assistindo</string>\n  <string name=\"textStatisticsTotalTimeItIs\">e é</string>\n  <string name=\"textStatisticsTotalTimeSpentHours\">%s horas</string>\n  <string name=\"textStatisticsTotalTimeSpentMinutes\">%s minutos</string>\n  <string name=\"textStatisticsTotalTimeSpentDays\">que é aproximadamente %s dias.</string>\n  <string name=\"textStatisticsTotalEpisodes\">Episódios assistidos</string>\n  <string name=\"textStatisticsTotalEpisodesCount\">%s episódios</string>\n  <string name=\"textStatisticsTotalEpisodesShowsCount\">espalhou-se por %s séries.</string>\n  <string name=\"textStatisticsTopGenre\">Top Gêneros</string>\n  <string name=\"textStatisticsTopGenreSubValue\">Seus favoritos. Toque para ver mais.</string>\n  <string name=\"textStatisticsTopGenreSubValue2\">Seus favoritos</string>\n  <string name=\"textStatisticsShowMore\">Mostrar mais</string>\n  <string name=\"textStatisticsRatings\">Avaliado recentemente</string>\n  <string name=\"textStatisticsHours\">Horas</string>\n  <string name=\"textStatisticsEpisodes\">Episódios</string>\n  <string name=\"textStatisticsSeasons\">Temporadas</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatistics\">Статистика</string>\n  <string name=\"textStatisticsEmpty\">По крайней мере один фильм должен присутствовать в <b>Мои фильмы</b>\\nперед отображением статистики.</string>\n  <string name=\"textStatisticsMostWatchedShows\">Самые просматриваемые сериалы</string>\n  <string name=\"textStatisticsTotalTimeSpent\">Общее время, потраченное на просмотр</string>\n  <string name=\"textStatisticsTotalTimeItIs\">это</string>\n  <string name=\"textStatisticsTotalTimeSpentHours\">%s часов</string>\n  <string name=\"textStatisticsTotalTimeSpentMinutes\">%s минут</string>\n  <string name=\"textStatisticsTotalTimeSpentDays\">которые составляют около %s дней.</string>\n  <string name=\"textStatisticsTotalEpisodes\">Количество просмотренных эпизодов</string>\n  <string name=\"textStatisticsTotalEpisodesCount\">%s эпизодов</string>\n  <string name=\"textStatisticsTotalEpisodesShowsCount\">из %s сериалов.</string>\n  <string name=\"textStatisticsTopGenre\">Топ жанров</string>\n  <string name=\"textStatisticsTopGenreSubValue\">кажется, это ваша чашка чая. Нажмите, чтобы увидеть больше.</string>\n  <string name=\"textStatisticsTopGenreSubValue2\">кажется, это ваша чашка чая.</string>\n  <string name=\"textStatisticsShowMore\">Показать больше</string>\n  <string name=\"textStatisticsRatings\">Недавно оцененные</string>\n  <string name=\"textStatisticsHours\">Часов</string>\n  <string name=\"textStatisticsEpisodes\">Эпизоды</string>\n  <string name=\"textStatisticsSeasons\">Сезоны</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"statisticsMostWatchedItemImageHeight\">120dp</dimen>\n  <dimen name=\"statisticsMostWatchedItemImageWidth\">80dp</dimen>\n</resources>"
  },
  {
    "path": "ui-statistics/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatistics\">İstatistikler</string>\n  <string name=\"textStatisticsEmpty\">İstatistiklerin görüntülenebilmesi için <b>Dizilerim</b>\\nbölümünde en az bir dizinin bulunması gerekir.</string>\n  <string name=\"textStatisticsMostWatchedShows\">En Çok Görüntülenen Diziler</string>\n  <string name=\"textStatisticsTotalTimeSpent\">Harcanan Toplam Süre</string>\n  <string name=\"textStatisticsTotalTimeItIs\">ve</string>\n  <string name=\"textStatisticsTotalTimeSpentHours\">%s saat</string>\n  <string name=\"textStatisticsTotalTimeSpentMinutes\">%s dakika</string>\n  <string name=\"textStatisticsTotalTimeSpentDays\">yaklaşık %s güne karşılık gelmektedir.</string>\n  <string name=\"textStatisticsTotalEpisodes\">İzlenen Toplam Bölüm Sayısı</string>\n  <string name=\"textStatisticsTotalEpisodesCount\">%s bölüm</string>\n  <string name=\"textStatisticsTotalEpisodesShowsCount\">%s diziye yayılmıştır.</string>\n  <string name=\"textStatisticsTopGenre\">En Popüler Türler</string>\n  <string name=\"textStatisticsTopGenreSubValue\">tam sizin zevkinize uygun görünüyor. Daha fazlasını görmek için dokunun.</string>\n  <string name=\"textStatisticsTopGenreSubValue2\">tam sizin zevkinize uygun görünüyor.</string>\n  <string name=\"textStatisticsShowMore\">Daha Fazlasını Göster</string>\n  <string name=\"textStatisticsRatings\">En Son Derecelendirilenler</string>\n  <string name=\"textStatisticsHours\">Saat</string>\n  <string name=\"textStatisticsEpisodes\">Bölüm</string>\n  <string name=\"textStatisticsSeasons\">Sezon</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatistics\">Статистика</string>\n  <string name=\"textStatisticsEmpty\">Принаймні один серіал має бути присутній в <b>Мої Серіали</b>\\nдля відображення статистики.</string>\n  <string name=\"textStatisticsMostWatchedShows\">Найпопулярніші серіали</string>\n  <string name=\"textStatisticsTotalTimeSpent\">Загальний час, витрачений на перегляд</string>\n  <string name=\"textStatisticsTotalTimeItIs\">тобто</string>\n  <string name=\"textStatisticsTotalTimeSpentHours\">%s годин</string>\n  <string name=\"textStatisticsTotalTimeSpentMinutes\">%s хвилин</string>\n  <string name=\"textStatisticsTotalTimeSpentDays\">це приблизно %s днів.</string>\n  <string name=\"textStatisticsTotalEpisodes\">Загалом переглянуто серій</string>\n  <string name=\"textStatisticsTotalEpisodesCount\">%s серій</string>\n  <string name=\"textStatisticsTotalEpisodesShowsCount\">в %s серіалах.</string>\n  <string name=\"textStatisticsTopGenre\">Топ жанрів</string>\n  <string name=\"textStatisticsTopGenreSubValue\">здається це ваші фаворити. Торкніться, щоб переглянути більше.</string>\n  <string name=\"textStatisticsTopGenreSubValue2\">здається це ваші фаворити.</string>\n  <string name=\"textStatisticsShowMore\">Показати більше</string>\n  <string name=\"textStatisticsRatings\">Нещодавно оцінені</string>\n  <string name=\"textStatisticsHours\">Годин</string>\n  <string name=\"textStatisticsEpisodes\">Серії</string>\n  <string name=\"textStatisticsSeasons\">Сезони</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <string name=\"textStatistics\">Thống kê</string>\n  <string name=\"textStatisticsEmpty\">Ít nhất một chương trình phải có trong <b>Chương trình của tôi</b>\\ntrước khi số liệu thống kê có thể được hiển thị.</string>\n  <string name=\"textStatisticsMostWatchedShows\">Chương trình được xem nhiều nhất</string>\n  <string name=\"textStatisticsTotalTimeSpent\">Tổng thời gian đã xem</string>\n  <string name=\"textStatisticsTotalTimeItIs\">và đó là</string>\n  <string name=\"textStatisticsTotalTimeSpentHours\">%s giờ</string>\n  <string name=\"textStatisticsTotalTimeSpentMinutes\">%s phút</string>\n  <string name=\"textStatisticsTotalTimeSpentDays\">tức là khoảng %s ngày.</string>\n  <string name=\"textStatisticsTotalEpisodes\">Tổng số tập đã xem</string>\n  <string name=\"textStatisticsTotalEpisodesCount\">%s tập</string>\n  <string name=\"textStatisticsTotalEpisodesShowsCount\">trải rộng trên %s chương trình.</string>\n  <string name=\"textStatisticsTopGenre\">Thể loại hàng đầu</string>\n  <string name=\"textStatisticsTopGenreSubValue\">dường như là điều bạn thích. Nhấn để xem thêm.</string>\n  <string name=\"textStatisticsTopGenreSubValue2\">có vẻ như là điều bạn thích.</string>\n  <string name=\"textStatisticsShowMore\">Hiển thị thêm</string>\n  <string name=\"textStatisticsRatings\">Được xếp hạng gần đây</string>\n  <string name=\"textStatisticsHours\">Giờ</string>\n  <string name=\"textStatisticsEpisodes\">Các tập</string>\n  <string name=\"textStatisticsSeasons\">Các mùa</string>\n\n</resources>\n"
  },
  {
    "path": "ui-statistics/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatistics\">统计信息</string>\n  <string name=\"textStatisticsEmpty\"><b>我的剧集</b>\\n中至少要有一部剧集，才能显示统计数据。</string>\n  <string name=\"textStatisticsMostWatchedShows\">观看最多的剧</string>\n  <string name=\"textStatisticsTotalTimeSpent\">总观看时间</string>\n  <string name=\"textStatisticsTotalTimeItIs\">相当于</string>\n  <string name=\"textStatisticsTotalTimeSpentHours\">%s 小时</string>\n  <string name=\"textStatisticsTotalTimeSpentMinutes\">%s 分钟</string>\n  <string name=\"textStatisticsTotalTimeSpentDays\">大约是 %s 天。</string>\n  <string name=\"textStatisticsTotalEpisodes\">已观看的单集总数</string>\n  <string name=\"textStatisticsTotalEpisodesCount\">%s 集</string>\n  <string name=\"textStatisticsTotalEpisodesShowsCount\">包含 %s 部剧。</string>\n  <string name=\"textStatisticsTopGenre\">最爱类别</string>\n  <string name=\"textStatisticsTopGenreSubValue\">似乎这正符合您的口味。点击查看更多。</string>\n  <string name=\"textStatisticsTopGenreSubValue2\">似乎这正符合您的口味。</string>\n  <string name=\"textStatisticsShowMore\">显示更多</string>\n  <string name=\"textStatisticsRatings\">最近评分</string>\n  <string name=\"textStatisticsHours\">小时</string>\n  <string name=\"textStatisticsEpisodes\">集</string>\n  <string name=\"textStatisticsSeasons\">季</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics/src/test/java/BaseMockTest.kt",
    "content": "import com.michaldrabik.common_test.MainDispatcherRule\nimport io.mockk.MockKAnnotations\nimport org.junit.Before\nimport org.junit.Rule\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nabstract class BaseMockTest {\n\n  @get:Rule\n  val mainDispatcherRule = MainDispatcherRule()\n\n  @Before\n  open fun setUp() {\n    MockKAnnotations.init(this)\n  }\n}\n"
  },
  {
    "path": "ui-statistics/src/test/java/TestData.kt",
    "content": "import com.michaldrabik.data_local.database.model.Episode\n\nobject TestData {\n  fun createEpisode() = Episode(1, 1, 1, 1, \"\", 1, 1, 1, 1, \"\", \"\", null, 0, 0F, 60, 0, false, null)\n}\n"
  },
  {
    "path": "ui-statistics/src/test/java/com/michaldrabik/ui_statistics/StatisticsViewModelTest.kt",
    "content": "package com.michaldrabik.ui_statistics\n\nimport BaseMockTest\nimport TestData\nimport androidx.lifecycle.viewModelScope\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.data_local.LocalDataSource\nimport com.michaldrabik.repository.TranslationsRepository\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.mappers.Mappers\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageFamily\nimport com.michaldrabik.ui_model.ImageSource\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.TraktRating\nimport com.michaldrabik.ui_statistics.cases.StatisticsLoadRatingsCase\nimport com.michaldrabik.ui_statistics.views.ratings.recycler.StatisticsRatingItem\nimport io.mockk.coEvery\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.impl.annotations.RelaxedMockK\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.flow.toList\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.runTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n@OptIn(ExperimentalCoroutinesApi::class)\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass StatisticsViewModelTest : BaseMockTest() {\n\n  @MockK lateinit var ratingsCase: StatisticsLoadRatingsCase\n  @MockK lateinit var showsRepository: ShowsRepository\n  @MockK lateinit var translationsRepository: TranslationsRepository\n  @MockK lateinit var imagesProvider: ShowImagesProvider\n  @RelaxedMockK lateinit var database: LocalDataSource\n  @RelaxedMockK lateinit var mappers: Mappers\n\n  private lateinit var SUT: StatisticsViewModel\n\n  private val stateResult = mutableListOf<StatisticsUiState>()\n  private val messagesResult = mutableListOf<MessageEvent>()\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n\n    coEvery { translationsRepository.getLanguage() } returns \"en\"\n    coEvery { imagesProvider.findCachedImage(any(), any()) } returns Image.createAvailable(\n      Ids.EMPTY,\n      ImageType.POSTER,\n      ImageFamily.SHOW,\n      \"\",\n      ImageSource.TMDB\n    )\n\n    SUT = StatisticsViewModel(\n      ratingsCase,\n      showsRepository,\n      translationsRepository,\n      imagesProvider,\n      database,\n      mappers\n    )\n  }\n\n  @After\n  fun tearDown() {\n    stateResult.clear()\n    messagesResult.clear()\n    SUT.viewModelScope.cancel()\n  }\n\n  @Test\n  internal fun `Should load ratings`() = runTest {\n    val movieItem = StatisticsRatingItem(Show.EMPTY, Image.createUnknown(ImageType.POSTER), false, TraktRating.EMPTY)\n    coEvery { ratingsCase.loadRatings() } returns listOf(movieItem)\n\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.loadRatings()\n\n    assertThat(stateResult.last().ratings?.size).isEqualTo(1)\n    assertThat(stateResult.last().ratings).contains(movieItem)\n\n    job.cancel()\n  }\n\n  @Test\n  internal fun `Should load empty ratings in case of error`() = runTest {\n    coEvery { ratingsCase.loadRatings() } throws Throwable(\"Test error\")\n\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.loadRatings()\n\n    assertThat(stateResult.last().ratings).isEmpty()\n\n    job.cancel()\n  }\n\n  @Test\n  internal fun `Should load statistics properly`() = runTest {\n    val shows = listOf(\n      Show.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = IdTrakt(1)), runtime = 1, genres = listOf(\"war\", \"drama\")),\n      Show.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = IdTrakt(2)), runtime = 2, genres = listOf(\"war\", \"animation\")),\n      Show.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = IdTrakt(3)), runtime = 3, genres = listOf(\"war\", \"animation\")),\n    )\n\n    val shows2 = listOf(\n      Show.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = IdTrakt(4)), runtime = 1, genres = listOf(\"war\", \"drama\")),\n      Show.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = IdTrakt(5)), runtime = 2, genres = listOf(\"war\", \"animation\")),\n      Show.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = IdTrakt(6)), runtime = 3, genres = listOf(\"war\", \"animation\")),\n    )\n\n    val shows3 = listOf(\n      Show.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = IdTrakt(7)), runtime = 1, genres = listOf(\"war\", \"drama\")),\n      Show.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = IdTrakt(8)), runtime = 2, genres = listOf(\"war\", \"animation\")),\n      Show.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = IdTrakt(9)), runtime = 3, genres = listOf(\"war\", \"animation\")),\n    )\n\n    coEvery { showsRepository.myShows.loadAll() } returns shows\n    coEvery { showsRepository.watchlistShows.loadAll() } returns shows2\n    coEvery { showsRepository.hiddenShows.loadAll() } returns shows3\n\n    coEvery { database.episodes.getAllWatchedForShows(any()) } returns listOf(\n      TestData.createEpisode().copy(idShowTrakt = 1, runtime = 5),\n      TestData.createEpisode().copy(idShowTrakt = 2, runtime = 6),\n      TestData.createEpisode().copy(idShowTrakt = 3, runtime = 7),\n      TestData.createEpisode().copy(idShowTrakt = 3, runtime = 7),\n    )\n\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.loadData(limit = 0, initialDelay = 0)\n\n    val result = stateResult.last()\n    assertThat(result.mostWatchedShows).hasSize(5)\n    assertThat(result.mostWatchedTotalCount).isEqualTo(9)\n    assertThat(result.totalTimeSpentMinutes).isEqualTo(25)\n    assertThat(result.totalWatchedEpisodes).isEqualTo(4)\n    assertThat(result.totalWatchedEpisodesShows).isEqualTo(3)\n    assertThat(result.topGenres?.size).isEqualTo(3)\n    assertThat(result.topGenres).containsExactly(Genre.WAR, Genre.ANIMATION, Genre.DRAMA)\n\n    job.cancel()\n  }\n}\n"
  },
  {
    "path": "ui-statistics/src/test/java/com/michaldrabik/ui_statistics/cases/StatisticsLoadRatingsCaseTest.kt",
    "content": "package com.michaldrabik.ui_statistics.cases\n\nimport BaseMockTest\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.images.ShowImagesProvider\nimport com.michaldrabik.repository.shows.ShowsRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Show\nimport com.michaldrabik.ui_model.TraktRating\nimport com.michaldrabik.ui_statistics.views.ratings.recycler.StatisticsRatingItem\nimport io.mockk.Runs\nimport io.mockk.coEvery\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.just\nimport kotlinx.coroutines.test.runBlockingTest\nimport org.junit.Before\nimport org.junit.Test\nimport java.time.ZoneId\nimport java.time.ZonedDateTime\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass StatisticsLoadRatingsCaseTest : BaseMockTest() {\n\n  @MockK lateinit var userTraktManager: UserTraktManager\n  @MockK lateinit var showsRepository: ShowsRepository\n  @MockK lateinit var ratingsRepository: RatingsRepository\n  @MockK lateinit var showImagesProvider: ShowImagesProvider\n\n  private lateinit var SUT: StatisticsLoadRatingsCase\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n\n    SUT = StatisticsLoadRatingsCase(\n      userTraktManager,\n      showsRepository,\n      ratingsRepository,\n      showImagesProvider\n    )\n  }\n\n  @Test\n  fun `Should return empty list if not authorized`() = runBlockingTest {\n    coEvery { userTraktManager.isAuthorized() } returns false\n\n    val result = SUT.loadRatings()\n\n    assertThat(result).isEmpty()\n  }\n\n  @Test\n  fun `Should load sorted ratings properly`() = runBlockingTest {\n    val ratings = listOf(\n      TraktRating.EMPTY.copy(IdTrakt(1), ratedAt = ZonedDateTime.of(2000, 3, 1, 1, 1, 1, 1, ZoneId.systemDefault())),\n      TraktRating.EMPTY.copy(IdTrakt(2), ratedAt = ZonedDateTime.of(2001, 3, 1, 1, 1, 1, 1, ZoneId.systemDefault())),\n      TraktRating.EMPTY.copy(IdTrakt(3), ratedAt = ZonedDateTime.of(2002, 3, 1, 1, 1, 1, 1, ZoneId.systemDefault())),\n    )\n\n    val shows = listOf(\n      Show.EMPTY.copy(Ids.EMPTY.copy(trakt = IdTrakt(1))),\n      Show.EMPTY.copy(Ids.EMPTY.copy(trakt = IdTrakt(2))),\n      Show.EMPTY.copy(Ids.EMPTY.copy(trakt = IdTrakt(3))),\n      Show.EMPTY.copy(Ids.EMPTY.copy(trakt = IdTrakt(4))),\n      Show.EMPTY.copy(Ids.EMPTY.copy(trakt = IdTrakt(5))),\n    )\n\n    val image = Image.createUnknown(ImageType.POSTER)\n\n    coEvery { userTraktManager.checkAuthorization() } just Runs\n    coEvery { userTraktManager.isAuthorized() } returns true\n    coEvery { ratingsRepository.shows.loadShowsRatings() } returns ratings\n    coEvery { showsRepository.myShows.loadAll(any()) } returns shows\n    coEvery { showImagesProvider.findCachedImage(any(), any()) } returns image\n\n    val result = SUT.loadRatings()\n\n    assertThat(result).hasSize(3)\n    assertThat(result).containsExactly(\n      StatisticsRatingItem(shows[2], image, false, ratings[2]),\n      StatisticsRatingItem(shows[1], image, false, ratings[1]),\n      StatisticsRatingItem(shows[0], image, false, ratings[0])\n    )\n  }\n}\n"
  },
  {
    "path": "ui-statistics-movies/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-statistics-movies/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_statistics_movies'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-local')\n  implementation project(':ui-base')\n  implementation project(':repository')\n  implementation project(':ui-model')\n  implementation project(':ui-navigation')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  testImplementation project(':common-test')\n  testImplementation libs.bundles.testing\n  androidTestImplementation libs.android.test.runner\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-statistics-movies/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "ui-statistics-movies/src/main/java/com/michaldrabik/ui_statistics_movies/StatisticsMoviesFragment.kt",
    "content": "package com.michaldrabik.ui_statistics_movies\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.os.bundleOf\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.fadeIf\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_navigation.java.NavigationArgs.ARG_MOVIE_ID\nimport com.michaldrabik.ui_statistics_movies.databinding.FragmentStatisticsMoviesBinding\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.launch\n\n@AndroidEntryPoint\nclass StatisticsMoviesFragment : BaseFragment<StatisticsMoviesViewModel>(R.layout.fragment_statistics_movies) {\n\n  override val viewModel by viewModels<StatisticsMoviesViewModel>()\n  private val binding by viewBinding(FragmentStatisticsMoviesBinding::bind)\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupStatusBar()\n\n    viewLifecycleOwner.lifecycleScope.launch {\n      repeatOnLifecycle(Lifecycle.State.STARTED) {\n        with(viewModel) {\n          launch { uiState.collect { render(it) } }\n          if (!isInitialized) {\n            loadData()\n            isInitialized = true\n          }\n          loadRatings()\n        }\n      }\n    }\n  }\n\n  private fun setupView() {\n    with(binding) {\n      statisticsMoviesToolbar.setNavigationOnClickListener { activity?.onBackPressed() }\n      statisticsMoviesRatings.onMovieClickListener = {\n        openMovieDetails(it.movie.traktId)\n      }\n    }\n  }\n\n  private fun setupStatusBar() {\n    binding.statisticsMoviesRoot.doOnApplyWindowInsets { view, insets, padding, _ ->\n      val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top\n      view.updatePadding(top = padding.top + inset)\n    }\n  }\n\n  private fun render(uiState: StatisticsMoviesUiState) {\n    uiState.run {\n      with(binding) {\n        statisticsMoviesTotalTimeSpent.bind(totalTimeSpentMinutes ?: 0)\n        statisticsMoviesTotalMovies.bind(totalWatchedMovies ?: 0)\n        statisticsMoviesTopGenres.bind(topGenres ?: emptyList())\n        statisticsMoviesRatings.bind(ratings ?: emptyList())\n        ratings?.let { statisticsMoviesRatings.visibleIf(it.isNotEmpty()) }\n        totalWatchedMovies?.let {\n          statisticsMoviesContent.fadeIf(it > 0)\n          statisticsMoviesEmptyView.root.fadeIf(it <= 0)\n        }\n      }\n    }\n  }\n\n  private fun openMovieDetails(traktId: Long) {\n    val bundle = bundleOf(ARG_MOVIE_ID to traktId)\n    navigateTo(R.id.actionStatisticsMoviesFragmentToMovieDetailsFragment, bundle)\n  }\n}\n"
  },
  {
    "path": "ui-statistics-movies/src/main/java/com/michaldrabik/ui_statistics_movies/StatisticsMoviesUiState.kt",
    "content": "package com.michaldrabik.ui_statistics_movies\n\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_statistics_movies.views.ratings.recycler.StatisticsMoviesRatingItem\n\ndata class StatisticsMoviesUiState(\n  val totalTimeSpentMinutes: Int? = null,\n  val totalWatchedMovies: Int? = null,\n  val topGenres: List<Genre>? = null,\n  val ratings: List<StatisticsMoviesRatingItem>? = null,\n)\n"
  },
  {
    "path": "ui-statistics-movies/src/main/java/com/michaldrabik/ui_statistics_movies/StatisticsMoviesViewModel.kt",
    "content": "package com.michaldrabik.ui_statistics_movies\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_statistics_movies.cases.StatisticsMoviesLoadRatingsCase\nimport com.michaldrabik.ui_statistics_movies.views.ratings.recycler.StatisticsMoviesRatingItem\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass StatisticsMoviesViewModel @Inject constructor(\n  private val ratingsCase: StatisticsMoviesLoadRatingsCase,\n  private val moviesRepository: MoviesRepository,\n) : ViewModel() {\n\n  private val totalTimeSpentState = MutableStateFlow<Int?>(null)\n  private val totalWatchedMoviesState = MutableStateFlow<Int?>(null)\n  private val topGenresState = MutableStateFlow<List<Genre>?>(null)\n  private val ratingsState = MutableStateFlow<List<StatisticsMoviesRatingItem>?>(null)\n\n  fun loadData(initialDelay: Long = 150L) {\n    viewModelScope.launch {\n      val myMovies = moviesRepository.myMovies.loadAll().distinctBy { it.traktId }\n      val genres = extractTopGenres(myMovies)\n\n      delay(initialDelay) // Let transition finish peacefully.\n\n      totalWatchedMoviesState.value = myMovies.count()\n      totalTimeSpentState.value = myMovies.sumOf { it.runtime }\n      topGenresState.value = genres\n    }\n  }\n\n  fun loadRatings() {\n    viewModelScope.launch {\n      try {\n        ratingsState.value = ratingsCase.loadRatings()\n      } catch (t: Throwable) {\n        ratingsState.value = emptyList()\n      }\n    }\n  }\n\n  private fun extractTopGenres(movies: List<Movie>) =\n    movies\n      .flatMap { it.genres }\n      .asSequence()\n      .filter { it.isNotBlank() }\n      .distinct()\n      .map { genre -> Pair(Genre.fromSlug(genre), movies.count { genre in it.genres }) }\n      .sortedByDescending { it.second }\n      .map { it.first }\n      .toList()\n      .filterNotNull()\n\n  val uiState = combine(\n    totalWatchedMoviesState,\n    totalTimeSpentState,\n    topGenresState,\n    ratingsState,\n  ) { s1, s2, s3, s4 ->\n    StatisticsMoviesUiState(\n      totalWatchedMovies = s1,\n      totalTimeSpentMinutes = s2,\n      topGenres = s3,\n      ratings = s4,\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = StatisticsMoviesUiState()\n  )\n}\n"
  },
  {
    "path": "ui-statistics-movies/src/main/java/com/michaldrabik/ui_statistics_movies/cases/StatisticsMoviesLoadRatingsCase.kt",
    "content": "package com.michaldrabik.ui_statistics_movies.cases\n\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_statistics_movies.views.ratings.recycler.StatisticsMoviesRatingItem\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass StatisticsMoviesLoadRatingsCase @Inject constructor(\n  private val userTraktManager: UserTraktManager,\n  private val moviesRepository: MoviesRepository,\n  private val ratingsRepository: RatingsRepository,\n  private val imagesProvider: MovieImagesProvider\n) {\n\n  companion object {\n    private const val LIMIT = 25\n  }\n\n  suspend fun loadRatings(): List<StatisticsMoviesRatingItem> {\n    if (!userTraktManager.isAuthorized()) {\n      return emptyList()\n    }\n\n    val ratings = ratingsRepository.movies.loadMoviesRatings()\n    val ratingsIds = ratings.map { it.idTrakt }\n    val myMovies = moviesRepository.myMovies.loadAll(ratingsIds).distinctBy { it.traktId }\n\n    return ratings\n      .filter { rating -> myMovies.any { it.traktId == rating.idTrakt.id } }\n      .take(LIMIT)\n      .map { rating ->\n        val movie = myMovies.first { it.traktId == rating.idTrakt.id }\n        StatisticsMoviesRatingItem(\n          isLoading = false,\n          movie = movie,\n          image = imagesProvider.findCachedImage(movie, ImageType.POSTER),\n          rating = rating\n        )\n      }.sortedByDescending { it.rating.ratedAt }\n  }\n}\n"
  },
  {
    "path": "ui-statistics-movies/src/main/java/com/michaldrabik/ui_statistics_movies/views/StatisticsMoviesTopGenresView.kt",
    "content": "package com.michaldrabik.ui_statistics_movies.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport com.google.android.material.card.MaterialCardView\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_statistics_movies.R\nimport com.michaldrabik.ui_statistics_movies.databinding.ViewStatisticsMoviesCardTopGenreBinding\n\n@SuppressLint(\"SetTextI18n\")\nclass StatisticsMoviesTopGenresView : MaterialCardView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewStatisticsMoviesCardTopGenreBinding.inflate(LayoutInflater.from(context), this)\n\n  private var topGenres = emptyList<Genre>()\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n    setCardBackgroundColor(context.colorFromAttr(R.attr.colorCardBackground))\n    cardElevation = context.dimenToPx(R.dimen.elevationSmall).toFloat()\n    strokeWidth = 0\n    onClick {\n      showGenres(10)\n      isClickable = false\n      binding.viewMoviesTopGenresSubValue.text = context.getString(R.string.textStatisticsMoviesTopGenreSubValue2)\n    }\n  }\n\n  fun bind(genres: List<Genre>) {\n    topGenres = genres.toList()\n    showGenres(3)\n  }\n\n  private fun showGenres(limit: Int) {\n    binding.viewMoviesTopGenresValue.text = topGenres\n      .take(limit)\n      .joinToString(\"\\n\") {\n        context.getString(it.displayName)\n      }\n  }\n}\n"
  },
  {
    "path": "ui-statistics-movies/src/main/java/com/michaldrabik/ui_statistics_movies/views/StatisticsMoviesTotalMoviesView.kt",
    "content": "package com.michaldrabik.ui_statistics_movies.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport com.google.android.material.card.MaterialCardView\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_statistics_movies.R\nimport com.michaldrabik.ui_statistics_movies.databinding.ViewStatisticsMoviesCardTotalMoviesBinding\nimport java.text.NumberFormat\nimport java.util.Locale\n\n@SuppressLint(\"SetTextI18n\")\nclass StatisticsMoviesTotalMoviesView : MaterialCardView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewStatisticsMoviesCardTotalMoviesBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n    setCardBackgroundColor(context.colorFromAttr(R.attr.colorCardBackground))\n    cardElevation = context.dimenToPx(R.dimen.elevationSmall).toFloat()\n    strokeWidth = 0\n  }\n\n  fun bind(moviesCount: Int) {\n    val formatter = NumberFormat.getNumberInstance(Locale.ENGLISH)\n    binding.viewMoviesTotalEpisodesValue.text = context.getString(\n      R.string.textStatisticsMoviesTotalMoviesCount,\n      formatter.format(moviesCount)\n    )\n  }\n}\n"
  },
  {
    "path": "ui-statistics-movies/src/main/java/com/michaldrabik/ui_statistics_movies/views/StatisticsMoviesTotalTimeSpentView.kt",
    "content": "package com.michaldrabik.ui_statistics_movies.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport com.google.android.material.card.MaterialCardView\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_statistics_movies.R\nimport com.michaldrabik.ui_statistics_movies.databinding.ViewStatisticsMoviesCardTotalTimeBinding\nimport java.text.NumberFormat\nimport java.util.Locale.ENGLISH\nimport java.util.concurrent.TimeUnit\n\n@SuppressLint(\"SetTextI18n\")\nclass StatisticsMoviesTotalTimeSpentView : MaterialCardView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewStatisticsMoviesCardTotalTimeBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n    setCardBackgroundColor(context.colorFromAttr(R.attr.colorCardBackground))\n    cardElevation = context.dimenToPx(R.dimen.elevationSmall).toFloat()\n    strokeWidth = 0\n  }\n\n  fun bind(timeMinutes: Int) {\n    val formatter = NumberFormat.getNumberInstance(ENGLISH)\n\n    val hours = TimeUnit.MINUTES.toHours(timeMinutes.toLong())\n    val days = TimeUnit.HOURS.toDays(hours)\n\n    with(binding) {\n      viewMoviesTotalTimeSpentHoursValue.text =\n        context.getString(R.string.textStatisticsMoviesTotalTimeSpentHours, formatter.format(hours))\n\n      viewMoviesTotalTimeSpentMinutesValue.text =\n        context.getString(R.string.textStatisticsMoviesTotalTimeSpentMinutes, formatter.format(timeMinutes))\n\n      viewMoviesTotalTimeSpentSubValue.text =\n        context.getString(R.string.textStatisticsMoviesTotalTimeSpentDays, formatter.format(days))\n    }\n  }\n}\n"
  },
  {
    "path": "ui-statistics-movies/src/main/java/com/michaldrabik/ui_statistics_movies/views/ratings/StatisticsMoviesRateItemView.kt",
    "content": "package com.michaldrabik.ui_statistics_movies.views.ratings\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.michaldrabik.ui_base.common.views.MovieView\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_statistics_movies.R\nimport com.michaldrabik.ui_statistics_movies.databinding.ViewStatisticsMoviesRateItemBinding\nimport com.michaldrabik.ui_statistics_movies.views.ratings.recycler.StatisticsMoviesRatingItem\n\nclass StatisticsMoviesRateItemView : MovieView<StatisticsMoviesRatingItem> {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewStatisticsMoviesRateItemBinding.inflate(LayoutInflater.from(context), this)\n\n  init {\n    val width = context.dimenToPx(R.dimen.statisticsMoviesRatingItemWidth)\n    layoutParams = LayoutParams(width, WRAP_CONTENT)\n    clipChildren = false\n    binding.viewMovieRateItemImageLayout.onClick { itemClickListener?.invoke(item) }\n  }\n\n  override val imageView: ImageView = binding.viewMovieRateItemImage\n  override val placeholderView: ImageView = binding.viewMovieRateItemPlaceholder\n\n  private lateinit var item: StatisticsMoviesRatingItem\n\n  override fun bind(item: StatisticsMoviesRatingItem) {\n    this.item = item\n    clear()\n    with(binding) {\n      viewMovieRateItemTitle.text = item.movie.title\n      viewMovieRateItemRating.text = \"${item.rating.rating}\"\n    }\n    loadImage(item)\n  }\n\n  private fun clear() {\n    with(binding) {\n      viewMovieRateItemTitle.gone()\n      viewMovieRateItemPlaceholder.gone()\n      Glide.with(this@StatisticsMoviesRateItemView).clear(viewMovieRateItemImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-statistics-movies/src/main/java/com/michaldrabik/ui_statistics_movies/views/ratings/StatisticsMoviesRatingsView.kt",
    "content": "package com.michaldrabik.ui_statistics_movies.views.ratings\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL\nimport androidx.recyclerview.widget.SimpleItemAnimator\nimport com.google.android.material.card.MaterialCardView\nimport com.michaldrabik.ui_base.common.MovieListItem\nimport com.michaldrabik.ui_base.utilities.extensions.addDivider\nimport com.michaldrabik.ui_base.utilities.extensions.colorFromAttr\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_statistics_movies.R\nimport com.michaldrabik.ui_statistics_movies.databinding.ViewStatisticsMoviesCardRatingsBinding\nimport com.michaldrabik.ui_statistics_movies.views.ratings.recycler.StatisticsMoviesRatingItem\nimport com.michaldrabik.ui_statistics_movies.views.ratings.recycler.StatisticsMoviesRatingsAdapter\n\n@SuppressLint(\"SetTextI18n\")\nclass StatisticsMoviesRatingsView : MaterialCardView {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewStatisticsMoviesCardRatingsBinding.inflate(LayoutInflater.from(context), this)\n\n  private val adapter by lazy {\n    StatisticsMoviesRatingsAdapter(\n      itemClickListener = { onMovieClickListener?.invoke(it) }\n    )\n  }\n  private val layoutManager by lazy { LinearLayoutManager(context, HORIZONTAL, false) }\n\n  var onMovieClickListener: ((MovieListItem) -> Unit)? = null\n\n  init {\n    layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)\n    clipToPadding = false\n    clipChildren = false\n    cardElevation = context.dimenToPx(R.dimen.elevationSmall).toFloat()\n    strokeWidth = 0\n    setCardBackgroundColor(context.colorFromAttr(R.attr.colorCardBackground))\n    setupRecycler()\n  }\n\n  private fun setupRecycler() {\n    binding.viewMoviesRatingsRecycler.apply {\n      setHasFixedSize(true)\n      adapter = this@StatisticsMoviesRatingsView.adapter\n      layoutManager = this@StatisticsMoviesRatingsView.layoutManager\n      (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false\n      addDivider(R.drawable.divider_statistics_ratings, HORIZONTAL)\n    }\n  }\n\n  fun bind(items: List<StatisticsMoviesRatingItem>) {\n    adapter.setItems(items)\n  }\n}\n"
  },
  {
    "path": "ui-statistics-movies/src/main/java/com/michaldrabik/ui_statistics_movies/views/ratings/recycler/StatisticsMoviesRatingItem.kt",
    "content": "package com.michaldrabik.ui_statistics_movies.views.ratings.recycler\n\nimport com.michaldrabik.ui_base.common.MovieListItem\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.TraktRating\n\ndata class StatisticsMoviesRatingItem(\n  override val movie: Movie,\n  override val image: Image,\n  override val isLoading: Boolean,\n  val rating: TraktRating\n) : MovieListItem\n"
  },
  {
    "path": "ui-statistics-movies/src/main/java/com/michaldrabik/ui_statistics_movies/views/ratings/recycler/StatisticsMoviesRatingsAdapter.kt",
    "content": "package com.michaldrabik.ui_statistics_movies.views.ratings.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_base.BaseMovieAdapter\nimport com.michaldrabik.ui_statistics_movies.views.ratings.StatisticsMoviesRateItemView\n\nclass StatisticsMoviesRatingsAdapter(\n  private val itemClickListener: (StatisticsMoviesRatingItem) -> Unit\n) : BaseMovieAdapter<StatisticsMoviesRatingItem>() {\n\n  override val asyncDiffer = AsyncListDiffer(this, StatisticsMoviesRatingsDiffCallback())\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    ViewHolderShow(\n      StatisticsMoviesRateItemView(parent.context).apply {\n        itemClickListener = this@StatisticsMoviesRatingsAdapter.itemClickListener\n      }\n    )\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    (holder.itemView as StatisticsMoviesRateItemView).bind(item)\n  }\n\n  class ViewHolderShow(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-statistics-movies/src/main/java/com/michaldrabik/ui_statistics_movies/views/ratings/recycler/StatisticsMoviesRatingsDiffCallback.kt",
    "content": "package com.michaldrabik.ui_statistics_movies.views.ratings.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass StatisticsMoviesRatingsDiffCallback : DiffUtil.ItemCallback<StatisticsMoviesRatingItem>() {\n\n  override fun areItemsTheSame(oldItem: StatisticsMoviesRatingItem, newItem: StatisticsMoviesRatingItem) =\n    oldItem.movie.ids.trakt == newItem.movie.ids.trakt\n\n  override fun areContentsTheSame(oldItem: StatisticsMoviesRatingItem, newItem: StatisticsMoviesRatingItem) =\n    oldItem.rating.rating == newItem.rating.rating &&\n      oldItem.image == newItem.image\n}\n"
  },
  {
    "path": "ui-statistics-movies/src/main/res/drawable/divider_statistics_ratings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <size\n    android:width=\"8dp\"\n    android:height=\"8dp\"\n    />\n</shape>"
  },
  {
    "path": "ui-statistics-movies/src/main/res/layout/fragment_statistics_movies.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView\n  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/statisticsMoviesRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  android:fillViewport=\"true\"\n  android:overScrollMode=\"never\"\n  android:paddingBottom=\"@dimen/spaceSmall\"\n  android:scrollbars=\"none\"\n  tools:background=\"@color/colorBackground\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/statisticsMoviesCoordinator\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    >\n\n    <com.google.android.material.appbar.MaterialToolbar\n      android:id=\"@+id/statisticsMoviesToolbar\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:background=\"?android:windowBackground\"\n      android:elevation=\"@dimen/elevationNormal\"\n      app:contentInsetStartWithNavigation=\"0dp\"\n      app:layout_constraintBottom_toTopOf=\"@id/statisticsMoviesTotalTimeSpent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:navigationIcon=\"@drawable/ic_arrow_back\"\n      app:title=\"@string/textStatisticsMovies\"\n      />\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:id=\"@+id/statisticsMoviesContent\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:clipChildren=\"false\"\n      android:clipToPadding=\"false\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      >\n\n      <com.michaldrabik.ui_statistics_movies.views.StatisticsMoviesTotalTimeSpentView\n        android:id=\"@+id/statisticsMoviesTotalTimeSpent\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceMedium\"\n        android:layout_marginTop=\"@dimen/statisticsMoviesTopPadding\"\n        android:layout_marginEnd=\"@dimen/spaceMedium\"\n        app:layout_constraintBottom_toTopOf=\"@id/statisticsMoviesTotalMovies\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_bias=\"0\"\n        app:layout_constraintVertical_chainStyle=\"packed\"\n        />\n\n      <com.michaldrabik.ui_statistics_movies.views.StatisticsMoviesTotalMoviesView\n        android:id=\"@+id/statisticsMoviesTotalMovies\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceMedium\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceMedium\"\n        app:layout_constraintBottom_toTopOf=\"@id/statisticsMoviesTopGenres\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/statisticsMoviesTotalTimeSpent\"\n        />\n\n      <com.michaldrabik.ui_statistics_movies.views.StatisticsMoviesTopGenresView\n        android:id=\"@+id/statisticsMoviesTopGenres\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceMedium\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceMedium\"\n        app:layout_constraintBottom_toTopOf=\"@+id/statisticsMoviesRatings\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/statisticsMoviesTotalMovies\"\n        />\n\n      <com.michaldrabik.ui_statistics_movies.views.ratings.StatisticsMoviesRatingsView\n        android:id=\"@+id/statisticsMoviesRatings\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceMedium\"\n        android:layout_marginTop=\"@dimen/spaceNormal\"\n        android:layout_marginEnd=\"@dimen/spaceMedium\"\n        android:layout_marginBottom=\"@dimen/spaceNormal\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/statisticsMoviesTopGenres\"\n        tools:visibility=\"visible\"\n        />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <include\n      android:id=\"@+id/statisticsMoviesEmptyView\"\n      layout=\"@layout/layout_statistics_movies_empty\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center\"\n      android:layout_marginStart=\"@dimen/spaceBig\"\n      android:layout_marginEnd=\"@dimen/spaceBig\"\n      android:visibility=\"gone\"\n      />\n\n  </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</androidx.core.widget.NestedScrollView>"
  },
  {
    "path": "ui-statistics-movies/src/main/res/layout/layout_statistics_movies_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  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:layout_gravity=\"center\"\n  android:orientation=\"vertical\"\n  tools:background=\"@color/colorBackground\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:text=\"@string/textStatisticsMovies\"\n    android:textAllCaps=\"true\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"20sp\"\n    android:textStyle=\"bold\"\n    />\n\n  <TextView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/textStatisticsMoviesEmpty\"\n    android:textColor=\"?android:attr/textColorSecondary\"\n    android:textSize=\"14sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-statistics-movies/src/main/res/layout/view_statistics_movies_card_ratings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  app:cardBackgroundColor=\"?attr/colorCardBackground\"\n  tools:cardBackgroundColor=\"@color/colorBackground\"\n  tools:parentTag=\"com.google.android.material.card.MaterialCardView\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <LinearLayout\n    android:id=\"@+id/viewMoviesRatingsContent\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:orientation=\"vertical\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewMoviesRatingsTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceMedium\"\n      android:layout_marginTop=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      android:layout_marginBottom=\"@dimen/spaceMedium\"\n      android:drawablePadding=\"@dimen/spaceSmall\"\n      android:gravity=\"center_vertical\"\n      android:text=\"@string/textStatisticsMoviesRatings\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"@dimen/statisticsMoviesHeaderSize\"\n      android:textStyle=\"bold\"\n      app:drawableStartCompat=\"@drawable/ic_star\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      />\n\n    <androidx.recyclerview.widget.RecyclerView\n      android:id=\"@+id/viewMoviesRatingsRecycler\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:clipChildren=\"false\"\n      android:clipToPadding=\"false\"\n      android:nestedScrollingEnabled=\"false\"\n      android:overScrollMode=\"never\"\n      android:paddingStart=\"@dimen/spaceMedium\"\n      android:paddingEnd=\"@dimen/spaceMedium\"\n      />\n\n  </LinearLayout>\n\n</merge>"
  },
  {
    "path": "ui-statistics-movies/src/main/res/layout/view_statistics_movies_card_top_genre.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  app:cardBackgroundColor=\"?attr/colorCardBackground\"\n  tools:background=\"@color/colorBackground\"\n  tools:parentTag=\"com.google.android.material.card.MaterialCardView\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/viewMoviesTopGenresContent\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingStart=\"@dimen/spaceMedium\"\n    android:paddingTop=\"@dimen/spaceNormal\"\n    android:paddingEnd=\"@dimen/spaceSmall\"\n    android:paddingBottom=\"@dimen/spaceNormal\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewMoviesTopGenresTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:drawablePadding=\"@dimen/spaceSmall\"\n      android:gravity=\"center_vertical\"\n      android:text=\"@string/textStatisticsMoviesTopGenre\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"@dimen/statisticsMoviesHeaderSize\"\n      android:textStyle=\"bold\"\n      app:drawableStartCompat=\"@drawable/ic_stars_round\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      tools:drawableStart=\"@drawable/ic_stars_round\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMoviesTopGenresValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      android:lineSpacingExtra=\"2dp\"\n      android:textColor=\"?attr/colorAccent\"\n      android:textSize=\"24sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewMoviesTopGenresTitle\"\n      tools:text=\"Drama\\nComedy\\nThriller\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMoviesTopGenresSubValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceTiny\"\n      android:text=\"@string/textStatisticsMoviesTopGenreSubValue\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewMoviesTopGenresValue\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-statistics-movies/src/main/res/layout/view_statistics_movies_card_total_movies.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  app:cardBackgroundColor=\"?attr/colorCardBackground\"\n  tools:background=\"@color/colorBackground\"\n  tools:parentTag=\"com.google.android.material.card.MaterialCardView\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <LinearLayout\n    android:id=\"@+id/viewMoviesTotalEpisodesContent\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:paddingStart=\"@dimen/spaceMedium\"\n    android:paddingTop=\"@dimen/spaceNormal\"\n    android:paddingEnd=\"@dimen/spaceMedium\"\n    android:paddingBottom=\"@dimen/spaceNormal\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewMoviesTotalEpisodesTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:drawablePadding=\"@dimen/spaceSmall\"\n      android:gravity=\"center_vertical\"\n      android:text=\"@string/textStatisticsMoviesTotalMovies\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"@dimen/statisticsMoviesHeaderSize\"\n      android:textStyle=\"bold\"\n      app:drawableStartCompat=\"@drawable/ic_eye\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      tools:drawableStart=\"@drawable/ic_eye\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMoviesTotalEpisodesValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:textColor=\"?attr/colorAccent\"\n      android:textSize=\"24sp\"\n      android:textStyle=\"bold\"\n      tools:text=\"298 episodes\"\n      />\n\n  </LinearLayout>\n\n</merge>"
  },
  {
    "path": "ui-statistics-movies/src/main/res/layout/view_statistics_movies_card_total_time.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:parentTag=\"com.google.android.material.card.MaterialCardView\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <LinearLayout\n    android:id=\"@+id/viewMoviesTotalTimeSpentContent\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:paddingStart=\"@dimen/spaceMedium\"\n    android:paddingTop=\"@dimen/spaceNormal\"\n    android:paddingEnd=\"@dimen/spaceMedium\"\n    android:paddingBottom=\"@dimen/spaceNormal\"\n    >\n\n    <TextView\n      android:id=\"@+id/viewMoviesTotalTimeSpentTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceSmall\"\n      android:drawablePadding=\"@dimen/spaceSmall\"\n      android:gravity=\"center_vertical\"\n      android:text=\"@string/textStatisticsMoviesTotalTimeSpent\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"@dimen/statisticsMoviesHeaderSize\"\n      android:textStyle=\"bold\"\n      app:drawableStartCompat=\"@drawable/ic_clock\"\n      app:drawableTint=\"?android:attr/textColorPrimary\"\n      tools:drawableStart=\"@drawable/ic_clock\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMoviesTotalTimeSpentMinutesValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:textColor=\"?attr/colorAccent\"\n      android:textSize=\"24sp\"\n      android:textStyle=\"bold\"\n      tools:text=\"1000000 minutes\"\n      />\n\n    <TextView\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:text=\"@string/textStatisticsMoviesTotalTimeItIs\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"14sp\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMoviesTotalTimeSpentHoursValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:textColor=\"?attr/colorAccent\"\n      android:textSize=\"24sp\"\n      android:textStyle=\"bold\"\n      tools:text=\"1 000 000 hours\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMoviesTotalTimeSpentSubValue\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"14sp\"\n      tools:text=\"which is about 342 days and 23 hours.\"\n      />\n\n  </LinearLayout>\n\n</merge>"
  },
  {
    "path": "ui-statistics-movies/src/main/res/layout/view_statistics_movies_rate_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:background=\"@color/colorBackground\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    >\n\n    <FrameLayout\n      android:id=\"@+id/viewMovieRateItemImageLayout\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"@dimen/statisticsMoviesRatingItemHeight\"\n      android:background=\"@drawable/bg_media_view_elevation_card\"\n      android:elevation=\"@dimen/elevationSmall\"\n      android:foreground=\"?attr/selectableItemBackground\"\n      >\n\n      <ImageView\n        android:id=\"@+id/viewMovieRateItemImage\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        />\n\n      <ImageView\n        android:id=\"@+id/viewMovieRateItemPlaceholder\"\n        android:layout_width=\"@dimen/showTilePlaceholder\"\n        android:layout_height=\"@dimen/showTilePlaceholder\"\n        android:layout_gravity=\"center\"\n        android:visibility=\"gone\"\n        app:srcCompat=\"@drawable/ic_television\"\n        app:tint=\"?attr/colorPlaceholderIcon\"\n        tools:visibility=\"visible\"\n        />\n\n      <TextView\n        android:id=\"@+id/viewMovieRateItemTitle\"\n        style=\"@style/ImageTitle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom|start\"\n        android:layout_margin=\"@dimen/spaceSmall\"\n        android:maxLines=\"1\"\n        android:textSize=\"12sp\"\n        android:visibility=\"gone\"\n        tools:text=\"Game Of Thrones\"\n        tools:visibility=\"visible\"\n        />\n\n    </FrameLayout>\n\n    <TextView\n      android:id=\"@+id/viewMovieRateItemRating\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"bottom|center\"\n      android:layout_marginTop=\"@dimen/spaceTiny\"\n      android:drawablePadding=\"@dimen/spaceMicro\"\n      android:gravity=\"center\"\n      android:maxLines=\"1\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"18sp\"\n      android:textStyle=\"bold\"\n      app:drawableStartCompat=\"@drawable/ic_star\"\n      app:drawableTint=\"?attr/colorAccent\"\n      tools:text=\"8\"\n      />\n\n  </LinearLayout>\n\n</merge>"
  },
  {
    "path": "ui-statistics-movies/src/main/res/layout-sw600dp/fragment_statistics_movies.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView\n  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/statisticsMoviesRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:clipChildren=\"false\"\n  android:clipToPadding=\"false\"\n  android:fillViewport=\"true\"\n  android:overScrollMode=\"never\"\n  android:paddingBottom=\"@dimen/spaceSmall\"\n  android:scrollbars=\"none\"\n  tools:background=\"@color/colorBackground\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.coordinatorlayout.widget.CoordinatorLayout\n    android:id=\"@+id/statisticsMoviesCoordinator\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    >\n\n    <com.google.android.material.appbar.MaterialToolbar\n      android:id=\"@+id/statisticsMoviesToolbar\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:background=\"?android:windowBackground\"\n      android:elevation=\"@dimen/elevationNormal\"\n      app:contentInsetStartWithNavigation=\"0dp\"\n      app:layout_constraintBottom_toTopOf=\"@id/statisticsMoviesTotalTimeSpent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:navigationIcon=\"@drawable/ic_arrow_back\"\n      app:title=\"@string/textStatisticsMovies\"\n      />\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n      android:id=\"@+id/statisticsMoviesContent\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:clipChildren=\"false\"\n      android:clipToPadding=\"false\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      >\n\n      <com.michaldrabik.ui_statistics_movies.views.StatisticsMoviesTotalTimeSpentView\n        android:id=\"@+id/statisticsMoviesTotalTimeSpent\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n        android:layout_marginTop=\"@dimen/statisticsMoviesTopPadding\"\n        android:layout_marginEnd=\"@dimen/spaceMedium\"\n        app:layout_constraintEnd_toStartOf=\"@id/statisticsMoviesTopGenres\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n      <com.michaldrabik.ui_statistics_movies.views.StatisticsMoviesTotalMoviesView\n        android:id=\"@+id/statisticsMoviesTotalMovies\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/screenMarginHorizontal\"\n        android:layout_marginTop=\"@dimen/spaceBig\"\n        android:layout_marginEnd=\"@dimen/spaceMedium\"\n        app:layout_constraintEnd_toStartOf=\"@id/statisticsMoviesRatings\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/statisticsMoviesTotalTimeSpent\"\n        app:layout_goneMarginEnd=\"@dimen/screenMarginHorizontal\"\n        />\n\n      <com.michaldrabik.ui_statistics_movies.views.StatisticsMoviesTopGenresView\n        android:id=\"@+id/statisticsMoviesTopGenres\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceMedium\"\n        android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/statisticsMoviesTotalTimeSpent\"\n        app:layout_constraintTop_toTopOf=\"@id/statisticsMoviesTotalTimeSpent\"\n        />\n      s\n\n      <com.michaldrabik.ui_statistics_movies.views.ratings.StatisticsMoviesRatingsView\n        android:id=\"@+id/statisticsMoviesRatings\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/spaceMedium\"\n        android:layout_marginTop=\"@dimen/spaceBig\"\n        android:layout_marginEnd=\"@dimen/screenMarginHorizontal\"\n        android:visibility=\"gone\"\n\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/statisticsMoviesTotalMovies\"\n        app:layout_constraintTop_toBottomOf=\"@id/statisticsMoviesTopGenres\"\n        tools:visibility=\"visible\"\n        />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <include\n      android:id=\"@+id/statisticsMoviesEmptyView\"\n      layout=\"@layout/layout_statistics_movies_empty\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center\"\n      android:layout_marginStart=\"@dimen/spaceBig\"\n      android:layout_marginEnd=\"@dimen/spaceBig\"\n      android:visibility=\"gone\"\n      />\n\n  </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</androidx.core.widget.NestedScrollView>"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"statisticsMoviesHeaderSize\">22sp</dimen>\n\n  <dimen name=\"statisticsMoviesTopPadding\">60dp</dimen>\n  <dimen name=\"statisticsMoviesRatingItemHeight\">100dp</dimen>\n  <dimen name=\"statisticsMoviesRatingItemWidth\">67dp</dimen>\n</resources>"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatisticsMovies\">Statistics</string>\n  <string name=\"textStatisticsMoviesEmpty\">At least one movie must be present in <b>My Movies</b>\\nbefore statistics can be displayed.</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpent\">Total Time Spent Watching</string>\n  <string name=\"textStatisticsMoviesTotalTimeItIs\">and it is</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentHours\">%s hours</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentMinutes\">%s minutes</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentDays\">which is about %s days.</string>\n  <string name=\"textStatisticsMoviesTotalMovies\">Total Movies Watched</string>\n  <string name=\"textStatisticsMoviesTotalMoviesCount\">%s Movies</string>\n  <string name=\"textStatisticsMoviesTopGenre\">Top Genres</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue\">seem to be your cup of tea. Tap to see more.</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue2\">seem to be your cup of tea.</string>\n  <string name=\"textStatisticsMoviesRatings\">Recently Rated</string>\n</resources>"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatisticsMovies\">الإحصائيات</string>\n  <string name=\"textStatisticsMoviesEmpty\">أضف فيلم واحد على الأقل في قائمة <b>أفلامي</b>\\nلعرض قائمة الإحصائيات.</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpent\">إجمالي الوقت المنقضي في المشاهدة</string>\n  <string name=\"textStatisticsMoviesTotalTimeItIs\">يساوي</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentHours\">%s ساعات</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentMinutes\">%s دقائق</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentDays\">وهو حوالي %s أيام.</string>\n  <string name=\"textStatisticsMoviesTotalMovies\">إجمالي الأفلام التي تمت مشاهدتها</string>\n  <string name=\"textStatisticsMoviesTotalMoviesCount\">%s أفلام</string>\n  <string name=\"textStatisticsMoviesTopGenre\">أعلى التصنيفات</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue\">يبدو أنهم تصنيفاتك المفضلة. انقر للمزيد.</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue2\">يبدو أنهم تصنيفاتك المفضلة.</string>\n  <string name=\"textStatisticsMoviesRatings\">تم تقييمه مؤخراً</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatisticsMovies\">Statistik</string>\n  <string name=\"textStatisticsMoviesEmpty\">Es muss mindestens ein Film unter <b>Meine Filme</b>\\n vorhanden sein, bevor deine Statistik angezeigt werden kann.</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpent\">Gesamtzeit</string>\n  <string name=\"textStatisticsMoviesTotalTimeItIs\">und das sind</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentHours\">%s Stunden</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentMinutes\">%s Minuten</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentDays\">das sind ca. %s Tage.</string>\n  <string name=\"textStatisticsMoviesTotalMovies\">Insgesamt geguckte Filme</string>\n  <string name=\"textStatisticsMoviesTotalMoviesCount\">%s Filme</string>\n  <string name=\"textStatisticsMoviesTopGenre\">Top Genre</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue\">scheint so, als wären es ein paar mehr Genres. Tippe hier um mehr anzuzeigen.</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue2\">scheint eine Tasse Tee zu sein.</string>\n  <string name=\"textStatisticsMoviesRatings\">Zuletzt Bewertet</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatisticsMovies\">Estadísticas</string>\n  <string name=\"textStatisticsMoviesEmpty\">Al menos una película debe estar presente en <b>Mis Películas</b>\\nantes de que se pueda mostrar las estadísticas.</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpent\">Tiempo Total Utilizado Viendo</string>\n  <string name=\"textStatisticsMoviesTotalTimeItIs\">y es</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentHours\">%s horas</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentMinutes\">%s minutos</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentDays\">que es alrededor de %s días.</string>\n  <string name=\"textStatisticsMoviesTotalMovies\">Películas Totales Vistas</string>\n  <string name=\"textStatisticsMoviesTotalMoviesCount\">%s Películas</string>\n  <string name=\"textStatisticsMoviesTopGenre\">Géneros Principales</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue\">parece ser santo de tu devoción. Toca para ver más.</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue2\">parece ser santo de tu devoción.</string>\n  <string name=\"textStatisticsMoviesRatings\">Calificado Recientemente</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatisticsMovies\">Tilastot</string>\n  <string name=\"textStatisticsMoviesEmpty\"><b>Omissa elokuvissa</b> on oltava ainakin\\nyksi elokuva ennen kuin tilastoja voidaan näyttää.</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpent\">Kokonaiskatseluaika on</string>\n  <string name=\"textStatisticsMoviesTotalTimeItIs\">joka on</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentHours\">%s tuntia</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentMinutes\">%s minuuttia</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentDays\">eli noin %s päivää.</string>\n  <string name=\"textStatisticsMoviesTotalMovies\">Elokuvia katsottu</string>\n  <string name=\"textStatisticsMoviesTotalMoviesCount\">%s elokuvaa</string>\n  <string name=\"textStatisticsMoviesTopGenre\">Suosituimmat tyylilajit</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue\">näyttävät olevan juttusi. Napauta nähdäksesi lisää.</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue2\">näyttävät olevan juttusi.</string>\n  <string name=\"textStatisticsMoviesRatings\">Viimeksi arvioidut</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatisticsMovies\">Statistiques</string>\n  <string name=\"textStatisticsMoviesEmpty\">Au moins un film doit être présent dans <b>Mes films</b>\\navant que les statistiques puissent être affichées.</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpent\">Temps total passé à visionner</string>\n  <string name=\"textStatisticsMoviesTotalTimeItIs\">ce qui correspond à</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentHours\">%s heures</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentMinutes\">%s minutes</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentDays\">ce qui équivaut à environ %s jours.</string>\n  <string name=\"textStatisticsMoviesTotalMovies\">Nombre Total de Films Visionnés</string>\n  <string name=\"textStatisticsMoviesTotalMoviesCount\">%s Films</string>\n  <string name=\"textStatisticsMoviesTopGenre\">Genres principaux</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue\">semblent être votre tasse de thé. Appuyez pour en voir plus.</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue2\">semblent être votre tasse de thé.</string>\n  <string name=\"textStatisticsMoviesRatings\">Notations récentes</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatisticsMovies\">Statistiche</string>\n  <string name=\"textStatisticsMoviesEmpty\">Deve essere presente almeno un film nella pagina\\n<b>I miei film</b> per visualizzare le statistiche.</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpent\">Tempo speso a guardare film</string>\n  <string name=\"textStatisticsMoviesTotalTimeItIs\">ovvero</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentHours\">%s ore</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentMinutes\">%s minuti</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentDays\">che sono approssimativamente %s giorni.</string>\n  <string name=\"textStatisticsMoviesTotalMovies\">Film visti in totale</string>\n  <string name=\"textStatisticsMoviesTotalMoviesCount\">%s film</string>\n  <string name=\"textStatisticsMoviesTopGenre\">Generi preferiti</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue\">sembrano fatti apposta per te. Tocca per vederne altri.</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue2\">sembrano fatti apposta per te.</string>\n  <string name=\"textStatisticsMoviesRatings\">Valutati di recente</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values-notnight/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"statisticsMoviesTopPadding\">70dp</dimen>\n</resources>"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatisticsMovies\">Statystyki</string>\n  <string name=\"textStatisticsMoviesEmpty\">Dodaj przynajmniej jeden film do <b>Kolekcji</b> aby zobaczyć statystyki.</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpent\">Całkowity Czas</string>\n  <string name=\"textStatisticsMoviesTotalTimeItIs\">czyli</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentHours\">%s godzin</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentMinutes\">%s minut</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentDays\">co daje około %s dni.</string>\n  <string name=\"textStatisticsMoviesTotalMovies\">Całkowita Ilość Filmów</string>\n  <string name=\"textStatisticsMoviesTotalMoviesCount\">%s Filmów</string>\n  <string name=\"textStatisticsMoviesTopGenre\">Ulubione Gatunki</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue\">zdają się być ulubionymi. Kliknij po więcej.</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue2\">zdają się być ulubionymi.</string>\n  <string name=\"textStatisticsMoviesRatings\">Niedawno Ocenione</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatisticsMovies\">Estatísticas</string>\n  <string name=\"textStatisticsMoviesEmpty\">Pelo menos um filme deve estar presente na aba <b>Meus filmes</b>\\npara que as estatísticas possam ser exibidas.</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpent\">Tempo total gasto assistindo</string>\n  <string name=\"textStatisticsMoviesTotalTimeItIs\">e é</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentHours\">%s horas</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentMinutes\">%s minutos</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentDays\">que é aproximadamente %s dias.</string>\n  <string name=\"textStatisticsMoviesTotalMovies\">Total de filmes assistidos</string>\n  <string name=\"textStatisticsMoviesTotalMoviesCount\">%s Filmes</string>\n  <string name=\"textStatisticsMoviesTopGenre\">Top Gêneros</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue\">Seus favoritos. Toque para ver mais.</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue2\">Seus favoritos</string>\n  <string name=\"textStatisticsMoviesRatings\">Avaliado recentemente</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatisticsMovies\">Статистика</string>\n  <string name=\"textStatisticsMoviesEmpty\">По крайней мере один фильм должен присутствовать в <b>Мои фильмы</b>\\nперед отображением статистики.</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpent\">Общее время, потраченное на просмотр</string>\n  <string name=\"textStatisticsMoviesTotalTimeItIs\">это</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentHours\">%s часов</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentMinutes\">%s минут</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentDays\">которые составляют около %s дней.</string>\n  <string name=\"textStatisticsMoviesTotalMovies\">Количество просмотренных фильмов</string>\n  <string name=\"textStatisticsMoviesTotalMoviesCount\">%s Фильмов</string>\n  <string name=\"textStatisticsMoviesTopGenre\">Топ жанров</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue\">кажется, это ваша чашка чая. Нажмите, чтобы увидеть больше.</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue2\">кажется, это ваша чашка чая.</string>\n  <string name=\"textStatisticsMoviesRatings\">Недавно оцененные</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatisticsMovies\">İstatistikler</string>\n  <string name=\"textStatisticsMoviesEmpty\">İstatistiklerin görüntülenebilmesi için <b>Filmlerim</b>\\nbölümünde en az bir filmin bulunması gerekir.</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpent\">Harcanan Toplam Süre</string>\n  <string name=\"textStatisticsMoviesTotalTimeItIs\">ve</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentHours\">%s saat</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentMinutes\">%s dakika</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentDays\">yaklaşık %s güne karşılık gelmektedir.</string>\n  <string name=\"textStatisticsMoviesTotalMovies\">İzlenen Toplam Film Sayısı</string>\n  <string name=\"textStatisticsMoviesTotalMoviesCount\">%s Film</string>\n  <string name=\"textStatisticsMoviesTopGenre\">En Popüler Türler</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue\">tam sizin zevkinize uygun görünüyor. Daha fazlasını görmek için dokunun.</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue2\">tam sizin zevkinize uygun görünüyor.</string>\n  <string name=\"textStatisticsMoviesRatings\">En Son Derecelendirilenler</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatisticsMovies\">Статистика</string>\n  <string name=\"textStatisticsMoviesEmpty\">Принаймні один фільм має бути присутній в <b>Мої Фільми</b>\\nдля відображення статистики.</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpent\">Загальний час, витрачений на перегляд</string>\n  <string name=\"textStatisticsMoviesTotalTimeItIs\">тобто</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentHours\">%s годин</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentMinutes\">%s хвилин</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentDays\">це приблизно %s днів.</string>\n  <string name=\"textStatisticsMoviesTotalMovies\">Загалом переглянуто фільмів</string>\n  <string name=\"textStatisticsMoviesTotalMoviesCount\">%s Фільмів</string>\n  <string name=\"textStatisticsMoviesTopGenre\">Топ жанрів</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue\">здається це ваші фаворити. Торкніться, щоб переглянути більше.</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue2\">здається це ваші фаворити.</string>\n  <string name=\"textStatisticsMoviesRatings\">Нещодавно оцінені</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatisticsMovies\">Thống kê</string>\n  <string name=\"textStatisticsMoviesEmpty\">Ít nhất một phim phải có trong <b>Phim của tôi</b>\\ntrước khi thống kê có thể được hiển thị.</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpent\">Tổng thời gian đã xem</string>\n  <string name=\"textStatisticsMoviesTotalTimeItIs\">và đó là</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentHours\">%s giờ</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentMinutes\">%s phút</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentDays\">tức là khoảng %s ngày.</string>\n  <string name=\"textStatisticsMoviesTotalMovies\">Tổng số phim đã xem</string>\n  <string name=\"textStatisticsMoviesTotalMoviesCount\">%s Phim</string>\n  <string name=\"textStatisticsMoviesTopGenre\">Thể loại hàng đầu</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue\">dường như là điều bạn thích. Nhấn để xem thêm.</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue2\">có vẻ như là điều bạn thích.</string>\n  <string name=\"textStatisticsMoviesRatings\">Được xếp hạng gần đây</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics-movies/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textStatisticsMovies\">统计信息</string>\n  <string name=\"textStatisticsMoviesEmpty\"><b>我的电影</b>\\n中至少要有一部电影，才能显示统计数据。</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpent\">总观看时间</string>\n  <string name=\"textStatisticsMoviesTotalTimeItIs\">相当于</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentHours\">%s 小时</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentMinutes\">%s 分钟</string>\n  <string name=\"textStatisticsMoviesTotalTimeSpentDays\">大约是 %s 天。</string>\n  <string name=\"textStatisticsMoviesTotalMovies\">已观看的电影总数</string>\n  <string name=\"textStatisticsMoviesTotalMoviesCount\">%s 部电影</string>\n  <string name=\"textStatisticsMoviesTopGenre\">最爱类别</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue\">似乎这正符合您的口味。点击查看更多。</string>\n  <string name=\"textStatisticsMoviesTopGenreSubValue2\">似乎这正符合您的口味。</string>\n  <string name=\"textStatisticsMoviesRatings\">最近评分</string>\n</resources>\n"
  },
  {
    "path": "ui-statistics-movies/src/test/java/BaseMockTest.kt",
    "content": "import com.michaldrabik.common_test.MainDispatcherRule\nimport io.mockk.MockKAnnotations\nimport org.junit.Before\nimport org.junit.Rule\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nabstract class BaseMockTest {\n\n  @get:Rule\n  val mainDispatcherRule = MainDispatcherRule()\n\n  @Before\n  open fun setUp() {\n    MockKAnnotations.init(this)\n  }\n}\n"
  },
  {
    "path": "ui-statistics-movies/src/test/java/com/michaldrabik/ui_statistics_movies/StatisticsMoviesViewModelTest.kt",
    "content": "package com.michaldrabik.ui_statistics_movies\n\nimport BaseMockTest\nimport androidx.lifecycle.viewModelScope\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_model.Genre\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.TraktRating\nimport com.michaldrabik.ui_statistics_movies.cases.StatisticsMoviesLoadRatingsCase\nimport com.michaldrabik.ui_statistics_movies.views.ratings.recycler.StatisticsMoviesRatingItem\nimport io.mockk.coEvery\nimport io.mockk.impl.annotations.MockK\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.flow.toList\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.runTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n@OptIn(ExperimentalCoroutinesApi::class)\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass StatisticsMoviesViewModelTest : BaseMockTest() {\n\n  @MockK lateinit var ratingsCase: StatisticsMoviesLoadRatingsCase\n  @MockK lateinit var moviesRepository: MoviesRepository\n\n  private lateinit var SUT: StatisticsMoviesViewModel\n\n  private val stateResult = mutableListOf<StatisticsMoviesUiState>()\n  private val messagesResult = mutableListOf<MessageEvent>()\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n\n    SUT = StatisticsMoviesViewModel(\n      ratingsCase,\n      moviesRepository\n    )\n  }\n\n  @After\n  fun tearDown() {\n    stateResult.clear()\n    messagesResult.clear()\n    SUT.viewModelScope.cancel()\n  }\n\n  @Test\n  internal fun `Should load ratings`() = runTest {\n    val movieItem = StatisticsMoviesRatingItem(Movie.EMPTY, Image.createUnknown(ImageType.POSTER), false, TraktRating.EMPTY)\n    coEvery { ratingsCase.loadRatings() } returns listOf(movieItem)\n\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.loadRatings()\n\n    assertThat(stateResult.last().ratings?.size).isEqualTo(1)\n    assertThat(stateResult.last().ratings).contains(movieItem)\n\n    job.cancel()\n  }\n\n  @Test\n  internal fun `Should load empty ratings in case of error`() = runTest {\n    coEvery { ratingsCase.loadRatings() } throws Throwable(\"Test error\")\n\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.loadRatings()\n\n    assertThat(stateResult.last().ratings).isEmpty()\n\n    job.cancel()\n  }\n\n  @Test\n  internal fun `Should load statistics properly`() = runTest {\n    val movies = listOf(\n      Movie.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = IdTrakt(1)), runtime = 1, genres = listOf(\"war\", \"drama\")),\n      Movie.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = IdTrakt(2)), runtime = 2, genres = listOf(\"war\", \"animation\")),\n      Movie.EMPTY.copy(ids = Ids.EMPTY.copy(trakt = IdTrakt(3)), runtime = 3, genres = listOf(\"war\", \"animation\")),\n    )\n\n    coEvery { moviesRepository.myMovies.loadAll() } returns movies\n\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n\n    SUT.loadData(initialDelay = 0)\n\n    val result = stateResult.last()\n    assertThat(result.totalWatchedMovies).isEqualTo(3)\n    assertThat(result.totalTimeSpentMinutes).isEqualTo(6)\n    assertThat(result.topGenres?.size).isEqualTo(3)\n    assertThat(result.topGenres).containsExactly(Genre.WAR, Genre.ANIMATION, Genre.DRAMA)\n\n    job.cancel()\n  }\n}\n"
  },
  {
    "path": "ui-statistics-movies/src/test/java/com/michaldrabik/ui_statistics_movies/cases/StatisticsMoviesLoadRatingsCaseTest.kt",
    "content": "package com.michaldrabik.ui_statistics_movies.cases\n\nimport BaseMockTest\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.images.MovieImagesProvider\nimport com.michaldrabik.repository.movies.MoviesRepository\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_model.Ids\nimport com.michaldrabik.ui_model.Image\nimport com.michaldrabik.ui_model.ImageType\nimport com.michaldrabik.ui_model.Movie\nimport com.michaldrabik.ui_model.TraktRating\nimport com.michaldrabik.ui_statistics_movies.views.ratings.recycler.StatisticsMoviesRatingItem\nimport io.mockk.Runs\nimport io.mockk.coEvery\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.just\nimport kotlinx.coroutines.test.runBlockingTest\nimport org.junit.Before\nimport org.junit.Test\nimport java.time.ZoneId\nimport java.time.ZonedDateTime\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass StatisticsMoviesLoadRatingsCaseTest : BaseMockTest() {\n\n  @MockK lateinit var userTraktManager: UserTraktManager\n  @MockK lateinit var moviesRepository: MoviesRepository\n  @MockK lateinit var ratingsRepository: RatingsRepository\n  @MockK lateinit var movieImagesProvider: MovieImagesProvider\n\n  private lateinit var SUT: StatisticsMoviesLoadRatingsCase\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n\n    SUT = StatisticsMoviesLoadRatingsCase(\n      userTraktManager,\n      moviesRepository,\n      ratingsRepository,\n      movieImagesProvider\n    )\n  }\n\n  @Test\n  fun `Should return empty list if not authorized`() = runBlockingTest {\n    coEvery { userTraktManager.isAuthorized() } returns false\n\n    val result = SUT.loadRatings()\n\n    assertThat(result).isEmpty()\n  }\n\n  @Test\n  fun `Should load sorted ratings properly`() = runBlockingTest {\n    val ratings = listOf(\n      TraktRating.EMPTY.copy(IdTrakt(1), ratedAt = ZonedDateTime.of(2000, 3, 1, 1, 1, 1, 1, ZoneId.systemDefault())),\n      TraktRating.EMPTY.copy(IdTrakt(2), ratedAt = ZonedDateTime.of(2001, 3, 1, 1, 1, 1, 1, ZoneId.systemDefault())),\n      TraktRating.EMPTY.copy(IdTrakt(3), ratedAt = ZonedDateTime.of(2002, 3, 1, 1, 1, 1, 1, ZoneId.systemDefault())),\n    )\n\n    val movies = listOf(\n      Movie.EMPTY.copy(Ids.EMPTY.copy(trakt = IdTrakt(1))),\n      Movie.EMPTY.copy(Ids.EMPTY.copy(trakt = IdTrakt(2))),\n      Movie.EMPTY.copy(Ids.EMPTY.copy(trakt = IdTrakt(3))),\n      Movie.EMPTY.copy(Ids.EMPTY.copy(trakt = IdTrakt(4))),\n      Movie.EMPTY.copy(Ids.EMPTY.copy(trakt = IdTrakt(5))),\n    )\n\n    val image = Image.createUnknown(ImageType.POSTER)\n\n    coEvery { userTraktManager.checkAuthorization() } just Runs\n    coEvery { userTraktManager.isAuthorized() } returns true\n    coEvery { ratingsRepository.movies.loadMoviesRatings() } returns ratings\n    coEvery { moviesRepository.myMovies.loadAll(any()) } returns movies\n    coEvery { movieImagesProvider.findCachedImage(any(), any()) } returns image\n\n    val result = SUT.loadRatings()\n\n    assertThat(result).hasSize(3)\n    assertThat(result).containsExactly(\n      StatisticsMoviesRatingItem(movies[2], image, false, ratings[2]),\n      StatisticsMoviesRatingItem(movies[1], image, false, ratings[1]),\n      StatisticsMoviesRatingItem(movies[0], image, false, ratings[0])\n    )\n  }\n}\n"
  },
  {
    "path": "ui-streamings/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-streamings/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_streamings'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':ui-base')\n  implementation project(':ui-model')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-streamings/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest />\n"
  },
  {
    "path": "ui-streamings/src/main/java/com/michaldrabik/ui_streamings/recycler/StreamingAdapter.kt",
    "content": "package com.michaldrabik.ui_streamings.recycler\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport com.michaldrabik.ui_model.StreamingService\nimport com.michaldrabik.ui_streamings.views.StreamingView\n\nclass StreamingAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n  private val asyncDiffer = AsyncListDiffer(this, StreamingItemDiffCallback())\n\n  fun setItems(newItems: List<StreamingService>) {\n    with(asyncDiffer) {\n      submitList(newItems)\n    }\n  }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n    ViewHolder(StreamingView(parent.context))\n\n  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n    val item = asyncDiffer.currentList[position]\n    (holder.itemView as StreamingView).bind(item)\n  }\n\n  override fun getItemCount() = asyncDiffer.currentList.size\n\n  class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)\n}\n"
  },
  {
    "path": "ui-streamings/src/main/java/com/michaldrabik/ui_streamings/recycler/StreamingItemDiffCallback.kt",
    "content": "package com.michaldrabik.ui_streamings.recycler\n\nimport androidx.recyclerview.widget.DiffUtil\nimport com.michaldrabik.ui_model.StreamingService\n\nclass StreamingItemDiffCallback : DiffUtil.ItemCallback<StreamingService>() {\n\n  override fun areItemsTheSame(oldItem: StreamingService, newItem: StreamingService) =\n    oldItem.name == newItem.name\n\n  override fun areContentsTheSame(oldItem: StreamingService, newItem: StreamingService) =\n    oldItem.name == newItem.name &&\n      oldItem.imagePath == newItem.imagePath &&\n      oldItem.link == newItem.link &&\n      oldItem.options.size == newItem.options.size &&\n      oldItem.options.containsAll(newItem.options)\n}\n"
  },
  {
    "path": "ui-streamings/src/main/java/com/michaldrabik/ui_streamings/views/StreamingView.kt",
    "content": "package com.michaldrabik.ui_streamings.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.widget.FrameLayout\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.openWebUrl\nimport com.michaldrabik.ui_model.StreamingService\nimport com.michaldrabik.ui_streamings.R\nimport com.michaldrabik.ui_streamings.databinding.ViewStreamingBinding\n\nclass StreamingView : FrameLayout {\n\n  companion object {\n    private const val NETFLIX = \"Netflix\"\n    private const val NETFLIX_FREE = \"Netflix Free\"\n    private const val GOOGLE_PLAY = \"Google Play Movies\"\n    private const val YOU_TUBE = \"YouTube\"\n    private const val AMAZON = \"Amazon Prime Video\"\n    private const val DISNEY = \"Disney Plus\"\n    private const val HBO = \"HBO Max\"\n  }\n\n  private val binding = ViewStreamingBinding.inflate(LayoutInflater.from(context), this)\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val cornerRadius by lazy { context.dimenToPx(R.dimen.streamingImageCorner) }\n  private val cornerAppleRadius by lazy { context.dimenToPx(R.dimen.streamingImageCornerApple) }\n\n  private val centerCropTransformation by lazy { CenterCrop() }\n  private val cornersTransformation by lazy { RoundedCorners(cornerRadius) }\n  private val cornersAppleTransformation by lazy { RoundedCorners(cornerAppleRadius) }\n\n  private lateinit var streaming: StreamingService\n\n  init {\n    layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)\n    binding.viewStreamingContent.onClick {\n      when (streaming.name) {\n        NETFLIX, NETFLIX_FREE -> openWebUrl(\"https://www.netflix.com/search/${streaming.mediaName}\")\n        GOOGLE_PLAY -> openWebUrl(\"https://tv.google.com/\")\n        YOU_TUBE -> openWebUrl(\"https://www.youtube.com/results?search_query=${streaming.mediaName} movie\")\n        AMAZON -> openWebUrl(\"https://www.primevideo.com/search?phrase=${streaming.mediaName}\")\n        DISNEY -> openWebUrl(\"https://www.disneyplus.com/\")\n        HBO -> openWebUrl(\"https://play.hbomax.com/\")\n        else -> openWebUrl(streaming.link)\n      }\n    }\n  }\n\n  fun bind(streaming: StreamingService) {\n    this.streaming = streaming\n    with(binding) {\n      viewStreamingName.text = streaming.name\n      viewStreamingOptions.text = streaming.options.joinToString(\", \") { context.getString(it.resId) }\n\n      val corners = if (streaming.name == \"Apple iTunes\") cornersAppleTransformation else cornersTransformation\n      Glide.with(this@StreamingView)\n        .load(\"${Config.TMDB_IMAGE_BASE_LOGO_URL}${streaming.imagePath}\")\n        .transform(centerCropTransformation, corners)\n        .into(viewStreamingImage)\n    }\n  }\n}\n"
  },
  {
    "path": "ui-streamings/src/main/res/drawable/bg_streaming.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"6dp\" />\n  <solid android:color=\"?attr/colorBadgeBackground\" />\n</shape>"
  },
  {
    "path": "ui-streamings/src/main/res/drawable/bg_streaming_ripple.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:color=\"?android:attr/colorControlHighlight\"\n  >\n  <item android:id=\"@android:id/mask\">\n    <shape android:shape=\"rectangle\">\n      <solid android:color=\"#000000\" />\n      <corners android:radius=\"6dp\" />\n    </shape>\n  </item>\n  <item android:drawable=\"@drawable/bg_streaming\" />\n</ripple>"
  },
  {
    "path": "ui-streamings/src/main/res/layout/view_streaming.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"wrap_content\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/viewStreamingContent\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/streamingTileHeight\"\n    android:background=\"@drawable/bg_streaming_ripple\"\n    android:elevation=\"@dimen/elevationTiny\"\n    android:paddingStart=\"@dimen/spaceSmall\"\n    android:paddingTop=\"@dimen/spaceTiny\"\n    android:paddingEnd=\"@dimen/spaceSmall\"\n    android:paddingBottom=\"@dimen/spaceTiny\"\n    >\n\n    <ImageView\n      android:id=\"@+id/viewStreamingImage\"\n      android:layout_width=\"@dimen/streamingImageSize\"\n      android:layout_height=\"@dimen/streamingImageSize\"\n      android:adjustViewBounds=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewStreamingName\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:fontFamily=\"sans-serif-medium\"\n      android:gravity=\"start|center_vertical\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewStreamingOptions\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toEndOf=\"@id/viewStreamingImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Google Play Movies\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewStreamingOptions\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:gravity=\"start|center_vertical\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"10sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toEndOf=\"@id/viewStreamingImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewStreamingName\"\n      tools:text=\"Google Play Movies\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-streamings/src/main/res/layout-sw600dp/view_streaming.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"wrap_content\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/viewStreamingContent\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/streamingTileHeight\"\n    android:background=\"@drawable/bg_streaming_ripple\"\n    android:elevation=\"@dimen/elevationTiny\"\n    android:paddingStart=\"@dimen/spaceSmall\"\n    android:paddingTop=\"@dimen/spaceTiny\"\n    android:paddingEnd=\"@dimen/spaceSmall\"\n    android:paddingBottom=\"@dimen/spaceTiny\"\n    >\n\n    <ImageView\n      android:id=\"@+id/viewStreamingImage\"\n      android:layout_width=\"@dimen/streamingImageSize\"\n      android:layout_height=\"@dimen/streamingImageSize\"\n      android:adjustViewBounds=\"true\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewStreamingName\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:fontFamily=\"sans-serif-medium\"\n      android:gravity=\"start|center_vertical\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewStreamingOptions\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toEndOf=\"@id/viewStreamingImage\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      tools:text=\"Google Play Movies\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewStreamingOptions\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:gravity=\"start|center_vertical\"\n      android:maxLines=\"1\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"11sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_bias=\"0\"\n      app:layout_constraintStart_toEndOf=\"@id/viewStreamingImage\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewStreamingName\"\n      tools:text=\"Google Play Movies\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-streamings/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"streamingTileHeight\">40dp</dimen>\n  <dimen name=\"streamingImageSize\">26dp</dimen>\n  <dimen name=\"streamingImageCorner\">4dp</dimen>\n  <dimen name=\"streamingImageCornerApple\">8dp</dimen>\n</resources>"
  },
  {
    "path": "ui-streamings/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"streamingTileHeight\">44dp</dimen>\n  <dimen name=\"streamingImageSize\">30dp</dimen>\n  <dimen name=\"streamingImageCorner\">6dp</dimen>\n  <dimen name=\"streamingImageCornerApple\">8dp</dimen>\n</resources>"
  },
  {
    "path": "ui-trakt-sync/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-trakt-sync/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  buildFeatures {\n    viewBinding true\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_trakt_sync'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':data-remote')\n  implementation project(':ui-base')\n  implementation project(':repository')\n  implementation project(':ui-model')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  testImplementation project(':common-test')\n  testImplementation libs.bundles.testing\n  androidTestImplementation libs.android.test.runner\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-trakt-sync/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "ui-trakt-sync/src/main/java/com/michaldrabik/ui_trakt_sync/TraktSyncFragment.kt",
    "content": "package com.michaldrabik.ui_trakt_sync\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport androidx.activity.result.contract.ActivityResultContracts.RequestPermission\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.michaldrabik.common.extensions.dateFromMillis\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.data_remote.Config\nimport com.michaldrabik.ui_base.BaseFragment\nimport com.michaldrabik.ui_base.common.OnTraktAuthorizeListener\nimport com.michaldrabik.ui_base.utilities.events.Event\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.doOnApplyWindowInsets\nimport com.michaldrabik.ui_base.utilities.extensions.gone\nimport com.michaldrabik.ui_base.utilities.extensions.launchAndRepeatStarted\nimport com.michaldrabik.ui_base.utilities.extensions.onClick\nimport com.michaldrabik.ui_base.utilities.extensions.openWebUrl\nimport com.michaldrabik.ui_base.utilities.extensions.visibleIf\nimport com.michaldrabik.ui_base.utilities.viewBinding\nimport com.michaldrabik.ui_model.TraktSyncSchedule\nimport com.michaldrabik.ui_trakt_sync.TraktSyncUiEvent.Finish\nimport com.michaldrabik.ui_trakt_sync.TraktSyncUiEvent.RequestNotificationsPermission\nimport com.michaldrabik.ui_trakt_sync.TraktSyncUiEvent.StartAuthorization\nimport com.michaldrabik.ui_trakt_sync.databinding.FragmentTraktSyncBinding\nimport com.michaldrabik.ui_trakt_sync.views.NotificationsRationaleView\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass TraktSyncFragment :\n  BaseFragment<TraktSyncViewModel>(R.layout.fragment_trakt_sync),\n  OnTraktAuthorizeListener {\n\n  override val viewModel by viewModels<TraktSyncViewModel>()\n  private val binding by viewBinding(FragmentTraktSyncBinding::bind)\n\n  override fun onResume() {\n    super.onResume()\n    hideNavigation()\n  }\n\n  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n    super.onViewCreated(view, savedInstanceState)\n    setupView()\n    setupStatusBar()\n\n    launchAndRepeatStarted(\n      { viewModel.uiState.collect { render(it) } },\n      { viewModel.eventFlow.collect { handleEvent(it) } },\n      { viewModel.messageFlow.collect { showSnack(it) } },\n      doAfterLaunch = { viewModel.invalidate() }\n    )\n  }\n\n  private fun setupView() {\n    with(binding) {\n      traktSyncToolbar.setNavigationOnClickListener { activity?.onBackPressed() }\n      traktSyncImportCheckbox.setOnCheckedChangeListener { _, isChecked ->\n        traktSyncButton.isEnabled = (isChecked || traktSyncExportCheckbox.isChecked)\n      }\n      traktSyncExportCheckbox.setOnCheckedChangeListener { _, isChecked ->\n        traktSyncButton.isEnabled = (isChecked || traktSyncImportCheckbox.isChecked)\n      }\n    }\n  }\n\n  private fun setupStatusBar() {\n    binding.traktSyncRoot.doOnApplyWindowInsets { view, insets, _, _ ->\n      val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top\n      view.updatePadding(top = inset)\n    }\n  }\n\n  private fun checkScheduleImport(currentSchedule: TraktSyncSchedule?, quickSyncEnabled: Boolean?) {\n    if (quickSyncEnabled == true && currentSchedule == TraktSyncSchedule.OFF) {\n      MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n        .setTitle(R.string.textSettingsScheduleImportConfirmationTitle)\n        .setMessage(R.string.textSettingsScheduleImportConfirmationMessage)\n        .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n        .setPositiveButton(R.string.textYes) { _, _ -> scheduleImport(currentSchedule) }\n        .setNegativeButton(R.string.textCancel) { _, _ -> }\n        .show()\n    } else {\n      scheduleImport(currentSchedule)\n    }\n  }\n\n  private fun scheduleImport(currentSchedule: TraktSyncSchedule?) {\n    val options = TraktSyncSchedule.values()\n    val optionsStrings = options.map { getString(it.stringRes) }.toTypedArray()\n    MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_dialog))\n      .setSingleChoiceItems(optionsStrings, options.indexOf(currentSchedule)) { dialog, index ->\n        val schedule = options[index]\n        viewModel.saveTraktSyncSchedule(schedule)\n        showSnack(MessageEvent.Info(schedule.confirmationStringRes))\n        dialog.dismiss()\n      }\n      .show()\n  }\n\n  override fun onAuthorizationResult(authData: Uri?) {\n    val code = authData?.getQueryParameter(\"code\")\n    viewModel.authorizeTrakt(code)\n  }\n\n  @SuppressLint(\"InlinedApi\")\n  private fun showNotificationsRationaleDialog() {\n    val context = requireContext()\n    val view = NotificationsRationaleView(context)\n    MaterialAlertDialogBuilder(context, R.style.AlertDialog)\n      .setBackground(ContextCompat.getDrawable(context, R.drawable.bg_dialog))\n      .setView(view)\n      .setPositiveButton(R.string.textYes) { _, _ ->\n        requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)\n      }\n      .setNegativeButton(R.string.textNo) { _, _ -> openTraktAuthWebsite() }\n      .show()\n  }\n\n  private fun openTraktAuthWebsite() {\n    openWebUrl(Config.TRAKT_AUTHORIZE_URL) ?: showSnack(MessageEvent.Error(R.string.errorCouldNotFindApp))\n  }\n\n  private fun render(uiState: TraktSyncUiState) {\n    uiState.run {\n      with(binding) {\n        isProgress.let {\n          traktSyncButton.visibleIf(!it, false)\n          traktSyncProgress.visibleIf(it)\n          traktSyncImportCheckbox.visibleIf(!it)\n          traktSyncExportCheckbox.visibleIf(!it)\n          traktSyncScheduleButton.visibleIf(!it)\n          traktLastSyncTimestamp.visibleIf(!it)\n        }\n        traktSyncStatus.text = progressStatus\n        traktSyncScheduleButton.setText(traktSyncSchedule.buttonStringRes)\n\n        if (lastTraktSyncTimestamp != 0L) {\n          val date = dateFormat?.format(dateFromMillis(lastTraktSyncTimestamp).toLocalZone())?.capitalizeWords()\n          traktLastSyncTimestamp.text = getString(R.string.textTraktSyncLastTimestamp, date)\n        }\n\n        if (isAuthorized) {\n          traktSyncButton.text = getString(R.string.textTraktSyncStart)\n          traktSyncButton.onClick {\n            viewModel.startImport(\n              isImport = traktSyncImportCheckbox.isChecked,\n              isExport = traktSyncExportCheckbox.isChecked\n            )\n          }\n          traktSyncScheduleButton.onClick { checkScheduleImport(traktSyncSchedule, quickSyncEnabled) }\n        } else {\n          traktSyncButton.text = getString(R.string.textSettingsTraktAuthorizeTitle)\n          traktSyncButton.onClick {\n            viewModel.checkNotificationsPermission(requireAppContext())\n          }\n          traktSyncScheduleButton.gone()\n        }\n      }\n    }\n  }\n\n  private fun handleEvent(event: Event<*>) {\n    when (event) {\n      StartAuthorization -> openTraktAuthWebsite()\n      RequestNotificationsPermission -> showNotificationsRationaleDialog()\n      Finish -> activity?.onBackPressed()\n    }\n  }\n\n  private val requestPermissionLauncher =\n    registerForActivityResult(RequestPermission()) { _ ->\n      openTraktAuthWebsite()\n    }\n}\n"
  },
  {
    "path": "ui-trakt-sync/src/main/java/com/michaldrabik/ui_trakt_sync/TraktSyncUiEvent.kt",
    "content": "package com.michaldrabik.ui_trakt_sync\n\nimport com.michaldrabik.ui_base.utilities.events.Event\n\nsealed class TraktSyncUiEvent<T>(action: T) : Event<T>(action) {\n  data object StartAuthorization : TraktSyncUiEvent<Unit>(Unit)\n  data object RequestNotificationsPermission : TraktSyncUiEvent<Unit>(Unit)\n  data object Finish : TraktSyncUiEvent<Unit>(Unit)\n}\n"
  },
  {
    "path": "ui-trakt-sync/src/main/java/com/michaldrabik/ui_trakt_sync/TraktSyncUiState.kt",
    "content": "package com.michaldrabik.ui_trakt_sync\n\nimport com.michaldrabik.ui_model.TraktSyncSchedule\nimport java.time.format.DateTimeFormatter\n\ndata class TraktSyncUiState(\n  val isProgress: Boolean = false,\n  val progressStatus: String = \"\",\n  val isAuthorized: Boolean = false,\n  val traktSyncSchedule: TraktSyncSchedule = TraktSyncSchedule.OFF,\n  val quickSyncEnabled: Boolean = false,\n  val lastTraktSyncTimestamp: Long = 0L,\n  val dateFormat: DateTimeFormatter? = null,\n)\n"
  },
  {
    "path": "ui-trakt-sync/src/main/java/com/michaldrabik/ui_trakt_sync/TraktSyncViewModel.kt",
    "content": "package com.michaldrabik.ui_trakt_sync\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.lifecycle.Observer\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkInfo\nimport androidx.work.WorkManager\nimport com.michaldrabik.common.errors.ErrorHelper\nimport com.michaldrabik.common.errors.ShowlyError.AccountLockedError\nimport com.michaldrabik.common.errors.ShowlyError.CoroutineCancellation\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_base.events.Event\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.TraktSyncAuthError\nimport com.michaldrabik.ui_base.events.TraktSyncError\nimport com.michaldrabik.ui_base.events.TraktSyncProgress\nimport com.michaldrabik.ui_base.events.TraktSyncStart\nimport com.michaldrabik.ui_base.events.TraktSyncSuccess\nimport com.michaldrabik.ui_base.trakt.TraktSyncWorker\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_base.utilities.extensions.SUBSCRIBE_STOP_TIMEOUT\nimport com.michaldrabik.ui_base.utilities.extensions.combine\nimport com.michaldrabik.ui_base.utilities.extensions.rethrowCancellation\nimport com.michaldrabik.ui_base.utilities.extensions.withApiAtLeast\nimport com.michaldrabik.ui_base.viewmodel.ChannelsDelegate\nimport com.michaldrabik.ui_base.viewmodel.DefaultChannelsDelegate\nimport com.michaldrabik.ui_model.TraktSyncSchedule\nimport com.michaldrabik.ui_trakt_sync.TraktSyncUiEvent.RequestNotificationsPermission\nimport com.michaldrabik.ui_trakt_sync.TraktSyncUiEvent.StartAuthorization\nimport com.michaldrabik.ui_trakt_sync.cases.TraktSyncRatingsCase\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport java.time.format.DateTimeFormatter\nimport javax.inject.Inject\nimport javax.inject.Named\n\n@HiltViewModel\nclass TraktSyncViewModel @Inject constructor(\n  @Named(\"miscPreferences\") private var miscPreferences: SharedPreferences,\n  private val userManager: UserTraktManager,\n  private val workManager: WorkManager,\n  private val ratingsCase: TraktSyncRatingsCase,\n  private val settingsRepository: SettingsRepository,\n  private val dateFormatProvider: DateFormatProvider,\n  private val eventsManager: EventsManager,\n) : ViewModel(),\n  ChannelsDelegate by DefaultChannelsDelegate(),\n  Observer<MutableList<WorkInfo>> {\n\n  private val progressState = MutableStateFlow(false)\n  private val progressStatusState = MutableStateFlow(\"\")\n  private val authorizedState = MutableStateFlow(false)\n  private val traktSyncScheduleState = MutableStateFlow(TraktSyncSchedule.OFF)\n  private val quickSyncEnabledState = MutableStateFlow(false)\n  private val dateFormatState = MutableStateFlow<DateTimeFormatter?>(null)\n  private val traktSyncTimestampState = MutableStateFlow(0L)\n\n  init {\n    viewModelScope.launch {\n      eventsManager.events.collect { handleEvent(it) }\n    }\n    workManager.getWorkInfosByTagLiveData(TraktSyncWorker.TAG_ID).observeForever { work ->\n      progressState.value = work.any { it.state == WorkInfo.State.RUNNING }\n    }\n  }\n\n  fun invalidate() {\n    viewModelScope.launch {\n      val settings = settingsRepository.load()\n\n      authorizedState.value = userManager.isAuthorized()\n      traktSyncScheduleState.value = settings.traktSyncSchedule\n      quickSyncEnabledState.value = settings.traktQuickSyncEnabled\n      dateFormatState.value = dateFormatProvider.loadFullHourFormat()\n      traktSyncTimestampState.value = miscPreferences.getLong(TraktSyncWorker.KEY_LAST_SYNC_TIMESTAMP, 0)\n    }\n  }\n\n  fun checkNotificationsPermission(context: Context) {\n    viewModelScope.launch {\n      withApiAtLeast(33) {\n        val areNotificationsEnabled = NotificationManagerCompat\n          .from(context.applicationContext)\n          .areNotificationsEnabled()\n\n        if (!areNotificationsEnabled) {\n          eventChannel.send(RequestNotificationsPermission)\n          return@launch\n        }\n      }\n      eventChannel.send(StartAuthorization)\n    }\n  }\n\n  fun authorizeTrakt(code: String?) {\n    viewModelScope.launch {\n      try {\n        if (code.isNullOrBlank()) {\n          throw IllegalStateException(\"Invalid Trakt authorization code.\")\n        }\n        userManager.authorize(code)\n        messageChannel.send(MessageEvent.Info(R.string.textTraktLoginSuccess))\n        invalidate()\n        saveTraktQuickRemove()\n        preloadRatings()\n      } catch (error: Throwable) {\n        when (ErrorHelper.parse(error)) {\n          is CoroutineCancellation -> rethrowCancellation(error)\n          is AccountLockedError -> messageChannel.send(MessageEvent.Error(R.string.errorTraktLocked))\n          else -> messageChannel.send(MessageEvent.Error(R.string.errorAuthorization))\n        }\n        Logger.record(error, \"TraktSyncViewModel::authorizeTrakt()\")\n      }\n    }\n  }\n\n  fun saveTraktSyncSchedule(schedule: TraktSyncSchedule) {\n    viewModelScope.launch {\n      val settings = settingsRepository.load()\n      settings.let {\n        val new = it.copy(traktSyncSchedule = schedule)\n        settingsRepository.update(new)\n      }\n      TraktSyncWorker.schedulePeriodic(workManager, schedule, cancelExisting = true)\n      traktSyncScheduleState.value = schedule\n    }\n  }\n\n  private fun saveTraktQuickRemove() {\n    viewModelScope.launch {\n      val settings = settingsRepository.load()\n      settings.let {\n        val new = it.copy(traktQuickRemoveEnabled = true)\n        settingsRepository.update(new)\n      }\n    }\n  }\n\n  private fun preloadRatings() {\n    viewModelScope.launch {\n      try {\n        ratingsCase.preloadRatings()\n      } catch (error: Throwable) {\n        Timber.e(\"Failed to preload some of ratings\")\n        rethrowCancellation(error)\n      }\n    }\n  }\n\n  fun handleEvent(event: Event) {\n    viewModelScope.launch {\n      when (event) {\n        is TraktSyncStart -> {\n          progressState.value = true\n          progressStatusState.value = \"\"\n          messageChannel.send(MessageEvent.Info(R.string.textTraktSyncStarted))\n        }\n        is TraktSyncProgress -> {\n          progressState.value = true\n          progressStatusState.value = event.status\n        }\n        is TraktSyncSuccess -> {\n          progressState.value = false\n          progressStatusState.value = \"\"\n          traktSyncTimestampState.value = miscPreferences.getLong(TraktSyncWorker.KEY_LAST_SYNC_TIMESTAMP, 0)\n          messageChannel.send(MessageEvent.Info(R.string.textTraktSyncComplete))\n        }\n        is TraktSyncError -> {\n          progressState.value = false\n          progressStatusState.value = \"\"\n          messageChannel.send(MessageEvent.Info(R.string.textTraktSyncError))\n        }\n        is TraktSyncAuthError -> {\n          progressState.value = false\n          progressStatusState.value = \"\"\n          messageChannel.send(MessageEvent.Error(R.string.errorTraktAuthorization))\n          eventChannel.send(TraktSyncUiEvent.Finish)\n        }\n        else -> Timber.d(\"Unsupported sync event\")\n      }\n    }\n  }\n\n  fun startImport(isImport: Boolean, isExport: Boolean) {\n    TraktSyncWorker.scheduleOneOff(workManager, isImport, isExport, false)\n  }\n\n  override fun onChanged(value: MutableList<WorkInfo>) {\n    val isAnyRunning = value.any { it.state == WorkInfo.State.RUNNING }\n    progressState.value = isAnyRunning\n  }\n\n  val uiState = combine(\n    progressState,\n    progressStatusState,\n    authorizedState,\n    traktSyncScheduleState,\n    quickSyncEnabledState,\n    dateFormatState,\n    traktSyncTimestampState\n  ) { s1, s2, s3, s4, s5, s6, s7 ->\n    TraktSyncUiState(\n      isProgress = s1,\n      progressStatus = s2,\n      isAuthorized = s3,\n      traktSyncSchedule = s4,\n      quickSyncEnabled = s5,\n      dateFormat = s6,\n      lastTraktSyncTimestamp = s7\n    )\n  }.stateIn(\n    scope = viewModelScope,\n    started = SharingStarted.WhileSubscribed(SUBSCRIBE_STOP_TIMEOUT),\n    initialValue = TraktSyncUiState()\n  )\n}\n"
  },
  {
    "path": "ui-trakt-sync/src/main/java/com/michaldrabik/ui_trakt_sync/cases/TraktSyncRatingsCase.kt",
    "content": "package com.michaldrabik.ui_trakt_sync.cases\n\nimport com.michaldrabik.repository.RatingsRepository\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass TraktSyncRatingsCase @Inject constructor(\n  private val userTraktManager: UserTraktManager,\n  private val settingsRepository: SettingsRepository,\n  private val ratingsRepository: RatingsRepository,\n) {\n\n  suspend fun preloadRatings() {\n    if (userTraktManager.isAuthorized()) {\n      userTraktManager.checkAuthorization()\n      with(ratingsRepository) {\n        shows.preloadRatings()\n        if (settingsRepository.isMoviesEnabled) {\n          movies.preloadRatings()\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-trakt-sync/src/main/java/com/michaldrabik/ui_trakt_sync/views/NotificationsRationaleView.kt",
    "content": "package com.michaldrabik.ui_trakt_sync.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport com.michaldrabik.ui_trakt_sync.databinding.ViewTraktNotificationsRationaleBinding\n\n@SuppressLint(\"SetTextI18n\")\nclass NotificationsRationaleView : FrameLayout {\n\n  constructor(context: Context) : super(context)\n  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n  private val binding = ViewTraktNotificationsRationaleBinding.inflate(LayoutInflater.from(context), this)\n}\n"
  },
  {
    "path": "ui-trakt-sync/src/main/res/drawable/ic_sync.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24.0\" android:viewportWidth=\"24.0\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z\"/>\n</vector>\n"
  },
  {
    "path": "ui-trakt-sync/src/main/res/layout/fragment_trakt_sync.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:fillViewport=\"true\"\n  android:overScrollMode=\"never\"\n  tools:background=\"@color/colorBackground\"\n  tools:theme=\"@style/AppTheme\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:id=\"@+id/traktSyncRoot\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    >\n\n    <com.google.android.material.appbar.MaterialToolbar\n      android:id=\"@+id/traktSyncToolbar\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:background=\"?android:windowBackground\"\n      android:elevation=\"@dimen/elevationNormal\"\n      app:contentInsetStartWithNavigation=\"0dp\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:navigationIcon=\"@drawable/ic_arrow_back\"\n      app:title=\"@string/textTraktSync\"\n      />\n\n    <ImageView\n      android:id=\"@+id/traktSyncIcon\"\n      android:layout_width=\"80dp\"\n      android:layout_height=\"0dp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/traktSyncIconArrow\"\n      app:layout_constraintEnd_toStartOf=\"@id/traktSyncIconArrow\"\n      app:layout_constraintHorizontal_chainStyle=\"packed\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/traktSyncIconArrow\"\n      app:srcCompat=\"@drawable/ic_trakt\"\n      />\n\n    <ImageView\n      android:id=\"@+id/traktSyncIconArrow\"\n      android:layout_width=\"80dp\"\n      android:layout_height=\"80dp\"\n      android:layout_marginTop=\"@dimen/traktSyncTopMargin\"\n      android:padding=\"18dp\"\n      app:layout_constraintBottom_toTopOf=\"@id/traktSyncTitle\"\n      app:layout_constraintEnd_toStartOf=\"@id/traktSyncIconShowly\"\n      app:layout_constraintHorizontal_chainStyle=\"packed\"\n      app:layout_constraintStart_toEndOf=\"@id/traktSyncIcon\"\n      app:layout_constraintTop_toBottomOf=\"@id/traktSyncToolbar\"\n      app:layout_constraintVertical_bias=\"0.1\"\n      app:layout_constraintVertical_chainStyle=\"packed\"\n      app:srcCompat=\"@drawable/ic_sync\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      />\n\n    <ImageView\n      android:id=\"@+id/traktSyncIconShowly\"\n      android:layout_width=\"80dp\"\n      android:layout_height=\"0dp\"\n      app:layout_constraintBottom_toBottomOf=\"@id/traktSyncIconArrow\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintHorizontal_chainStyle=\"packed\"\n      app:layout_constraintStart_toEndOf=\"@id/traktSyncIconArrow\"\n      app:layout_constraintTop_toTopOf=\"@id/traktSyncIconArrow\"\n      app:srcCompat=\"@drawable/ic_showly\"\n      />\n\n    <TextView\n      android:id=\"@+id/traktSyncTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"20dp\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:text=\"@string/textTraktSync\"\n      android:textAllCaps=\"true\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"20sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintBottom_toTopOf=\"@id/traktSyncDescription\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/traktSyncIconArrow\"\n      />\n\n    <TextView\n      android:id=\"@+id/traktSyncDescription\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceBig\"\n      android:layout_marginTop=\"@dimen/spaceSmall\"\n      android:layout_marginEnd=\"@dimen/spaceBig\"\n      android:gravity=\"center\"\n      android:text=\"@string/textTraktSyncDescription\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/traktSyncImportCheckbox\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/traktSyncTitle\"\n      />\n\n    <com.google.android.material.checkbox.MaterialCheckBox\n      android:id=\"@+id/traktSyncImportCheckbox\"\n      style=\"@style/ShowlyCheckbox\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceBig\"\n      android:layout_marginTop=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceBig\"\n      android:checked=\"true\"\n      android:gravity=\"center\"\n      android:text=\"@string/textTraktSyncFromTraktToShowly\"\n      app:layout_constraintBottom_toTopOf=\"@id/traktSyncExportCheckbox\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/traktSyncDescription\"\n      />\n\n    <com.google.android.material.checkbox.MaterialCheckBox\n      android:id=\"@+id/traktSyncExportCheckbox\"\n      style=\"@style/ShowlyCheckbox\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceBig\"\n      android:layout_marginEnd=\"@dimen/spaceBig\"\n      android:checked=\"true\"\n      android:gravity=\"center\"\n      android:text=\"@string/textTraktSyncFromShowlyToTrakt\"\n      app:layout_constraintBottom_toTopOf=\"@id/traktSyncButton\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/traktSyncImportCheckbox\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/traktSyncButton\"\n      style=\"@style/RoundMaterialButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceNormal\"\n      android:minWidth=\"200dp\"\n      android:text=\"@string/textTraktSyncStart\"\n      android:textColor=\"?attr/textColorOnSurface\"\n      app:backgroundTint=\"?attr/colorAccent\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/traktSyncExportCheckbox\"\n      app:rippleColor=\"?android:attr/textColorPrimary\"\n      app:strokeColor=\"?android:attr/textColorPrimary\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/traktSyncScheduleButton\"\n      style=\"@style/RoundTextButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:minWidth=\"200dp\"\n      android:text=\"@string/textTraktSyncSchedule\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/traktSyncButton\"\n      app:rippleColor=\"?android:attr/textColorPrimary\"\n      app:strokeColor=\"?android:attr/textColorPrimary\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/traktSyncProgress\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:foregroundTint=\"?attr/colorAccent\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"@id/traktSyncButton\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"@id/traktSyncButton\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/traktSyncStatus\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceBig\"\n      android:layout_marginTop=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceBig\"\n      android:gravity=\"center\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/traktSyncProgress\"\n      tools:text=\"Syncing...\"\n      />\n\n    <TextView\n      android:id=\"@+id/traktLastSyncTimestamp\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceBig\"\n      android:layout_marginTop=\"@dimen/spaceNormal\"\n      android:layout_marginEnd=\"@dimen/spaceBig\"\n      android:layout_marginBottom=\"@dimen/spaceNormal\"\n      android:gravity=\"center\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"12sp\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/traktSyncScheduleButton\"\n      app:layout_constraintVertical_bias=\"0\"\n      tools:text=\"Last Sync: 23 Feb 2020 17:23\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</ScrollView>\n"
  },
  {
    "path": "ui-trakt-sync/src/main/res/layout/view_trakt_notifications_rationale.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <androidx.constraintlayout.widget.ConstraintLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@drawable/bg_dialog\"\n    android:elevation=\"@dimen/elevationNormal\"\n    android:paddingStart=\"@dimen/spaceNormal\"\n    android:paddingTop=\"@dimen/spaceBig\"\n    android:paddingEnd=\"@dimen/spaceNormal\"\n    android:paddingBottom=\"@dimen/spaceSmall\"\n    >\n\n    <ImageView\n      android:id=\"@+id/viewIcon\"\n      android:layout_width=\"100dp\"\n      android:layout_height=\"100dp\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:srcCompat=\"@drawable/ic_notification_bell\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewTitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"@dimen/spaceMedium\"\n      android:text=\"@string/textTraktSync\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"24sp\"\n      android:textStyle=\"bold\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewIcon\"\n      />\n\n    <TextView\n      android:id=\"@+id/viewMessage\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"32dp\"\n      android:layout_marginBottom=\"@dimen/spaceNormal\"\n      android:maxLines=\"10\"\n      android:text=\"@string/textTraktNotificationsRationale\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"14sp\"\n      app:layout_constraintBottom_toTopOf=\"@id/viewYesButton\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toBottomOf=\"@id/viewTitle\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/viewYesButton\"\n      style=\"@style/RoundTextButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:text=\"@string/textYes\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      />\n\n    <com.google.android.material.button.MaterialButton\n      android:id=\"@+id/viewNoButton\"\n      style=\"@style/RoundTextButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:layout_marginEnd=\"@dimen/spaceSmall\"\n      android:text=\"@string/textNo\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:visibility=\"gone\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toStartOf=\"@id/viewYesButton\"\n      />\n\n  </androidx.constraintlayout.widget.ConstraintLayout>\n\n</merge>"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"traktSyncTopMargin\">0dp</dimen>\n</resources>"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">You\\'ve been successfully authorized.</string>\n  <string name=\"textTraktSyncStarted\">Sync started. It will continue to run in the background.</string>\n  <string name=\"textTraktSyncStart\">Sync Now</string>\n  <string name=\"textTraktSyncSchedule\">Schedule</string>\n  <string name=\"textTraktSyncFromTraktToShowly\">Import from Trakt.tv to Showly</string>\n  <string name=\"textTraktSyncFromShowlyToTrakt\">Export from Showly to Trakt.tv</string>\n  <string name=\"textTraktSyncDescription\">Sync your Trakt.tv account progress and watchlist data with Showly.\\n\\nNone of the items from your current collection will be deleted. Sync will only handle missing items in your collections.</string>\n  <string name=\"textTraktSyncLastTimestamp\">Last Sync: %s</string>\n\n  <string name=\"textSettingsTraktAuthorizeTitle\">Trakt.tv Authorization</string>\n  <string name=\"textSettingsScheduleImportConfirmationTitle\">Instant Sync Enabled</string>\n  <string name=\"textSettingsScheduleImportConfirmationMessage\">\\nIt seems like Instant Sync is already turned on.\\n\\nDo you still want to schedule automatic sync?\\n</string>\n</resources>"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">لقد تمت المصادقة بنجاح.</string>\n  <string name=\"textTraktSyncStarted\">بدأت المزامنة، ستستمر المزامنة في الخلفية.</string>\n  <string name=\"textTraktSyncStart\">المزامنة الآن</string>\n  <string name=\"textTraktSyncSchedule\">مزامنة مجدولة</string>\n  <string name=\"textTraktSyncFromTraktToShowly\">إستيراد من Trakt.tv إلى Showly</string>\n  <string name=\"textTraktSyncFromShowlyToTrakt\">تصدير من Showly إلى Trakt.tv</string>\n  <string name=\"textTraktSyncDescription\">مزامنة مستوى تقدمك وقائمة مشاهدتك في Trakt.tv مع Showly.\\n\\nلن يتم حذف أي شيء في التطبيق، سيتم إضافة العناصر الغير موجودة فقط.</string>\n  <string name=\"textTraktSyncLastTimestamp\">وقت آخر مزامنة: %s</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">مصادقة Trakt.tv</string>\n  <string name=\"textSettingsScheduleImportConfirmationTitle\">تم تفعيل المزامنة الفورية</string>\n  <string name=\"textSettingsScheduleImportConfirmationMessage\">\\nالمزامنة الفورية مفعلة بالفعل.\\n\\nأمازلت ترغب في تفعيل المزامنة التلقائية؟\\n</string>\n</resources>\n"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Sie wurden erfolgreich autorisiert.</string>\n  <string name=\"textTraktSyncStarted\">Synchronisation im Hintergrund gestartet.</string>\n  <string name=\"textTraktSyncStart\">Jetzt Synchronisieren</string>\n  <string name=\"textTraktSyncSchedule\">Zeitplan</string>\n  <string name=\"textTraktSyncFromTraktToShowly\">Importiere von Trakt.tv zu Showly</string>\n  <string name=\"textTraktSyncFromShowlyToTrakt\">Exportiere von Showly nach Trakt.tv</string>\n  <string name=\"textTraktSyncDescription\">Synchronisiere den Fortschritt deines Trakt.tv-Kontos und die Daten der Watchlist mit Showly.\\n\\nKeines der Elemente aus deiner aktuellen Sammlung wird gelöscht. Bei der Synchronisierung werden nur fehlende Elemente in deiner Sammlungen verarbeitet.</string>\n  <string name=\"textTraktSyncLastTimestamp\">Letzte Synchronisierung: %s</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Trakt.tv Autorisierung</string>\n  <string name=\"textSettingsScheduleImportConfirmationTitle\">Sofortige Synchronisierung aktiviert</string>\n  <string name=\"textSettingsScheduleImportConfirmationMessage\">\\nEs scheint, dass sofortige Synchronisierung bereits aktiviert ist.\\n\\nMöchtest du trotzdem eine automatische Synchronisierung planen?\\n</string>\n</resources>\n"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Has sido autorizado/a con éxito.</string>\n  <string name=\"textTraktSyncStarted\">Sincronización iniciada. Seguirá ejecutándose en segundo plano.</string>\n  <string name=\"textTraktSyncStart\">Sincronizar Ahora</string>\n  <string name=\"textTraktSyncSchedule\">Programar</string>\n  <string name=\"textTraktSyncFromTraktToShowly\">Importar desde Trakt.tv a Showly</string>\n  <string name=\"textTraktSyncFromShowlyToTrakt\">Exportar desde Showly a Trakt.tv</string>\n  <string name=\"textTraktSyncDescription\">Sincroniza el progreso de tu cuenta de Trakt.tv y los datos de la lista de pendientes por ver con Showly.\\n\\nNo se eliminará ninguno de los elementos de tu colección actual. Sincronizar solo se ocupará de los elementos que falten en tus colecciones.</string>\n  <string name=\"textTraktSyncLastTimestamp\">Última sincronización: %s</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Autorización de Trakt.tv</string>\n  <string name=\"textSettingsScheduleImportConfirmationTitle\">Sincronización Instantánea Habilitada</string>\n  <string name=\"textSettingsScheduleImportConfirmationMessage\">\\nParece que la Sincronización Instantánea ya está activada.\\n\\n¿Aún deseas programar la sincronización automática?\\n</string>\n</resources>\n"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Olet tunnistautunut.</string>\n  <string name=\"textTraktSyncStarted\">Synkronointi aloitettu ja se jatkuu taustalla.</string>\n  <string name=\"textTraktSyncStart\">Synkronoi nyt</string>\n  <string name=\"textTraktSyncSchedule\">Ajoitus</string>\n  <string name=\"textTraktSyncFromTraktToShowly\">Tuo Trakt.tv-kokoelmasta Showlyyn</string>\n  <string name=\"textTraktSyncFromShowlyToTrakt\">Vie Showlysta Trakt.tv-kokoelmaan</string>\n  <string name=\"textTraktSyncDescription\">Synkronoi katselutilasi ja -listasi Showlyn kanssa.\\n\\nNykyisestä kokoelmastasi ei poisteta mitään, vaan synkronointi käsittelee vain kokoelmistasi puuttuvia kohteita.</string>\n  <string name=\"textTraktSyncLastTimestamp\">Viimeisin synkronointi: %s</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Trakt.tv-valtuutus</string>\n  <string name=\"textSettingsScheduleImportConfirmationTitle\">Pikasynkronointi on käytössä</string>\n  <string name=\"textSettingsScheduleImportConfirmationMessage\">\\nPikasynkronointi näyttäisi olevan jo käytössä.\\n\\nHaluatko silti ajoittaa automaattisen synkronoinnin?\\n</string>\n</resources>\n"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Vous avez été autorisé avec succès.</string>\n  <string name=\"textTraktSyncStarted\">La synchronisation a commencé. Elle se poursuivra en arrière-plan.</string>\n  <string name=\"textTraktSyncStart\">Synchroniser maintenant</string>\n  <string name=\"textTraktSyncSchedule\">Planifier</string>\n  <string name=\"textTraktSyncFromTraktToShowly\">Importer de Trakt.tv vers Showly</string>\n  <string name=\"textTraktSyncFromShowlyToTrakt\">Exporter de Showly vers Trakt.tv</string>\n  <string name=\"textTraktSyncDescription\">Synchronisez la progression et les données de visionnement de votre compte Trakt.tv avec Showly.\\n\\nAucun des éléments de votre collection actuelle ne sera supprimé. La synchronisation ne prendra en compte que les éléments manquants dans vos collections.</string>\n  <string name=\"textTraktSyncLastTimestamp\">Dernière synchronisation : %s</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Autorisation pour Trakt.tv</string>\n  <string name=\"textSettingsScheduleImportConfirmationTitle\">Synchronisation Instantanée Activée</string>\n  <string name=\"textSettingsScheduleImportConfirmationMessage\">\\nIl semblerait que la synchronisation instantanée soit déjà activée.\\n\\nVoulez-vous toujours planifier la synchronisation automatique?\\n</string>\n</resources>\n"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Sei stato autorizzato correttamente.</string>\n  <string name=\"textTraktSyncStarted\">Sincronizzazione avviata. Continuerà l\\'esecuzione in background.</string>\n  <string name=\"textTraktSyncStart\">Sincronizza ora</string>\n  <string name=\"textTraktSyncSchedule\">Pianifica</string>\n  <string name=\"textTraktSyncFromTraktToShowly\">Importa da Trakt.tv in Showly</string>\n  <string name=\"textTraktSyncFromShowlyToTrakt\">Esporta da Showly in Trakt.tv</string>\n  <string name=\"textTraktSyncDescription\">Sincronizza i progressi e la lista da vedere del tuo account Trakt.tv con Showly.\\n\\nNessuno degli elementi della tua attuale raccolta verrà eliminato. La sincronizzazione si occuperà solo degli elementi non presenti nelle tue raccolte.</string>\n  <string name=\"textTraktSyncLastTimestamp\">Ultima sincronizzazione: %s</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Autorizzazione Trakt.tv</string>\n  <string name=\"textSettingsScheduleImportConfirmationTitle\">Sincronizzazione istantanea attiva</string>\n  <string name=\"textSettingsScheduleImportConfirmationMessage\">\\nSembra che la sincronizzazione istantanea sia attiva.\\n\\nVuoi comunque attivare la sincronizzazione automatica?\\n</string>\n</resources>\n"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Zalogowano.</string>\n  <string name=\"textTraktSyncStarted\">Synchronizacja rozpoczęta i będzie kontynuowana w tle.</string>\n  <string name=\"textTraktSyncStart\">Synchronizuj</string>\n  <string name=\"textTraktSyncSchedule\">Zaplanuj</string>\n  <string name=\"textTraktSyncFromTraktToShowly\">Import z Trakt.tv do Showly</string>\n  <string name=\"textTraktSyncFromShowlyToTrakt\">Export z Showly do Trakt.tv</string>\n  <string name=\"textTraktSyncDescription\">Zsynchronizuj swoją kolekcję Trakt.tv z aplikacją Showly.\\n\\nŻadne z twoich obecnych danych nie zostaną usunięte. Synchronizacja uzupełni kolekcję o brakujące elementy.</string>\n  <string name=\"textTraktSyncLastTimestamp\">Ostatnio: %s</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Autoryzacja Trakt.tv</string>\n  <string name=\"textSettingsScheduleImportConfirmationTitle\">Szybka Synchronizacja</string>\n  <string name=\"textSettingsScheduleImportConfirmationMessage\">\\nOpcja \\'Szybka Synchronizacja\\' jest już włączona.\\n\\nCzy wciąż chcesz włączyć planowaną synchronizację?\\n</string>\n</resources>\n"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Você foi autorizado com sucesso.</string>\n  <string name=\"textTraktSyncStarted\">Sincronização iniciada. Ela continuará sendo executada em segundo plano.</string>\n  <string name=\"textTraktSyncStart\">Sincronizar agora</string>\n  <string name=\"textTraktSyncSchedule\">Programação</string>\n  <string name=\"textTraktSyncFromTraktToShowly\">Importar de Trakt.tv para Showly</string>\n  <string name=\"textTraktSyncFromShowlyToTrakt\">Exportar de Showly para Trakt.tv</string>\n  <string name=\"textTraktSyncDescription\">Sincronize o progresso da sua conta do Trakt.tv e os dados da lista de desejos com o Showly.\\n\\nNenhum dos itens da sua coleção atual serão excluídos. A sincronização lidará apenas com itens ausentes em suas coleções.</string>\n  <string name=\"textTraktSyncLastTimestamp\">Última sincronização: %s</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Autorização de Trakt.tv</string>\n  <string name=\"textSettingsScheduleImportConfirmationTitle\">Sincronização instantânea ativada</string>\n  <string name=\"textSettingsScheduleImportConfirmationMessage\">\\nParece que a Sincronização Instantânea já está ativada.\\n\\nVocê ainda deseja agendar a sincronização automática?\\n</string>\n</resources>\n"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Вы успешно авторизовались.</string>\n  <string name=\"textTraktSyncStarted\">Синхронизация запущена. Она продолжит работать в фоновом режиме.</string>\n  <string name=\"textTraktSyncStart\">Синхронизировать</string>\n  <string name=\"textTraktSyncSchedule\">Расписание</string>\n  <string name=\"textTraktSyncFromTraktToShowly\">Импортировать из Trakt.tv в Showly</string>\n  <string name=\"textTraktSyncFromShowlyToTrakt\">Экспортировать из Showly в Trakt.tv</string>\n  <string name=\"textTraktSyncDescription\">Синхронизируйте прогресс вашего аккаунта Trakt.tv и данные Showly.\\n\\nНи один из элементов вашей текущей коллекции не будет удален. Синхронизация будет обрабатывать только недостающие элементы в ваших коллекциях.</string>\n  <string name=\"textTraktSyncLastTimestamp\">Последняя синхронизация: %s</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Авторизация Trakt.tv</string>\n  <string name=\"textSettingsScheduleImportConfirmationTitle\">Мгновенная синхронизация включена</string>\n  <string name=\"textSettingsScheduleImportConfirmationMessage\">\\nпохоже, Мгновенная синхронизация уже включена.\\n\\nВы все еще хотите запланировать автоматическую синхронизацию?\\n</string>\n</resources>\n"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"traktSyncTopMargin\">86dp</dimen>\n</resources>"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Başarılı bir şekilde yetkilendirdiniz.</string>\n  <string name=\"textTraktSyncStarted\">Eşitleme başladı. Arka planda çalışmaya devam edecek.</string>\n  <string name=\"textTraktSyncStart\">Şimdi Eşitle</string>\n  <string name=\"textTraktSyncSchedule\">Zamanla</string>\n  <string name=\"textTraktSyncFromTraktToShowly\">Trakt.tv hizmetinden Showly uygulamasına aktar</string>\n  <string name=\"textTraktSyncFromShowlyToTrakt\">Showly uygulamasından Trakt.tv hizmetine aktar</string>\n  <string name=\"textTraktSyncDescription\">Trakt.tv hesabınızın ilerleme ve istek listesi verilerini Showly ile eşitleyin.\\n\\nMevcut koleksiyonunuzdaki ögelerden hiçbiri silinmeyecek. Eşitleme, yalnızca koleksiyonlarınızdaki eksik ögeleri ele alır.</string>\n  <string name=\"textTraktSyncLastTimestamp\">Son Eşitleme: %s</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Trakt.tv Yetkilendirmesi</string>\n  <string name=\"textSettingsScheduleImportConfirmationTitle\">Anlık Eşitleme Etkinleştirildi</string>\n  <string name=\"textSettingsScheduleImportConfirmationMessage\">\\nAnlık Eşitleme zaten açık gibi görünüyor.\\n\\nHala otomatik eşitlemeyi programlamak istiyor musunuz?\\n</string>\n</resources>\n"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Ви успішно авторизовані.</string>\n  <string name=\"textTraktSyncStarted\">Синхронізацію розпочато. Процес продовжить роботу у фоновому режимі.</string>\n  <string name=\"textTraktSyncStart\">Синхронізувати</string>\n  <string name=\"textTraktSyncSchedule\">Розклад</string>\n  <string name=\"textTraktSyncFromTraktToShowly\">Імпорт з Trakt.tv до Showly</string>\n  <string name=\"textTraktSyncFromShowlyToTrakt\">Експорт з Showly до Trakt.tv</string>\n  <string name=\"textTraktSyncDescription\">Синхронізуйте дані вашого облікового запису Trakt.tv з Showly.\\n\\nЖоден з елементів вашої поточної колекції не буде видалений. Синхронізація буде обробляти тільки відсутні елементи в ваших колекціях.</string>\n  <string name=\"textTraktSyncLastTimestamp\">Востаннє синхронізовано: %s</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Авторизація Trakt.tv</string>\n  <string name=\"textSettingsScheduleImportConfirmationTitle\">Миттєва синхронізація увімкнена</string>\n  <string name=\"textSettingsScheduleImportConfirmationMessage\">\\nСхоже, що миттєва синхронізація вже увімкнена.\\n\\nВи все ще хочете запланувати автоматичну синхронізацію?\\n</string>\n</resources>\n"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">Bạn đã được ủy quyền thành công.</string>\n  <string name=\"textTraktSyncStarted\">Đã bắt đầu đồng bộ hóa. Nó sẽ tiếp tục chạy ở chế độ nền.</string>\n  <string name=\"textTraktSyncStart\">Đồng bộ hóa ngay bây giờ</string>\n  <string name=\"textTraktSyncSchedule\">Lịch trình</string>\n  <string name=\"textTraktSyncFromTraktToShowly\">Nhập từ Trakt.tv sang Showly</string>\n  <string name=\"textTraktSyncFromShowlyToTrakt\">Xuất từ Showly sang Trakt.tv</string>\n  <string name=\"textTraktSyncDescription\">Đồng bộ hóa dữ liệu danh sách xem và tiến độ tài khoản Trakt.tv của bạn với Showly.\\n\\nKhông có mục nào trong bộ sưu tập hiện tại của bạn sẽ bị xóa. Đồng bộ hóa sẽ chỉ xử lý các mục bị thiếu trong bộ sưu tập của bạn.</string>\n  <string name=\"textTraktSyncLastTimestamp\">Đồng bộ hóa lần cuối: %s</string>\n\n  <string name=\"textSettingsTraktAuthorizeTitle\">Ủy quyền của Trakt.tv</string>\n  <string name=\"textSettingsScheduleImportConfirmationTitle\">Đã bật đồng bộ hóa tức thì</string>\n  <string name=\"textSettingsScheduleImportConfirmationMessage\">\\nCó vẻ như Đồng bộ hóa tức thì đã được bật.\\n\\nBạn vẫn muốn lên lịch đồng bộ hóa tự động phải không?\\n</string>\n</resources>\n"
  },
  {
    "path": "ui-trakt-sync/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"textTraktLoginSuccess\">您已经授权成功。</string>\n  <string name=\"textTraktSyncStarted\">同步已开始。将继续在后台运行。</string>\n  <string name=\"textTraktSyncStart\">立即同步</string>\n  <string name=\"textTraktSyncSchedule\">定时同步时间</string>\n  <string name=\"textTraktSyncFromTraktToShowly\">从 Trakt.tv 导入到 Showly</string>\n  <string name=\"textTraktSyncFromShowlyToTrakt\">从 Showly 导出到 Trakt.tv</string>\n  <string name=\"textTraktSyncDescription\">与 Showly 同步您 Trakt.tv 账号的观看进度和待看列表数据。\\n\\n您当前列表中的任何项目都不会被删除。同步只处理您当前合集中缺失的项目。</string>\n  <string name=\"textTraktSyncLastTimestamp\">上次同步：%s</string>\n  <string name=\"textSettingsTraktAuthorizeTitle\">Trakt.tv 授权</string>\n  <string name=\"textSettingsScheduleImportConfirmationTitle\">即时同步已启用</string>\n  <string name=\"textSettingsScheduleImportConfirmationMessage\">\\n似乎即时同步已开启。\\n\\n您是否仍然想要设置自动同步？\\n</string>\n</resources>\n"
  },
  {
    "path": "ui-trakt-sync/src/test/java/BaseMockTest.kt",
    "content": "import com.michaldrabik.common_test.MainDispatcherRule\nimport io.mockk.MockKAnnotations\nimport org.junit.Before\nimport org.junit.Rule\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nabstract class BaseMockTest {\n\n  @get:Rule\n  val mainDispatcherRule = MainDispatcherRule()\n\n  @Before\n  open fun setUp() {\n    MockKAnnotations.init(this)\n  }\n}\n"
  },
  {
    "path": "ui-trakt-sync/src/test/java/com/michaldrabik/ui_trakt_sync/TraktSyncViewModelTest.kt",
    "content": "package com.michaldrabik.ui_trakt_sync\n\nimport BaseMockTest\nimport android.content.SharedPreferences\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkManager\nimport com.google.common.truth.Truth.assertThat\nimport com.michaldrabik.repository.UserTraktManager\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.R.string\nimport com.michaldrabik.ui_base.dates.DateFormatProvider\nimport com.michaldrabik.ui_base.events.EventsManager\nimport com.michaldrabik.ui_base.events.TraktSyncAuthError\nimport com.michaldrabik.ui_base.events.TraktSyncError\nimport com.michaldrabik.ui_base.events.TraktSyncProgress\nimport com.michaldrabik.ui_base.events.TraktSyncStart\nimport com.michaldrabik.ui_base.events.TraktSyncSuccess\nimport com.michaldrabik.ui_base.utilities.events.MessageEvent\nimport com.michaldrabik.ui_model.Settings\nimport com.michaldrabik.ui_model.TraktSyncSchedule\nimport com.michaldrabik.ui_trakt_sync.cases.TraktSyncRatingsCase\nimport io.mockk.Runs\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.impl.annotations.MockK\nimport io.mockk.impl.annotations.RelaxedMockK\nimport io.mockk.just\nimport io.mockk.mockkObject\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.toList\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.runTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\nimport java.time.format.DateTimeFormatter\n\n@OptIn(ExperimentalCoroutinesApi::class)\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nclass TraktSyncViewModelTest : BaseMockTest() {\n\n  @RelaxedMockK lateinit var workManager: WorkManager\n  @MockK lateinit var miscPreferences: SharedPreferences\n  @MockK lateinit var userTraktManager: UserTraktManager\n  @MockK lateinit var settingsRepository: SettingsRepository\n  @MockK lateinit var eventsManager: EventsManager\n  @MockK lateinit var ratingsCase: TraktSyncRatingsCase\n  @MockK lateinit var dateFormatProvider: DateFormatProvider\n\n  private lateinit var SUT: TraktSyncViewModel\n\n  private val stateResult = mutableListOf<TraktSyncUiState>()\n  private val messagesResult = mutableListOf<MessageEvent>()\n\n  @Before\n  override fun setUp() {\n    super.setUp()\n\n    mockkObject(Logger)\n    coEvery { userTraktManager.revokeToken() } just Runs\n    coEvery { eventsManager.events } returns MutableSharedFlow()\n    coEvery { Logger.record(any(), any()) } just Runs\n\n    SUT = TraktSyncViewModel(\n      miscPreferences,\n      userTraktManager,\n      workManager,\n      ratingsCase,\n      settingsRepository,\n      dateFormatProvider,\n      eventsManager\n    )\n  }\n\n  @After\n  fun tearDown() {\n    stateResult.clear()\n    messagesResult.clear()\n    SUT.viewModelScope.cancel()\n  }\n\n  @Test\n  internal fun `Should invalidate properly`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val job2 = launch(UnconfinedTestDispatcher()) { SUT.messageFlow.toList(messagesResult) }\n\n    coEvery { settingsRepository.load() } returns Settings.createInitial()\n    coEvery { userTraktManager.isAuthorized() } returns true\n    coEvery { dateFormatProvider.loadFullHourFormat() } returns DateTimeFormatter.ISO_DATE\n    coEvery { miscPreferences.getLong(any(), 0) } returns 0\n\n    SUT.invalidate()\n\n    assertThat(stateResult.last().isAuthorized).isTrue()\n    assertThat(stateResult.last().traktSyncSchedule).isEqualTo(TraktSyncSchedule.OFF)\n    assertThat(stateResult.last().quickSyncEnabled).isEqualTo(false)\n    assertThat(stateResult.last().dateFormat).isEqualTo(DateTimeFormatter.ISO_DATE)\n    assertThat(stateResult.last().lastTraktSyncTimestamp).isEqualTo(0L)\n    coVerify(exactly = 1) { settingsRepository.load() }\n\n    job.cancel()\n    job2.cancel()\n  }\n\n  @Test\n  internal fun `Should not authorize trakt if URI is null`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val job2 = launch(UnconfinedTestDispatcher()) { SUT.messageFlow.toList(messagesResult) }\n\n    SUT.authorizeTrakt(null)\n    coVerify(exactly = 0) { userTraktManager.authorize(any()) }\n    assertThat(messagesResult.last().consume()).isEqualTo(string.errorAuthorization)\n\n    job.cancel()\n    job2.cancel()\n  }\n\n  @Test\n  internal fun `Should authorize trakt properly`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val job2 = launch(UnconfinedTestDispatcher()) { SUT.messageFlow.toList(messagesResult) }\n\n    coEvery { settingsRepository.load() } returns Settings.createInitial()\n    coEvery { settingsRepository.update(any()) } just Runs\n    coEvery { userTraktManager.isAuthorized() } returns true\n    coEvery { userTraktManager.authorize(any()) } just Runs\n    coEvery { dateFormatProvider.loadFullHourFormat() } returns DateTimeFormatter.ISO_DATE\n    coEvery { miscPreferences.getLong(any(), 0) } returns 0\n\n    SUT.authorizeTrakt(\"testCode\")\n\n    assertThat(messagesResult.last().consume()).isEqualTo(R.string.textTraktLoginSuccess)\n\n    job.cancel()\n    job2.cancel()\n  }\n\n  @Test\n  internal fun `Should save Trakt Sync Schedule`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val job2 = launch(UnconfinedTestDispatcher()) { SUT.messageFlow.toList(messagesResult) }\n\n    coEvery { settingsRepository.load() } returns Settings.createInitial()\n    coEvery { settingsRepository.update(any()) } just Runs\n\n    val schedule = TraktSyncSchedule.EVERY_6_HOURS\n    SUT.saveTraktSyncSchedule(schedule)\n\n    assertThat(stateResult.last().traktSyncSchedule).isEqualTo(schedule)\n    assertThat(messagesResult).isEmpty()\n    coVerify(exactly = 1) { settingsRepository.load() }\n    coVerify(exactly = 1) { settingsRepository.update(any()) }\n    coVerify(exactly = 1) { workManager.cancelUniqueWork(any()) }\n    coVerify(exactly = 1) { workManager.enqueueUniquePeriodicWork(any(), any(), any()) }\n\n    job.cancel()\n    job2.cancel()\n  }\n\n  @Test\n  internal fun `Should handle TraktSyncStart event`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val job2 = launch(UnconfinedTestDispatcher()) { SUT.messageFlow.toList(messagesResult) }\n\n    SUT.handleEvent(TraktSyncStart)\n\n    assertThat(stateResult.last().isProgress).isTrue()\n    assertThat(stateResult.last().progressStatus).isEqualTo(\"\")\n    assertThat(messagesResult.last().consume()).isEqualTo(R.string.textTraktSyncStarted)\n\n    job.cancel()\n    job2.cancel()\n  }\n\n  @Test\n  internal fun `Should handle TraktSyncProgress event`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val job2 = launch(UnconfinedTestDispatcher()) { SUT.messageFlow.toList(messagesResult) }\n\n    SUT.handleEvent(TraktSyncProgress(\"test\"))\n\n    assertThat(stateResult.last().isProgress).isTrue()\n    assertThat(stateResult.last().progressStatus).isEqualTo(\"test\")\n    assertThat(messagesResult).isEmpty()\n\n    job.cancel()\n    job2.cancel()\n  }\n\n  @Test\n  internal fun `Should handle TraktSyncSuccess event`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val job2 = launch(UnconfinedTestDispatcher()) { SUT.messageFlow.toList(messagesResult) }\n    coEvery { miscPreferences.getLong(any(), 0) } returns 0\n\n    SUT.handleEvent(TraktSyncSuccess)\n\n    assertThat(stateResult.last().isProgress).isFalse()\n    assertThat(stateResult.last().progressStatus).isEqualTo(\"\")\n    assertThat(messagesResult.last().consume()).isEqualTo(string.textTraktSyncComplete)\n\n    job.cancel()\n    job2.cancel()\n  }\n\n  @Test\n  internal fun `Should handle TraktSyncError event`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val job2 = launch(UnconfinedTestDispatcher()) { SUT.messageFlow.toList(messagesResult) }\n\n    SUT.handleEvent(TraktSyncError)\n\n    assertThat(stateResult.last().isProgress).isFalse()\n    assertThat(stateResult.last().progressStatus).isEqualTo(\"\")\n    assertThat(messagesResult.last().consume()).isEqualTo(string.textTraktSyncError)\n\n    job.cancel()\n    job2.cancel()\n  }\n\n  @Test\n  internal fun `Should handle TraktSyncAuthError event`() = runTest {\n    val job = launch(UnconfinedTestDispatcher()) { SUT.uiState.toList(stateResult) }\n    val job2 = launch(UnconfinedTestDispatcher()) { SUT.messageFlow.toList(messagesResult) }\n\n    SUT.handleEvent(TraktSyncAuthError)\n\n    assertThat(stateResult.last().isProgress).isFalse()\n    assertThat(stateResult.last().progressStatus).isEqualTo(\"\")\n    assertThat(messagesResult.last().consume()).isEqualTo(string.errorTraktAuthorization)\n\n    job.cancel()\n    job2.cancel()\n  }\n}\n"
  },
  {
    "path": "ui-widgets/.gitignore",
    "content": "/build"
  },
  {
    "path": "ui-widgets/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'dagger.hilt.android.plugin'\napply from: '../versions.gradle'\n\nandroid {\n  kotlinOptions { jvmTarget = \"17\" }\n  compileOptions {\n    coreLibraryDesugaringEnabled true\n    sourceCompatibility JavaVersion.VERSION_17\n    targetCompatibility JavaVersion.VERSION_17\n  }\n\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.targetSdk\n    compileSdkVersion versions.compileSdk\n\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  namespace 'com.michaldrabik.ui_widgets'\n}\n\ndependencies {\n  implementation project(':common')\n  implementation project(':ui-base')\n  implementation project(':repository')\n  implementation project(':ui-model')\n  implementation project(':ui-episodes')\n  implementation project(':ui-progress')\n  implementation project(':ui-progress-movies')\n\n  implementation libs.hilt.android\n  ksp libs.hilt.compiler\n\n  coreLibraryDesugaring libs.android.desugar\n}\n"
  },
  {
    "path": "ui-widgets/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n  <application android:theme=\"@style/AppTheme\">\n\n    <service\n      android:name=\"com.michaldrabik.ui_widgets.progress.ProgressWidgetService\"\n      android:permission=\"android.permission.BIND_REMOTEVIEWS\"\n      />\n\n    <service\n      android:name=\"com.michaldrabik.ui_widgets.progress.ProgressWidgetEpisodeCheckService\"\n      android:exported=\"true\"\n      android:permission=\"android.permission.BIND_JOB_SERVICE\"\n      />\n\n    <service\n      android:name=\"com.michaldrabik.ui_widgets.progress_movies.ProgressMoviesWidgetService\"\n      android:permission=\"android.permission.BIND_REMOTEVIEWS\"\n      />\n\n    <service\n      android:name=\"com.michaldrabik.ui_widgets.progress_movies.ProgressMoviesWidgetCheckService\"\n      android:exported=\"true\"\n      android:permission=\"android.permission.BIND_JOB_SERVICE\"\n      />\n\n    <service\n      android:name=\"com.michaldrabik.ui_widgets.calendar.CalendarWidgetService\"\n      android:permission=\"android.permission.BIND_REMOTEVIEWS\"\n      />\n\n    <service\n      android:name=\"com.michaldrabik.ui_widgets.calendar_movies.CalendarMoviesWidgetService\"\n      android:permission=\"android.permission.BIND_REMOTEVIEWS\"\n      />\n\n    <receiver\n      android:name=\"com.michaldrabik.ui_widgets.progress.ProgressWidgetProvider\"\n      android:exported=\"true\"\n      android:label=\"Shows Progress\"\n      >\n      <intent-filter>\n        <action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\" />\n      </intent-filter>\n\n      <meta-data\n        android:name=\"android.appwidget.provider\"\n        android:resource=\"@xml/progress_widgets_provider\"\n        />\n    </receiver>\n\n    <receiver\n      android:name=\"com.michaldrabik.ui_widgets.progress_movies.ProgressMoviesWidgetProvider\"\n      android:exported=\"true\"\n      android:label=\"Movies Progress\"\n      >\n      <intent-filter>\n        <action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\" />\n      </intent-filter>\n\n      <meta-data\n        android:name=\"android.appwidget.provider\"\n        android:resource=\"@xml/progress_movies_widgets_provider\"\n        />\n    </receiver>\n\n    <receiver\n      android:name=\"com.michaldrabik.ui_widgets.calendar.CalendarWidgetProvider\"\n      android:exported=\"true\"\n      android:label=\"Shows Calendar\"\n      >\n      <intent-filter>\n        <action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\" />\n      </intent-filter>\n\n      <meta-data\n        android:name=\"android.appwidget.provider\"\n        android:resource=\"@xml/calendar_widgets_provider\"\n        />\n    </receiver>\n\n    <receiver\n      android:name=\"com.michaldrabik.ui_widgets.calendar_movies.CalendarMoviesWidgetProvider\"\n      android:exported=\"true\"\n      android:label=\"Movies Calendar\"\n      >\n      <intent-filter>\n        <action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\" />\n      </intent-filter>\n\n      <meta-data\n        android:name=\"android.appwidget.provider\"\n        android:resource=\"@xml/calendar_movies_widgets_provider\"\n        />\n    </receiver>\n\n    <receiver\n      android:name=\"com.michaldrabik.ui_widgets.search.SearchWidgetProvider\"\n      android:exported=\"true\"\n      android:label=\"Search\"\n      >\n      <intent-filter>\n        <action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\" />\n      </intent-filter>\n\n      <meta-data\n        android:name=\"android.appwidget.provider\"\n        android:resource=\"@xml/search_widgets_provider\"\n        />\n    </receiver>\n\n  </application>\n\n</manifest>\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/BaseWidgetProvider.kt",
    "content": "package com.michaldrabik.ui_widgets\n\nimport android.appwidget.AppWidgetManager\nimport android.appwidget.AppWidgetProvider\nimport android.content.Context\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_model.Settings\nimport kotlinx.coroutines.runBlocking\nimport javax.inject.Inject\n\nabstract class BaseWidgetProvider : AppWidgetProvider() {\n\n  companion object {\n    const val ACTION_CLICK = \"ACTION_CLICK\"\n    const val EXTRA_MODE_CLICK = \"EXTRA_MODE_CLICK\"\n    const val EXTRA_SHOW_ID = \"EXTRA_SHOW_ID\"\n    const val EXTRA_MOVIE_ID = \"EXTRA_MOVIE_ID\"\n  }\n\n  @Inject lateinit var settingsRepository: SettingsRepository\n  protected lateinit var settings: Settings\n\n  abstract fun getLayoutResId(): Int\n\n  protected fun getBackgroundResId() =\n    when (settingsRepository.widgets.widgetsTransparency) {\n      75 -> R.drawable.bg_widget_75\n      50 -> R.drawable.bg_widget_50\n      25 -> R.drawable.bg_widget_25\n      0 -> R.drawable.bg_widget_0\n      else -> R.drawable.bg_widget\n    }\n\n  override fun onUpdate(\n    context: Context,\n    appWidgetManager: AppWidgetManager,\n    appWidgetIds: IntArray?,\n  ) {\n    requireSettings()\n    super.onUpdate(context, appWidgetManager, appWidgetIds)\n  }\n\n  private fun requireSettings() {\n    if (!this::settings.isInitialized) {\n      settings = runBlocking { settingsRepository.load() }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/calendar/CalendarWidgetProvider.kt",
    "content": "package com.michaldrabik.ui_widgets.calendar\n\nimport android.app.PendingIntent\nimport android.app.PendingIntent.FLAG_IMMUTABLE\nimport android.app.PendingIntent.FLAG_MUTABLE\nimport android.app.PendingIntent.FLAG_UPDATE_CURRENT\nimport android.appwidget.AppWidgetManager\nimport android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE\nimport android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport android.view.View.GONE\nimport android.view.View.VISIBLE\nimport android.widget.RemoteViews\nimport androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_widgets.BaseWidgetProvider\nimport com.michaldrabik.ui_widgets.R\nimport dagger.hilt.android.AndroidEntryPoint\nimport timber.log.Timber\n\n@AndroidEntryPoint\nclass CalendarWidgetProvider : BaseWidgetProvider() {\n\n  companion object {\n    fun requestUpdate(context: Context) {\n      val applicationContext = context.applicationContext\n      val appWidgetManager = AppWidgetManager.getInstance(applicationContext)\n      val intent = Intent(applicationContext, CalendarWidgetProvider::class.java).apply {\n        val ids = appWidgetManager.getAppWidgetIds(ComponentName(applicationContext, CalendarWidgetProvider::class.java))\n        action = ACTION_APPWIDGET_UPDATE\n        putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)\n      }\n      applicationContext.sendBroadcast(intent)\n      Timber.d(\"Widget update requested.\")\n    }\n  }\n\n  override fun getLayoutResId(): Int {\n    val isLight = settingsRepository.widgets.widgetsTheme == MODE_NIGHT_NO\n    return when {\n      isLight -> R.layout.widget_calendar_day\n      else -> R.layout.widget_calendar_night\n    }\n  }\n\n  override fun onUpdate(\n    context: Context,\n    appWidgetManager: AppWidgetManager,\n    appWidgetIds: IntArray?,\n  ) {\n    super.onUpdate(context, appWidgetManager, appWidgetIds)\n    appWidgetIds?.forEach { updateWidget(context, appWidgetManager, it) }\n  }\n\n  private fun updateWidget(context: Context, appWidgetManager: AppWidgetManager, widgetId: Int) {\n    val intent = Intent(context, CalendarWidgetService::class.java).apply {\n      putExtra(EXTRA_APPWIDGET_ID, widgetId)\n      data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))\n    }\n\n    val remoteViews = RemoteViews(context.packageName, getLayoutResId()).apply {\n      setRemoteAdapter(R.id.calendarWidgetList, intent)\n      setEmptyView(R.id.calendarWidgetList, R.id.calendarWidgetEmptyView)\n\n      val spaceTiny = context.dimenToPx(R.dimen.spaceTiny)\n      val paddingTop = if (settings.widgetsShowLabel) context.dimenToPx(R.dimen.widgetPaddingTop) else spaceTiny\n      val labelVisibility = if (settings.widgetsShowLabel) VISIBLE else GONE\n      setViewPadding(R.id.calendarWidgetList, 0, paddingTop, 0, spaceTiny)\n      setViewPadding(R.id.calendarWidgetEmptyView, 0, paddingTop, 0, 0)\n      setViewVisibility(R.id.calendarWidgetLabel, labelVisibility)\n\n      setInt(R.id.calendarWidgetNightRoot, \"setBackgroundResource\", getBackgroundResId())\n      setInt(R.id.calendarWidgetDayRoot, \"setBackgroundResource\", getBackgroundResId())\n\n      when (settingsRepository.widgets.getWidgetCalendarMode(Mode.SHOWS, widgetId)) {\n        CalendarMode.PRESENT_FUTURE -> {\n          setImageViewResource(R.id.calendarWidgetEmptyViewIcon, R.drawable.ic_history)\n          setTextViewText(R.id.calendarWidgetEmptyViewSubtitle, context.getString(R.string.textCalendarEmpty))\n        }\n        CalendarMode.RECENTS -> {\n          setImageViewResource(R.id.calendarWidgetEmptyViewIcon, R.drawable.ic_calendar)\n          setTextViewText(R.id.calendarWidgetEmptyViewSubtitle, context.getString(R.string.textRecentsEmpty))\n        }\n      }\n    }\n\n    val mainIntent = PendingIntent.getActivity(\n      context,\n      0,\n      Intent().apply { setClassName(context, Config.HOST_ACTIVITY_NAME) },\n      FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT\n    )\n    remoteViews.setOnClickPendingIntent(R.id.calendarWidgetLabelImage, mainIntent)\n    remoteViews.setOnClickPendingIntent(R.id.calendarWidgetLabelText, mainIntent)\n\n    val modeClickIntent = PendingIntent.getBroadcast(\n      context,\n      1,\n      Intent(ACTION_CLICK).apply {\n        setClass(context, this@CalendarWidgetProvider.javaClass)\n        putExtra(EXTRA_MODE_CLICK, true)\n        putExtra(EXTRA_APPWIDGET_ID, widgetId)\n      },\n      FLAG_MUTABLE or FLAG_UPDATE_CURRENT\n    )\n    remoteViews.setOnClickPendingIntent(R.id.calendarWidgetEmptyViewIcon, modeClickIntent)\n\n    val listClickIntent = Intent(context, CalendarWidgetProvider::class.java).apply {\n      action = ACTION_CLICK\n      data = Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))\n    }\n\n    val listIntent = PendingIntent.getBroadcast(context, 0, listClickIntent, FLAG_MUTABLE or FLAG_UPDATE_CURRENT)\n    remoteViews.setPendingIntentTemplate(R.id.calendarWidgetList, listIntent)\n\n    appWidgetManager.updateAppWidget(widgetId, remoteViews)\n    appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, R.id.calendarWidgetList)\n  }\n\n  private fun toggleCalendarMode(widgetId: Int) {\n    when (settingsRepository.widgets.getWidgetCalendarMode(Mode.SHOWS, widgetId)) {\n      CalendarMode.PRESENT_FUTURE ->\n        settingsRepository.widgets.setWidgetCalendarMode(Mode.SHOWS, widgetId, CalendarMode.RECENTS)\n      CalendarMode.RECENTS ->\n        settingsRepository.widgets.setWidgetCalendarMode(Mode.SHOWS, widgetId, CalendarMode.PRESENT_FUTURE)\n    }\n  }\n\n  override fun onReceive(context: Context, intent: Intent) {\n\n    fun onListItemClick() {\n      val showId = intent.getLongExtra(EXTRA_SHOW_ID, -1L)\n      context.startActivity(\n        Intent().apply {\n          setClassName(context, Config.HOST_ACTIVITY_NAME)\n          putExtra(EXTRA_SHOW_ID, showId.toString())\n          flags = Intent.FLAG_ACTIVITY_NEW_TASK\n        }\n      )\n    }\n\n    fun onHeaderIconClick(widgetId: Int) {\n      toggleCalendarMode(widgetId)\n      requestUpdate(context.applicationContext)\n    }\n\n    super.onReceive(context, intent)\n    if (intent.action == ACTION_CLICK) {\n      when {\n        intent.extras?.containsKey(EXTRA_SHOW_ID) == true -> onListItemClick()\n        intent.extras?.containsKey(EXTRA_MODE_CLICK) == true -> {\n          val widgetId = intent.extras?.getInt(EXTRA_APPWIDGET_ID) ?: 0\n          onHeaderIconClick(widgetId)\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/calendar/CalendarWidgetService.kt",
    "content": "package com.michaldrabik.ui_widgets.calendar\n\nimport android.appwidget.AppWidgetManager\nimport android.content.Intent\nimport android.widget.RemoteViewsService\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_progress.calendar.cases.items.CalendarFutureCase\nimport com.michaldrabik.ui_progress.calendar.cases.items.CalendarRecentsCase\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass CalendarWidgetService : RemoteViewsService() {\n\n  @Inject lateinit var calendarFutureCase: CalendarFutureCase\n  @Inject lateinit var calendarRecentsCase: CalendarRecentsCase\n  @Inject lateinit var settingsRepository: SettingsRepository\n\n  override fun onGetViewFactory(intent: Intent?): CalendarWidgetViewsFactory {\n    val widgetId = intent?.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: 0\n    return CalendarWidgetViewsFactory(\n      widgetId,\n      applicationContext,\n      calendarFutureCase,\n      calendarRecentsCase,\n      settingsRepository\n    )\n  }\n}\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/calendar/CalendarWidgetViewsFactory.kt",
    "content": "package com.michaldrabik.ui_widgets.calendar\n\nimport android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID\nimport android.content.Context\nimport android.content.Intent\nimport android.view.View.GONE\nimport android.view.View.VISIBLE\nimport android.widget.RemoteViews\nimport android.widget.RemoteViewsService\nimport androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO\nimport androidx.core.os.bundleOf\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.common.extensions.toLocalZone\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.replace\nimport com.michaldrabik.ui_model.CalendarMode.PRESENT_FUTURE\nimport com.michaldrabik.ui_model.CalendarMode.RECENTS\nimport com.michaldrabik.ui_model.ImageStatus\nimport com.michaldrabik.ui_progress.calendar.cases.items.CalendarFutureCase\nimport com.michaldrabik.ui_progress.calendar.cases.items.CalendarRecentsCase\nimport com.michaldrabik.ui_progress.calendar.recycler.CalendarListItem\nimport com.michaldrabik.ui_widgets.BaseWidgetProvider.Companion.EXTRA_MODE_CLICK\nimport com.michaldrabik.ui_widgets.BaseWidgetProvider.Companion.EXTRA_SHOW_ID\nimport com.michaldrabik.ui_widgets.R\nimport kotlinx.coroutines.runBlocking\nimport java.util.Locale\n\nclass CalendarWidgetViewsFactory(\n  private val widgetId: Int,\n  private val context: Context,\n  private val calendarFutureCase: CalendarFutureCase,\n  private val calendarRecentsCase: CalendarRecentsCase,\n  private val settingsRepository: SettingsRepository,\n) : RemoteViewsService.RemoteViewsFactory {\n\n  private val imageCorner by lazy { context.dimenToPx(R.dimen.mediaTileCorner) }\n  private val imageWidth by lazy { context.dimenToPx(R.dimen.widgetImageWidth) }\n  private val imageHeight by lazy { context.dimenToPx(R.dimen.widgetImageHeight) }\n  private var mode = PRESENT_FUTURE\n\n  private val adapterItems = mutableListOf<CalendarListItem>()\n\n  override fun onDataSetChanged() {\n    runBlocking {\n      mode = settingsRepository.widgets.getWidgetCalendarMode(Mode.SHOWS, widgetId)\n      val items = when (mode) {\n        PRESENT_FUTURE -> calendarFutureCase.loadItems()\n        RECENTS -> calendarRecentsCase.loadItems()\n      }\n      adapterItems.replace(items)\n    }\n  }\n\n  override fun getViewAt(position: Int) =\n    when (val item = adapterItems[position]) {\n      is CalendarListItem.Episode -> createItemRemoteView(item)\n      is CalendarListItem.Header -> createHeaderRemoteView(item, showIcon = position == 0)\n    }\n\n  private fun createHeaderRemoteView(item: CalendarListItem.Header, showIcon: Boolean) =\n    RemoteViews(context.packageName, getHeaderLayout()).apply {\n      setTextViewText(R.id.progressWidgetHeaderTitle, context.getString(item.textResId))\n      setViewVisibility(R.id.progressWidgetHeaderTitleIcon, if (mode == RECENTS) VISIBLE else GONE)\n\n      if (showIcon) {\n        when (mode) {\n          PRESENT_FUTURE -> setImageViewResource(R.id.progressWidgetHeaderIcon, R.drawable.ic_history)\n          RECENTS -> setImageViewResource(R.id.progressWidgetHeaderIcon, R.drawable.ic_calendar)\n        }\n        setViewVisibility(R.id.progressWidgetHeaderIcon, VISIBLE)\n        val fillIntent = Intent().apply {\n          putExtras(bundleOf(EXTRA_MODE_CLICK to true))\n          putExtras(bundleOf(EXTRA_APPWIDGET_ID to widgetId))\n        }\n        setOnClickFillInIntent(R.id.progressWidgetHeaderIcon, fillIntent)\n      } else {\n        setViewVisibility(R.id.progressWidgetHeaderIcon, GONE)\n      }\n    }\n\n  private fun createItemRemoteView(item: CalendarListItem.Episode): RemoteViews {\n\n    val remoteView = RemoteViews(context.packageName, getItemLayout()).apply {\n      val translatedTitle = item.translations?.show?.title\n      val title =\n        if (translatedTitle?.isBlank() == false) translatedTitle\n        else item.show.title\n      setTextViewText(R.id.calendarWidgetItemTitle, title)\n\n      val date = item.episode.firstAired?.toLocalZone()?.let { item.dateFormat?.format(it)?.capitalizeWords() }\n      setTextViewText(R.id.calendarWidgetItemDate, date)\n\n      val isNewSeason = item.episode.number == 1\n      if (isNewSeason) {\n        setTextViewText(R.id.calendarWidgetItemOverview, String.format(Locale.ENGLISH, context.getString(R.string.textSeason), item.episode.season))\n        setTextViewText(R.id.calendarWidgetItemBadge, context.getString(R.string.textNewSeason))\n      } else {\n        val episodeTitle = when {\n          item.episode.title.isBlank() -> context.getString(R.string.textTba)\n          item.translations?.episode?.title?.isBlank() == false -> item.translations?.episode?.title\n          item.episode.title == \"Episode ${item.episode.number}\" -> String.format(\n            Locale.ENGLISH,\n            context.getString(R.string.textEpisode),\n            item.episode.number\n          )\n          else -> item.episode.title\n        }\n        val badgeTitle = String.format(\n          Locale.ENGLISH,\n          context.getString(com.michaldrabik.ui_progress.R.string.textSeasonEpisode),\n          item.episode.season,\n          item.episode.number\n        ).plus(\n          item.episode.numberAbs?.let { if (it > 0 && item.show.isAnime) \" ($it)\" else \"\" } ?: \"\"\n        )\n\n        setTextViewText(R.id.calendarWidgetItemOverview, episodeTitle)\n        setTextViewText(R.id.calendarWidgetItemBadge, badgeTitle)\n      }\n\n      val fillIntent = Intent().apply {\n        putExtras(bundleOf(EXTRA_SHOW_ID to item.show.traktId))\n      }\n      setOnClickFillInIntent(R.id.calendarWidgetItem, fillIntent)\n\n      setViewVisibility(R.id.calendarWidgetItemImageBadge, if (item.isWatchlist) VISIBLE else GONE)\n    }\n\n    if (item.image.status != ImageStatus.AVAILABLE) {\n      remoteView.setViewVisibility(R.id.calendarWidgetItemImage, GONE)\n      remoteView.setViewVisibility(R.id.calendarWidgetItemPlaceholder, VISIBLE)\n      return remoteView\n    }\n\n    try {\n      remoteView.setViewVisibility(R.id.calendarWidgetItemImage, GONE)\n      remoteView.setViewVisibility(R.id.calendarWidgetItemPlaceholder, GONE)\n\n      val bitmap = Glide.with(context)\n        .asBitmap()\n        .load(item.image.fullFileUrl)\n        .transform(CenterCrop(), RoundedCorners(imageCorner))\n        .submit(imageWidth, imageHeight)\n        .get()\n\n      remoteView.setImageViewBitmap(R.id.calendarWidgetItemImage, bitmap)\n      remoteView.setViewVisibility(R.id.calendarWidgetItemImage, VISIBLE)\n    } catch (t: Throwable) {\n      remoteView.setViewVisibility(R.id.calendarWidgetItemImage, GONE)\n      remoteView.setViewVisibility(R.id.calendarWidgetItemPlaceholder, VISIBLE)\n    }\n\n    return remoteView\n  }\n\n  private fun getItemLayout(): Int {\n    val isLight = settingsRepository.widgets.widgetsTheme == MODE_NIGHT_NO\n    return when {\n      isLight -> R.layout.widget_calendar_item_day\n      else -> R.layout.widget_calendar_item_night\n    }\n  }\n\n  private fun getHeaderLayout(): Int {\n    val isLight = settingsRepository.widgets.widgetsTheme == MODE_NIGHT_NO\n    return when {\n      isLight -> R.layout.widget_header_day\n      else -> R.layout.widget_header_night\n    }\n  }\n\n  override fun getItemId(position: Int) = adapterItems[position].show.traktId\n\n  override fun getLoadingView() = RemoteViews(context.packageName, R.layout.widget_loading_item)\n\n  override fun getCount() = adapterItems.size\n\n  override fun hasStableIds() = true\n\n  override fun getViewTypeCount() = 4\n\n  override fun onCreate() = Unit\n\n  override fun onDestroy() = Unit\n}\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/calendar_movies/CalendarMoviesWidgetProvider.kt",
    "content": "package com.michaldrabik.ui_widgets.calendar_movies\n\nimport android.app.PendingIntent\nimport android.app.PendingIntent.FLAG_IMMUTABLE\nimport android.app.PendingIntent.FLAG_MUTABLE\nimport android.app.PendingIntent.FLAG_UPDATE_CURRENT\nimport android.appwidget.AppWidgetManager\nimport android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE\nimport android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport android.view.View.GONE\nimport android.view.View.VISIBLE\nimport android.widget.RemoteViews\nimport androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_widgets.BaseWidgetProvider\nimport com.michaldrabik.ui_widgets.R\nimport dagger.hilt.android.AndroidEntryPoint\nimport timber.log.Timber\n\n@AndroidEntryPoint\nclass CalendarMoviesWidgetProvider : BaseWidgetProvider() {\n\n  companion object {\n    fun requestUpdate(context: Context) {\n      val applicationContext = context.applicationContext\n      val intent = Intent(applicationContext, CalendarMoviesWidgetProvider::class.java).apply {\n        val ids: IntArray = AppWidgetManager.getInstance(applicationContext)\n          .getAppWidgetIds(ComponentName(applicationContext, CalendarMoviesWidgetProvider::class.java))\n        action = ACTION_APPWIDGET_UPDATE\n        putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)\n      }\n      applicationContext.sendBroadcast(intent)\n      Timber.d(\"Widget update requested.\")\n    }\n  }\n\n  override fun getLayoutResId(): Int {\n    val isLight = settingsRepository.widgets.widgetsTheme == MODE_NIGHT_NO\n    return when {\n      isLight -> R.layout.widget_movies_calendar_day\n      else -> R.layout.widget_movies_calendar_night\n    }\n  }\n\n  override fun onUpdate(\n    context: Context,\n    appWidgetManager: AppWidgetManager,\n    appWidgetIds: IntArray?,\n  ) {\n    super.onUpdate(context, appWidgetManager, appWidgetIds)\n    appWidgetIds?.forEach { updateWidget(context, appWidgetManager, it) }\n  }\n\n  private fun updateWidget(context: Context, appWidgetManager: AppWidgetManager, widgetId: Int) {\n    val spaceTiny = context.dimenToPx(R.dimen.spaceTiny)\n\n    val intent = Intent(context, CalendarMoviesWidgetService::class.java).apply {\n      putExtra(EXTRA_APPWIDGET_ID, widgetId)\n      data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))\n    }\n\n    val remoteViews = RemoteViews(context.packageName, getLayoutResId()).apply {\n      setRemoteAdapter(R.id.calendarWidgetMoviesList, intent)\n      setEmptyView(R.id.calendarWidgetMoviesList, R.id.calendarWidgetMoviesEmptyView)\n\n      val paddingTop = if (settings.widgetsShowLabel) context.dimenToPx(R.dimen.widgetPaddingTop) else spaceTiny\n      val labelVisibility = if (settings.widgetsShowLabel) VISIBLE else GONE\n      setViewPadding(R.id.calendarWidgetMoviesList, 0, paddingTop, 0, spaceTiny)\n      setViewPadding(R.id.calendarWidgetMoviesEmptyView, 0, paddingTop, 0, 0)\n      setViewVisibility(R.id.calendarWidgetMoviesLabel, labelVisibility)\n\n      setInt(R.id.calendarWidgetMoviesNightRoot, \"setBackgroundResource\", getBackgroundResId())\n      setInt(R.id.calendarWidgetMoviesDayRoot, \"setBackgroundResource\", getBackgroundResId())\n\n      when (settingsRepository.widgets.getWidgetCalendarMode(Mode.MOVIES, widgetId)) {\n        CalendarMode.PRESENT_FUTURE -> {\n          setImageViewResource(R.id.calendarWidgetMoviesEmptyViewIcon, R.drawable.ic_history)\n          setTextViewText(R.id.calendarWidgetMoviesEmptyViewSubtitle, context.getString(R.string.textMoviesCalendarEmpty))\n        }\n        CalendarMode.RECENTS -> {\n          setImageViewResource(R.id.calendarWidgetMoviesEmptyViewIcon, R.drawable.ic_calendar)\n          setTextViewText(R.id.calendarWidgetMoviesEmptyViewSubtitle, context.getString(R.string.textMoviesCalendarRecentsEmpty))\n        }\n      }\n    }\n\n    val listClickIntent = Intent(context, CalendarMoviesWidgetProvider::class.java).apply {\n      action = ACTION_CLICK\n      data = Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))\n    }\n\n    val mainIntent = PendingIntent.getActivity(\n      context,\n      0,\n      Intent().apply { setClassName(context, Config.HOST_ACTIVITY_NAME) },\n      FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT\n    )\n    remoteViews.setOnClickPendingIntent(R.id.calendarWidgetMoviesLabelImage, mainIntent)\n    remoteViews.setOnClickPendingIntent(R.id.calendarWidgetMoviesLabelText, mainIntent)\n\n    val modeClickIntent = PendingIntent.getBroadcast(\n      context,\n      2,\n      Intent(ACTION_CLICK).apply {\n        setClass(context, this@CalendarMoviesWidgetProvider.javaClass)\n        putExtra(EXTRA_MODE_CLICK, true)\n        putExtra(EXTRA_APPWIDGET_ID, widgetId)\n      },\n      FLAG_MUTABLE or FLAG_UPDATE_CURRENT\n    )\n    remoteViews.setOnClickPendingIntent(R.id.calendarWidgetMoviesEmptyViewIcon, modeClickIntent)\n\n    val listIntent = PendingIntent.getBroadcast(context, 0, listClickIntent, FLAG_MUTABLE or FLAG_UPDATE_CURRENT)\n    remoteViews.setPendingIntentTemplate(R.id.calendarWidgetMoviesList, listIntent)\n\n    appWidgetManager.updateAppWidget(widgetId, remoteViews)\n    appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, R.id.calendarWidgetMoviesList)\n  }\n\n  private fun toggleCalendarMode(widgetId: Int) {\n    when (settingsRepository.widgets.getWidgetCalendarMode(Mode.MOVIES, widgetId)) {\n      CalendarMode.PRESENT_FUTURE ->\n        settingsRepository.widgets.setWidgetCalendarMode(Mode.MOVIES, widgetId, CalendarMode.RECENTS)\n      CalendarMode.RECENTS ->\n        settingsRepository.widgets.setWidgetCalendarMode(Mode.MOVIES, widgetId, CalendarMode.PRESENT_FUTURE)\n    }\n  }\n\n  override fun onReceive(context: Context, intent: Intent) {\n\n    fun onListItemClick() {\n      val movieId = intent.getLongExtra(EXTRA_MOVIE_ID, -1L)\n      context.startActivity(\n        Intent().apply {\n          setClassName(context, Config.HOST_ACTIVITY_NAME)\n          putExtra(EXTRA_MOVIE_ID, movieId.toString())\n          flags = Intent.FLAG_ACTIVITY_NEW_TASK\n        }\n      )\n    }\n\n    fun onHeaderIconClick(widgetId: Int) {\n      toggleCalendarMode(widgetId)\n      requestUpdate(context.applicationContext)\n    }\n\n    super.onReceive(context, intent)\n    if (intent.action == ACTION_CLICK) {\n      when {\n        intent.extras?.containsKey(EXTRA_MOVIE_ID) == true -> onListItemClick()\n        intent.extras?.containsKey(EXTRA_MODE_CLICK) == true -> {\n          val widgetId = intent.extras?.getInt(EXTRA_APPWIDGET_ID) ?: 0\n          onHeaderIconClick(widgetId)\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/calendar_movies/CalendarMoviesWidgetService.kt",
    "content": "package com.michaldrabik.ui_widgets.calendar_movies\n\nimport android.appwidget.AppWidgetManager\nimport android.content.Intent\nimport android.widget.RemoteViewsService\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_progress_movies.calendar.cases.items.CalendarMoviesFutureCase\nimport com.michaldrabik.ui_progress_movies.calendar.cases.items.CalendarMoviesRecentsCase\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass CalendarMoviesWidgetService : RemoteViewsService() {\n\n  @Inject lateinit var calendarFutureCase: CalendarMoviesFutureCase\n  @Inject lateinit var calendarRecentsCase: CalendarMoviesRecentsCase\n  @Inject lateinit var settingsRepository: SettingsRepository\n\n  override fun onGetViewFactory(intent: Intent?): CalendarMoviesWidgetViewsFactory {\n    val widgetId = intent?.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: 0\n    return CalendarMoviesWidgetViewsFactory(\n      widgetId,\n      applicationContext,\n      calendarFutureCase,\n      calendarRecentsCase,\n      settingsRepository\n    )\n  }\n}\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/calendar_movies/CalendarMoviesWidgetViewsFactory.kt",
    "content": "package com.michaldrabik.ui_widgets.calendar_movies\n\nimport android.appwidget.AppWidgetManager\nimport android.content.Context\nimport android.content.Intent\nimport android.view.View.GONE\nimport android.view.View.VISIBLE\nimport android.widget.RemoteViews\nimport android.widget.RemoteViewsService\nimport androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO\nimport androidx.core.os.bundleOf\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.michaldrabik.common.Mode\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.extensions.capitalizeWords\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.replace\nimport com.michaldrabik.ui_model.CalendarMode\nimport com.michaldrabik.ui_model.ImageStatus\nimport com.michaldrabik.ui_progress_movies.calendar.cases.items.CalendarMoviesFutureCase\nimport com.michaldrabik.ui_progress_movies.calendar.cases.items.CalendarMoviesRecentsCase\nimport com.michaldrabik.ui_progress_movies.calendar.recycler.CalendarMovieListItem\nimport com.michaldrabik.ui_widgets.BaseWidgetProvider\nimport com.michaldrabik.ui_widgets.BaseWidgetProvider.Companion.EXTRA_MOVIE_ID\nimport com.michaldrabik.ui_widgets.R\nimport kotlinx.coroutines.runBlocking\n\nclass CalendarMoviesWidgetViewsFactory(\n  private val widgetId: Int,\n  private val context: Context,\n  private val futureItemsCase: CalendarMoviesFutureCase,\n  private val recentItemsCase: CalendarMoviesRecentsCase,\n  private val settingsRepository: SettingsRepository,\n) : RemoteViewsService.RemoteViewsFactory {\n\n  private val imageCorner by lazy { context.dimenToPx(R.dimen.mediaTileCorner) }\n  private val imageWidth by lazy { context.dimenToPx(R.dimen.widgetImageWidth) }\n  private val imageHeight by lazy { context.dimenToPx(R.dimen.widgetImageHeight) }\n  private var mode = CalendarMode.PRESENT_FUTURE\n\n  private val adapterItems = mutableListOf<CalendarMovieListItem>()\n\n  override fun onDataSetChanged() {\n    runBlocking {\n      mode = settingsRepository.widgets.getWidgetCalendarMode(Mode.MOVIES, widgetId)\n      val items = when (mode) {\n        CalendarMode.PRESENT_FUTURE -> futureItemsCase.loadItems()\n        CalendarMode.RECENTS -> recentItemsCase.loadItems()\n      }\n      adapterItems.replace(items)\n    }\n  }\n\n  override fun getViewAt(position: Int) =\n    when (val item = adapterItems[position]) {\n      is CalendarMovieListItem.MovieItem -> createItemRemoteView(item)\n      is CalendarMovieListItem.Header -> createHeaderRemoteView(item, showIcon = position == 0)\n    }\n\n  private fun createHeaderRemoteView(item: CalendarMovieListItem.Header, showIcon: Boolean) =\n    RemoteViews(context.packageName, getHeaderLayout()).apply {\n      setTextViewText(R.id.progressWidgetHeaderTitle, context.getString(item.textResId))\n      setViewVisibility(R.id.progressWidgetHeaderTitleIcon, if (mode == CalendarMode.RECENTS) VISIBLE else GONE)\n\n      if (showIcon) {\n        when (mode) {\n          CalendarMode.PRESENT_FUTURE -> setImageViewResource(R.id.progressWidgetHeaderIcon, R.drawable.ic_history)\n          CalendarMode.RECENTS -> setImageViewResource(R.id.progressWidgetHeaderIcon, R.drawable.ic_calendar)\n        }\n        setViewVisibility(R.id.progressWidgetHeaderIcon, VISIBLE)\n        val fillIntent = Intent().apply {\n          putExtras(bundleOf(BaseWidgetProvider.EXTRA_MODE_CLICK to true))\n          putExtras(bundleOf(AppWidgetManager.EXTRA_APPWIDGET_ID to widgetId))\n        }\n        setOnClickFillInIntent(R.id.progressWidgetHeaderIcon, fillIntent)\n      } else {\n        setViewVisibility(R.id.progressWidgetHeaderIcon, GONE)\n      }\n    }\n\n  private fun createItemRemoteView(item: CalendarMovieListItem.MovieItem): RemoteViews {\n    val translatedTitle = item.translation?.title\n    val title =\n      if (translatedTitle?.isBlank() == false) translatedTitle\n      else item.movie.title\n\n    val translatedDescription = item.translation?.overview\n    val overview =\n      if (translatedDescription?.isBlank() == false) translatedDescription\n      else item.movie.overview\n\n    val date = if (item.movie.released != null) {\n      item.dateFormat?.format(item.movie.released)?.capitalizeWords()\n    } else {\n      context.getString(R.string.textTba)\n    }\n\n    val remoteView = RemoteViews(context.packageName, getItemLayout()).apply {\n      setTextViewText(R.id.calendarMoviesWidgetItemTitle, title)\n      setTextViewText(R.id.calendarMoviesWidgetItemOverview, overview)\n      setViewVisibility(R.id.calendarMoviesWidgetItemOverview, if (overview.isBlank()) GONE else VISIBLE)\n      setTextViewText(R.id.calendarMoviesWidgetItemDate, date)\n\n      val fillIntent = Intent().apply {\n        putExtras(bundleOf(EXTRA_MOVIE_ID to item.movie.traktId))\n      }\n      setOnClickFillInIntent(R.id.calendarMoviesWidgetItem, fillIntent)\n    }\n\n    if (item.image.status != ImageStatus.AVAILABLE) {\n      remoteView.setViewVisibility(R.id.calendarMoviesWidgetItemImage, GONE)\n      remoteView.setViewVisibility(R.id.calendarMoviesWidgetItemPlaceholder, VISIBLE)\n      return remoteView\n    }\n\n    try {\n      remoteView.setViewVisibility(R.id.calendarMoviesWidgetItemImage, GONE)\n      remoteView.setViewVisibility(R.id.calendarMoviesWidgetItemPlaceholder, GONE)\n\n      val bitmap = Glide.with(context)\n        .asBitmap()\n        .load(item.image.fullFileUrl)\n        .transform(CenterCrop(), RoundedCorners(imageCorner))\n        .submit(imageWidth, imageHeight)\n        .get()\n\n      remoteView.setImageViewBitmap(R.id.calendarMoviesWidgetItemImage, bitmap)\n      remoteView.setViewVisibility(R.id.calendarMoviesWidgetItemImage, VISIBLE)\n    } catch (t: Throwable) {\n      remoteView.setViewVisibility(R.id.calendarMoviesWidgetItemImage, GONE)\n      remoteView.setViewVisibility(R.id.calendarMoviesWidgetItemPlaceholder, VISIBLE)\n    }\n\n    return remoteView\n  }\n\n  private fun getItemLayout(): Int {\n    val isLight = settingsRepository.widgets.widgetsTheme == MODE_NIGHT_NO\n    return when {\n      isLight -> R.layout.widget_movies_calendar_item_day\n      else -> R.layout.widget_movies_calendar_item_night\n    }\n  }\n\n  private fun getHeaderLayout(): Int {\n    val isLight = settingsRepository.widgets.widgetsTheme == MODE_NIGHT_NO\n    return when {\n      isLight -> R.layout.widget_header_day\n      else -> R.layout.widget_header_night\n    }\n  }\n\n  override fun getItemId(position: Int) = adapterItems[position].movie.traktId\n\n  override fun getLoadingView() =\n    RemoteViews(context.packageName, R.layout.widget_loading_item)\n\n  override fun getCount() = adapterItems.size\n\n  override fun hasStableIds() = true\n\n  override fun getViewTypeCount() = 4\n\n  override fun onCreate() = Unit\n\n  override fun onDestroy() = Unit\n}\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/progress/ProgressWidgetEpisodeCheckService.kt",
    "content": "package com.michaldrabik.ui_widgets.progress\n\nimport android.content.Context\nimport android.content.Intent\nimport androidx.core.app.JobIntentService\nimport com.michaldrabik.repository.EpisodesManager\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.common.WidgetsProvider\nimport com.michaldrabik.ui_base.trakt.quicksync.QuickSyncManager\nimport com.michaldrabik.ui_model.IdTrakt\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.runBlocking\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ProgressWidgetEpisodeCheckService : JobIntentService(), CoroutineScope {\n\n  companion object {\n    private const val JOB_ID = 1001\n    private const val EXTRA_EPISODE_ID = \"EXTRA_EPISODE_ID\"\n    private const val EXTRA_SEASON_ID = \"EXTRA_SEASON_ID\"\n    private const val EXTRA_SHOW_ID = \"EXTRA_SHOW_ID\"\n\n    fun initialize(\n      context: Context,\n      episodeId: Long,\n      seasonId: Long,\n      showId: IdTrakt,\n    ) {\n      val intent = Intent().apply {\n        putExtra(EXTRA_EPISODE_ID, episodeId)\n        putExtra(EXTRA_SEASON_ID, seasonId)\n        putExtra(EXTRA_SHOW_ID, showId.id)\n      }\n      enqueueWork(\n        context, ProgressWidgetEpisodeCheckService::class.java,\n        JOB_ID, intent\n      )\n    }\n  }\n\n  override val coroutineContext = Job() + Dispatchers.Main\n\n  @Inject lateinit var episodesManager: EpisodesManager\n  @Inject lateinit var quickSyncManager: QuickSyncManager\n\n  override fun onHandleWork(intent: Intent) {\n    val episodeId = intent.getLongExtra(EXTRA_EPISODE_ID, -1)\n    val seasonId = intent.getLongExtra(EXTRA_SEASON_ID, -1)\n    val showId = intent.getLongExtra(EXTRA_SHOW_ID, -1)\n\n    if (episodeId == -1L || seasonId == -1L || showId == -1L) {\n      val error = Throwable(\"Invalid ID.\")\n      Logger.record(error, \"ProgressWidgetEpisodeCheckService::onHandleWork()\")\n      return\n    }\n\n    runBlocking {\n      episodesManager.setEpisodeWatched(episodeId, seasonId, IdTrakt(showId))\n      quickSyncManager.scheduleEpisodes(\n        showId = showId,\n        episodesIds = listOf(episodeId)\n      )\n      (applicationContext as WidgetsProvider).requestShowsWidgetsUpdate()\n    }\n  }\n\n  override fun onDestroy() {\n    cancel()\n    super.onDestroy()\n  }\n}\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/progress/ProgressWidgetProvider.kt",
    "content": "package com.michaldrabik.ui_widgets.progress\n\nimport android.app.PendingIntent\nimport android.app.PendingIntent.FLAG_IMMUTABLE\nimport android.app.PendingIntent.FLAG_MUTABLE\nimport android.app.PendingIntent.FLAG_UPDATE_CURRENT\nimport android.appwidget.AppWidgetManager\nimport android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE\nimport android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID\nimport android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS\nimport android.appwidget.AppWidgetManager.getInstance\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.Intent\nimport android.content.Intent.URI_INTENT_SCHEME\nimport android.net.Uri\nimport android.view.View.GONE\nimport android.view.View.VISIBLE\nimport android.widget.RemoteViews\nimport androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO\nimport com.michaldrabik.common.Config.HOST_ACTIVITY_NAME\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_widgets.BaseWidgetProvider\nimport com.michaldrabik.ui_widgets.R\nimport dagger.hilt.android.AndroidEntryPoint\nimport timber.log.Timber\n\n@AndroidEntryPoint\nclass ProgressWidgetProvider : BaseWidgetProvider() {\n\n  companion object {\n    const val EXTRA_SEASON_ID = \"EXTRA_SEASON_ID\"\n    const val EXTRA_EPISODE_ID = \"EXTRA_EPISODE_ID\"\n\n    fun requestUpdate(context: Context) {\n      val applicationContext = context.applicationContext\n      val intent = Intent(applicationContext, ProgressWidgetProvider::class.java).apply {\n        val ids: IntArray = getInstance(applicationContext)\n          .getAppWidgetIds(ComponentName(applicationContext, ProgressWidgetProvider::class.java))\n        action = ACTION_APPWIDGET_UPDATE\n        putExtra(EXTRA_APPWIDGET_IDS, ids)\n      }\n      applicationContext.sendBroadcast(intent)\n      Timber.d(\"Widget update requested.\")\n    }\n  }\n\n  override fun getLayoutResId(): Int {\n    val isLight = settingsRepository.widgets.widgetsTheme == MODE_NIGHT_NO\n    return when {\n      isLight -> R.layout.widget_progress_day\n      else -> R.layout.widget_progress_night\n    }\n  }\n\n  override fun onUpdate(\n    context: Context,\n    appWidgetManager: AppWidgetManager,\n    appWidgetIds: IntArray?,\n  ) {\n    super.onUpdate(context, appWidgetManager, appWidgetIds)\n    appWidgetIds?.forEach { updateWidget(context, appWidgetManager, it) }\n  }\n\n  private fun updateWidget(context: Context, appWidgetManager: AppWidgetManager, widgetId: Int) {\n    val intent = Intent(context, ProgressWidgetService::class.java).apply {\n      putExtra(EXTRA_APPWIDGET_ID, widgetId)\n      data = Uri.parse(toUri(URI_INTENT_SCHEME))\n    }\n\n    val remoteViews = RemoteViews(context.packageName, getLayoutResId()).apply {\n      setRemoteAdapter(R.id.progressWidgetList, intent)\n      setEmptyView(R.id.progressWidgetList, R.id.progressWidgetEmptyView)\n\n      val spaceTiny = context.dimenToPx(R.dimen.spaceTiny)\n      val paddingTop = if (settings.widgetsShowLabel) context.dimenToPx(R.dimen.widgetPaddingTop) else spaceTiny\n      val labelVisibility = if (settings.widgetsShowLabel) VISIBLE else GONE\n      setViewPadding(R.id.progressWidgetList, 0, paddingTop, 0, spaceTiny)\n      setViewVisibility(R.id.progressWidgetLabel, labelVisibility)\n\n      setInt(R.id.progressWidgetNightRoot, \"setBackgroundResource\", getBackgroundResId())\n      setInt(R.id.progressWidgetDayRoot, \"setBackgroundResource\", getBackgroundResId())\n    }\n\n    val mainIntent = PendingIntent.getActivity(\n      context,\n      0,\n      Intent().apply { setClassName(context, HOST_ACTIVITY_NAME) },\n      FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT\n    )\n    remoteViews.setOnClickPendingIntent(R.id.progressWidgetLabel, mainIntent)\n\n    val listClickIntent = Intent(context, ProgressWidgetProvider::class.java).apply {\n      action = ACTION_CLICK\n      data = Uri.parse(intent.toUri(URI_INTENT_SCHEME))\n    }\n    val showDetailsPendingIntent = PendingIntent.getBroadcast(context, 0, listClickIntent, FLAG_MUTABLE or FLAG_UPDATE_CURRENT)\n    remoteViews.setPendingIntentTemplate(R.id.progressWidgetList, showDetailsPendingIntent)\n\n    appWidgetManager.updateAppWidget(widgetId, remoteViews)\n    appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, R.id.progressWidgetList)\n  }\n\n  override fun onReceive(context: Context, intent: Intent) {\n    super.onReceive(context, intent)\n    if (intent.action.equals(ACTION_CLICK)) {\n      when {\n        intent.extras?.containsKey(EXTRA_EPISODE_ID) == true -> {\n          val episodeId = intent.getLongExtra(EXTRA_EPISODE_ID, -1L)\n          val seasonId = intent.getLongExtra(EXTRA_SEASON_ID, -1L)\n          val showId = intent.getLongExtra(EXTRA_SHOW_ID, -1L)\n          ProgressWidgetEpisodeCheckService.initialize(\n            context.applicationContext,\n            episodeId,\n            seasonId,\n            IdTrakt(showId)\n          )\n        }\n        intent.extras?.containsKey(EXTRA_SHOW_ID) == true -> {\n          val showId = intent.getLongExtra(EXTRA_SHOW_ID, -1L)\n          context.startActivity(\n            Intent().apply {\n              setClassName(context, HOST_ACTIVITY_NAME)\n              putExtra(EXTRA_SHOW_ID, showId.toString())\n              flags = Intent.FLAG_ACTIVITY_NEW_TASK\n            }\n          )\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/progress/ProgressWidgetService.kt",
    "content": "package com.michaldrabik.ui_widgets.progress\n\nimport android.content.Intent\nimport android.widget.RemoteViewsService\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_progress.progress.cases.ProgressItemsCase\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ProgressWidgetService : RemoteViewsService() {\n\n  @Inject lateinit var progressItemsCase: ProgressItemsCase\n  @Inject lateinit var settingsRepository: SettingsRepository\n\n  override fun onGetViewFactory(intent: Intent?) =\n    ProgressWidgetViewsFactory(\n      applicationContext,\n      progressItemsCase,\n      settingsRepository\n    )\n}\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/progress/ProgressWidgetViewsFactory.kt",
    "content": "package com.michaldrabik.ui_widgets.progress\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.View.GONE\nimport android.view.View.VISIBLE\nimport android.widget.RemoteViews\nimport android.widget.RemoteViewsService\nimport androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.DurationPrinter\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.replace\nimport com.michaldrabik.ui_model.ImageStatus\nimport com.michaldrabik.ui_model.Season\nimport com.michaldrabik.ui_progress.progress.cases.ProgressItemsCase\nimport com.michaldrabik.ui_progress.progress.recycler.ProgressListItem\nimport com.michaldrabik.ui_widgets.BaseWidgetProvider.Companion.EXTRA_SHOW_ID\nimport com.michaldrabik.ui_widgets.R\nimport com.michaldrabik.ui_widgets.progress.ProgressWidgetProvider.Companion.EXTRA_EPISODE_ID\nimport com.michaldrabik.ui_widgets.progress.ProgressWidgetProvider.Companion.EXTRA_SEASON_ID\nimport kotlinx.coroutines.runBlocking\nimport java.util.Locale.ENGLISH\nimport kotlin.math.roundToInt\n\nclass ProgressWidgetViewsFactory(\n  private val context: Context,\n  private val itemsCase: ProgressItemsCase,\n  private val settingsRepository: SettingsRepository,\n) : RemoteViewsService.RemoteViewsFactory {\n\n  private val imageCorner by lazy { context.dimenToPx(R.dimen.mediaTileCorner) }\n  private val imageWidth by lazy { context.dimenToPx(R.dimen.widgetImageWidth) }\n  private val imageHeight by lazy { context.dimenToPx(R.dimen.widgetImageHeight) }\n  private val checkWidth by lazy { context.dimenToPx(R.dimen.widgetCheckButtonWidth) }\n  private val spaceMedium by lazy { context.dimenToPx(R.dimen.spaceMedium) }\n  private val adapterItems by lazy { mutableListOf<ProgressListItem>() }\n  private val durationPrinter by lazy { DurationPrinter(context.applicationContext) }\n\n  override fun onDataSetChanged() {\n    runBlocking {\n      val items = itemsCase.loadItems(\"\")\n        .filterNot { it is ProgressListItem.Filters }\n      adapterItems.replace(items)\n    }\n  }\n\n  override fun getViewAt(position: Int) =\n    when (val item = adapterItems[position]) {\n      is ProgressListItem.Episode -> createItemRemoteView(item)\n      is ProgressListItem.Header -> createHeaderRemoteView(item)\n      else -> throw IllegalStateException()\n    }\n\n  private fun createItemRemoteView(item: ProgressListItem.Episode): RemoteViews {\n    val title =\n      if (item.translations?.show?.title?.isBlank() == false) item.translations?.show?.title\n      else item.show.title\n\n    val subtitle = String.format(ENGLISH, \"S.%02d E.%02d\", item.episode?.season, item.episode?.number)\n      .plus(item.episode?.numberAbs?.let { if (it > 0 && item.show.isAnime) \" ($it)\" else \"\" } ?: \"\")\n\n    var percent = 0\n    if (item.totalCount != 0) {\n      percent = ((item.watchedCount.toFloat() / item.totalCount.toFloat()) * 100F).roundToInt()\n    }\n    val progressText =\n      String.format(ENGLISH, \"%d/%d (%d%%)\", item.watchedCount, item.totalCount, percent)\n    val imageUrl = item.image.fullFileUrl\n    val hasAired = item.episode?.hasAired(item.season ?: Season.EMPTY) == true\n    val subtitle2 = when {\n      item.episode?.title?.isBlank() == true -> context.getString(R.string.textTba)\n      item.translations?.episode?.title?.isBlank() == false ->\n        item.translations?.episode?.title ?: context.getString(R.string.textTba)\n      item.episode?.title == \"Episode ${item.episode?.number}\" ->\n        String.format(ENGLISH, context.getString(R.string.textEpisode), item.episode?.number)\n      else -> item.episode?.title\n    }\n\n    val remoteView = RemoteViews(context.packageName, getItemLayout()).apply {\n      setTextViewText(R.id.progressWidgetItemTitle, title)\n      setTextViewText(R.id.progressWidgetItemSubtitle, subtitle)\n      setTextViewText(R.id.progressWidgetItemSubtitle2, subtitle2)\n      setTextViewText(R.id.progressWidgetItemProgressText, progressText)\n      setViewVisibility(R.id.progressWidgetItemBadge, if (item.isNew()) VISIBLE else GONE)\n      setProgressBar(R.id.progressWidgetItemProgress, item.totalCount, item.watchedCount, false)\n      if (hasAired) {\n        setViewVisibility(R.id.progressWidgetItemCheckButton, VISIBLE)\n        setViewVisibility(R.id.progressWidgetItemDateButton, GONE)\n        setViewPadding(R.id.progressWidgetItemProgress, 0, 0, checkWidth, 0)\n      } else {\n        setViewVisibility(R.id.progressWidgetItemCheckButton, GONE)\n        setViewVisibility(R.id.progressWidgetItemDateButton, VISIBLE)\n        setTextViewText(R.id.progressWidgetItemDateButton, durationPrinter.print(item.episode?.firstAired))\n        setViewPadding(R.id.progressWidgetItemProgress, 0, 0, spaceMedium, 0)\n      }\n\n      val fillIntent = Intent().apply {\n        putExtras(\n          Bundle().apply {\n            putExtra(EXTRA_SHOW_ID, item.show.traktId)\n          }\n        )\n      }\n      setOnClickFillInIntent(R.id.progressWidgetItem, fillIntent)\n\n      val checkFillIntent = Intent().apply {\n        putExtras(\n          Bundle().apply {\n            putExtra(EXTRA_EPISODE_ID, item.episode?.ids?.trakt?.id)\n            putExtra(EXTRA_SEASON_ID, item.season?.ids?.trakt?.id)\n            putExtra(EXTRA_SHOW_ID, item.show.traktId)\n          }\n        )\n      }\n      setOnClickFillInIntent(R.id.progressWidgetItemCheckButton, checkFillIntent)\n    }\n\n    if (item.image.status != ImageStatus.AVAILABLE) {\n      remoteView.setViewVisibility(R.id.progressWidgetItemImage, GONE)\n      remoteView.setViewVisibility(R.id.progressWidgetItemPlaceholder, VISIBLE)\n      return remoteView\n    }\n\n    try {\n      remoteView.setViewVisibility(R.id.progressWidgetItemImage, GONE)\n      remoteView.setViewVisibility(R.id.progressWidgetItemPlaceholder, GONE)\n\n      val bitmap = Glide.with(context)\n        .asBitmap()\n        .load(imageUrl)\n        .transform(CenterCrop(), RoundedCorners(imageCorner))\n        .submit(imageWidth, imageHeight)\n        .get()\n\n      remoteView.setImageViewBitmap(R.id.progressWidgetItemImage, bitmap)\n      remoteView.setViewVisibility(R.id.progressWidgetItemImage, VISIBLE)\n    } catch (t: Throwable) {\n      remoteView.setViewVisibility(R.id.progressWidgetItemImage, GONE)\n      remoteView.setViewVisibility(R.id.progressWidgetItemPlaceholder, VISIBLE)\n    }\n\n    return remoteView\n  }\n\n  private fun createHeaderRemoteView(item: ProgressListItem.Header) =\n    RemoteViews(context.packageName, getHeaderLayout()).apply {\n      setTextViewText(R.id.progressWidgetHeaderTitle, context.getString(item.textResId))\n    }\n\n  private fun getItemLayout(): Int {\n    val isLight = settingsRepository.widgets.widgetsTheme == MODE_NIGHT_NO\n    return when {\n      isLight -> R.layout.widget_progress_item_day\n      else -> R.layout.widget_progress_item_night\n    }\n  }\n\n  private fun getHeaderLayout(): Int {\n    val isLight = settingsRepository.widgets.widgetsTheme == MODE_NIGHT_NO\n    return when {\n      isLight -> R.layout.widget_header_day\n      else -> R.layout.widget_header_night\n    }\n  }\n\n  override fun getItemId(position: Int) = adapterItems[position].show.traktId\n\n  override fun getLoadingView() = RemoteViews(context.packageName, R.layout.widget_loading_item)\n\n  override fun getCount() = adapterItems.size\n\n  override fun hasStableIds() = true\n\n  override fun getViewTypeCount() = 4\n\n  override fun onCreate() = Unit\n\n  override fun onDestroy() = Unit\n}\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/progress_movies/ProgressMoviesWidgetCheckService.kt",
    "content": "package com.michaldrabik.ui_widgets.progress_movies\n\nimport android.content.Context\nimport android.content.Intent\nimport androidx.core.app.JobIntentService\nimport com.michaldrabik.ui_base.Logger\nimport com.michaldrabik.ui_base.common.WidgetsProvider\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_progress_movies.main.cases.ProgressMoviesMainCase\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.runBlocking\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ProgressMoviesWidgetCheckService : JobIntentService(), CoroutineScope {\n\n  companion object {\n    private const val JOB_ID = 1010\n    private const val EXTRA_MOVIE_ID = \"EXTRA_MOVIE_ID\"\n\n    fun initialize(context: Context, movieId: IdTrakt) {\n      val intent = Intent().apply {\n        putExtra(EXTRA_MOVIE_ID, movieId.id)\n      }\n      enqueueWork(\n        context, ProgressMoviesWidgetCheckService::class.java,\n        JOB_ID, intent\n      )\n    }\n  }\n\n  override val coroutineContext = Job() + Dispatchers.Main\n\n  @Inject lateinit var progressMoviesCase: ProgressMoviesMainCase\n\n  override fun onHandleWork(intent: Intent) {\n    val movieId = intent.getLongExtra(EXTRA_MOVIE_ID, -1)\n    if (movieId == -1L) {\n      val error = Throwable(\"Invalid ID.\")\n      Logger.record(error, \"ProgressMoviesWidgetCheckService::onHandleWork()\")\n      return\n    }\n\n    runBlocking {\n      progressMoviesCase.addToMyMovies(IdTrakt(movieId))\n      (applicationContext as WidgetsProvider).requestMoviesWidgetsUpdate()\n    }\n  }\n\n  override fun onDestroy() {\n    cancel()\n    super.onDestroy()\n  }\n}\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/progress_movies/ProgressMoviesWidgetProvider.kt",
    "content": "package com.michaldrabik.ui_widgets.progress_movies\n\nimport android.app.PendingIntent\nimport android.app.PendingIntent.FLAG_IMMUTABLE\nimport android.app.PendingIntent.FLAG_MUTABLE\nimport android.app.PendingIntent.FLAG_UPDATE_CURRENT\nimport android.appwidget.AppWidgetManager\nimport android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport android.view.View.GONE\nimport android.view.View.VISIBLE\nimport android.widget.RemoteViews\nimport androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO\nimport com.michaldrabik.common.Config\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_model.IdTrakt\nimport com.michaldrabik.ui_widgets.BaseWidgetProvider\nimport com.michaldrabik.ui_widgets.R\nimport dagger.hilt.android.AndroidEntryPoint\nimport timber.log.Timber\n\n@AndroidEntryPoint\nclass ProgressMoviesWidgetProvider : BaseWidgetProvider() {\n\n  companion object {\n    const val EXTRA_CHECK_MOVIE_ID = \"EXTRA_CHECK_MOVIE_ID\"\n\n    fun requestUpdate(context: Context) {\n      val applicationContext = context.applicationContext\n      val intent = Intent(applicationContext, ProgressMoviesWidgetProvider::class.java).apply {\n        val ids: IntArray = AppWidgetManager.getInstance(applicationContext)\n          .getAppWidgetIds(ComponentName(applicationContext, ProgressMoviesWidgetProvider::class.java))\n        action = ACTION_APPWIDGET_UPDATE\n        putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)\n      }\n      applicationContext.sendBroadcast(intent)\n      Timber.d(\"Widget update requested.\")\n    }\n  }\n\n  override fun getLayoutResId(): Int {\n    val isLight = settingsRepository.widgets.widgetsTheme == MODE_NIGHT_NO\n    return when {\n      isLight -> R.layout.widget_movies_progress_day\n      else -> R.layout.widget_movies_progress_night\n    }\n  }\n\n  override fun onUpdate(\n    context: Context,\n    appWidgetManager: AppWidgetManager,\n    appWidgetIds: IntArray?,\n  ) {\n    super.onUpdate(context, appWidgetManager, appWidgetIds)\n    appWidgetIds?.forEach { updateWidget(context, appWidgetManager, it) }\n  }\n\n  private fun updateWidget(context: Context, appWidgetManager: AppWidgetManager, widgetId: Int) {\n    val intent = Intent(context, ProgressMoviesWidgetService::class.java).apply {\n      putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)\n      data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))\n    }\n\n    val remoteViews = RemoteViews(context.packageName, getLayoutResId()).apply {\n      setRemoteAdapter(R.id.progressWidgetMoviesList, intent)\n      setEmptyView(R.id.progressWidgetMoviesList, R.id.progressWidgetMoviesEmptyView)\n\n      val spaceTiny = context.dimenToPx(R.dimen.spaceTiny)\n      val paddingTop = if (settings.widgetsShowLabel) context.dimenToPx(R.dimen.widgetPaddingTop) else spaceTiny\n      val labelVisibility = if (settings.widgetsShowLabel) VISIBLE else GONE\n      setViewPadding(R.id.progressWidgetMoviesList, 0, paddingTop, 0, spaceTiny)\n      setViewVisibility(R.id.progressWidgetMoviesLabel, labelVisibility)\n\n      setInt(R.id.progressWidgetMoviesNightRoot, \"setBackgroundResource\", getBackgroundResId())\n      setInt(R.id.progressWidgetMoviesDayRoot, \"setBackgroundResource\", getBackgroundResId())\n    }\n\n    val mainIntent = PendingIntent.getActivity(\n      context,\n      0,\n      Intent().apply { setClassName(context, Config.HOST_ACTIVITY_NAME) },\n      FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT\n    )\n    remoteViews.setOnClickPendingIntent(R.id.progressWidgetMoviesLabel, mainIntent)\n\n    val listClickIntent = Intent(context, ProgressMoviesWidgetProvider::class.java).apply {\n      action = ACTION_CLICK\n      data = Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))\n    }\n\n    val listIntent = PendingIntent.getBroadcast(context, 0, listClickIntent, FLAG_MUTABLE or FLAG_UPDATE_CURRENT)\n    remoteViews.setPendingIntentTemplate(R.id.progressWidgetMoviesList, listIntent)\n\n    appWidgetManager.updateAppWidget(widgetId, remoteViews)\n    appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, R.id.progressWidgetMoviesList)\n  }\n\n  override fun onReceive(context: Context, intent: Intent) {\n    super.onReceive(context, intent)\n    if (intent.action == ACTION_CLICK) {\n      when {\n        intent.extras?.containsKey(EXTRA_MOVIE_ID) == true -> {\n          val movieId = intent.getLongExtra(EXTRA_MOVIE_ID, -1L)\n          context.startActivity(\n            Intent().apply {\n              setClassName(context, Config.HOST_ACTIVITY_NAME)\n              putExtra(EXTRA_MOVIE_ID, movieId.toString())\n              flags = Intent.FLAG_ACTIVITY_NEW_TASK\n            }\n          )\n        }\n        intent.extras?.containsKey(EXTRA_CHECK_MOVIE_ID) == true -> {\n          val movieId = intent.getLongExtra(EXTRA_CHECK_MOVIE_ID, -1L)\n          ProgressMoviesWidgetCheckService.initialize(\n            context.applicationContext,\n            IdTrakt(movieId)\n          )\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/progress_movies/ProgressMoviesWidgetService.kt",
    "content": "package com.michaldrabik.ui_widgets.progress_movies\n\nimport android.content.Intent\nimport android.widget.RemoteViewsService\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_progress_movies.progress.cases.ProgressMoviesItemsCase\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ProgressMoviesWidgetService : RemoteViewsService() {\n\n  @Inject lateinit var progressLoadItemsCase: ProgressMoviesItemsCase\n  @Inject lateinit var settingsRepository: SettingsRepository\n\n  override fun onGetViewFactory(intent: Intent?) =\n    ProgressMoviesWidgetViewsFactory(\n      applicationContext,\n      progressLoadItemsCase,\n      settingsRepository\n    )\n}\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/progress_movies/ProgressMoviesWidgetViewsFactory.kt",
    "content": "package com.michaldrabik.ui_widgets.progress_movies\n\nimport android.content.Context\nimport android.content.Intent\nimport android.view.View.GONE\nimport android.view.View.VISIBLE\nimport android.widget.RemoteViews\nimport android.widget.RemoteViewsService\nimport androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO\nimport androidx.core.os.bundleOf\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners\nimport com.michaldrabik.repository.settings.SettingsRepository\nimport com.michaldrabik.ui_base.utilities.extensions.dimenToPx\nimport com.michaldrabik.ui_base.utilities.extensions.replace\nimport com.michaldrabik.ui_model.ImageStatus\nimport com.michaldrabik.ui_progress_movies.progress.cases.ProgressMoviesItemsCase\nimport com.michaldrabik.ui_progress_movies.progress.recycler.ProgressMovieListItem\nimport com.michaldrabik.ui_widgets.BaseWidgetProvider.Companion.EXTRA_MOVIE_ID\nimport com.michaldrabik.ui_widgets.R\nimport com.michaldrabik.ui_widgets.progress_movies.ProgressMoviesWidgetProvider.Companion.EXTRA_CHECK_MOVIE_ID\nimport kotlinx.coroutines.runBlocking\n\nclass ProgressMoviesWidgetViewsFactory(\n  private val context: Context,\n  private val loadItemsCase: ProgressMoviesItemsCase,\n  private val settingsRepository: SettingsRepository,\n) : RemoteViewsService.RemoteViewsFactory {\n\n  private val imageCorner by lazy { context.dimenToPx(R.dimen.mediaTileCorner) }\n  private val imageWidth by lazy { context.dimenToPx(R.dimen.widgetImageWidth) }\n  private val imageHeight by lazy { context.dimenToPx(R.dimen.widgetImageHeight) }\n  private val adapterItems by lazy { mutableListOf<ProgressMovieListItem>() }\n\n  override fun onDataSetChanged() {\n    runBlocking {\n      val items = loadItemsCase.loadItems(\"\")\n        .filterIsInstance<ProgressMovieListItem.MovieItem>()\n      adapterItems.replace(items)\n    }\n  }\n\n  override fun getViewAt(position: Int): RemoteViews {\n    val item = adapterItems[position] as ProgressMovieListItem.MovieItem\n    return createItemRemoteView(item)\n  }\n\n  private fun createItemRemoteView(item: ProgressMovieListItem.MovieItem): RemoteViews {\n    val translatedTitle = item.translation?.title\n    val title =\n      if (translatedTitle?.isBlank() == false) translatedTitle\n      else item.movie.title\n\n    val translatedDescription = item.translation?.overview\n    val description =\n      if (translatedDescription?.isBlank() == false) translatedDescription\n      else item.movie.overview\n\n    val remoteView = RemoteViews(context.packageName, getItemLayout()).apply {\n      setTextViewText(R.id.progressMoviesWidgetItemTitle, title)\n      setTextViewText(R.id.progressMoviesWidgetItemSubtitle2, description)\n\n      val fillIntent = Intent().apply {\n        putExtras(bundleOf(EXTRA_MOVIE_ID to item.movie.traktId))\n      }\n      setOnClickFillInIntent(R.id.progressMoviesWidgetItem, fillIntent)\n\n      val checkFillIntent = Intent().apply {\n        putExtras(bundleOf(EXTRA_CHECK_MOVIE_ID to item.movie.traktId))\n      }\n      setOnClickFillInIntent(R.id.progressMoviesWidgetItemCheckButton, checkFillIntent)\n    }\n\n    if (item.image.status != ImageStatus.AVAILABLE) {\n      remoteView.setViewVisibility(R.id.progressMoviesWidgetItemImage, GONE)\n      remoteView.setViewVisibility(R.id.progressMoviesWidgetItemPlaceholder, VISIBLE)\n      return remoteView\n    }\n\n    try {\n      remoteView.setViewVisibility(R.id.progressMoviesWidgetItemImage, GONE)\n      remoteView.setViewVisibility(R.id.progressMoviesWidgetItemPlaceholder, GONE)\n\n      val bitmap = Glide.with(context)\n        .asBitmap()\n        .load(item.image.fullFileUrl)\n        .transform(CenterCrop(), RoundedCorners(imageCorner))\n        .submit(imageWidth, imageHeight)\n        .get()\n\n      remoteView.setImageViewBitmap(R.id.progressMoviesWidgetItemImage, bitmap)\n      remoteView.setViewVisibility(R.id.progressMoviesWidgetItemImage, VISIBLE)\n    } catch (t: Throwable) {\n      remoteView.setViewVisibility(R.id.progressMoviesWidgetItemImage, GONE)\n      remoteView.setViewVisibility(R.id.progressMoviesWidgetItemPlaceholder, VISIBLE)\n    }\n\n    return remoteView\n  }\n\n  private fun getItemLayout(): Int {\n    val isLight = settingsRepository.widgets.widgetsTheme == MODE_NIGHT_NO\n    return when {\n      isLight -> R.layout.widget_movies_progress_item_day\n      else -> R.layout.widget_movies_progress_item_night\n    }\n  }\n\n  override fun getItemId(position: Int) = adapterItems[position].movie.traktId\n\n  override fun getLoadingView() = RemoteViews(context.packageName, R.layout.widget_loading_item)\n\n  override fun getCount() = adapterItems.size\n\n  override fun hasStableIds() = true\n\n  override fun getViewTypeCount() = 2\n\n  override fun onCreate() = Unit\n\n  override fun onDestroy() = Unit\n}\n"
  },
  {
    "path": "ui-widgets/src/main/java/com/michaldrabik/ui_widgets/search/SearchWidgetProvider.kt",
    "content": "package com.michaldrabik.ui_widgets.search\n\nimport android.app.PendingIntent\nimport android.app.PendingIntent.FLAG_IMMUTABLE\nimport android.app.PendingIntent.FLAG_UPDATE_CURRENT\nimport android.appwidget.AppWidgetManager\nimport android.appwidget.AppWidgetProvider\nimport android.content.Context\nimport android.content.Intent\nimport android.widget.RemoteViews\nimport com.michaldrabik.common.Config.HOST_ACTIVITY_NAME\nimport com.michaldrabik.ui_widgets.R\n\nclass SearchWidgetProvider : AppWidgetProvider() {\n\n  companion object {\n    const val EXTRA_WIDGET_SEARCH_CLICK = \"EXTRA_WIDGET_SEARCH_CLICK\"\n  }\n\n  override fun onUpdate(\n    context: Context,\n    appWidgetManager: AppWidgetManager,\n    appWidgetIds: IntArray?\n  ) {\n    appWidgetIds?.forEach { updateWidget(context, appWidgetManager, it) }\n    super.onUpdate(context, appWidgetManager, appWidgetIds)\n  }\n\n  private fun updateWidget(\n    context: Context,\n    appWidgetManager: AppWidgetManager,\n    widgetId: Int\n  ) {\n    val remoteViews = RemoteViews(context.packageName, R.layout.widget_search).apply {\n      val intent = Intent().apply {\n        setClassName(context, HOST_ACTIVITY_NAME)\n        putExtra(EXTRA_WIDGET_SEARCH_CLICK, true)\n      }\n      val pendingIntent = PendingIntent.getActivity(context, 2, intent, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)\n      setOnClickPendingIntent(R.id.searchWidgetRoot, pendingIntent)\n    }\n    appWidgetManager.updateAppWidget(widgetId, remoteViews)\n  }\n}\n"
  },
  {
    "path": "ui-widgets/src/main/res/drawable/bg_widget.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"@dimen/widgetCorner\" />\n  <solid android:color=\"?attr/colorWidgetBackground\" />\n</shape>"
  },
  {
    "path": "ui-widgets/src/main/res/drawable/bg_widget_0.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"@dimen/widgetCorner\" />\n  <solid android:color=\"?attr/colorWidgetBackground0\" />\n</shape>"
  },
  {
    "path": "ui-widgets/src/main/res/drawable/bg_widget_25.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"@dimen/widgetCorner\" />\n  <solid android:color=\"?attr/colorWidgetBackground25\" />\n</shape>"
  },
  {
    "path": "ui-widgets/src/main/res/drawable/bg_widget_50.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"@dimen/widgetCorner\" />\n  <solid android:color=\"?attr/colorWidgetBackground50\" />\n</shape>"
  },
  {
    "path": "ui-widgets/src/main/res/drawable/bg_widget_75.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"@dimen/widgetCorner\" />\n  <solid android:color=\"?attr/colorWidgetBackground75\" />\n</shape>"
  },
  {
    "path": "ui-widgets/src/main/res/drawable/bg_widget_check_button.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"4dp\" />\n  <solid android:color=\"@android:color/transparent\" />\n</shape>"
  },
  {
    "path": "ui-widgets/src/main/res/drawable/bg_widget_media_view_elevation.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <stroke\n    android:width=\"1dp\"\n    android:color=\"?attr/colorPlaceholderStroke\"\n    />\n  <solid android:color=\"?attr/colorWidgetBackground\" />\n  <corners android:radius=\"@dimen/mediaTileCorner\" />\n</shape>"
  },
  {
    "path": "ui-widgets/src/main/res/drawable/bg_widget_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners android:radius=\"1000dp\" />\n  <solid android:color=\"#222327\" />\n</shape>"
  },
  {
    "path": "ui-widgets/src/main/res/drawable/bg_widget_toolbar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\"\n  >\n  <corners\n    android:topLeftRadius=\"@dimen/widgetCorner\"\n    android:topRightRadius=\"@dimen/widgetCorner\"\n    />\n  <solid android:color=\"?attr/colorWidgetStatusBackground\" />\n</shape>"
  },
  {
    "path": "ui-widgets/src/main/res/layout/layout_widget_calendar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:ignore=\"HardcodedText,SmallSp\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <ListView\n    android:id=\"@+id/calendarWidgetList\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:clipToPadding=\"false\"\n    android:divider=\"@android:color/transparent\"\n    android:paddingTop=\"@dimen/widgetPaddingTop\"\n    android:paddingBottom=\"@dimen/spaceTiny\"\n    android:scrollbars=\"none\"\n    />\n\n  <FrameLayout\n    android:id=\"@+id/calendarWidgetEmptyView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingTop=\"@dimen/widgetLabelHeight\"\n    >\n\n    <LinearLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:layout_margin=\"@dimen/spaceBig\"\n      android:gravity=\"center\"\n      android:orientation=\"vertical\"\n      >\n\n      <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"@dimen/spaceTiny\"\n        android:text=\"@string/menuCalendar\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"20sp\"\n        android:textStyle=\"bold\"\n        />\n\n      <TextView\n        android:id=\"@+id/calendarWidgetEmptyViewSubtitle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/textCalendarEmpty\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"14sp\"\n        />\n\n    </LinearLayout>\n\n    <ImageView\n      android:id=\"@+id/calendarWidgetEmptyViewIcon\"\n      android:layout_width=\"56dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"end|top\"\n      android:layout_marginTop=\"1dp\"\n      android:paddingStart=\"20dp\"\n      android:paddingTop=\"4dp\"\n      android:paddingEnd=\"0dp\"\n      android:paddingBottom=\"4dp\"\n      android:src=\"@drawable/ic_history\"\n      android:tint=\"?android:attr/textColorPrimary\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:ignore=\"UseAppTint\"\n      />\n\n  </FrameLayout>\n\n  <LinearLayout\n    android:id=\"@+id/calendarWidgetLabel\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/widgetLabelHeight\"\n    android:background=\"@drawable/bg_widget_toolbar\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:paddingTop=\"@dimen/spaceTiny\"\n    android:paddingBottom=\"@dimen/spaceTiny\"\n    >\n\n    <ImageView\n      android:id=\"@+id/calendarWidgetLabelImage\"\n      android:layout_width=\"14dp\"\n      android:layout_height=\"14dp\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:src=\"@drawable/ic_showly_round\"\n      />\n\n    <TextView\n      android:id=\"@+id/calendarWidgetLabelText\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"72dp\"\n      android:gravity=\"start\"\n      android:text=\"Shows Calendar\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"@color/colorWhite\"\n      android:textSize=\"10sp\"\n      />\n\n  </LinearLayout>\n\n</merge>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/layout_widget_calendar_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  tools:ignore=\"SmallSp\"\n  tools:parentTag=\"android.widget.LinearLayout\"\n  >\n\n  <FrameLayout\n    android:layout_width=\"@dimen/widgetImageWidth\"\n    android:layout_height=\"@dimen/widgetImageHeight\"\n    android:layout_gravity=\"center_vertical\"\n    android:layout_marginStart=\"@dimen/spaceMedium\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:layout_marginBottom=\"@dimen/spaceSmall\"\n    android:background=\"@drawable/bg_widget_media_view_elevation\"\n    android:elevation=\"@dimen/elevationSmall\"\n    >\n\n    <ImageView\n      android:id=\"@+id/calendarWidgetItemImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/calendarWidgetItemPlaceholder\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:padding=\"@dimen/spaceSmall\"\n      android:src=\"@drawable/ic_television\"\n      android:tint=\"?attr/colorPlaceholderIcon\"\n      android:visibility=\"gone\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:ignore=\"UseAppTint\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/calendarWidgetItemImageBadge\"\n      style=\"@style/Badge.Watchlist\"\n      android:layout_width=\"16dp\"\n      android:layout_height=\"16dp\"\n      android:layout_marginEnd=\"1dp\"\n      android:src=\"@drawable/ic_bookmark_full\"\n      android:translationY=\"-2dp\"\n      tools:visibility=\"visible\"\n      />\n\n  </FrameLayout>\n\n  <RelativeLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:gravity=\"center_vertical\"\n    >\n\n    <TextView\n      android:id=\"@+id/calendarWidgetItemTitle\"\n      style=\"@style/WidgetItem.Title\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      tools:ignore=\"RtlSymmetry\"\n      tools:text=\"Breaking Bad\"\n      />\n\n    <TextView\n      android:id=\"@+id/calendarWidgetItemOverview\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_below=\"@id/calendarWidgetItemTitle\"\n      android:layout_alignBaseline=\"@id/calendarWidgetItemBadge\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:layout_marginTop=\"@dimen/spaceMicro\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:layout_toEndOf=\"@id/calendarWidgetItemBadge\"\n      android:ellipsize=\"end\"\n      android:includeFontPadding=\"false\"\n      android:maxLines=\"1\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"12sp\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <TextView\n      android:id=\"@+id/calendarWidgetItemBadge\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_below=\"@id/calendarWidgetItemTitle\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:background=\"@drawable/bg_badge\"\n      android:elevation=\"@dimen/elevationTiny\"\n      android:ellipsize=\"end\"\n      android:includeFontPadding=\"false\"\n      android:maxLines=\"1\"\n      android:paddingStart=\"6dp\"\n      android:paddingTop=\"2dp\"\n      android:paddingEnd=\"6dp\"\n      android:paddingBottom=\"2dp\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"12sp\"\n      tools:text=\"S.01 E.01\"\n      />\n\n    <TextView\n      android:id=\"@+id/calendarWidgetItemDate\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_below=\"@id/calendarWidgetItemBadge\"\n      android:gravity=\"start|center_vertical\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"11sp\"\n      tools:text=\"Wednesday, 27 June 2019\"\n      />\n\n  </RelativeLayout>\n\n</merge>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/layout_widget_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:parentTag=\"android.widget.LinearLayout\"\n  >\n\n  <TextView\n    android:layout_width=\"@dimen/spaceMedium\"\n    android:layout_height=\"0dp\"\n    />\n\n  <ImageView\n    android:id=\"@+id/progressWidgetHeaderTitleIcon\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"28dp\"\n    android:layout_gravity=\"end|center_vertical\"\n    android:layout_marginStart=\"-4dp\"\n    android:layout_marginEnd=\"@dimen/spaceTiny\"\n    android:paddingTop=\"4dp\"\n    android:paddingBottom=\"4dp\"\n    android:src=\"@drawable/ic_history\"\n    android:tint=\"?android:attr/textColorPrimary\"\n    android:translationY=\"1dp\"\n    android:visibility=\"gone\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    tools:ignore=\"UseAppTint\"\n    tools:visibility=\"visible\"\n    />\n\n  <TextView\n    android:id=\"@+id/progressWidgetHeaderTitle\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/spaceTiny\"\n    android:layout_marginBottom=\"@dimen/spaceTiny\"\n    android:textColor=\"?android:attr/textColorPrimary\"\n    android:textSize=\"16sp\"\n    android:textStyle=\"bold\"\n    tools:text=\"Coming Soon\"\n    />\n\n  <TextView\n    android:layout_width=\"0dp\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\"\n    />\n\n  <ImageView\n    android:id=\"@+id/progressWidgetHeaderIcon\"\n    android:layout_width=\"56dp\"\n    android:layout_height=\"28dp\"\n    android:layout_gravity=\"end|center_vertical\"\n    android:paddingStart=\"20dp\"\n    android:paddingTop=\"4dp\"\n    android:paddingEnd=\"0dp\"\n    android:paddingBottom=\"4dp\"\n    android:src=\"@drawable/ic_history\"\n    android:tint=\"?android:attr/textColorPrimary\"\n    android:visibility=\"gone\"\n    app:tint=\"?android:attr/textColorPrimary\"\n    tools:ignore=\"UseAppTint\"\n    tools:visibility=\"visible\"\n    />\n\n</merge>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/layout_widget_movies_calendar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:ignore=\"HardcodedText,SmallSp\"\n  tools:parentTag=\"android.widget.FrameLayout\"\n  >\n\n  <ListView\n    android:id=\"@+id/calendarWidgetMoviesList\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:clipToPadding=\"false\"\n    android:divider=\"@android:color/transparent\"\n    android:paddingTop=\"@dimen/widgetPaddingTop\"\n    android:paddingBottom=\"@dimen/spaceTiny\"\n    android:scrollbars=\"none\"\n    />\n\n  <FrameLayout\n    android:id=\"@+id/calendarWidgetMoviesEmptyView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingTop=\"@dimen/widgetLabelHeight\"\n    >\n\n    <LinearLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:layout_margin=\"@dimen/spaceBig\"\n      android:gravity=\"center\"\n      android:orientation=\"vertical\"\n      >\n\n      <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"@dimen/spaceTiny\"\n        android:text=\"@string/menuCalendar\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"20sp\"\n        android:textStyle=\"bold\"\n        />\n\n      <TextView\n        android:id=\"@+id/calendarWidgetMoviesEmptyViewSubtitle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/textMoviesCalendarEmpty\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"14sp\"\n        />\n\n    </LinearLayout>\n\n    <ImageView\n      android:id=\"@+id/calendarWidgetMoviesEmptyViewIcon\"\n      android:layout_width=\"56dp\"\n      android:layout_height=\"28dp\"\n      android:layout_gravity=\"end|top\"\n      android:layout_marginTop=\"1dp\"\n      android:paddingStart=\"20dp\"\n      android:paddingTop=\"4dp\"\n      android:paddingEnd=\"0dp\"\n      android:paddingBottom=\"4dp\"\n      android:src=\"@drawable/ic_history\"\n      android:tint=\"?android:attr/textColorPrimary\"\n      app:tint=\"?android:attr/textColorPrimary\"\n      tools:ignore=\"UseAppTint\"\n      />\n\n  </FrameLayout>\n\n  <LinearLayout\n    android:id=\"@+id/calendarWidgetMoviesLabel\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/widgetLabelHeight\"\n    android:background=\"@drawable/bg_widget_toolbar\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:paddingTop=\"@dimen/spaceTiny\"\n    android:paddingBottom=\"@dimen/spaceTiny\"\n    >\n\n    <ImageView\n      android:id=\"@+id/calendarWidgetMoviesLabelImage\"\n      android:layout_width=\"14dp\"\n      android:layout_height=\"14dp\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:src=\"@drawable/ic_showly_round\"\n      />\n\n    <TextView\n      android:id=\"@+id/calendarWidgetMoviesLabelText\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"72dp\"\n      android:gravity=\"start\"\n      android:text=\"Movies Calendar\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"@color/colorWhite\"\n      android:textSize=\"10sp\"\n      />\n\n  </LinearLayout>\n\n</merge>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/layout_widget_movies_calendar_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:ignore=\"SmallSp\"\n  tools:parentTag=\"android.widget.LinearLayout\"\n  >\n\n  <FrameLayout\n    android:layout_width=\"@dimen/widgetImageWidth\"\n    android:layout_height=\"@dimen/widgetImageHeight\"\n    android:layout_gravity=\"center_vertical\"\n    android:layout_marginStart=\"@dimen/spaceMedium\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:layout_marginBottom=\"@dimen/spaceSmall\"\n    android:background=\"@drawable/bg_widget_media_view_elevation\"\n    android:elevation=\"@dimen/elevationSmall\"\n    >\n\n    <ImageView\n      android:id=\"@+id/calendarMoviesWidgetItemImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/calendarMoviesWidgetItemPlaceholder\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:padding=\"@dimen/spaceSmall\"\n      android:src=\"@drawable/ic_film\"\n      android:tint=\"?attr/colorPlaceholderIcon\"\n      android:visibility=\"gone\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:ignore=\"UseAppTint\"\n      tools:visibility=\"visible\"\n      />\n\n  </FrameLayout>\n\n  <RelativeLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:gravity=\"center_vertical\"\n    >\n\n    <TextView\n      android:id=\"@+id/calendarMoviesWidgetItemTitle\"\n      style=\"@style/WidgetItem.Title\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:layout_toStartOf=\"@id/calendarMoviesWidgetItemDate\"\n      android:layout_alignParentStart=\"true\"\n      tools:ignore=\"RtlSymmetry\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <TextView\n      android:id=\"@+id/calendarMoviesWidgetItemOverview\"\n      style=\"@style/WidgetItem.Subtitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_below=\"@id/calendarMoviesWidgetItemTitle\"\n      android:layout_marginTop=\"@dimen/spaceTiny\"\n      tools:text=\"@tools:sample/lorem/random\"\n      />\n\n    <TextView\n      android:id=\"@+id/calendarMoviesWidgetItemDate\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_alignTop=\"@id/calendarMoviesWidgetItemTitle\"\n      android:layout_alignBottom=\"@id/calendarMoviesWidgetItemTitle\"\n      android:layout_alignParentEnd=\"true\"\n      android:background=\"@drawable/bg_badge\"\n      android:elevation=\"@dimen/elevationTiny\"\n      android:gravity=\"start|center_vertical\"\n      android:paddingStart=\"6dp\"\n      android:paddingTop=\"2dp\"\n      android:paddingEnd=\"6dp\"\n      android:paddingBottom=\"2dp\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"11sp\"\n      tools:text=\"Wednesday, 27 June 2019\"\n      />\n\n  </RelativeLayout>\n\n</merge>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/layout_widget_movies_progress.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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=\"match_parent\"\n  tools:ignore=\"HardcodedText,SmallSp\"\n  >\n\n  <ListView\n    android:id=\"@+id/progressWidgetMoviesList\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:clipToPadding=\"false\"\n    android:divider=\"@android:color/transparent\"\n    android:paddingTop=\"@dimen/widgetPaddingTop\"\n    android:paddingBottom=\"@dimen/spaceTiny\"\n    android:scrollbars=\"none\"\n    />\n\n  <LinearLayout\n    android:id=\"@+id/progressWidgetMoviesEmptyView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_margin=\"@dimen/spaceBig\"\n    android:gravity=\"center\"\n    android:orientation=\"vertical\"\n    >\n\n    <TextView\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:text=\"@string/menuProgress\"\n      android:textAllCaps=\"true\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"20sp\"\n      android:textStyle=\"bold\"\n      />\n\n    <TextView\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:text=\"@string/textMoviesProgressEmpty\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"14sp\"\n      />\n\n  </LinearLayout>\n\n  <LinearLayout\n    android:id=\"@+id/progressWidgetMoviesLabel\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/widgetLabelHeight\"\n    android:background=\"@drawable/bg_widget_toolbar\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:paddingTop=\"@dimen/spaceTiny\"\n    android:paddingBottom=\"@dimen/spaceTiny\"\n    >\n\n    <ImageView\n      android:layout_width=\"14dp\"\n      android:layout_height=\"14dp\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:src=\"@drawable/ic_showly_round\"\n      />\n\n    <TextView\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"start\"\n      android:text=\"Movies Progress\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"@color/colorWhite\"\n      android:textSize=\"10sp\"\n      />\n\n  </LinearLayout>\n\n</merge>"
  },
  {
    "path": "ui-widgets/src/main/res/layout/layout_widget_movies_progress_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  tools:ignore=\"SmallSp\"\n  tools:parentTag=\"android.widget.LinearLayout\"\n  >\n\n  <FrameLayout\n    android:layout_width=\"@dimen/widgetImageWidth\"\n    android:layout_height=\"@dimen/widgetImageHeight\"\n    android:layout_gravity=\"center_vertical\"\n    android:layout_marginStart=\"@dimen/spaceMedium\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:layout_marginBottom=\"@dimen/spaceSmall\"\n    android:background=\"@drawable/bg_widget_media_view_elevation\"\n    android:elevation=\"@dimen/elevationSmall\"\n    >\n\n    <ImageView\n      android:id=\"@+id/progressMoviesWidgetItemImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/progressMoviesWidgetItemPlaceholder\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:padding=\"@dimen/spaceSmall\"\n      android:src=\"@drawable/ic_film\"\n      android:tint=\"?attr/colorPlaceholderIcon\"\n      android:visibility=\"gone\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:ignore=\"UseAppTint\"\n      tools:visibility=\"visible\"\n      />\n\n  </FrameLayout>\n\n  <RelativeLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center_vertical\"\n    >\n\n    <LinearLayout\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_alignParentStart=\"true\"\n      android:layout_centerVertical=\"true\"\n      android:layout_toStartOf=\"@id/progressMoviesWidgetItemCheckButton\"\n      android:orientation=\"vertical\"\n      >\n\n      <TextView\n        android:id=\"@+id/progressMoviesWidgetItemTitle\"\n        style=\"@style/WidgetItem.Title\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"@dimen/spaceMicro\"\n        tools:ignore=\"RtlSymmetry\"\n        tools:text=\"Breaking Bad\"\n        />\n\n      <TextView\n        android:id=\"@+id/progressMoviesWidgetItemSubtitle2\"\n        style=\"@style/WidgetItem.Subtitle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        tools:text=\"Lorem Ipsum\"\n        />\n\n    </LinearLayout>\n\n    <ImageButton\n      android:id=\"@+id/progressMoviesWidgetItemCheckButton\"\n      style=\"@style/WidgetItem.CheckButton\"\n      android:layout_width=\"@dimen/widgetCheckButtonWidth\"\n      android:layout_height=\"@dimen/widgetImageHeight\"\n      android:layout_alignParentEnd=\"true\"\n      android:layout_centerVertical=\"true\"\n      />\n\n  </RelativeLayout>\n\n</merge>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/layout_widget_progress.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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=\"match_parent\"\n  tools:ignore=\"HardcodedText,SmallSp\"\n  >\n\n  <ListView\n    android:id=\"@+id/progressWidgetList\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:clipToPadding=\"false\"\n    android:divider=\"@android:color/transparent\"\n    android:paddingTop=\"@dimen/widgetPaddingTop\"\n    android:paddingBottom=\"@dimen/spaceTiny\"\n    android:scrollbars=\"none\"\n    />\n\n  <LinearLayout\n    android:id=\"@+id/progressWidgetEmptyView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_margin=\"@dimen/spaceBig\"\n    android:gravity=\"center\"\n    android:orientation=\"vertical\"\n    >\n\n    <TextView\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:text=\"@string/menuProgress\"\n      android:textAllCaps=\"true\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"20sp\"\n      android:textStyle=\"bold\"\n      />\n\n    <TextView\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:text=\"@string/textProgressEmpty\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"14sp\"\n      />\n\n  </LinearLayout>\n\n  <LinearLayout\n    android:id=\"@+id/progressWidgetLabel\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/widgetLabelHeight\"\n    android:background=\"@drawable/bg_widget_toolbar\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:paddingTop=\"@dimen/spaceTiny\"\n    android:paddingBottom=\"@dimen/spaceTiny\"\n    >\n\n    <ImageView\n      android:layout_width=\"14dp\"\n      android:layout_height=\"14dp\"\n      android:layout_marginStart=\"@dimen/spaceTiny\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:src=\"@drawable/ic_showly_round\"\n      />\n\n    <TextView\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"start\"\n      android:text=\"Shows Progress\"\n      android:textAlignment=\"viewStart\"\n      android:textColor=\"@color/colorWhite\"\n      android:textSize=\"10sp\"\n      />\n\n  </LinearLayout>\n\n</merge>"
  },
  {
    "path": "ui-widgets/src/main/res/layout/layout_widget_progress_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  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:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:orientation=\"horizontal\"\n  tools:ignore=\"SmallSp\"\n  tools:parentTag=\"android.widget.LinearLayout\"\n  >\n\n  <FrameLayout\n    android:layout_width=\"@dimen/widgetImageWidth\"\n    android:layout_height=\"@dimen/widgetImageHeight\"\n    android:layout_gravity=\"center_vertical\"\n    android:layout_marginStart=\"@dimen/spaceMedium\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:layout_marginBottom=\"@dimen/spaceSmall\"\n    android:background=\"@drawable/bg_widget_media_view_elevation\"\n    android:elevation=\"@dimen/elevationTiny\"\n    >\n\n    <ImageView\n      android:id=\"@+id/progressWidgetItemImage\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n    <ImageView\n      android:id=\"@+id/progressWidgetItemPlaceholder\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:padding=\"@dimen/spaceSmall\"\n      android:src=\"@drawable/ic_television\"\n      android:tint=\"?attr/colorPlaceholderIcon\"\n      android:visibility=\"gone\"\n      app:tint=\"?attr/colorPlaceholderIcon\"\n      tools:ignore=\"UseAppTint\"\n      tools:visibility=\"visible\"\n      />\n\n  </FrameLayout>\n\n  <RelativeLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center_vertical\"\n    android:gravity=\"center_vertical\"\n    >\n\n    <TextView\n      android:id=\"@+id/progressWidgetItemTitle\"\n      style=\"@style/WidgetItem.Title\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_alignParentStart=\"true\"\n      android:layout_marginEnd=\"@dimen/spaceTiny\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:layout_toStartOf=\"@id/progressWidgetItemDateButton\"\n      tools:ignore=\"RtlSymmetry\"\n      tools:text=\"Title\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressWidgetItemBadge\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_alignTop=\"@id/progressWidgetItemTitle\"\n      android:layout_alignBottom=\"@id/progressWidgetItemTitle\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:layout_toEndOf=\"@id/progressWidgetItemTitle\"\n      android:gravity=\"center\"\n      android:text=\"@string/textNew\"\n      android:textAllCaps=\"true\"\n      android:textColor=\"?attr/colorAccent\"\n      android:textSize=\"13sp\"\n      android:textStyle=\"bold\"\n      android:visibility=\"gone\"\n      tools:visibility=\"visible\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressWidgetItemSubtitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_below=\"@id/progressWidgetItemTitle\"\n      android:background=\"@drawable/bg_badge\"\n      android:elevation=\"@dimen/elevationTiny\"\n      android:ellipsize=\"end\"\n      android:includeFontPadding=\"false\"\n      android:maxLines=\"1\"\n      android:paddingStart=\"6dp\"\n      android:paddingTop=\"2dp\"\n      android:paddingEnd=\"6dp\"\n      android:paddingBottom=\"2dp\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"12sp\"\n      tools:text=\"S.01 E.01\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressWidgetItemSubtitle2\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_below=\"@id/progressWidgetItemTitle\"\n      android:layout_alignBaseline=\"@id/progressWidgetItemSubtitle\"\n      android:layout_marginStart=\"6dp\"\n      android:layout_marginBottom=\"@dimen/spaceTiny\"\n      android:layout_toStartOf=\"@id/progressWidgetItemCheckButton\"\n      android:layout_toEndOf=\"@id/progressWidgetItemSubtitle\"\n      android:ellipsize=\"end\"\n      android:includeFontPadding=\"false\"\n      android:maxLines=\"1\"\n      android:textColor=\"?android:attr/textColorPrimary\"\n      android:textSize=\"12sp\"\n      tools:text=\"Some Title of an Episode\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressWidgetItemProgressText\"\n      style=\"@style/WidgetItem.Subtitle\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_below=\"@id/progressWidgetItemSubtitle\"\n      android:layout_marginTop=\"@dimen/spaceTiny\"\n      android:gravity=\"start|center_vertical\"\n      android:maxLines=\"1\"\n      tools:text=\"999/999\"\n      />\n\n    <ProgressBar\n      android:id=\"@+id/progressWidgetItemProgress\"\n      style=\"@style/Widget.AppCompat.ProgressBar.Horizontal\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_alignTop=\"@id/progressWidgetItemProgressText\"\n      android:layout_alignBottom=\"@id/progressWidgetItemProgressText\"\n      android:layout_alignParentEnd=\"true\"\n      android:layout_marginStart=\"@dimen/spaceSmall\"\n      android:layout_toEndOf=\"@id/progressWidgetItemProgressText\"\n      android:paddingStart=\"0dp\"\n      android:paddingEnd=\"@dimen/widgetCheckButtonWidth\"\n      android:progressBackgroundTint=\"?android:attr/textColorSecondary\"\n      android:progressTint=\"?attr/colorAccent\"\n      android:scaleY=\"0.75\"\n      android:translationY=\"1dp\"\n      />\n\n    <ImageButton\n      android:id=\"@+id/progressWidgetItemCheckButton\"\n      style=\"@style/WidgetItem.CheckButton\"\n      android:layout_alignParentTop=\"true\"\n      android:layout_alignParentEnd=\"true\"\n      />\n\n    <TextView\n      android:id=\"@+id/progressWidgetItemDateButton\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_alignTop=\"@id/progressWidgetItemTitle\"\n      android:layout_alignBottom=\"@id/progressWidgetItemTitle\"\n      android:layout_alignParentEnd=\"true\"\n      android:layout_marginEnd=\"@dimen/spaceMedium\"\n      android:gravity=\"center\"\n      android:textAllCaps=\"true\"\n      android:textColor=\"?android:attr/textColorSecondary\"\n      android:textSize=\"11sp\"\n      android:visibility=\"gone\"\n      tools:text=\"Airs in 99 days\"\n      tools:visibility=\"visible\"\n      />\n\n  </RelativeLayout>\n\n</merge>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_calendar_day.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/calendarWidgetDayRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:background=\"@drawable/bg_widget\"\n  android:theme=\"@style/AppTheme.Widget.Light\"\n  tools:ignore=\"HardcodedText,SmallSp\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_calendar\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</FrameLayout>"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_calendar_item_day.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/calendarWidgetItem\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:descendantFocusability=\"blocksDescendants\"\n  android:orientation=\"horizontal\"\n  android:theme=\"@style/AppTheme.Widget.Light\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_calendar_item\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_calendar_item_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/calendarWidgetItem\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:descendantFocusability=\"blocksDescendants\"\n  android:orientation=\"horizontal\"\n  android:theme=\"@style/AppTheme.Widget.Dark\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_calendar_item\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_calendar_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/calendarWidgetNightRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:background=\"@drawable/bg_widget\"\n  android:theme=\"@style/AppTheme.Widget.Dark\"\n  tools:ignore=\"HardcodedText,SmallSp\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_calendar\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</FrameLayout>"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_header_day.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/calendarWidgetHeader\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:orientation=\"horizontal\"\n  android:theme=\"@style/AppTheme.Widget.Light\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_header\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    />\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_header_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/calendarWidgetHeader\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:orientation=\"horizontal\"\n  android:theme=\"@style/AppTheme.Widget.Dark\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_header\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    />\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_loading_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  >\n\n  <ProgressBar\n    android:layout_width=\"32dp\"\n    android:layout_height=\"@dimen/widgetImageHeight\"\n    android:layout_gravity=\"center\"\n    android:layout_marginTop=\"@dimen/spaceSmall\"\n    android:layout_marginBottom=\"@dimen/spaceSmall\"\n    android:indeterminateTint=\"@color/colorGrayDark\"\n    />\n\n</FrameLayout>"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_movies_calendar_day.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/calendarWidgetMoviesDayRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:background=\"@drawable/bg_widget\"\n  android:theme=\"@style/AppTheme.Widget.Light\"\n  tools:ignore=\"HardcodedText,SmallSp\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_movies_calendar\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</FrameLayout>"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_movies_calendar_item_day.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/calendarMoviesWidgetItem\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:descendantFocusability=\"blocksDescendants\"\n  android:orientation=\"horizontal\"\n  android:theme=\"@style/AppTheme.Widget.Light\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_movies_calendar_item\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_movies_calendar_item_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/calendarMoviesWidgetItem\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:descendantFocusability=\"blocksDescendants\"\n  android:orientation=\"horizontal\"\n  android:theme=\"@style/AppTheme.Widget.Dark\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_movies_calendar_item\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_movies_calendar_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/calendarWidgetMoviesNightRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:background=\"@drawable/bg_widget\"\n  android:theme=\"@style/AppTheme.Widget.Dark\"\n  tools:ignore=\"HardcodedText,SmallSp\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_movies_calendar\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</FrameLayout>"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_movies_progress_day.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/progressWidgetMoviesDayRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:background=\"@drawable/bg_widget\"\n  android:theme=\"@style/AppTheme.Widget.Light\"\n  tools:ignore=\"HardcodedText,SmallSp\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_movies_progress\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</FrameLayout>"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_movies_progress_item_day.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/progressMoviesWidgetItem\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:descendantFocusability=\"blocksDescendants\"\n  android:orientation=\"horizontal\"\n  android:theme=\"@style/AppTheme.Widget.Light\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_movies_progress_item\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_movies_progress_item_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/progressMoviesWidgetItem\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:descendantFocusability=\"blocksDescendants\"\n  android:orientation=\"horizontal\"\n  android:theme=\"@style/AppTheme.Widget.Dark\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_movies_progress_item\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_movies_progress_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/progressWidgetMoviesNightRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:background=\"@drawable/bg_widget\"\n  android:theme=\"@style/AppTheme.Widget.Dark\"\n  tools:ignore=\"HardcodedText,SmallSp\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_movies_progress\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</FrameLayout>"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_progress_day.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/progressWidgetDayRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:background=\"@drawable/bg_widget\"\n  android:theme=\"@style/AppTheme.Widget.Light\"\n  tools:ignore=\"HardcodedText,SmallSp\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_progress\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</FrameLayout>"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_progress_item_day.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/progressWidgetItem\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:descendantFocusability=\"blocksDescendants\"\n  android:orientation=\"horizontal\"\n  android:theme=\"@style/AppTheme.Widget.Light\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_progress_item\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_progress_item_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/progressWidgetItem\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:clipChildren=\"false\"\n  android:descendantFocusability=\"blocksDescendants\"\n  android:orientation=\"horizontal\"\n  android:theme=\"@style/AppTheme.Widget.Dark\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_progress_item\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</LinearLayout>\n"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_progress_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/progressWidgetNightRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:background=\"@drawable/bg_widget\"\n  android:theme=\"@style/AppTheme.Widget.Dark\"\n  tools:ignore=\"HardcodedText,SmallSp\"\n  >\n\n  <include\n    layout=\"@layout/layout_widget_progress\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n\n</FrameLayout>"
  },
  {
    "path": "ui-widgets/src/main/res/layout/widget_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/searchWidgetRoot\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"@dimen/searchViewHeight\"\n  android:background=\"@drawable/bg_widget_search\"\n  android:elevation=\"2dp\"\n  android:orientation=\"horizontal\"\n  android:paddingStart=\"@dimen/spaceMedium\"\n  android:paddingEnd=\"@dimen/spaceMedium\"\n  >\n\n  <ImageView\n    android:id=\"@+id/searchWidgetIcon\"\n    android:layout_width=\"30dp\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginEnd=\"@dimen/spaceMedium\"\n    android:elevation=\"2dp\"\n    android:src=\"@drawable/ic_showly_round\"\n    />\n\n  <TextView\n    android:id=\"@+id/searchWidgetText\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:gravity=\"center_vertical\"\n    android:text=\"@string/textSearchFor\"\n    android:textColor=\"@color/colorGrayLight\"\n    android:textSize=\"16sp\"\n    />\n\n</LinearLayout>"
  },
  {
    "path": "ui-widgets/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <dimen name=\"widgetImageHeight\">60dp</dimen>\n  <dimen name=\"widgetImageWidth\">40dp</dimen>\n  <dimen name=\"widgetPaddingTop\">26dp</dimen>\n  <dimen name=\"widgetCorner\">6dp</dimen>\n  <dimen name=\"widgetLabelHeight\">24dp</dimen>\n  <dimen name=\"widgetCheckButtonWidth\">46dp</dimen>\n  <dimen name=\"widgetCheckButtonHeight\">60dp</dimen>\n</resources>"
  },
  {
    "path": "ui-widgets/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">Progress</string>\n  <string name=\"menuCalendar\">Calendar</string>\n</resources>"
  },
  {
    "path": "ui-widgets/src/main/res/values/styles.xml",
    "content": "<resources>\n\n  <!-- Widgets -->\n\n  <style name=\"AppTheme.Widget.Dark\" parent=\"AppTheme\">\n    <item name=\"colorWidgetBackground\">#121212</item>\n    <item name=\"colorWidgetBackground75\">#BF121212</item>\n    <item name=\"colorWidgetBackground50\">#80121212</item>\n    <item name=\"colorWidgetBackground25\">#41121212</item>\n    <item name=\"colorWidgetBackground0\">#00121212</item>\n    <item name=\"android:textColorPrimary\">@color/colorWhite</item>\n    <item name=\"android:textColorSecondary\">@color/colorGrayLight</item>\n    <item name=\"colorPlaceholderIcon\">#6A6A6A</item>\n    <item name=\"colorPlaceholderStroke\">#6A6A6A</item>\n    <item name=\"colorAccent\">#F44336</item>\n    <item name=\"colorBadgeBackground\">@color/colorGrayDark</item>\n  </style>\n\n  <style name=\"AppTheme.Widget.Light\" parent=\"AppTheme\">\n    <item name=\"colorWidgetBackground\">#F6F7F9</item>\n    <item name=\"colorWidgetBackground75\">#BEF6F7F9</item>\n    <item name=\"colorWidgetBackground50\">#80F6F7F9</item>\n    <item name=\"colorWidgetBackground25\">#40F6F7F9</item>\n    <item name=\"colorWidgetBackground0\">#00F6F7F9</item>\n    <item name=\"android:textColorPrimary\">@color/colorBlue</item>\n    <item name=\"android:textColorSecondary\">@color/colorBlueLight</item>\n    <item name=\"colorPlaceholderIcon\">#4B6383</item>\n    <item name=\"colorPlaceholderStroke\">#4B6383</item>\n    <item name=\"colorAccent\">#4B6383</item>\n    <item name=\"colorBadgeBackground\">#E1E6EC</item>\n  </style>\n\n  <style name=\"WidgetItem\" />\n\n  <style name=\"WidgetItem.CheckButton\" parent=\"WidgetItem\">\n    <item name=\"android:layout_width\">@dimen/widgetCheckButtonWidth</item>\n    <item name=\"android:layout_height\">@dimen/widgetCheckButtonHeight</item>\n    <item name=\"android:background\">@drawable/bg_widget_check_button</item>\n    <item name=\"android:src\">@drawable/ic_check_small</item>\n    <item name=\"android:tint\">?android:attr/textColorPrimary</item>\n    <item name=\"tint\">?android:attr/textColorPrimary</item>\n  </style>\n\n  <style name=\"WidgetItem.Title\" parent=\"WidgetItem\">\n    <item name=\"android:fontFamily\">sans-serif-medium</item>\n    <item name=\"android:ellipsize\">end</item>\n    <item name=\"android:maxLines\">1</item>\n    <item name=\"android:textColor\">?android:attr/textColorPrimary</item>\n    <item name=\"android:textSize\">15sp</item>\n  </style>\n\n  <style name=\"WidgetItem.Subtitle\" parent=\"WidgetItem\">\n    <item name=\"android:ellipsize\">end</item>\n    <item name=\"android:maxLines\">2</item>\n    <item name=\"android:textColor\">?android:attr/textColorSecondary</item>\n    <item name=\"android:textSize\">11sp</item>\n  </style>\n\n</resources>"
  },
  {
    "path": "ui-widgets/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">مستوى التقدم</string>\n  <string name=\"menuCalendar\">تقويم الأفلام</string>\n</resources>"
  },
  {
    "path": "ui-widgets/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">Fortschritt</string>\n  <string name=\"menuCalendar\">Kalender</string>\n</resources>"
  },
  {
    "path": "ui-widgets/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">Progreso</string>\n  <string name=\"menuCalendar\">Calendario</string>\n</resources>"
  },
  {
    "path": "ui-widgets/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">Progressi</string>\n  <string name=\"menuCalendar\">Calendario</string>\n</resources>"
  },
  {
    "path": "ui-widgets/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">Postęp</string>\n  <string name=\"menuCalendar\">Kalendarz</string>\n</resources>"
  },
  {
    "path": "ui-widgets/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"menuProgress\">Tiến độ</string>\n  <string name=\"menuCalendar\">Lịch</string>\n</resources>"
  },
  {
    "path": "ui-widgets/src/main/res/xml/calendar_movies_widgets_provider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<appwidget-provider\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:minWidth=\"300dp\"\n  android:minHeight=\"200dp\"\n  android:minResizeWidth=\"30dp\"\n  android:minResizeHeight=\"20dp\"\n  android:previewImage=\"@drawable/ic_showly_round\"\n  android:resizeMode=\"horizontal|vertical\"\n  android:updatePeriodMillis=\"900000\"\n  android:widgetCategory=\"home_screen\"\n  />"
  },
  {
    "path": "ui-widgets/src/main/res/xml/calendar_widgets_provider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<appwidget-provider\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:minWidth=\"300dp\"\n  android:minHeight=\"200dp\"\n  android:minResizeWidth=\"30dp\"\n  android:minResizeHeight=\"20dp\"\n  android:previewImage=\"@drawable/ic_showly_round\"\n  android:resizeMode=\"horizontal|vertical\"\n  android:updatePeriodMillis=\"900000\"\n  android:widgetCategory=\"home_screen\"\n  />"
  },
  {
    "path": "ui-widgets/src/main/res/xml/progress_movies_widgets_provider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<appwidget-provider\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:minWidth=\"300dp\"\n  android:minHeight=\"200dp\"\n  android:minResizeWidth=\"30dp\"\n  android:minResizeHeight=\"20dp\"\n  android:previewImage=\"@drawable/ic_showly_round\"\n  android:resizeMode=\"horizontal|vertical\"\n  android:updatePeriodMillis=\"900000\"\n  android:widgetCategory=\"home_screen\"\n  />"
  },
  {
    "path": "ui-widgets/src/main/res/xml/progress_widgets_provider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<appwidget-provider\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:minWidth=\"300dp\"\n  android:minHeight=\"200dp\"\n  android:minResizeWidth=\"30dp\"\n  android:minResizeHeight=\"20dp\"\n  android:previewImage=\"@drawable/ic_showly_round\"\n  android:resizeMode=\"horizontal|vertical\"\n  android:updatePeriodMillis=\"900000\"\n  android:widgetCategory=\"home_screen\"\n  />"
  },
  {
    "path": "ui-widgets/src/main/res/xml/search_widgets_provider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<appwidget-provider\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:initialLayout=\"@layout/widget_search\"\n  android:minWidth=\"80dp\"\n  android:minHeight=\"@dimen/searchViewHeight\"\n  android:previewImage=\"@drawable/ic_showly_round\"\n  android:resizeMode=\"horizontal\"\n  android:updatePeriodMillis=\"1800000\"\n  android:widgetCategory=\"home_screen\"\n  />"
  },
  {
    "path": "versions.gradle",
    "content": "ext.versions = [\n    versionCode: 625,\n    versionName: '3.28.0',\n\n    minSdk     : 21,\n    compileSdk : 33,\n    targetSdk  : 33\n]\n"
  }
]