From 1bc9fa7dd41811a6513264723806a32a9a1f2498 Mon Sep 17 00:00:00 2001 From: Balackburn Date: Sat, 14 Feb 2026 19:02:58 +0100 Subject: [PATCH] Extract YTLitePlus as standalone tweak - Removed all other tweak integrations (YTLite, YTUHD, YouPiP, etc.) - Cleaned Makefile to build only YTLitePlus - Kept only required YouTubeHeader dependency - Preserved localization bundle - Updated README for standalone usage --- .DS_Store | Bin 0 -> 8196 bytes .gitmodules | 3 + LICENSE | 674 +++++++++ Makefile | 22 + README.md | 43 + Source/LowContrastMode.xm | 511 +++++++ Source/Settings.xm | 739 ++++++++++ Source/SettingsKeys.h | 52 + Source/Themes.xm | 822 +++++++++++ Source/VersionSpooferLite.xm | 49 + Source/get_keys.py | 100 ++ Tweaks/YouTubeHeader | 1 + YTLitePlus.h | 309 ++++ YTLitePlus.xm | 1312 +++++++++++++++++ control | 9 + lang/YTLitePlus.bundle/Back.png | Bin 0 -> 3734 bytes .../YTLitePlusColored-1024.png | Bin 0 -> 10981 bytes .../YTLitePlusColored-128.png | Bin 0 -> 1241 bytes .../YTLitePlusDarkMode-1024.png | Bin 0 -> 11289 bytes .../YTLitePlusLightMode-1024.png | Bin 0 -> 14797 bytes .../ar.lproj/Localizable.strings | 193 +++ .../bg.lproj/Localizable.strings | 190 +++ .../de.lproj/Localizable.strings | 197 +++ .../en.lproj/Localizable.strings | 190 +++ .../es.lproj/Localizable.strings | 190 +++ .../fr.lproj/Localizable.strings | 199 +++ .../ja.lproj/Localizable.strings | 191 +++ .../ko.lproj/Localizable.strings | 190 +++ .../pt.lproj/Localizable.strings | 191 +++ .../ro.lproj/Localizable.strings | 190 +++ .../ru.lproj/Localizable.strings | 190 +++ .../template.lproj/Localizable.strings | 205 +++ .../tr.lproj/Localizable.strings | 190 +++ .../vi.lproj/Localizable.strings | 190 +++ .../zh_CN.lproj/Localizable.strings | 194 +++ .../zh_TW.lproj/Localizable.strings | 204 +++ 36 files changed, 7740 insertions(+) create mode 100644 .DS_Store create mode 100644 .gitmodules create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 Source/LowContrastMode.xm create mode 100644 Source/Settings.xm create mode 100644 Source/SettingsKeys.h create mode 100644 Source/Themes.xm create mode 100644 Source/VersionSpooferLite.xm create mode 100644 Source/get_keys.py create mode 160000 Tweaks/YouTubeHeader create mode 100644 YTLitePlus.h create mode 100644 YTLitePlus.xm create mode 100644 control create mode 100644 lang/YTLitePlus.bundle/Back.png create mode 100644 lang/YTLitePlus.bundle/YTLitePlusColored-1024.png create mode 100644 lang/YTLitePlus.bundle/YTLitePlusColored-128.png create mode 100644 lang/YTLitePlus.bundle/YTLitePlusDarkMode-1024.png create mode 100644 lang/YTLitePlus.bundle/YTLitePlusLightMode-1024.png create mode 100644 lang/YTLitePlus.bundle/ar.lproj/Localizable.strings create mode 100644 lang/YTLitePlus.bundle/bg.lproj/Localizable.strings create mode 100644 lang/YTLitePlus.bundle/de.lproj/Localizable.strings create mode 100644 lang/YTLitePlus.bundle/en.lproj/Localizable.strings create mode 100644 lang/YTLitePlus.bundle/es.lproj/Localizable.strings create mode 100644 lang/YTLitePlus.bundle/fr.lproj/Localizable.strings create mode 100644 lang/YTLitePlus.bundle/ja.lproj/Localizable.strings create mode 100644 lang/YTLitePlus.bundle/ko.lproj/Localizable.strings create mode 100644 lang/YTLitePlus.bundle/pt.lproj/Localizable.strings create mode 100644 lang/YTLitePlus.bundle/ro.lproj/Localizable.strings create mode 100644 lang/YTLitePlus.bundle/ru.lproj/Localizable.strings create mode 100644 lang/YTLitePlus.bundle/template.lproj/Localizable.strings create mode 100644 lang/YTLitePlus.bundle/tr.lproj/Localizable.strings create mode 100644 lang/YTLitePlus.bundle/vi.lproj/Localizable.strings create mode 100644 lang/YTLitePlus.bundle/zh_CN.lproj/Localizable.strings create mode 100644 lang/YTLitePlus.bundle/zh_TW.lproj/Localizable.strings diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..7e63b1866f43bed418b5107013fdd4de882637c0 GIT binary patch literal 8196 zcmeI1F^dyH7>3_5Ar}XA0TKY z;x%@*BBcFNrz-P9UcFl9&MYUB2)Peuf z0l7aUtfHyc+^M~Fu(3w~YLnfz@OoP=sEOCqYwna07UPCe-B9C?7{(1}f8z5}uenn< z9L66$j9=OK6N<5`bN-7ecY{0(u zhaX>B%j0)9;`s2l@xz1S-GF)amP4_1wk{5^z#v>57%rE2$klyVA^x}2CJ5soc%>BmD1F(FHdr17(w5wcu= z^YLtNO7n!i)Mv#!f`jwe#Sxtuk!i;7NhTWmQ=b>IJwI8ot(`L@{8R24&L;iZBilG) zYg|4h-6|IHe80Qm3F5_`t&2zNHr~qzZ=p|57I=hxjH0#8{Bb!RuK)VdwL3HSJBsEA zeW=TcILxu*xP>KmX%7!d`*ZU3iscC6A;;%e!S^@HDZHr5i8wxe<1+gEm(9?k4s5ss z4ae7!^Z#!B`~MC96pE(~r~`lFfNA#ny)I|)XY0yba@I~*@3BgeyxgfB!p0uQsrEQd eUHxIm>x8K?CiR*-We + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a477beb --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +# YTLitePlus - Standalone Tweak Build Configuration +export TARGET = iphone:clang:latest:14.0 +export ARCHS = arm64 arm64e +export THEOS_PACKAGE_SCHEME = rootless + +INSTALL_TARGET_PROCESSES = YouTube +TWEAK_NAME = YTLitePlus +PACKAGE_VERSION = 1.0.0 + +YTLitePlus_FILES = YTLitePlus.xm $(shell find Source -name '*.xm' -o -name '*.x' -o -name '*.m') +YTLitePlus_FRAMEWORKS = UIKit Security Foundation AVFoundation AVKit MediaPlayer MobileCoreServices UniformTypeIdentifiers +YTLitePlus_CFLAGS = -fobjc-arc -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-unsupported-availability-guard -Wno-vla-cxx-extension -DTWEAK_VERSION=\"$(PACKAGE_VERSION)\" -I$(THEOS_PROJECT_DIR)/Tweaks + +include $(THEOS)/makefiles/common.mk +include $(THEOS_MAKE_PATH)/tweak.mk + +after-install:: + install.exec "mkdir -p /var/jb/Library/Application\ Support && cp -r /tmp/YTLitePlus.bundle /var/jb/Library/Application\ Support/" || true + +before-package:: + @mkdir -p $(THEOS_STAGING_DIR)/Library/Application\ Support + @cp -r lang/YTLitePlus.bundle $(THEOS_STAGING_DIR)/Library/Application\ Support/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..d383ee8 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# YTLitePlus Tweak + +A standalone iOS tweak that enhances YouTube with custom themes, settings, and UI improvements. + +## Features +- Custom themes and color schemes +- Low contrast mode +- Extensive settings customization +- Version spoofing support +- Localized in 15+ languages + +## Building + +### Prerequisites +- Theos installed +- iOS SDK +- Git with submodules support + +### Build Steps +```bash +# Clone with submodules +git clone --recursive + +# Or initialize submodules after cloning +git submodule update --init --recursive + +# Build +make clean +make package +``` + +## Installation +1. Build the .deb package +2. Install via package manager (Sileo, Zebra, Cydia) +3. Respring or restart YouTube + +## Dependencies +- YouTubeHeader (included as submodule) +- iOS 14.0 or later +- CydiaSubstrate/libhooker + +## License +See LICENSE file. diff --git a/Source/LowContrastMode.xm b/Source/LowContrastMode.xm new file mode 100644 index 0000000..3bd50b0 --- /dev/null +++ b/Source/LowContrastMode.xm @@ -0,0 +1,511 @@ +#import "../YTLitePlus.h" + +// Color Configuration +static UIColor *lcmHexColor = nil; +static UIColor *const kLowContrastColor = [UIColor colorWithRed:0.56 green:0.56 blue:0.56 alpha:1.0]; +static UIColor *const kDefaultTextColor = [UIColor whiteColor]; + +// Utility Functions +static inline int contrastMode() { + return [[NSUserDefaults standardUserDefaults] integerForKey:@"lcm"]; +} + +static inline BOOL lowContrastMode() { + return IS_ENABLED(@"lowContrastMode_enabled") && contrastMode() == 0; +} + +static inline BOOL customContrastMode() { + return IS_ENABLED(@"lowContrastMode_enabled") && contrastMode() == 1; +} + +// static inline UIColor *activeColor() { +// return customContrastMode() && lcmHexColor ? lcmHexColor : kLowContrastColor; +// } + +// Low Contrast Mode v1.7.1.1 (Compatible with only YouTube v19.01.1-v20.21.6) +%group gLowContrastMode +%hook UIColor ++ (UIColor *)colorNamed:(NSString *)name { + NSArray *targetColors = @[ + @"whiteColor", @"lightTextColor", @"lightGrayColor", @"ychGrey7", + @"skt_chipBackgroundColor", @"placeholderTextColor", @"systemLightGrayColor", + @"systemExtraLightGrayColor", @"labelColor", @"secondaryLabelColor", + @"tertiaryLabelColor", @"quaternaryLabelColor" + ]; + return [targetColors containsObject:name] ? kLowContrastColor : %orig; +} + ++ (UIColor *)whiteColor { return kLowContrastColor; } ++ (UIColor *)lightTextColor { return kLowContrastColor; } ++ (UIColor *)lightGrayColor { return kLowContrastColor; } +%end + +%hook YTCommonColorPalette +- (UIColor *)textPrimary { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)textSecondary { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)overlayTextPrimary { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)overlayTextSecondary { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)iconActive { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)iconActiveOther { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)brandIconActive { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)staticBrandWhite { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)overlayIconActiveOther { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)overlayIconInactive { + return self.pageStyle == 1 ? [kDefaultTextColor colorWithAlphaComponent:0.7] : %orig; +} +- (UIColor *)overlayIconDisabled { + return self.pageStyle == 1 ? [kDefaultTextColor colorWithAlphaComponent:0.3] : %orig; +} +- (UIColor *)overlayFilledButtonActive { + return self.pageStyle == 1 ? [kDefaultTextColor colorWithAlphaComponent:0.2] : %orig; +} +%end + +%hook YTColor ++ (BOOL)darkerPaletteTextColorEnabled { return NO; } ++ (UIColor *)white1 { return kLowContrastColor; } ++ (UIColor *)white2 { return kLowContrastColor; } ++ (UIColor *)white3 { return kLowContrastColor; } ++ (UIColor *)white4 { return kLowContrastColor; } ++ (UIColor *)white5 { return kLowContrastColor; } ++ (UIColor *)grey1 { return kLowContrastColor; } ++ (UIColor *)grey2 { return kLowContrastColor; } +%end + +%hook _ASDisplayView +- (void)layoutSubviews { + %orig; + NSArray *targetLabels = @[@"connect account", @"Thanks", @"Save to playlist", @"Report"]; + for (UIView *subview in self.subviews) { + if ([targetLabels containsObject:subview.accessibilityLabel]) { + subview.backgroundColor = kLowContrastColor; + if ([subview isKindOfClass:[UILabel class]]) { + ((UILabel *)subview).textColor = [UIColor blackColor]; + } + } + } +} +%end + +%hook QTMColorGroup +- (UIColor *)tint100 { return kDefaultTextColor; } +- (UIColor *)tint300 { return kDefaultTextColor; } +- (UIColor *)tint500 { return kDefaultTextColor; } +- (UIColor *)tint700 { return kDefaultTextColor; } +- (UIColor *)accent200 { return kDefaultTextColor; } +- (UIColor *)accent400 { return kDefaultTextColor; } +- (UIColor *)accentColor { return kDefaultTextColor; } +- (UIColor *)brightAccentColor { return kDefaultTextColor; } +- (UIColor *)regularColor { return kDefaultTextColor; } +- (UIColor *)darkerColor { return kDefaultTextColor; } +- (UIColor *)bodyTextColor { return kDefaultTextColor; } +- (UIColor *)lightBodyTextColor { return kDefaultTextColor; } +- (UIColor *)bodyTextColorOnRegularColor { return kDefaultTextColor; } +- (UIColor *)bodyTextColorOnLighterColor { return kDefaultTextColor; } +- (UIColor *)bodyTextColorOnDarkerColor { return kDefaultTextColor; } +- (UIColor *)bodyTextColorOnAccentColor { return kDefaultTextColor; } +- (UIColor *)buttonBackgroundColor { return kDefaultTextColor; } +- (UIColor *)Color { return kDefaultTextColor; } +%end + +%hook YTQTMButton +- (void)setImage:(UIImage *)image { + UIImage *tintedImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + [self setTintColor:kDefaultTextColor]; + %orig(tintedImage); +} +%end + +%hook UIExtendedSRGColorSpace +- (void)setTextColor:(UIColor *)textColor { + %orig([kDefaultTextColor colorWithAlphaComponent:0.9]); +} +%end + +%hook UIExtendedSRGBColorSpace +- (void)setTextColor:(UIColor *)textColor { + %orig([kDefaultTextColor colorWithAlphaComponent:1.0]); +} +%end + +%hook UIExtendedGrayColorSpace +- (void)setTextColor:(UIColor *)textColor { + %orig([kDefaultTextColor colorWithAlphaComponent:1.0]); +} +%end + +%hook VideoTitleLabel +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook UILabel ++ (void)load { + [[UILabel appearance] setTextColor:kDefaultTextColor]; +} +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook UITextField +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook UITextView +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook UISearchBar +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook UISegmentedControl +- (void)setTitleTextAttributes:(NSDictionary *)attributes forState:(UIControlState)state { + NSMutableDictionary *modifiedAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes]; + modifiedAttributes[NSForegroundColorAttributeName] = kDefaultTextColor; + %orig(modifiedAttributes, state); +} +%end + +%hook UIButton +- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state { + %orig(kDefaultTextColor, state); +} +%end + +%hook UIBarButtonItem +- (void)setTitleTextAttributes:(NSDictionary *)attributes forState:(UIControlState)state { + NSMutableDictionary *modifiedAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes]; + modifiedAttributes[NSForegroundColorAttributeName] = kDefaultTextColor; + %orig(modifiedAttributes, state); +} +%end + +%hook NSAttributedString +- (instancetype)initWithString:(NSString *)str attributes:(NSDictionary *)attrs { + NSMutableDictionary *modifiedAttributes = [NSMutableDictionary dictionaryWithDictionary:attrs]; + modifiedAttributes[NSForegroundColorAttributeName] = kDefaultTextColor; + return %orig(str, modifiedAttributes); +} +%end + +%hook CATextLayer +- (void)setTextColor:(CGColorRef)textColor { + %orig(kDefaultTextColor.CGColor); +} +%end + +%hook ASTextNode +- (NSAttributedString *)attributedString { + NSAttributedString *original = %orig; + NSMutableAttributedString *modified = [original mutableCopy]; + [modified addAttribute:NSForegroundColorAttributeName value:kDefaultTextColor range:NSMakeRange(0, modified.length)]; + return modified; +} +%end + +%hook ASTextFieldNode +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook ASTextView +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook ASButtonNode +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook UIControl +- (UIColor *)backgroundColor { + return [UIColor blackColor]; +} +%end +%end + +// Custom Contrast Mode +%group gCustomContrastMode +%hook UIColor ++ (UIColor *)colorNamed:(NSString *)name { + NSArray *targetColors = @[ + @"whiteColor", @"lightTextColor", @"lightGrayColor", @"ychGrey7", + @"skt_chipBackgroundColor", @"placeholderTextColor", @"systemLightGrayColor", + @"systemExtraLightGrayColor", @"labelColor", @"secondaryLabelColor", + @"tertiaryLabelColor", @"quaternaryLabelColor" + ]; + return [targetColors containsObject:name] ? lcmHexColor : %orig; +} + ++ (UIColor *)whiteColor { return lcmHexColor; } ++ (UIColor *)lightTextColor { return lcmHexColor; } ++ (UIColor *)lightGrayColor { return lcmHexColor; } +%end + +%hook YTCommonColorPalette +- (UIColor *)textPrimary { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)textSecondary { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)overlayTextPrimary { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)overlayTextSecondary { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)iconActive { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)iconActiveOther { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)brandIconActive { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)staticBrandWhite { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)overlayIconActiveOther { + return self.pageStyle == 1 ? kDefaultTextColor : %orig; +} +- (UIColor *)overlayIconInactive { + return self.pageStyle == 1 ? [kDefaultTextColor colorWithAlphaComponent:0.7] : %orig; +} +- (UIColor *)overlayIconDisabled { + return self.pageStyle == 1 ? [kDefaultTextColor colorWithAlphaComponent:0.3] : %orig; +} +- (UIColor *)overlayFilledButtonActive { + return self.pageStyle == 1 ? [kDefaultTextColor colorWithAlphaComponent:0.2] : %orig; +} +%end + +%hook YTColor ++ (BOOL)darkerPaletteTextColorEnabled { return NO; } ++ (UIColor *)white1 { return lcmHexColor; } ++ (UIColor *)white2 { return lcmHexColor; } ++ (UIColor *)white3 { return lcmHexColor; } ++ (UIColor *)white4 { return lcmHexColor; } ++ (UIColor *)white5 { return lcmHexColor; } ++ (UIColor *)grey1 { return lcmHexColor; } ++ (UIColor *)grey2 { return lcmHexColor; } +%end + +%hook _ASDisplayView +- (void)layoutSubviews { + %orig; + NSArray *targetLabels = @[@"connect account", @"Thanks", @"Save to playlist", @"Report"]; + for (UIView *subview in self.subviews) { + if ([targetLabels containsObject:subview.accessibilityLabel]) { + subview.backgroundColor = lcmHexColor; + if ([subview isKindOfClass:[UILabel class]]) { + ((UILabel *)subview).textColor = [UIColor blackColor]; + } + } + } +} +%end + +%hook QTMColorGroup +- (UIColor *)tint100 { return kDefaultTextColor; } +- (UIColor *)tint300 { return kDefaultTextColor; } +- (UIColor *)tint500 { return kDefaultTextColor; } +- (UIColor *)tint700 { return kDefaultTextColor; } +- (UIColor *)accent200 { return kDefaultTextColor; } +- (UIColor *)accent400 { return kDefaultTextColor; } +- (UIColor *)accentColor { return kDefaultTextColor; } +- (UIColor *)brightAccentColor { return kDefaultTextColor; } +- (UIColor *)regularColor { return kDefaultTextColor; } +- (UIColor *)darkerColor { return kDefaultTextColor; } +- (UIColor *)bodyTextColor { return kDefaultTextColor; } +- (UIColor *)lightBodyTextColor { return kDefaultTextColor; } +- (UIColor *)bodyTextColorOnRegularColor { return kDefaultTextColor; } +- (UIColor *)bodyTextColorOnLighterColor { return kDefaultTextColor; } +- (UIColor *)bodyTextColorOnDarkerColor { return kDefaultTextColor; } +- (UIColor *)bodyTextColorOnAccentColor { return kDefaultTextColor; } +- (UIColor *)buttonBackgroundColor { return kDefaultTextColor; } +- (UIColor *)Color { return kDefaultTextColor; } +%end + +%hook YTQTMButton +- (void)setImage:(UIImage *)image { + UIImage *tintedImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + [self setTintColor:kDefaultTextColor]; + %orig(tintedImage); +} +%end + +%hook UIExtendedSRGColorSpace +- (void)setTextColor:(UIColor *)textColor { + %orig([kDefaultTextColor colorWithAlphaComponent:0.9]); +} +%end + +%hook UIExtendedSRGBColorSpace +- (void)setTextColor:(UIColor *)textColor { + %orig([kDefaultTextColor colorWithAlphaComponent:1.0]); +} +%end + +%hook UIExtendedGrayColorSpace +- (void)setTextColor:(UIColor *)textColor { + %orig([kDefaultTextColor colorWithAlphaComponent:1.0]); +} +%end + +%hook VideoTitleLabel +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook UILabel ++ (void)load { + [[UILabel appearance] setTextColor:kDefaultTextColor]; +} +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook UITextField +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook UITextView +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook UISearchBar +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook UISegmentedControl +- (void)setTitleTextAttributes:(NSDictionary *)attributes forState:(UIControlState)state { + NSMutableDictionary *modifiedAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes]; + modifiedAttributes[NSForegroundColorAttributeName] = kDefaultTextColor; + %orig(modifiedAttributes, state); +} +%end + +%hook UIButton +- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state { + %orig(kDefaultTextColor, state); +} +%end + +%hook UIBarButtonItem +- (void)setTitleTextAttributes:(NSDictionary *)attributes forState:(UIControlState)state { + NSMutableDictionary *modifiedAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes]; + modifiedAttributes[NSForegroundColorAttributeName] = kDefaultTextColor; + %orig(modifiedAttributes, state); +} +%end + +%hook NSAttributedString +- (instancetype)initWithString:(NSString *)str attributes:(NSDictionary *)attrs { + NSMutableDictionary *modifiedAttributes = [NSMutableDictionary dictionaryWithDictionary:attrs]; + modifiedAttributes[NSForegroundColorAttributeName] = kDefaultTextColor; + return %orig(str, modifiedAttributes); +} +%end + +%hook CATextLayer +- (void)setTextColor:(CGColorRef)color { + %orig(kDefaultTextColor.CGColor); +} +%end + +%hook ASTextNode +- (NSAttributedString *)attributedString { + NSAttributedString *original = %orig; + NSMutableAttributedString *modified = [original mutableCopy]; + [modified addAttribute:NSForegroundColorAttributeName value:kDefaultTextColor range:NSMakeRange(0, modified.length)]; + return modified; +} +%end + +%hook ASTextFieldNode +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook ASTextView +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook ASButtonNode +- (void)setTextColor:(UIColor *)textColor { + %orig(kDefaultTextColor); +} +%end + +%hook UIControl +- (UIColor *)backgroundColor { + return [UIColor blackColor]; +} +%end +%end + +// Constructor +%ctor { + %init; + if (lowContrastMode()) { + %init(gLowContrastMode); + } + if (customContrastMode()) { + NSData *colorData = [[NSUserDefaults standardUserDefaults] objectForKey:@"kCustomUIColor"]; + if (colorData) { + NSError *error = nil; + NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:colorData error:&error]; + if (!error) { + [unarchiver setRequiresSecureCoding:NO]; + lcmHexColor = [unarchiver decodeObjectForKey:NSKeyedArchiveRootObjectKey]; + if (lcmHexColor) { + %init(gCustomContrastMode); + } + } + } + } +} diff --git a/Source/Settings.xm b/Source/Settings.xm new file mode 100644 index 0000000..e528b87 --- /dev/null +++ b/Source/Settings.xm @@ -0,0 +1,739 @@ +#import +#import +#import + +#import "../YTLitePlus.h" +#import "../Tweaks/YouTubeHeader/YTSettingsViewController.h" +#import "../Tweaks/YouTubeHeader/YTSearchableSettingsViewController.h" +#import "../Tweaks/YouTubeHeader/YTSettingsSectionItem.h" +#import "../Tweaks/YouTubeHeader/YTSettingsSectionItemManager.h" +#import "../Tweaks/YouTubeHeader/YTUIUtils.h" +#import "../Tweaks/YouTubeHeader/YTSettingsPickerViewController.h" +#import "SettingsKeys.h" +// #import "AppIconOptionsController.h" + + +// Basic switch item +#define BASIC_SWITCH(title, description, key) \ + [YTSettingsSectionItemClass switchItemWithTitle:title \ + titleDescription:description \ + accessibilityIdentifier:nil \ + switchOn:IsEnabled(key) \ + switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) { \ + [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:key]; \ + return YES; \ + } \ + settingItemId:0] + +/* +// Custom switch item that has customizable code +#define CUSTOM_SWITCH(title, description, key, code) \ + [YTSettingsSectionItemClass switchItemWithTitle:title \ + titleDescription:description \ + accessibilityIdentifier:nil \ + switchOn:IsEnabled(key) \ + switchBlock:^BOOL (YTSettingsCell *cell, BOOL enable) { \ + code \ + } \ + settingItemId:0] +*/ + +static int contrastMode() { + return [[NSUserDefaults standardUserDefaults] integerForKey:@"lcm"]; +} +static int appVersionSpoofer() { + return [[NSUserDefaults standardUserDefaults] integerForKey:@"versionSpoofer"]; +} + +@interface YTSettingsSectionItemManager (YTLitePlus) +- (void)updateYTLitePlusSectionWithEntry:(id)entry; +@end + +extern NSBundle *YTLitePlusBundle(); + +// Add both YTLite and YTLitePlus to YouGroupSettings +static const NSInteger YTLitePlusSection = 788; +static const NSInteger YTLiteSection = 789; +%hook YTSettingsGroupData ++ (NSMutableArray *)tweaks { + NSMutableArray *originalTweaks = %orig; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [originalTweaks addObject:@(YTLitePlusSection)]; + [originalTweaks addObject:@(YTLiteSection)]; + }); + + return originalTweaks; +} +%end + + +// Add YTLitePlus to the settings list +%hook YTAppSettingsPresentationData ++ (NSArray *)settingsCategoryOrder { + NSArray *order = %orig; + NSMutableArray *mutableOrder = [order mutableCopy]; + NSUInteger insertIndex = [order indexOfObject:@(1)]; + if (insertIndex != NSNotFound) + [mutableOrder insertObject:@(YTLitePlusSection) atIndex:insertIndex + 1]; + return mutableOrder; +} +%end + +%hook YTSettingsSectionController + +- (void)setSelectedItem:(NSUInteger)selectedItem { + if (selectedItem != NSNotFound) %orig; +} + +%end + +%hook YTSettingsSectionItemManager +%new(v@:@) +- (void)updateYTLitePlusSectionWithEntry:(id)entry { + NSMutableArray *sectionItems = [NSMutableArray array]; + NSBundle *tweakBundle = YTLitePlusBundle(); + Class YTSettingsSectionItemClass = %c(YTSettingsSectionItem); + YTSettingsViewController *settingsViewController = [self valueForKey:@"_settingsViewControllerDelegate"]; + + // Add item for going to the YTLitePlus GitHub page + YTSettingsSectionItem *main = [%c(YTSettingsSectionItem) + itemWithTitle:[NSString stringWithFormat:LOC(@"VERSION"), @(OS_STRINGIFY(TWEAK_VERSION))] + titleDescription:LOC(@"VERSION_CHECK") + accessibilityIdentifier:nil + detailTextBlock:nil + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + return [%c(YTUIUtils) openURL:[NSURL URLWithString:@"https://github.com/YTLitePlus/YTLitePlus/releases/latest"]]; + }]; + [sectionItems addObject:main]; + +# pragma mark - Copy and Paste Settings + YTSettingsSectionItem *copySettings = [%c(YTSettingsSectionItem) + itemWithTitle:IS_ENABLED(@"switchCopyandPasteFunctionality_enabled") ? LOC(@"EXPORT_SETTINGS") : LOC(@"COPY_SETTINGS") + titleDescription:IS_ENABLED(@"switchCopyandPasteFunctionality_enabled") ? LOC(@"EXPORT_SETTINGS_DESC") : LOC(@"COPY_SETTINGS_DESC") + accessibilityIdentifier:nil + detailTextBlock:nil + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + if (IS_ENABLED(@"switchCopyandPasteFunctionality_enabled")) { + // Export Settings functionality + NSURL *tempFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"YTLitePlusSettings.txt"]]; + NSMutableString *settingsString = [NSMutableString string]; + for (NSString *key in NSUserDefaultsCopyKeys) { + id value = [[NSUserDefaults standardUserDefaults] objectForKey:key]; + id defaultValue = NSUserDefaultsCopyKeysDefaults[key]; + + // Only include the setting if it is different from the default value + // If no default value is found, include it by default + if (value && (!defaultValue || ![value isEqual:defaultValue])) { + [settingsString appendFormat:@"%@: %@\n", key, value]; + } + } + [settingsString writeToURL:tempFileURL atomically:YES encoding:NSUTF8StringEncoding error:nil]; + UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithURL:tempFileURL inMode:UIDocumentPickerModeExportToService]; + documentPicker.delegate = (id)self; + documentPicker.allowsMultipleSelection = NO; + [settingsViewController presentViewController:documentPicker animated:YES completion:nil]; + } else { + // Copy Settings functionality (DEFAULT - Copies to Clipboard) + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + NSMutableString *settingsString = [NSMutableString string]; + for (NSString *key in NSUserDefaultsCopyKeys) { + id value = [userDefaults objectForKey:key]; + id defaultValue = NSUserDefaultsCopyKeysDefaults[key]; + + // Only include the setting if it is different from the default value + // If no default value is found, include it by default + if (value && (!defaultValue || ![value isEqual:defaultValue])) { + [settingsString appendFormat:@"%@: %@\n", key, value]; + } + } + [[UIPasteboard generalPasteboard] setString:settingsString]; + // Show a confirmation message or perform some other action here + [[%c(GOOHUDManagerInternal) sharedInstance] showMessageMainThread:[%c(YTHUDMessage) messageWithText:@"Settings copied"]]; + + // Show an option to export YouTube Plus settings + UIAlertController *exportAlert = [UIAlertController alertControllerWithTitle:@"Export Settings" + message:@"Note: This feature cannot save most YouTube settings.\n\nWould you like to also export your YouTube Plus Settings?" + preferredStyle:UIAlertControllerStyleAlert]; + [exportAlert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; + [exportAlert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + // Export YouTube Plus Settings functionality + [%c(YTLUserDefaults) exportYtlSettings]; + }]]; + // Present the alert from the root view controller + [settingsViewController presentViewController:exportAlert animated:YES completion:nil]; + } + + return YES; + } + ]; + [sectionItems addObject:copySettings]; + + YTSettingsSectionItem *pasteSettings = [%c(YTSettingsSectionItem) + itemWithTitle:IS_ENABLED(@"switchCopyandPasteFunctionality_enabled") ? LOC(@"IMPORT_SETTINGS") : LOC(@"PASTE_SETTINGS") + titleDescription:IS_ENABLED(@"switchCopyandPasteFunctionality_enabled") ? LOC(@"IMPORT_SETTINGS_DESC") : LOC(@"PASTE_SETTINGS_DESC") + accessibilityIdentifier:nil + detailTextBlock:nil + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + if (IS_ENABLED(@"switchCopyandPasteFunctionality_enabled")) { + // Paste Settings functionality (ALTERNATE - Pastes from ".txt") + UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.text"] inMode:UIDocumentPickerModeImport]; + documentPicker.delegate = (id)self; + documentPicker.allowsMultipleSelection = NO; + [settingsViewController presentViewController:documentPicker animated:YES completion:nil]; + } else { + // Paste Settings functionality (DEFAULT - Pastes from Clipboard) + UIAlertController *confirmPasteAlert = [UIAlertController alertControllerWithTitle:LOC(@"PASTE_SETTINGS_ALERT") message:nil preferredStyle:UIAlertControllerStyleAlert]; + [confirmPasteAlert addAction:[UIAlertAction actionWithTitle:LOC(@"Cancel") style:UIAlertActionStyleCancel handler:nil]]; + [confirmPasteAlert addAction:[UIAlertAction actionWithTitle:LOC(@"Confirm") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + NSString *settingsString = [[UIPasteboard generalPasteboard] string]; + if (settingsString.length > 0) { + NSArray *lines = [settingsString componentsSeparatedByString:@"\n"]; + for (NSString *line in lines) { + NSArray *components = [line componentsSeparatedByString:@": "]; + if (components.count == 2) { + NSString *key = components[0]; + NSString *value = components[1]; + [[NSUserDefaults standardUserDefaults] setObject:value forKey:key]; + } + } + [settingsViewController reloadData]; + // Show a confirmation toast + [[%c(GOOHUDManagerInternal) sharedInstance] showMessageMainThread:[%c(YTHUDMessage) messageWithText:@"Settings applied"]]; + // Show a reminder to import YouTube Plus settings as well + UIAlertController *reminderAlert = [UIAlertController alertControllerWithTitle:@"Reminder" + message:@"Remember to import your YouTube Plus settings as well" + preferredStyle:UIAlertControllerStyleAlert]; + [reminderAlert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]]; + [settingsViewController presentViewController:reminderAlert animated:YES completion:nil]; + } + }]]; + [settingsViewController presentViewController:confirmPasteAlert animated:YES completion:nil]; + } + + return YES; + } + ]; + [sectionItems addObject:pasteSettings]; + +/* + YTSettingsSectionItem *appIcon = [%c(YTSettingsSectionItem) + itemWithTitle:LOC(@"CHANGE_APP_ICON") + titleDescription:nil + accessibilityIdentifier:nil + detailTextBlock:nil + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + AppIconOptionsController *appIconController = [[AppIconOptionsController alloc] init]; + [settingsViewController.navigationController pushViewController:appIconController animated:YES]; + return YES; + } + ]; + [sectionItems addObject:appIcon]; +*/ + +# pragma mark - Player Gestures - @bhackel + // Helper to get the selected gesture mode + static NSString* (^sectionGestureSelectedModeToString)(GestureMode) = ^(GestureMode sectionIndex) { + switch (sectionIndex) { + case GestureModeVolume: + return LOC(@"VOLUME"); + case GestureModeBrightness: + return LOC(@"BRIGHTNESS"); + case GestureModeSeek: + return LOC(@"SEEK"); + case GestureModeDisabled: + return LOC(@"DISABLED"); + default: + return @"Invalid index - Report bug"; + } + }; + + // Helper to generate checkmark setting items for selecting gesture modes + static YTSettingsSectionItem* (^gestureCheckmarkSettingItem)(NSInteger, NSString *) = ^(NSInteger idx, NSString *key) { + return [YTSettingsSectionItemClass + checkmarkItemWithTitle:sectionGestureSelectedModeToString((GestureMode)idx) + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:idx forKey:key]; + [settingsViewController reloadData]; + return YES; + } + ]; + }; + + // Helper to generate a section item for selecting a gesture mode + YTSettingsSectionItem *(^createSectionGestureSelector)(NSString *, NSString *) = ^YTSettingsSectionItem *(NSString *sectionLabel, NSString *sectionKey) { + return [YTSettingsSectionItemClass itemWithTitle:LOC(sectionLabel) + accessibilityIdentifier:nil + detailTextBlock:^NSString *() { + return sectionGestureSelectedModeToString((GestureMode)GetInteger(sectionKey)); + } + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + NSArray *rows = @[ + gestureCheckmarkSettingItem(0, sectionKey), // Volume + gestureCheckmarkSettingItem(1, sectionKey), // Brightness + gestureCheckmarkSettingItem(2, sectionKey), // Seek + gestureCheckmarkSettingItem(3, sectionKey) // Disabled + ]; + // Present picker when selecting this settings item + YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] + initWithNavTitle:LOC(sectionLabel) + pickerSectionTitle:nil + rows:rows + selectedItemIndex:GetInteger(sectionKey) + parentResponder:[self parentResponder] + ]; + [settingsViewController pushViewController:picker]; + return YES; + } + ]; + }; + // Configuration picker for deadzone to pick from 0 to 100 pixels with interval of 10 + NSMutableArray *deadzoneValues = [NSMutableArray array]; + for (CGFloat value = 0; value <= 100; value += 10) { + [deadzoneValues addObject:@(value)]; + } + YTSettingsSectionItem *deadzonePicker = [YTSettingsSectionItemClass + itemWithTitle:LOC(@"DEADZONE") + titleDescription:LOC(@"DEADZONE_DESC") + accessibilityIdentifier:nil + detailTextBlock:^NSString *() { + return [NSString stringWithFormat:@"%ld px", (long)GetFloat(@"playerGesturesDeadzone")]; + } + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + // Generate rows for deadzone picker using the predefined array + NSMutableArray *deadzoneRows = [NSMutableArray array]; + for (NSNumber *deadzoneValue in deadzoneValues) { + CGFloat deadzone = [deadzoneValue floatValue]; + [deadzoneRows addObject:[YTSettingsSectionItemClass + checkmarkItemWithTitle:[NSString stringWithFormat:@"%ld px", (long)deadzone] + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setFloat:deadzone forKey:@"playerGesturesDeadzone"]; + [settingsViewController reloadData]; + return YES; + } + ]]; + } + // Determine the index of the currently selected deadzone + CGFloat currentDeadzone = GetFloat(@"playerGesturesDeadzone"); + NSUInteger selectedIndex = [deadzoneValues indexOfObject:@(currentDeadzone)]; + if (selectedIndex == NSNotFound) { + selectedIndex = 0; // Default to the first item if the current deadzone is not found + } + // Present deadzone picker when selecting this settings item + YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] + initWithNavTitle:LOC(@"DEADZONE") + pickerSectionTitle:nil + rows:deadzoneRows + selectedItemIndex:selectedIndex + parentResponder:[self parentResponder] + ]; + [settingsViewController pushViewController:picker]; + return YES; + } + ]; + + // Configuration picker for sensitivity to pick from 0.5 to 2.0 with interval of 0.1 + NSMutableArray *sensitivityValues = [NSMutableArray array]; + for (CGFloat value = 0.5; value <= 2.0; value += 0.1) { + [sensitivityValues addObject:@(value)]; + } + YTSettingsSectionItem *sensitivityPicker = [YTSettingsSectionItemClass + itemWithTitle:LOC(@"SENSITIVITY") + titleDescription:LOC(@"SENSITIVITY_DESC") + accessibilityIdentifier:nil + detailTextBlock:^NSString *() { + return [NSString stringWithFormat:@"%.1f", GetFloat(@"playerGesturesSensitivity")]; + } + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + // Generate rows for sensitivity picker using the predefined array + NSMutableArray *sensitivityRows = [NSMutableArray array]; + for (NSNumber *sensitivityValue in sensitivityValues) { + CGFloat sensitivity = [sensitivityValue floatValue]; + [sensitivityRows addObject:[YTSettingsSectionItemClass + checkmarkItemWithTitle:[NSString stringWithFormat:@"%.1f", sensitivity] + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setFloat:sensitivity forKey:@"playerGesturesSensitivity"]; + [settingsViewController reloadData]; + return YES; + } + ]]; + } + // Determine the index of the currently selected sensitivity + CGFloat currentSensitivity = GetFloat(@"playerGesturesSensitivity"); + NSUInteger selectedIndex = [sensitivityValues indexOfObject:@(currentSensitivity)]; + if (selectedIndex == NSNotFound) { + selectedIndex = 0; // Default to the first item if the current sensitivity is not found + } + // Present sensitivity picker + YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] + initWithNavTitle:LOC(@"SENSITIVITY") + pickerSectionTitle:nil + rows:sensitivityRows + selectedItemIndex:selectedIndex + parentResponder:[self parentResponder] + ]; + [settingsViewController pushViewController:picker]; + return YES; + } + ]; + + // Create and add items to the high level gestures menu + YTSettingsSectionItem *playerGesturesGroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"PLAYER_GESTURES_TITLE") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + NSArray *rows = @[ + // Description header item + [YTSettingsSectionItemClass + itemWithTitle:nil + titleDescription:LOC(@"PLAYER_GESTURES_DESC") + accessibilityIdentifier:nil + detailTextBlock:nil + selectBlock:nil + ], + // Toggle for enabling gestures + BASIC_SWITCH(LOC(@"PLAYER_GESTURES_TOGGLE"), nil, @"playerGestures_enabled"), + // Pickers for each gesture section + createSectionGestureSelector(@"TOP_SECTION", @"playerGestureTopSelection"), + createSectionGestureSelector(@"MIDDLE_SECTION", @"playerGestureMiddleSelection"), + createSectionGestureSelector(@"BOTTOM_SECTION", @"playerGestureBottomSelection"), + // Pickers for configuration settings + deadzonePicker, + sensitivityPicker, + // Toggle for haptic feedback + BASIC_SWITCH(LOC(@"PLAYER_GESTURES_HAPTIC_FEEDBACK"), nil, @"playerGesturesHapticFeedback_enabled"), + ]; + YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"PLAYER_GESTURES_TITLE") pickerSectionTitle:nil rows:rows selectedItemIndex:NSNotFound parentResponder:[self parentResponder]]; + [settingsViewController pushViewController:picker]; + return YES; + }]; + [sectionItems addObject:playerGesturesGroup]; + +# pragma mark - Video Controls Overlay Options + YTSettingsSectionItem *videoControlOverlayGroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"VIDEO_CONTROLS_OVERLAY_OPTIONS") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + NSArray *rows = @[ + BASIC_SWITCH(LOC(@"ENABLE_SHARE_BUTTON"), LOC(@"ENABLE_SHARE_BUTTON_DESC"), @"enableShareButton_enabled"), + BASIC_SWITCH(LOC(@"ENABLE_SAVE_TO_PLAYLIST_BUTTON"), LOC(@"ENABLE_SAVE_TO_PLAYLIST_BUTTON_DESC"), @"enableSaveToButton_enabled"), + BASIC_SWITCH(LOC(@"HIDE_SHADOW_OVERLAY_BUTTONS"), LOC(@"HIDE_SHADOW_OVERLAY_BUTTONS_DESC"), @"hideVideoPlayerShadowOverlayButtons_enabled"), + BASIC_SWITCH(LOC(@"HIDE_RIGHT_PANEL"), LOC(@"HIDE_RIGHT_PANEL_DESC"), @"hideRightPanel_enabled"), + BASIC_SWITCH(LOC(@"HIDE_HEATWAVES"), LOC(@"HIDE_HEATWAVES_DESC"), @"hideHeatwaves_enabled"), + BASIC_SWITCH(LOC(@"DISABLE_AMBIENT_PORTRAIT"), LOC(@"DISABLE_AMBIENT_PORTRAIT_DESC"), @"disableAmbientModePortrait_enabled"), + BASIC_SWITCH(LOC(@"DISABLE_AMBIENT_FULLSCREEN"), LOC(@"DISABLE_AMBIENT_FULLSCREEN_DESC"), @"disableAmbientModeFullscreen_enabled"), + BASIC_SWITCH(LOC(@"FULLSCREEN_TO_THE_RIGHT"), LOC(@"FULLSCREEN_TO_THE_RIGHT_DESC"), @"fullscreenToTheRight_enabled"), + BASIC_SWITCH(LOC(@"SEEK_ANYWHERE"), LOC(@"SEEK_ANYWHERE_DESC"), @"seekAnywhere_enabled"), + BASIC_SWITCH(LOC(@"ENABLE_TAP_TO_SEEK"), LOC(@"ENABLE_TAP_TO_SEEK_DESC"), @"YTTapToSeek_enabled"), + BASIC_SWITCH(LOC(@"DISABLE_PULL_TO_FULLSCREEN_GESTURE"), LOC(@"DISABLE_PULL_TO_FULLSCREEN_GESTURE_DESC"), @"disablePullToFull_enabled"), + BASIC_SWITCH(LOC(@"ALWAYS_USE_REMAINING_TIME"), LOC(@"ALWAYS_USE_REMAINING_TIME_DESC"), @"alwaysShowRemainingTime_enabled"), + BASIC_SWITCH(LOC(@"DISABLE_TOGGLE_TIME_REMAINING"), LOC(@"DISABLE_TOGGLE_TIME_REMAINING_DESC"), @"disableRemainingTime_enabled"), + BASIC_SWITCH(LOC(@"DISABLE_ENGAGEMENT_OVERLAY"), LOC(@"DISABLE_ENGAGEMENT_OVERLAY_DESC"), @"disableEngagementOverlay_enabled"), + BASIC_SWITCH(LOC(@"HIDE_COMMENT_PREVIEWS_UNDER_PLAYER"), LOC(@"HIDE_COMMENT_PREVIEWS_UNDER_PLAYER_DESC"), @"hidePreviewCommentSection_enabled"), + BASIC_SWITCH(LOC(@"HIDE_AUTOPLAY_MINI_PREVIEW"), LOC(@"HIDE_AUTOPLAY_MINI_PREVIEW_DESC"), @"hideAutoplayMiniPreview_enabled"), + BASIC_SWITCH(LOC(@"HIDE_HUD_MESSAGES"), LOC(@"HIDE_HUD_MESSAGES_DESC"), @"hideHUD_enabled"), + BASIC_SWITCH(LOC(@"HIDE_COLLAPSE_BUTTON"), LOC(@"HIDE_COLLAPSE_BUTTON_DESC"), @"disableCollapseButton_enabled"), + BASIC_SWITCH(LOC(@"HIDE_SPEED_TOAST"), LOC(@"HIDE_SPEED_TOAST_DESC"), @"hideSpeedToast_enabled"), + ]; + YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"VIDEO_CONTROLS_OVERLAY_OPTIONS") pickerSectionTitle:nil rows:rows selectedItemIndex:NSNotFound parentResponder:[self parentResponder]]; + [settingsViewController pushViewController:picker]; + return YES; + }]; + [sectionItems addObject:videoControlOverlayGroup]; + +# pragma mark - App Settings Overlay Options + YTSettingsSectionItem *appSettingsOverlayGroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"APP_SETTINGS_OVERLAY_OPTIONS") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + NSArray *rows = @[ + BASIC_SWITCH(LOC(@"HIDE_ACCOUNT_SECTION"), LOC(@"APP_RESTART_DESC"), @"disableAccountSection_enabled"), + BASIC_SWITCH(LOC(@"HIDE_AUTOPLAY_SECTION"), LOC(@"APP_RESTART_DESC"), @"disableAutoplaySection_enabled"), + BASIC_SWITCH(LOC(@"HIDE_TRYNEWFEATURES_SECTION"), LOC(@"APP_RESTART_DESC"), @"disableTryNewFeaturesSection_enabled"), + BASIC_SWITCH(LOC(@"HIDE_VIDEOQUALITYPREFERENCES_SECTION"), LOC(@"APP_RESTART_DESC"), @"disableVideoQualityPreferencesSection_enabled"), + BASIC_SWITCH(LOC(@"HIDE_NOTIFICATIONS_SECTION"), LOC(@"APP_RESTART_DESC"), @"disableNotificationsSection_enabled"), + BASIC_SWITCH(LOC(@"HIDE_MANAGEALLHISTORY_SECTION"), LOC(@"APP_RESTART_DESC"), @"disableManageAllHistorySection_enabled"), + BASIC_SWITCH(LOC(@"HIDE_YOURDATAINYOUTUBE_SECTION"), LOC(@"APP_RESTART_DESC"), @"disableYourDataInYouTubeSection_enabled"), + BASIC_SWITCH(LOC(@"HIDE_PRIVACY_SECTION"), LOC(@"APP_RESTART_DESC"), @"disablePrivacySection_enabled"), + BASIC_SWITCH(LOC(@"HIDE_LIVECHAT_SECTION"), LOC(@"APP_RESTART_DESC"), @"disableLiveChatSection_enabled") + ]; + YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"APP_SETTINGS_OVERLAY_OPTIONS") pickerSectionTitle:nil rows:rows selectedItemIndex:NSNotFound parentResponder:[self parentResponder]]; + [settingsViewController pushViewController:picker]; + return YES; + }]; + [sectionItems addObject:appSettingsOverlayGroup]; + +# pragma mark - LowContrastMode + YTSettingsSectionItem *lowContrastModeSection = [YTSettingsSectionItemClass itemWithTitle:LOC(@"LOW_CONTRAST_MODE") + accessibilityIdentifier:nil + detailTextBlock:^NSString *() { + switch (contrastMode()) { + case 1: + return LOC(@"Hex Color"); + case 0: + default: + return LOC(@"Default"); + } + } + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + NSArray *rows = @[ + [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"Default") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:@"lcm"]; + [settingsViewController reloadData]; + return YES; + }], + [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"Hex Color") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:@"lcm"]; + [settingsViewController reloadData]; + return YES; + }] + ]; + YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"LOW_CONTRAST_MODE") pickerSectionTitle:nil rows:rows selectedItemIndex:contrastMode() parentResponder:[self parentResponder]]; + [settingsViewController pushViewController:picker]; + return YES; + }]; + +# pragma mark - VersionSpooferLite + YTSettingsSectionItem *versionSpooferSection = [YTSettingsSectionItemClass itemWithTitle:LOC(@"VERSION_SPOOFER_TITLE") + accessibilityIdentifier:nil + detailTextBlock:^NSString *() { + switch (appVersionSpoofer()) { + case 1: + return @"v18.34.5 (Enable Library Tab)"; + case 2: + return @"v18.33.3 (Removes Playables)"; + case 3: + return @"v18.18.2 (Fixes YTClassicVideoQuality & YTSpeed)"; + case 4: + return @"v18.01.2 (First v18 Version)"; + case 5: + return @"v17.49.6 (Removes Rounded Miniplayer)"; + case 6: + return @"v17.38.10 (Fixes LowContrastMode)"; + case 7: + return @"v17.33.2 (Oldest Supported Version)"; + case 0: + default: + return @"v18.49.3 (Last v18 Version)"; + } + } + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + NSArray *rows = @[ + [YTSettingsSectionItemClass checkmarkItemWithTitle:@"v18.49.3 (Last v18 Version)" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:@"versionSpoofer"]; + [settingsViewController reloadData]; + return YES; + }], + [YTSettingsSectionItemClass checkmarkItemWithTitle:@"v18.34.5 (Enable Library Tab)" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:@"versionSpoofer"]; + [settingsViewController reloadData]; + return YES; + }], + [YTSettingsSectionItemClass checkmarkItemWithTitle:@"v18.33.3 (Removes Playables)" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:2 forKey:@"versionSpoofer"]; + [settingsViewController reloadData]; + return YES; + }], + [YTSettingsSectionItemClass checkmarkItemWithTitle:@"v18.18.2 (Fixes YTClassicVideoQuality & YTSpeed)" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:3 forKey:@"versionSpoofer"]; + [settingsViewController reloadData]; + return YES; + }], + [YTSettingsSectionItemClass checkmarkItemWithTitle:@"v18.01.2 (First v18 Version)" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:4 forKey:@"versionSpoofer"]; + [settingsViewController reloadData]; + return YES; + }], + [YTSettingsSectionItemClass checkmarkItemWithTitle:@"v17.49.6 (Removes Rounded Miniplayer)" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:5 forKey:@"versionSpoofer"]; + [settingsViewController reloadData]; + return YES; + }], + [YTSettingsSectionItemClass checkmarkItemWithTitle:@"v17.38.10 (Fixes LowContrastMode)" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:6 forKey:@"versionSpoofer"]; + [settingsViewController reloadData]; + return YES; + }], + [YTSettingsSectionItemClass checkmarkItemWithTitle:@"v17.33.2 (Oldest Supported Version)" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:7 forKey:@"versionSpoofer"]; + [settingsViewController reloadData]; + return YES; + }] + ]; + YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:@"VERSION_SPOOFER_TITLE" pickerSectionTitle:nil rows:rows selectedItemIndex:appVersionSpoofer() parentResponder:[self parentResponder]]; + [settingsViewController pushViewController:picker]; + return YES; + }]; + +# pragma mark - Theme + YTSettingsSectionItem *themeGroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"THEME_OPTIONS") + accessibilityIdentifier:nil + detailTextBlock:^NSString *() { + switch (GetInteger(@"appTheme")) { + case 1: + return LOC(@"OLD_DARK_THEME"); + case 0: + default: + return LOC(@"DEFAULT_THEME"); + } + } + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + NSArray *rows = @[ + [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"DEFAULT_THEME") titleDescription:LOC(@"DEFAULT_THEME_DESC") selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:@"appTheme"]; + [settingsViewController reloadData]; + return YES; + }], + [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"OLD_DARK_THEME") titleDescription:LOC(@"OLD_DARK_THEME_DESC") selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:@"appTheme"]; + [settingsViewController reloadData]; + return YES; + }], + BASIC_SWITCH(LOC(@"OLED_KEYBOARD"), LOC(@"OLED_KEYBOARD_DESC"), @"oledKeyBoard_enabled"), + BASIC_SWITCH(LOC(@"LOW_CONTRAST_MODE"), LOC(@"LOW_CONTRAST_MODE_DESC"), @"lowContrastMode_enabled"), + lowContrastModeSection + ]; + YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"THEME_OPTIONS") pickerSectionTitle:nil rows:rows selectedItemIndex:GetInteger(@"appTheme") parentResponder:[self parentResponder]]; + [settingsViewController pushViewController:picker]; + return YES; + }]; + [sectionItems addObject:themeGroup]; + +# pragma mark - Copy of Playback in feeds section - @bhackel + // This section is hidden in vanilla YouTube when using the new settings UI, so + // we can recreate it here + YTSettingsSectionItem *playbackInFeedsGroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"PLAYBACK_IN_FEEDS") + accessibilityIdentifier:nil + detailTextBlock:^NSString *() { + // The specific values were gathered by checking the value for each setting + switch (GetInteger(@"inline_muted_playback_enabled")) { + case 3: + return LOC(@"PLAYBACK_IN_FEEDS_WIFI_ONLY"); + case 1: + return LOC(@"PLAYBACK_IN_FEEDS_OFF"); + case 2: + default: + return LOC(@"PLAYBACK_IN_FEEDS_ALWAYS_ON"); + } + } + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + NSArray *rows = @[ + [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"PLAYBACK_IN_FEEDS_OFF") selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:@"inline_muted_playback_enabled"]; + [settingsViewController reloadData]; + return YES; + }], + [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"PLAYBACK_IN_FEEDS_ALWAYS_ON") selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:2 forKey:@"inline_muted_playback_enabled"]; + [settingsViewController reloadData]; + return YES; + }], + [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"PLAYBACK_IN_FEEDS_WIFI_ONLY") selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [[NSUserDefaults standardUserDefaults] setInteger:3 forKey:@"inline_muted_playback_enabled"]; + [settingsViewController reloadData]; + return YES; + }], + ]; + // It seems values greater than 3 act the same as Always On (Index 1) + // Convert the stored value to an index for a picker (0 to 2) + NSInteger (^getInlineSelection)(void) = ^NSInteger(void) { + NSInteger selection = GetInteger(@"inline_muted_playback_enabled") - 1; + // Check if selection is within the valid bounds [0, 1, 2] + if (selection < 0 || selection > 2) { + return 1; + } + return selection; + }; + YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"PLAYBACK_IN_FEEDS") pickerSectionTitle:nil rows:rows selectedItemIndex:getInlineSelection() parentResponder:[self parentResponder]]; + [settingsViewController pushViewController:picker]; + return YES; + } + ]; + +# pragma mark - Miscellaneous + YTSettingsSectionItem *miscellaneousGroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"MISCELLANEOUS") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + NSArray *rows = @[ + playbackInFeedsGroup, + // BASIC_SWITCH(LOC(@"NEW_SETTINGS_UI"), LOC(@"NEW_SETTINGS_UI_DESC"), @"newSettingsUI_enabled"), // disabled because YTLite is probably forcing it to NO + BASIC_SWITCH(LOC(@"ENABLE_YT_STARTUP_ANIMATION"), LOC(@"ENABLE_YT_STARTUP_ANIMATION_DESC"), @"ytStartupAnimation_enabled"), + BASIC_SWITCH(LOC(@"HIDE_MODERN_INTERFACE"), LOC(@"HIDE_MODERN_INTERFACE_DESC"), @"ytNoModernUI_enabled"), + BASIC_SWITCH(LOC(@"IPAD_LAYOUT"), LOC(@"IPAD_LAYOUT_DESC"), @"iPadLayout_enabled"), + BASIC_SWITCH(LOC(@"IPHONE_LAYOUT"), LOC(@"IPHONE_LAYOUT_DESC"), @"iPhoneLayout_enabled"), + BASIC_SWITCH(LOC(@"CAST_CONFIRM"), LOC(@"CAST_CONFIRM_DESC"), @"castConfirm_enabled"), + BASIC_SWITCH(LOC(@"NEW_MINIPLAYER_STYLE"), LOC(@"NEW_MINIPLAYER_STYLE_DESC"), @"bigYTMiniPlayer_enabled"), + BASIC_SWITCH(LOC(@"HIDE_CAST_BUTTON"), LOC(@"HIDE_CAST_BUTTON_DESC"), @"hideCastButton_enabled"), + BASIC_SWITCH(LOC(@"VIDEO_PLAYER_BUTTON"), LOC(@"VIDEO_PLAYER_BUTTON_DESC"), @"videoPlayerButton_enabled"), + BASIC_SWITCH(LOC(@"HIDE_HOME_TAB"), LOC(@"HIDE_HOME_TAB_DESC"), @"hideHomeTab_enabled"), + BASIC_SWITCH(LOC(@"FIX_CASTING"), LOC(@"FIX_CASTING_DESC"), @"fixCasting_enabled"), + BASIC_SWITCH(LOC(@"REPLACE_COPY_AND_PASTE_BUTTONS"), LOC(@"REPLACE_COPY_AND_PASTE_BUTTONS_DESC"), @"switchCopyandPasteFunctionality_enabled"), + BASIC_SWITCH(LOC(@"ENABLE_FLEX"), LOC(@"ENABLE_FLEX_DESC"), @"flex_enabled"), + BASIC_SWITCH(LOC(@"APP_VERSION_SPOOFER_LITE"), LOC(@"APP_VERSION_SPOOFER_LITE_DESC"), @"enableVersionSpoofer_enabled"), + versionSpooferSection + ]; + YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"MISCELLANEOUS") pickerSectionTitle:nil rows:rows selectedItemIndex:NSNotFound parentResponder:[self parentResponder]]; + [settingsViewController pushViewController:picker]; + return YES; + }]; + [sectionItems addObject:miscellaneousGroup]; + + if ([settingsViewController respondsToSelector:@selector(setSectionItems:forCategory:title:icon:titleDescription:headerHidden:)]) + [settingsViewController setSectionItems:sectionItems forCategory:YTLitePlusSection title:@"YTLitePlus" icon:nil titleDescription:LOC(@"TITLE DESCRIPTION") headerHidden:YES]; + else + [settingsViewController setSectionItems:sectionItems forCategory:YTLitePlusSection title:@"YTLitePlus" titleDescription:LOC(@"TITLE DESCRIPTION") headerHidden:YES];} + +- (void)updateSectionForCategory:(NSUInteger)category withEntry:(id)entry { + if (category == YTLitePlusSection) { + [self updateYTLitePlusSectionWithEntry:entry]; + return; + } + %orig; +} + +// Implement the delegate method for document picker +%new +- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { + if (urls.count > 0) { + NSURL *pickedURL = [urls firstObject]; + NSError *error; + // Check which mode the document picker is in + if (controller.documentPickerMode == UIDocumentPickerModeImport) { + // Import mode: Handle the import of settings from a text file + NSString *fileType = [pickedURL resourceValuesForKeys:@[NSURLTypeIdentifierKey] error:&error][NSURLTypeIdentifierKey]; + + UTType *utType = [UTType typeWithIdentifier:fileType]; + if ([utType conformsToType:UTTypePlainText]) { + NSString *fileContents = [NSString stringWithContentsOfURL:pickedURL encoding:NSUTF8StringEncoding error:nil]; + NSArray *lines = [fileContents componentsSeparatedByString:@"\n"]; + for (NSString *line in lines) { + NSArray *components = [line componentsSeparatedByString:@": "]; + if (components.count == 2) { + NSString *key = components[0]; + NSString *value = components[1]; + [[NSUserDefaults standardUserDefaults] setObject:value forKey:key]; + } + } + // Reload settings view after importing + YTSettingsViewController *settingsViewController = [self valueForKey:@"_settingsViewControllerDelegate"]; + [settingsViewController reloadData]; + // Show a confirmation message or perform some other action here + [[%c(GOOHUDManagerInternal) sharedInstance] showMessageMainThread:[%c(YTHUDMessage) messageWithText:@"Settings applied"]]; + // Show a reminder to import YouTube Plus settings as well + UIAlertController *reminderAlert = [UIAlertController alertControllerWithTitle:@"Reminder" + message:@"Remember to import your YouTube Plus settings as well" + preferredStyle:UIAlertControllerStyleAlert]; + [reminderAlert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]]; + [settingsViewController presentViewController:reminderAlert animated:YES completion:nil]; + } + + } else if (controller.documentPickerMode == UIDocumentPickerModeExportToService || controller.documentPickerMode == UIDocumentPickerModeMoveToService) { + [[%c(GOOHUDManagerInternal) sharedInstance] showMessageMainThread:[%c(YTHUDMessage) messageWithText:@"Settings saved"]]; + // Export mode: Display a reminder to save YouTube Plus settings + UIAlertController *exportAlert = [UIAlertController alertControllerWithTitle:@"Export Settings" + message:@"Note: This feature cannot save most YouTube settings.\n\nWould you like to also export your YouTube Plus Settings?" + preferredStyle:UIAlertControllerStyleAlert]; + [exportAlert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; + [exportAlert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + // Export YouTube Plus Settings functionality + [%c(YTLUserDefaults) exportYtlSettings]; + }]]; + YTSettingsViewController *settingsViewController = [self valueForKey:@"_settingsViewControllerDelegate"]; + // Present the alert from the root view controller + [settingsViewController presentViewController:exportAlert animated:YES completion:nil]; + } + } +} + +%end + diff --git a/Source/SettingsKeys.h b/Source/SettingsKeys.h new file mode 100644 index 0000000..d38802b --- /dev/null +++ b/Source/SettingsKeys.h @@ -0,0 +1,52 @@ +#import "../YTLitePlus.h" + +// Keys for "Copy Settings" button (for: YTLitePlus) +// In alphabetical order for tweaks after YTLitePlus +NSArray *NSUserDefaultsCopyKeys = @[ + // YTLitePlus - gathered using get_keys.py + @"YTTapToSeek_enabled", @"alwaysShowRemainingTime_enabled", @"bigYTMiniPlayer_enabled", @"castConfirm_enabled", + @"disableAccountSection_enabled", @"disableAmbientModeFullscreen_enabled", + @"disableAmbientModePortrait_enabled", @"disableAutoplaySection_enabled", @"disableCollapseButton_enabled", + @"disableEngagementOverlay_enabled", @"disableLiveChatSection_enabled", + @"disableManageAllHistorySection_enabled", @"disableNotificationsSection_enabled", + @"disablePrivacySection_enabled", @"disablePullToFull_enabled", @"disableRemainingTime_enabled", + @"disableTryNewFeaturesSection_enabled", @"disableVideoQualityPreferencesSection_enabled", + @"disableYourDataInYouTubeSection_enabled", @"enableSaveToButton_enabled", @"enableShareButton_enabled", + @"enableVersionSpoofer_enabled", @"fixCasting_enabled", @"flex_enabled", @"fullscreenToTheRight_enabled", + @"hideAutoplayMiniPreview_enabled", @"hideCastButton_enabled", @"hideHUD_enabled", @"hideHeatwaves_enabled", + @"hideHomeTab_enabled", @"hidePreviewCommentSection_enabled", @"hideRightPanel_enabled", + @"hideSpeedToast_enabled", @"hideVideoPlayerShadowOverlayButtons_enabled", + @"iPadLayout_enabled", @"iPhoneLayout_enabled", @"inline_muted_playback_enabled", @"lowContrastMode_enabled", + @"newSettingsUI_enabled", @"oledKeyBoard_enabled", @"playerGesturesHapticFeedback_enabled", + @"playerGestures_enabled", @"seekAnywhere_enabled", @"switchCopyandPasteFunctionality_enabled", + @"videoPlayerButton_enabled", @"ytNoModernUI_enabled", @"ytStartupAnimation_enabled", + // DEMC - https://github.com/therealFoxster/DontEatMyContent/blob/master/Tweak.h + @"DEMC_enabled", @"DEMC_colorViewsEnabled", @"DEMC_safeAreaConstant", @"DEMC_disableAmbientMode", + @"DEMC_limitZoomToFill", @"DEMC_enableForAllVideos", + // iSponsorBlock has been removed in v5 - YTLite has built-in SponsorBlock + // Return-YouTube-Dislike - https://github.com/PoomSmart/Return-YouTube-Dislikes/blob/main/TweakSettings.h + @"RYD-ENABLED", @"RYD-VOTE-SUBMISSION", @"RYD-EXACT-LIKE-NUMBER", @"RYD-EXACT-NUMBER", + // YTVideoOverlay Tweaks - https://github.com/PoomSmart/YTVideoOverlay/blob/0fc6d29d1aa9e75f8c13d675daec9365f753d45e/Tweak.x#L28C1-L41C84 + @"YTVideoOverlay-YouTimeStamp-Enabled", @"YTVideoOverlay-YouTimeStamp-Position", + // YouPiP - https://github.com/PoomSmart/YouPiP/blob/main/Header.h + @"YouPiPPosition", @"CompatibilityModeKey", @"PiPActivationMethodKey", @"PiPActivationMethod2Key", + @"NoMiniPlayerPiPKey", @"NonBackgroundableKey", + // YTABConfig cannot be reasonably exported using this method + // YouTube Plus / YTLite cannot be exported using this method + // YTUHD - https://github.com/PoomSmart/YTUHD/blob/master/Header.h + @"EnableVP9", @"AllVP9", + // Useful YouTube Keys + @"inline_muted_playback_enabled", +]; + + +// Some default values to ignore when exporting settings +NSDictionary *NSUserDefaultsCopyKeysDefaults = @{ + @"fixCasting_enabled": @1, + @"inline_muted_playback_enabled": @5, + @"newSettingsUI_enabled": @1, + @"DEMC_safeAreaConstant": @21.5, + @"RYD-ENABLED": @1, + @"RYD-VOTE-SUBMISSION": @1, + // Duplicate keys are not allowed in NSDictionary. If present, only the last one will be kept. +}; \ No newline at end of file diff --git a/Source/Themes.xm b/Source/Themes.xm new file mode 100644 index 0000000..94cb879 --- /dev/null +++ b/Source/Themes.xm @@ -0,0 +1,822 @@ +#import "../YTLitePlus.h" + +static BOOL isDarkMode() { + return ([[NSUserDefaults standardUserDefaults] integerForKey:@"page_style"] == 1); +} +static BOOL oldDarkTheme() { + return ([[NSUserDefaults standardUserDefaults] integerForKey:@"appTheme"] == 1); +} + +// Themes.xm - Theme Options +// Old dark theme (gray) +%group gOldDarkTheme +UIColor *customColor = [UIColor colorWithRed:0.129 green:0.129 blue:0.129 alpha:1.0]; +%hook YTCommonColorPalette +- (UIColor *)background1 { + return self.pageStyle == 1 ? customColor : %orig; +} +- (UIColor *)background2 { + return self.pageStyle == 1 ? customColor : %orig; +} +- (UIColor *)background3 { + return self.pageStyle == 1 ? customColor : %orig; +} +- (UIColor *)brandBackgroundSolid { + return self.pageStyle == 1 ? customColor : %orig; +} +- (UIColor *)brandBackgroundPrimary { + return self.pageStyle == 1 ? customColor : %orig; +} +- (UIColor *)brandBackgroundSecondary { + return self.pageStyle == 1 ? [customColor colorWithAlphaComponent:0.9] : %orig; +} +- (UIColor *)raisedBackground { + return self.pageStyle == 1 ? customColor : %orig; +} +- (UIColor *)staticBrandBlack { + return self.pageStyle == 1 ? customColor : %orig; +} +- (UIColor *)generalBackgroundA { + return self.pageStyle == 1 ? customColor : %orig; +} +- (UIColor *)generalBackgroundB { + return self.pageStyle == 1 ? customColor : %orig; +} +- (UIColor *)baseBackground { + return self.pageStyle == 1 ? customColor : %orig; +} +- (UIColor *)menuBackground { + return self.pageStyle == 1 ? customColor : %orig; +} +%end +%hook YTAsyncCollectionView +- (void)setBackgroundColor:(UIColor *)color { + if ([self.nextResponder isKindOfClass:NSClassFromString(@"YTRelatedVideosCollectionViewController")]) { + color = [UIColor clearColor]; + } else if ([self.nextResponder isKindOfClass:NSClassFromString(@"YTFullscreenMetadataHighlightsCollectionViewController")]) { + color = [UIColor clearColor]; + } else { + return isDarkMode() ? %orig(customColor) : %orig; + } + %orig; +} +- (UIColor *)darkBackgroundColor { + return isDarkMode() ? customColor : %orig; +} +- (void)setDarkBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +- (void)layoutSubviews { + %orig(); + if ([self.nextResponder isKindOfClass:NSClassFromString(@"YTWatchNextResultsViewController")]) { + if (isDarkMode()) { + self.subviews[0].subviews[0].backgroundColor = customColor; + } + } +} +%end + +// Hide separators +%hook YTCollectionSeparatorView +- (void)setHidden:(BOOL)arg1 { + %orig(YES); +} +%end + +%hook ASScrollView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end + +%hook YTPivotBarView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTHeaderView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTSubheaderContainerView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTAppView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTCollectionView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTChannelListSubMenuView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTSettingsCell +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTSlideForActionsView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTPageView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTWatchView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTPlaylistMiniBarView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTEngagementPanelView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTEngagementPanelHeaderView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTPlaylistPanelControlsView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTHorizontalCardListView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTWatchMiniBarView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTCreateCommentAccessoryView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTCreateCommentTextView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTSearchView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTSearchBoxView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTTabTitlesView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTPrivacyTosFooterView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTOfflineStorageUsageView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTInlineSignInView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTFeedChannelFilterHeaderView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YCHLiveChatView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YCHLiveChatActionPanelView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTEmojiTextView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTTopAlignedView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +- (void)layoutSubviews { + %orig(); + if (isDarkMode()) { + MSHookIvar(self, "_contentView").backgroundColor = customColor; + } +} +%end +%hook GOODialogView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTNavigationBar +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +- (void)setBarTintColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTChannelMobileHeaderView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTChannelSubMenuView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTWrapperSplitView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTReelShelfCell +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTReelShelfItemView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTReelShelfView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTCommentView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTChannelListSubMenuAvatarView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTSearchBarView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTDialogContainerScrollView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTShareTitleView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTShareBusyView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTELMView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTActionSheetHeaderView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig(customColor) : %orig; +} +%end +%hook YTShareMainView +- (void)layoutSubviews { + %orig(); + if (isDarkMode()) { + MSHookIvar(self, "_cancelButton").backgroundColor = customColor; + MSHookIvar(self, "_safeArea").backgroundColor = customColor; + } +} +%end +%hook _ASDisplayView +- (void)layoutSubviews { + %orig; + if (isDarkMode()) { + UIResponder *responder = [self nextResponder]; + while (responder != nil) { + if ([responder isKindOfClass:NSClassFromString(@"YTActionSheetDialogViewController")]) { + self.backgroundColor = customColor; + } + if ([responder isKindOfClass:NSClassFromString(@"YTPanelLoadingStrategyViewController")]) { + self.backgroundColor = customColor; + } + if ([responder isKindOfClass:NSClassFromString(@"YTTabHeaderElementsViewController")]) { + self.backgroundColor = customColor; + } + if ([responder isKindOfClass:NSClassFromString(@"YTEditSheetControllerElementsContentViewController")]) { + self.backgroundColor = customColor; + } + responder = [responder nextResponder]; + } + } +} +- (void)didMoveToWindow { + %orig; + if (isDarkMode()) { + if ([self.nextResponder isKindOfClass:%c(ASScrollView)]) { self.backgroundColor = [UIColor clearColor]; } + if ([self.accessibilityIdentifier isEqualToString:@"eml.cvr"]) { self.backgroundColor = customColor; } + if ([self.accessibilityIdentifier isEqualToString:@"rich_header"]) { self.backgroundColor = customColor; } + if ([self.accessibilityIdentifier isEqualToString:@"id.ui.comment_cell"]) { self.backgroundColor = customColor; } + if ([self.accessibilityIdentifier isEqualToString:@"id.ui.cancel.button"]) { self.superview.backgroundColor = [UIColor clearColor]; } + if ([self.accessibilityIdentifier isEqualToString:@"id.elements.components.comment_composer"]) { self.backgroundColor = customColor; } + if ([self.accessibilityIdentifier isEqualToString:@"id.elements.components.filter_chip_bar"]) { self.backgroundColor = customColor; } + if ([self.accessibilityIdentifier isEqualToString:@"id.elements.components.video_list_entry"]) { self.backgroundColor = customColor; } + if ([self.accessibilityIdentifier isEqualToString:@"id.comment.guidelines_text"]) { self.superview.backgroundColor = customColor; } + if ([self.accessibilityIdentifier isEqualToString:@"id.comment.channel_guidelines_bottom_sheet_container"]) { self.backgroundColor = customColor; } + if ([self.accessibilityIdentifier isEqualToString:@"id.comment.channel_guidelines_entry_banner_container"]) { self.backgroundColor = customColor; } + if ([self.accessibilityIdentifier isEqualToString:@"id.comment.comment_group_detail_container"]) { self.backgroundColor = [UIColor clearColor]; } + } +} +%end +%hook YTCinematicContainerView +- (void)setHidden:(BOOL)arg1 { + %orig(YES); +} +%end +%end + +// OLED dark mode by @BandarHL and modified by @arichorn +/* +%group gOLED +%hook YTCommonColorPalette +- (UIColor *)background1 { + return self.pageStyle == 1 ? [UIColor blackColor] : %orig; +} +- (UIColor *)background2 { + return self.pageStyle == 1 ? [UIColor blackColor] : %orig; +} +- (UIColor *)background3 { + return self.pageStyle == 1 ? [UIColor blackColor] : %orig; +} +- (UIColor *)brandBackgroundSolid { + return self.pageStyle == 1 ? [UIColor blackColor] : %orig; +} +- (UIColor *)brandBackgroundPrimary { + return self.pageStyle == 1 ? [UIColor blackColor] : %orig; +} +- (UIColor *)brandBackgroundSecondary { + return self.pageStyle == 1 ? [[UIColor blackColor] colorWithAlphaComponent:0.9] : %orig; +} +- (UIColor *)raisedBackground { + return self.pageStyle == 1 ? [UIColor blackColor] : %orig; +} +- (UIColor *)staticBrandBlack { + return self.pageStyle == 1 ? [UIColor blackColor] : %orig; +} +- (UIColor *)generalBackgroundA { + return self.pageStyle == 1 ? [UIColor blackColor] : %orig; +} +- (UIColor *)generalBackgroundB { + return self.pageStyle == 1 ? [UIColor blackColor] : %orig; +} +- (UIColor *)baseBackground { + return self.pageStyle == 1 ? [UIColor blackColor] : %orig; +} +- (UIColor *)menuBackground { + return self.pageStyle == 1 ? [UIColor blackColor] : %orig; +} +%end +%hook SponsorBlockSettingsController +- (void)viewDidLoad { + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + %orig; + self.tableView.backgroundColor = [UIColor blackColor]; + } else { return %orig; } +} +%end +%hook SponsorBlockViewController +- (void)viewDidLoad { + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + %orig; + self.view.backgroundColor = [UIColor blackColor]; + } else { return %orig; } +} +%end +%hook YTAsyncCollectionView +- (void)setBackgroundColor:(UIColor *)color { + if ([self.nextResponder isKindOfClass:NSClassFromString(@"YTRelatedVideosCollectionViewController")]) { + color = [UIColor clearColor]; + } else if ([self.nextResponder isKindOfClass:NSClassFromString(@"YTFullscreenMetadataHighlightsCollectionViewController")]) { + color = [UIColor clearColor]; + } else { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; + } + %orig; +} +- (UIColor *)darkBackgroundColor { + return isDarkMode() ? [UIColor blackColor] : %orig; +} +- (void)setDarkBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +- (void)layoutSubviews { + %orig(); + if ([self.nextResponder isKindOfClass:NSClassFromString(@"YTWatchNextResultsViewController")]) { + if (isDarkMode()) { + self.subviews[0].subviews[0].backgroundColor = [UIColor blackColor]; + } + } +} +%end + +// Hide separators +%hook YTCollectionSeparatorView +- (void)setHidden:(BOOL)arg1 { + %orig(YES); +} +%end + +%hook YTPivotBarView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end + +%hook ASScrollView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end + +%hook YTHeaderView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTSubheaderContainerView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTAppView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTCollectionView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTChannelListSubMenuView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTSettingsCell +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTSlideForActionsView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTPageView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTWatchView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTPlaylistMiniBarView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTEngagementPanelView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTEngagementPanelHeaderView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTPlaylistPanelControlsView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTHorizontalCardListView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTWatchMiniBarView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTCreateCommentAccessoryView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTCreateCommentTextView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTSearchView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTSearchBoxView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTTabTitlesView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTPrivacyTosFooterView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTOfflineStorageUsageView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTInlineSignInView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTFeedChannelFilterHeaderView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YCHLiveChatView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YCHLiveChatActionPanelView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTEmojiTextView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTTopAlignedView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +- (void)layoutSubviews { + %orig(); + if (isDarkMode()) { + MSHookIvar(self, "_contentView").backgroundColor = [UIColor blackColor]; + } +} +%end +%hook GOODialogView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTNavigationBar +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +- (void)setBarTintColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTChannelMobileHeaderView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTChannelSubMenuView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTWrapperSplitView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTReelShelfCell +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTReelShelfItemView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTReelShelfView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTCommentView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTChannelListSubMenuAvatarView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTSearchBarView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTDialogContainerScrollView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTShareTitleView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTShareBusyView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTELMView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTActionSheetHeaderView +- (void)setBackgroundColor:(UIColor *)color { + return isDarkMode() ? %orig([UIColor blackColor]) : %orig; +} +%end +%hook YTShareMainView +- (void)layoutSubviews { + %orig(); + if (isDarkMode()) { + MSHookIvar(self, "_cancelButton").backgroundColor = [UIColor blackColor]; + MSHookIvar(self, "_safeArea").backgroundColor = [UIColor blackColor]; + } +} +%end +%hook _ASDisplayView +- (void)layoutSubviews { + %orig; + if (isDarkMode()) { + UIResponder *responder = [self nextResponder]; + while (responder != nil) { + if ([responder isKindOfClass:NSClassFromString(@"YTActionSheetDialogViewController")]) { + self.backgroundColor = [UIColor blackColor]; + } + if ([responder isKindOfClass:NSClassFromString(@"YTPanelLoadingStrategyViewController")]) { + self.backgroundColor = [UIColor blackColor]; + } + if ([responder isKindOfClass:NSClassFromString(@"YTTabHeaderElementsViewController")]) { + self.backgroundColor = [UIColor blackColor]; + } + if ([responder isKindOfClass:NSClassFromString(@"YTEditSheetControllerElementsContentViewController")]) { + self.backgroundColor = [UIColor blackColor]; + } + responder = [responder nextResponder]; + } + } +} +- (void)didMoveToWindow { + %orig; + if (isDarkMode()) { + if ([self.nextResponder isKindOfClass:%c(ASScrollView)]) { self.backgroundColor = [UIColor clearColor]; } + if ([self.accessibilityIdentifier isEqualToString:@"eml.cvr"]) { self.backgroundColor = [UIColor blackColor]; } + if ([self.accessibilityIdentifier isEqualToString:@"rich_header"]) { self.backgroundColor = [UIColor blackColor]; } + if ([self.accessibilityIdentifier isEqualToString:@"id.ui.comment_cell"]) { self.backgroundColor = [UIColor blackColor]; } + if ([self.accessibilityIdentifier isEqualToString:@"id.ui.cancel.button"]) { self.superview.backgroundColor = [UIColor clearColor]; } + if ([self.accessibilityIdentifier isEqualToString:@"id.elements.components.comment_composer"]) { self.backgroundColor = [UIColor blackColor]; } + if ([self.accessibilityIdentifier isEqualToString:@"id.elements.components.filter_chip_bar"]) { self.backgroundColor = [UIColor blackColor]; } + if ([self.accessibilityIdentifier isEqualToString:@"id.elements.components.video_list_entry"]) { self.backgroundColor = [UIColor blackColor]; } + if ([self.accessibilityIdentifier isEqualToString:@"id.comment.guidelines_text"]) { self.superview.backgroundColor = [UIColor blackColor]; } + if ([self.accessibilityIdentifier isEqualToString:@"id.comment.channel_guidelines_bottom_sheet_container"]) { self.backgroundColor = [UIColor blackColor]; } + if ([self.accessibilityIdentifier isEqualToString:@"id.comment.channel_guidelines_entry_banner_container"]) { self.backgroundColor = [UIColor blackColor]; } + if ([self.accessibilityIdentifier isEqualToString:@"id.comment.comment_group_detail_container"]) { self.backgroundColor = [UIColor clearColor]; } + } +} +%end +%hook YTCinematicContainerView +- (void)setHidden:(BOOL)arg1 { + %orig(YES); +} +%end +%end +*/ + +// OLED keyboard by @ichitaso <3 - http://gist.github.com/ichitaso/935100fd53a26f18a9060f7195a1be0e +%group gOLEDKB +%hook TUIEmojiSearchView +- (void)didMoveToWindow { + %orig; + self.backgroundColor = [UIColor blackColor]; +} +%end + +%hook UIPredictionViewController +- (void)loadView { + %orig; + [self.view setBackgroundColor:[UIColor blackColor]]; +} +%end + +%hook UICandidateViewController +- (void)loadView { + %orig; + [self.view setBackgroundColor:[UIColor blackColor]]; +} +%end + +%hook UIKeyboardDockView +- (void)didMoveToWindow { + %orig; + self.backgroundColor = [UIColor blackColor]; +} +%end + +%hook UIKeyboardLayoutStar +- (void)didMoveToWindow { + %orig; + self.backgroundColor = [UIColor blackColor]; +} +%end + +%hook UIKBRenderConfig // Prediction text color +- (void)setLightKeyboard:(BOOL)arg1 { %orig(NO); } +%end +%end + +# pragma mark - ctor +%ctor { + %init; + if (IsEnabled(@"oledKeyBoard_enabled")) { + %init(gOLEDKB); + } + if (oldDarkTheme()) { + %init(gOldDarkTheme); + } +} diff --git a/Source/VersionSpooferLite.xm b/Source/VersionSpooferLite.xm new file mode 100644 index 0000000..0d05e4b --- /dev/null +++ b/Source/VersionSpooferLite.xm @@ -0,0 +1,49 @@ +#import "../YTLitePlus.h" + +typedef struct { + int version; + NSString *appVersion; +} VersionMapping; + +static VersionMapping versionMappings[] = { + {0, @"19.49.7"}, // Last v19 App Version + {1, @"19.28.1"}, // New 2024 Thin Outline Icons + {2, @"19.26.5"}, // Restore 2020's Thin Outline Icons + {3, @"18.49.3"}, // Last v18 App Version + {4, @"18.35.4"}, // Oldest Supported App Version (v18) - this is a replacement of v17.33.2. + {5, @"18.34.5"}, // Brings back Library Tab - Deprecated/Unsupported + {6, @"18.33.3"}, // Removes Playables in Explore - Deprecated/Unsupported + {7, @"18.18.2"}, // Fixes YTClassicVideoQuality + YTSpeed - Deprecated/Unsupported + {8, @"18.01.2"}, // First v18 App Version - Deprecated/Unsupported + {9, @"17.49.6"}, // Last v17 App Version - Deprecated/Unsupported + {10, @"17.38.10"}, // Fixes LowContrastMode + No Rounded Thumbnails - Deprecated/Unsupported + {11, @"17.33.2"} // Oldest Supported App Version (v17) - Deprecated/Unsupported +}; + +static int appVersionSpoofer() { + return [[NSUserDefaults standardUserDefaults] integerForKey:@"versionSpoofer"]; +} + +static BOOL isVersionSpooferEnabled() { + return IS_ENABLED(@"enableVersionSpoofer_enabled"); +} + +static NSString* getAppVersionForSpoofedVersion(int spoofedVersion) { + for (int i = 0; i < sizeof(versionMappings) / sizeof(versionMappings[0]); i++) { + if (versionMappings[i].version == spoofedVersion) { + return versionMappings[i].appVersion; + } + } + return nil; +} + +%hook YTVersionUtils ++ (NSString *)appVersion { + if (!isVersionSpooferEnabled()) { + return %orig; + } + int spoofedVersion = appVersionSpoofer(); + NSString *appVersion = getAppVersionForSpoofedVersion(spoofedVersion); + return appVersion ? appVersion : %orig; +} +%end diff --git a/Source/get_keys.py b/Source/get_keys.py new file mode 100644 index 0000000..dd6f221 --- /dev/null +++ b/Source/get_keys.py @@ -0,0 +1,100 @@ +import re +import os + +def extract_values_from_file(file_path): + """ + Extracts keys that match the pattern @\"_enabled\" from the given file. + + Args: + file_path (str): The path to the file to be searched. + + Returns: + list: A list of matching keys found in the file. + """ + # Define the regex pattern to match the strings that resemble the given examples + pattern = r'@\"[a-zA-Z0-9_]+_enabled\"' + matches = [] + + try: + # Read the content of the file + with open(file_path, 'r') as file: + file_content = file.read() + + # Find all matches + matches = re.findall(pattern, file_content) + except Exception as e: + print(f"Error reading {file_path}: {e}") + + return matches + +def format_output(keys): + """ + Formats the keys with indentation and line breaks if the segment exceeds 120 characters (116 excluding indentation). + + Args: + keys (list): The list of keys to be formatted. + + Returns: + str: A formatted string with the keys. + """ + indent = " " * 4 + line_length_limit = 116 # Limit excluding indentation + current_line = indent + formatted_output = "" + + for key in keys: + # Check if adding the next key would exceed the line length limit + if len(current_line) + len(key) + 2 > line_length_limit: # +2 accounts for the comma and space + # Add the current line to the formatted output and start a new line + formatted_output += current_line.rstrip(", ") + ",\n" + current_line = indent # Start a new indented line + + # Add the key to the current line + current_line += key + ", " + + # Add the last line to the output + formatted_output += current_line.rstrip(", ") # Remove trailing comma and space from the final line + return formatted_output + +def find_and_extract_keys(): + """ + Recursively searches for .xm and .h files in the parent directory and extracts keys + that match the pattern @\"_enabled\". The matching keys are then printed + with indentation and line breaks if the line exceeds 120 characters. + Ignores SettingsKeys.h + + Usage: + 1. Place this script in the desired directory. + 2. Run the script with the command: python extract_keys.py + 3. The script will search for all .xm and .h files in the parent directory and + print any matching keys it finds. + + Note: + - The script searches the directory where it is located (the parent directory). + - It only looks for files with extensions .xm and .h. + """ + # Get the parent directory + parent_directory = os.path.dirname(os.path.abspath(__file__)) + + # Store the found keys + found_keys = set() # Use a set to automatically remove duplicates + + # Walk through the parent directory and find all .xm and .h files + for root, dirs, files in os.walk(parent_directory): + for file in files: + if file.endswith(('.xm', '.h')): + # Skip SettingsKeys.h + if file == "SettingsKeys.h": + continue + file_path = os.path.join(root, file) + found_keys.update(extract_values_from_file(file_path)) + + # Print the found keys with formatting + if found_keys: + sorted_keys = sorted(found_keys) + print(format_output(sorted_keys)) + else: + print("No keys found.") + +if __name__ == "__main__": + find_and_extract_keys() diff --git a/Tweaks/YouTubeHeader b/Tweaks/YouTubeHeader new file mode 160000 index 0000000..ec9473c --- /dev/null +++ b/Tweaks/YouTubeHeader @@ -0,0 +1 @@ +Subproject commit ec9473cd9008d180f939b5f7df1cfbabf38386f3 diff --git a/YTLitePlus.h b/YTLitePlus.h new file mode 100644 index 0000000..1bd03c9 --- /dev/null +++ b/YTLitePlus.h @@ -0,0 +1,309 @@ +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import // For AVPlayer and AVPlayerViewController +#import // For kUTTypeMovie and kUTTypeVideo +#import + +#import "Tweaks/YouTubeHeader/YTAppDelegate.h" +#import "Tweaks/YouTubeHeader/YTPlayerViewController.h" +#import "Tweaks/YouTubeHeader/YTQTMButton.h" +#import "Tweaks/YouTubeHeader/YTVideoQualitySwitchOriginalController.h" +#import "Tweaks/YouTubeHeader/YTPlayerViewController.h" +#import "Tweaks/YouTubeHeader/YTWatchController.h" +#import "Tweaks/YouTubeHeader/YTIGuideResponse.h" +#import "Tweaks/YouTubeHeader/YTIGuideResponseSupportedRenderers.h" +#import "Tweaks/YouTubeHeader/YTIPivotBarSupportedRenderers.h" +#import "Tweaks/YouTubeHeader/YTIPivotBarRenderer.h" +#import "Tweaks/YouTubeHeader/YTIBrowseRequest.h" +#import "Tweaks/YouTubeHeader/YTCommonColorPalette.h" +#import "Tweaks/YouTubeHeader/YTSettingsSectionItemManager.h" +#import "Tweaks/YouTubeHeader/ASCollectionView.h" +#import "Tweaks/YouTubeHeader/YTPlayerOverlay.h" +#import "Tweaks/YouTubeHeader/YTPlayerOverlayProvider.h" +#import "Tweaks/YouTubeHeader/YTReelWatchPlaybackOverlayView.h" +#import "Tweaks/YouTubeHeader/YTReelPlayerBottomButton.h" +#import "Tweaks/YouTubeHeader/YTReelPlayerViewController.h" +#import "Tweaks/YouTubeHeader/YTAlertView.h" +#import "Tweaks/YouTubeHeader/YTISectionListRenderer.h" +#import "Tweaks/YouTubeHeader/YTPivotBarItemView.h" +#import "Tweaks/YouTubeHeader/YTVideoWithContextNode.h" +#import "Tweaks/YouTubeHeader/ELMCellNode.h" +#import "Tweaks/YouTubeHeader/ELMNodeController.h" +#import "Tweaks/YouTubeHeader/YTMainAppVideoPlayerOverlayViewController.h" +#import "Tweaks/YouTubeHeader/YTInlinePlayerBarContainerView.h" +#import "Tweaks/YouTubeHeader/YTWatchViewController.h" +#import "Tweaks/YouTubeHeader/YTWatchPullToFullController.h" +#import "Tweaks/YouTubeHeader/YTPlayerBarController.h" +#import "Tweaks/YouTubeHeader/YTResponder.h" +#import "Tweaks/YouTubeHeader/YTMainAppControlsOverlayView.h" +#import "Tweaks/YouTubeHeader/YTMultiSizeViewController.h" +#import "Tweaks/YouTubeHeader/YTWatchLayerViewController.h" +#import "Tweaks/YouTubeHeader/YTPageStyleController.h" +#import "Tweaks/YouTubeHeader/YTRightNavigationButtons.h" +#import "Tweaks/YouTubeHeader/YTInlinePlayerBarView.h" + +#define LOC(x) [tweakBundle localizedStringForKey:x value:nil table:nil] +#define YT_BUNDLE_ID @"com.google.ios.youtube" +#define YT_NAME @"YouTube" +#define LOWCONTRASTMODE_CUTOFF_VERSION @"17.38.10" +#define IS_ENABLED(k) [[NSUserDefaults standardUserDefaults] boolForKey:k] +#define APP_THEME_IDX [[NSUserDefaults standardUserDefaults] integerForKey:@"appTheme"] + +// Avoid issues with multiple includes of this file +#pragma once + +// Helper methods for key retrieval +#define IsEnabled(key) [[NSUserDefaults standardUserDefaults] boolForKey:key] +#define GetInteger(key) [[NSUserDefaults standardUserDefaults] integerForKey:key] // NSInteger type +#define GetFloat(key) [[NSUserDefaults standardUserDefaults] floatForKey:key] // float type + + +// Player Gesture selected mode enum +typedef NS_ENUM(NSUInteger, GestureMode) { + GestureModeVolume, + GestureModeBrightness, + GestureModeSeek, + GestureModeDisabled +}; +// Gesture Section Enum +typedef NS_ENUM(NSUInteger, GestureSection) { + GestureSectionTop, + GestureSectionMiddle, + GestureSectionBottom, + GestureSectionInvalid +}; + +// YTSpeed +@interface YTVarispeedSwitchControllerOption : NSObject +- (id)initWithTitle:(id)title rate:(float)rate; +@end + +@interface MLHAMQueuePlayer : NSObject +@property id playerEventCenter; +@property id delegate; +- (void)setRate:(float)rate; +- (void)internalSetRate; +@end + +@interface HAMPlayerInternal : NSObject +- (void)setRate:(float)rate; +@end + +@interface SSOConfiguration : NSObject +@end + +// YTLitePlus +@interface YTChipCloudCell : UIView +@end + +@interface YTPlayabilityResolutionUserActionUIController : NSObject // Skips content warning before playing *some videos - @PoomSmart +- (void)confirmAlertDidPressConfirm; +@end + +@interface YTTransportControlsButtonView : UIView +@end + +@interface _ASCollectionViewCell : UICollectionViewCell +- (id)node; +@end + +@interface YTAsyncCollectionView : UICollectionView +- (void)removeShortsAndFeaturesAdsAtIndexPath:(NSIndexPath *)indexPath; +@end + +@interface YTPlaybackButton : UIControl +@end + +@interface YTSegmentableInlinePlayerBarView +@property (nonatomic, assign, readwrite) BOOL enableSnapToChapter; +@end + +// HelperVC - @bhackel +@interface HelperVC : UIViewController +@end + +// Hide Autoplay Mini Preview - @bhackel +@interface YTAutonavPreviewView : UIView +@end + +// OLED Live Chat - @bhackel +@interface YTLUserDefaults : NSUserDefaults ++ (void)exportYtlSettings; +@end + +// Hide Home Tab - @bhackel +@interface YTPivotBarViewController : UIViewController +@property NSString *selectedPivotIdentifier; +@property YTIPivotBarRenderer *renderer; +- (void)selectItemWithPivotIdentifier:(NSString *)pivotIdentifier; +- (void)resetViewControllersCache; +@end + +// Disable ambient mode & Fullscreen to the Right - @bhackel +@interface YTWatchViewController (YTLitePlus) +@property (nonatomic, assign, readwrite, getter=isFullscreen) BOOL fullscreen; +@end + +@interface YTWatchCinematicContainerController : NSObject +@property id parentResponder; +@end + +// Player Gestures - @bhackel +@interface YTFineScrubberFilmstripView : UIView +@end +@interface YTFineScrubberFilmstripCollectionView : UICollectionView +@end +@interface YTPlayerViewController (YTLitePlus) +@property (nonatomic, retain) UIPanGestureRecognizer *YTLitePlusPanGesture; +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; +@end +@interface YTWatchFullscreenViewController : YTMultiSizeViewController +@end +@interface MPVolumeController : NSObject +@property (nonatomic, assign, readwrite) float volumeValue; +@end +@interface YTPlayerBarController (YTLitePlus) +- (void)didScrub:(UIPanGestureRecognizer *)gestureRecognizer; +- (void)startScrubbing; +- (void)didScrubToPoint:(CGPoint)point; +- (void)endScrubbingForSeekSource:(int)seekSource; +@end +@interface YTMainAppVideoPlayerOverlayViewController (YTLitePlus) +@property (nonatomic, strong, readwrite) YTPlayerBarController *playerBarController; +@end +@interface YTInlinePlayerBarContainerView (YTLitePlus) +@property UIPanGestureRecognizer *scrubGestureRecognizer; +@property (nonatomic, strong, readwrite) YTFineScrubberFilmstripView *fineScrubberFilmstrip; +- (CGFloat)scrubXForScrubRange:(CGFloat)scrubRange; +@end + +// Hide Collapse Button - @arichornlover +@interface YTMainAppControlsOverlayView (YTLitePlus) +@property (nonatomic, assign, readwrite) YTQTMButton *watchCollapseButton; +@end + +// SponsorBlock button in Nav bar - removed (now built into YTLite v5.x) +@interface MDCButton : UIButton +@end + +@interface YTRightNavigationButtons (YTLitePlus) +@property YTQTMButton *notificationButton; +@property YTQTMButton *videoPlayerButton; +@end + +// BigYTMiniPlayer +@interface YTMainAppVideoPlayerOverlayView (YTLitePlus) +- (UIViewController *)_viewControllerForAncestor; +@end + +@interface YTWatchMiniBarView : UIView +@end + +// YTAutoFullscreen +@interface YTPlayerViewController (YTAFS) +- (void)autoFullscreen; +@end + +// App Theme +@interface YTColor : NSObject ++ (UIColor *)white1; ++ (UIColor *)white2; ++ (UIColor *)white3; ++ (UIColor *)white4; ++ (UIColor *)white5; ++ (UIColor *)black0; ++ (UIColor *)black1; ++ (UIColor *)black2; ++ (UIColor *)black3; ++ (UIColor *)black4; ++ (UIColor *)blackPure; ++ (UIColor *)grey1; ++ (UIColor *)grey2; ++ (UIColor *)white1Alpha98; ++ (UIColor *)white1Alpha95; +@end + +@interface YCHLiveChatView : UIView +@end + +@interface YTFullscreenEngagementOverlayView : UIView +@end + +@interface YTRelatedVideosView : UIView +@end + +@interface YTTopAlignedView : UIView +@end + +@interface ELMView : UIView +@end + +@interface ASWAppSwitcherCollectionViewCell : UIView +@end + +@interface ASScrollView : UIView +@end + +@interface UIKeyboardLayoutStar : UIView +@end + +@interface UIKeyboardDockView : UIView +@end + +@interface _ASDisplayView : UIView +@end + +@interface ELMContainerNode : NSObject +@end + +@interface YTAutonavEndscreenView : UIView +@end + +@interface YTPivotBarIndicatorView : UIView +@end + +@interface YTCommentDetailHeaderCell : UIView +@end + +@interface UICandidateViewController : UIViewController +@end + +@interface UIPredictionViewController : UIViewController +@end + +@interface TUIEmojiSearchView : UIView +@end + +@interface FRPreferences : UITableViewController +@end + +@interface FRPSelectListTable : UITableViewController +@end + +@interface settingsReorderTable : UIViewController +@property(nonatomic, strong) UITableView *tableView; +@end + +// Snack bar +@interface YTHUDMessage : NSObject ++ (id)messageWithText:(id)text; +- (void)setAction:(id)action; +@end + +@interface GOOHUDMessageAction : NSObject +- (void)setTitle:(NSString *)title; +- (void)setHandler:(void (^)(id))handler; +@end + +@interface GOOHUDManagerInternal : NSObject +- (void)showMessageMainThread:(id)message; ++ (id)sharedInstance; +@end diff --git a/YTLitePlus.xm b/YTLitePlus.xm new file mode 100644 index 0000000..40d1cf4 --- /dev/null +++ b/YTLitePlus.xm @@ -0,0 +1,1312 @@ +#import "YTLitePlus.h" + +NSBundle *YTLitePlusBundle() { + static NSBundle *bundle = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *tweakBundlePath = [[NSBundle mainBundle] pathForResource:@"YTLitePlus" ofType:@"bundle"]; + if (tweakBundlePath) + bundle = [NSBundle bundleWithPath:tweakBundlePath]; + else + bundle = [NSBundle bundleWithPath:ROOT_PATH_NS(@"/Library/Application Support/YTLitePlus.bundle")]; + }); + return bundle; +} +NSBundle *tweakBundle = YTLitePlusBundle(); + +// Keychain fix +static NSString *accessGroupID() { + NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: + (__bridge NSString *)kSecClassGenericPassword, (__bridge NSString *)kSecClass, + @"bundleSeedID", kSecAttrAccount, + @"", kSecAttrService, + (id)kCFBooleanTrue, kSecReturnAttributes, + nil]; + CFDictionaryRef result = nil; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); + if (status == errSecItemNotFound) + status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); + if (status != errSecSuccess) + return nil; + NSString *accessGroup = [(__bridge NSDictionary *)result objectForKey:(__bridge NSString *)kSecAttrAccessGroup]; + + return accessGroup; +} + +# pragma mark - Tweaks + +// Activate FLEX +%hook YTAppDelegate +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + BOOL didFinishLaunching = %orig; + if (IsEnabled(@"flex_enabled")) { + [[%c(FLEXManager) performSelector:@selector(sharedManager)] performSelector:@selector(showExplorer)]; + } + return didFinishLaunching; +} +- (void)appWillResignActive:(id)arg1 { + %orig; + if (IsEnabled(@"flex_enabled")) { + [[%c(FLEXManager) performSelector:@selector(sharedManager)] performSelector:@selector(showExplorer)]; + } +} +%end + +// Enable Alternate Icons +%hook UIApplication +- (BOOL)supportsAlternateIcons { + return YES; +} +%end + +/* TEMP-DISABLED +// Fix Google Sign in by @PoomSmart, @level3tjg & Dayanch96 (qnblackcat/uYouPlus#684) +BOOL isSelf() { + NSArray *address = [NSThread callStackReturnAddresses]; + Dl_info info = {0}; + if (dladdr((void *)[address[2] longLongValue], &info) == 0) return NO; + NSString *path = [NSString stringWithUTF8String:info.dli_fname]; + return [path hasPrefix:NSBundle.mainBundle.bundlePath]; +} +%hook NSBundle +- (NSString *)bundleIdentifier { + return isSelf() ? "com.google.ios.youtube" : %orig; +} +- (NSDictionary *)infoDictionary { + NSDictionary *dict = %orig; + if (!isSelf()) + return %orig; + NSMutableDictionary *info = [dict mutableCopy]; + if (info[@"CFBundleIdentifier"]) info[@"CFBundleIdentifier"] = @"com.google.ios.youtube"; + if (info[@"CFBundleDisplayName"]) info[@"CFBundleDisplayName"] = @"YouTube"; + if (info[@"CFBundleName"]) info[@"CFBundleName"] = @"YouTube"; + return info; +} +- (id)objectForInfoDictionaryKey:(NSString *)key { + if (!isSelf()) + return %orig; + if ([key isEqualToString:@"CFBundleIdentifier"]) + return @"com.google.ios.youtube"; + if ([key isEqualToString:@"CFBundleDisplayName"] || [key isEqualToString:@"CFBundleName"]) + return @"YouTube"; + return %orig; +} +%end +*/ + +// Skips content warning before playing *some videos - @PoomSmart +%hook YTPlayabilityResolutionUserActionUIController +- (void)showConfirmAlert { [self confirmAlertDidPressConfirm]; } +%end + +// YTMiniPlayerEnabler: https://github.com/level3tjg/YTMiniplayerEnabler/ +%hook YTWatchMiniBarViewController +- (void)updateMiniBarPlayerStateFromRenderer { + if (IsEnabled(@"ytMiniPlayer_enabled")) {} + else { return %orig; } +} +%end + +// Hide Video Player Cast Button +%group gHideCastButton +%hook MDXPlaybackRouteButtonController +- (BOOL)isPersistentCastIconEnabled { return NO; } +- (void)updateRouteButton:(id)arg1 {} // hide Cast button in video controls overlay +%end + +%hook YTSettings +- (void)setDisableMDXDeviceDiscovery:(BOOL)arg1 { %orig(YES); } +%end +%end + +// Enable Share Button / Enable Save to Playlist Button +%hook YTMainAppControlsOverlayView +- (void)setShareButtonAvailable:(BOOL)arg1 { // enable Share button + if (IsEnabled(@"enableShareButton_enabled")) { + %orig(YES); + } else { + %orig(NO); + } +} +- (void)setAddToButtonAvailable:(BOOL)arg1 { // enable Save To Playlist button + if (IsEnabled(@"enableSaveToButton_enabled")) { + %orig(YES); + } else { + %orig(NO); + } +} +%end + +// Disable the right panel in fullscreen mode +%hook YTColdConfig +- (BOOL)isLandscapeEngagementPanelEnabled { + return IsEnabled(@"hideRightPanel_enabled") ? NO : %orig; +} +%end + +%group gHideVideoPlayerShadowOverlayButtons +%hook YTMainAppControlsOverlayView +- (void)layoutSubviews { + %orig(); + MSHookIvar(self, "_previousButtonView").backgroundColor = nil; + MSHookIvar(self, "_nextButtonView").backgroundColor = nil; + MSHookIvar(self, "_seekBackwardAccessibilityButtonView").backgroundColor = nil; + MSHookIvar(self, "_seekForwardAccessibilityButtonView").backgroundColor = nil; + MSHookIvar(self, "_playPauseButton").backgroundColor = nil; +} +%end +%end + +// A/B flags +%hook YTColdConfig +- (BOOL)respectDeviceCaptionSetting { return NO; } // YouRememberCaption: https://poomsmart.github.io/repo/depictions/youremembercaption.html +- (BOOL)isLandscapeEngagementPanelSwipeRightToDismissEnabled { return YES; } // Swipe right to dismiss the right panel in fullscreen mode +- (BOOL)commercePlatformClientEnablePopupWebviewInWebviewDialogController { return NO;} +%end + +// Hide Upgrade Dialog - @arichornlover +%hook YTGlobalConfig +- (BOOL)shouldBlockUpgradeDialog { return YES;} +- (BOOL)shouldForceUpgrade { return NO;} +- (BOOL)shouldShowUpgrade { return NO;} +- (BOOL)shouldShowUpgradeDialog { return NO;} +%end + +// Hide Speed Toast - @bhackel +// YTLite Speed Toast +%hook PlayerToast +- (void)showPlayerToastWithText:(id)text + value:(CGFloat)value + style:(NSInteger)style + onView:(id)view +{ + if (IsEnabled(@"hideSpeedToast_enabled")) { + return; + } + %orig; +} +%end +// Default YouTube Speed Toast +%hook YTInlinePlayerScrubUserEducationView +- (void)setVisible:(BOOL)visible { + if (IsEnabled(@"hideSpeedToast_enabled")) { + return; + } + %orig; +} +%end + +// Hide Home Tab - @bhackel +%group gHideHomeTab +%hook YTPivotBarView +- (void)setRenderer:(YTIPivotBarRenderer *)renderer { + // Iterate over each renderer item + NSUInteger indexToRemove = -1; + NSMutableArray *itemsArray = renderer.itemsArray; + for (NSUInteger i = 0; i < itemsArray.count; i++) { + YTIPivotBarSupportedRenderers *item = itemsArray[i]; + // Check if this is the home tab button + YTIPivotBarItemRenderer *pivotBarItemRenderer = item.pivotBarItemRenderer; + NSString *pivotIdentifier = pivotBarItemRenderer.pivotIdentifier; + if ([pivotIdentifier isEqualToString:@"FEwhat_to_watch"]) { + // Remove the home tab button + indexToRemove = i; + break; + } + } + if (indexToRemove != -1) { + [itemsArray removeObjectAtIndex:indexToRemove]; + } + %orig; +} +%end +// Fix bug where contents of leftmost tab is replaced by Home tab +BOOL isTabSelected = NO; +%hook YTPivotBarViewController +- (void)viewDidAppear:(BOOL)animated { + %orig; + if (!isTabSelected) { + // Get the identifier of the selected pivot + NSString *selectedPivotIdentifier = self.selectedPivotIdentifier; + // Find any different tab to switch from by looping through the renderer items + YTIPivotBarRenderer *renderer = self.renderer; + NSArray *itemsArray = renderer.itemsArray; + for (YTIPivotBarSupportedRenderers *item in itemsArray) { + YTIPivotBarItemRenderer *pivotBarItemRenderer = item.pivotBarItemRenderer; + NSString *pivotIdentifier = pivotBarItemRenderer.pivotIdentifier; + if (![pivotIdentifier isEqualToString:selectedPivotIdentifier]) { + // Switch to this tab + [self selectItemWithPivotIdentifier:pivotIdentifier]; + break; + } + } + // Clear any cached controllers to delete the broken home tab + [self resetViewControllersCache]; + // Switch back to the original tab + [self selectItemWithPivotIdentifier:selectedPivotIdentifier]; + // Update flag to not do it again + isTabSelected = YES; + } +} +%end +%end + +// Disable fullscreen engagement overlay - @bhackel +%group gDisableEngagementOverlay +%hook YTFullscreenEngagementOverlayController +- (void)setEnabled:(BOOL)enabled { + %orig(NO); +} +%end +%end + +// YTNoModernUI - @arichornlover +%group gYTNoModernUI +%hook YTVersionUtils // YTNoModernUI Original Version ++ (NSString *)appVersion { return @"17.38.10"; } +%end + +%hook YTSettingsCell // Remove v17.38.10 Version Number - @Dayanch96 +- (void)setDetailText:(id)arg1 { + NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary]; + NSString *appVersion = infoDictionary[@"CFBundleShortVersionString"]; + + if ([arg1 isEqualToString:@"17.38.10"]) { + arg1 = appVersion; + } %orig(arg1); +} +%end + +%hook YTInlinePlayerBarContainerView // Red Progress Bar - YTNoModernUI +- (id)quietProgressBarColor { + return [UIColor redColor]; +} +%end + +%hook YTSegmentableInlinePlayerBarView // Gray Buffer Progress - YTNoModernUI +- (void)setBufferedProgressBarColor:(id)arg1 { + [UIColor colorWithRed:1.00 green:1.00 blue:1.00 alpha:0.90]; +} +%end + +%hook YTQTMButton // No Modern/Rounded Buttons - YTNoModernUI ++ (BOOL)buttonModernizationEnabled { return NO; } +%end + +%hook YTBubbleHintView // No Modern/Rounded Hints - YTNoModernUI ++ (BOOL)modernRoundedCornersEnabled { return NO; } +%end + +%hook YTColdConfig +// Disable Modern Content - YTNoModernUI +- (BOOL)creatorClientConfigEnableStudioModernizedMdeThumbnailPickerForClient { return NO; } +- (BOOL)cxClientEnableModernizedActionSheet { return NO; } +- (BOOL)enableClientShortsSheetsModernization { return NO; } +- (BOOL)enableTimestampModernizationForNative { return NO; } +- (BOOL)mainAppCoreClientEnableModernIaFeedStretchBottom { return NO; } +- (BOOL)mainAppCoreClientEnableModernIaFrostedBottomBar { return NO; } +- (BOOL)mainAppCoreClientEnableModernIaFrostedPivotBar { return NO; } +- (BOOL)mainAppCoreClientEnableModernIaFrostedPivotBarUpdatedBackdrop { return NO; } +- (BOOL)mainAppCoreClientEnableModernIaFrostedTopBar { return NO; } +- (BOOL)mainAppCoreClientEnableModernIaOpacityPivotBar { return NO; } +- (BOOL)mainAppCoreClientEnableModernIaTopAndBottomBarIconRefresh { return NO; } +- (BOOL)mainAppCoreClientEnableModernizedBedtimeReminderU18DefaultSettings { return NO; } +- (BOOL)modernizeCameoNavbar { return NO; } +- (BOOL)modernizeCollectionLockups { return NO; } +- (BOOL)modernizeCollectionLockupsShowVideoCount { return NO; } +- (BOOL)modernizeElementsBgColor { return NO; } +- (BOOL)modernizeElementsTextColor { return NO; } +- (BOOL)postsCreatorClientEnableModernButtonsUi { return NO; } +- (BOOL)pullToFullModernEdu { return NO; } +- (BOOL)showModernMiniplayerRedesign { return NO; } +- (BOOL)uiSystemsClientGlobalConfigEnableModernButtonsForNative { return NO; } +- (BOOL)uiSystemsClientGlobalConfigIosEnableModernTabsForNative { return NO; } +- (BOOL)uiSystemsClientGlobalConfigIosEnableSnackbarModernization { return NO; } +- (BOOL)uiSystemsClientGlobalConfigModernizeNativeBgColor { return NO; } +- (BOOL)uiSystemsClientGlobalConfigModernizeNativeTextColor { return NO; } +// Disable Rounded Content - YTNoModernUI +- (BOOL)iosDownloadsPageRoundedThumbs { return NO; } +- (BOOL)iosRoundedSearchBarSuggestZeroPadding { return NO; } +- (BOOL)uiSystemsClientGlobalConfigEnableRoundedDialogForNative { return NO; } +- (BOOL)uiSystemsClientGlobalConfigEnableRoundedThumbnailsForNative { return NO; } +- (BOOL)uiSystemsClientGlobalConfigEnableRoundedThumbnailsForNativeLongTail { return NO; } +- (BOOL)uiSystemsClientGlobalConfigEnableRoundedTimestampForNative { return NO; } +// Disable Darker Dark Mode - YTNoModernUI +- (BOOL)enableDarkerDarkMode { return NO; } +- (BOOL)useDarkerPaletteBgColorForElements { return NO; } +- (BOOL)useDarkerPaletteTextColorForElements { return NO; } +- (BOOL)uiSystemsClientGlobalConfigUseDarkerPaletteTextColorForNative { return NO; } +- (BOOL)uiSystemsClientGlobalConfigUseDarkerPaletteBgColorForNative { return NO; } +// Disable Ambient Mode - YTNoModernUI +- (BOOL)disableCinematicForLowPowerMode { return NO; } +- (BOOL)enableCinematicContainer { return NO; } +- (BOOL)enableCinematicContainerOnClient { return NO; } +- (BOOL)enableCinematicContainerOnTablet { return NO; } +- (BOOL)enableTurnOffCinematicForFrameWithBlackBars { return YES; } +- (BOOL)enableTurnOffCinematicForVideoWithBlackBars { return YES; } +- (BOOL)iosCinematicContainerClientImprovement { return NO; } +- (BOOL)iosEnableGhostCardInlineTitleCinematicContainerFix { return NO; } +- (BOOL)iosUseFineScrubberMosaicStoreForCinematic { return NO; } +- (BOOL)mainAppCoreClientEnableClientCinematicPlaylists { return NO; } +- (BOOL)mainAppCoreClientEnableClientCinematicPlaylistsPostMvp { return NO; } +- (BOOL)mainAppCoreClientEnableClientCinematicTablets { return NO; } +- (BOOL)iosEnableFullScreenAmbientMode { return NO; } +// 16.42.3 Styled YouTube Channel Page Interface - YTNoModernUI +- (BOOL)channelsClientConfigIosChannelNavRestructuring { return NO; } +- (BOOL)channelsClientConfigIosMultiPartChannelHeader { return NO; } +// Disable Optional Content - YTNoModernUI +- (BOOL)elementsClientIosElementsEnableLayoutUpdateForIob { return NO; } +- (BOOL)supportElementsInMenuItemSupportedRenderers { return NO; } +- (BOOL)isNewRadioButtonStyleEnabled { return NO; } +- (BOOL)uiSystemsClientGlobalConfigEnableButtonSentenceCasingForNative { return NO; } +- (BOOL)mainAppCoreClientEnableClientYouTab { return NO; } +- (BOOL)mainAppCoreClientEnableClientYouLatency { return NO; } +- (BOOL)mainAppCoreClientEnableClientYouTabTablet { return NO; } +%end + +%hook YTHotConfig +- (BOOL)liveChatIosUseModernRotationDetection { return NO; } // Disable Modern Content (YTHotConfig) +- (BOOL)liveChatModernizeClassicElementizeTextMessage { return NO; } +- (BOOL)iosShouldRepositionChannelBar { return NO; } +- (BOOL)enableElementRendererOnChannelCreation { return NO; } +%end +%end + +// Hide YouTube Heatwaves in Video Player (YouTube v17.19.2-present) - @level3tjg - https://www.reddit.com/r/jailbreak/comments/v29yvk/ +%group gHideHeatwaves +%hook YTInlinePlayerBarContainerView +- (BOOL)canShowHeatwave { return NO; } +%end +%end + +%hook YTYouThereController +- (BOOL)shouldShowYouTherePrompt { return NO; } +%end +// Fix login for YouTube 18.13.2 and higher +%hook SSOKeychainHelper ++ (NSString *)accessGroup { + return accessGroupID(); +} ++ (NSString *)sharedAccessGroup { + return accessGroupID(); +} +%end + +// Fix login for YouTube 17.33.2 and higher - @BandarHL +// https://gist.github.com/BandarHL/492d50de46875f9ac7a056aad084ac10 +%hook SSOKeychainCore ++ (NSString *)accessGroup { + return accessGroupID(); +} + ++ (NSString *)sharedAccessGroup { + return accessGroupID(); +} +%end + +// Fix App Group Directory by move it to document directory +%hook NSFileManager +- (NSURL *)containerURLForSecurityApplicationGroupIdentifier:(NSString *)groupIdentifier { + if (groupIdentifier != nil) { + NSArray *paths = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]; + NSURL *documentsURL = [paths lastObject]; + return [documentsURL URLByAppendingPathComponent:@"AppGroup"]; + } + return %orig(groupIdentifier); +} +%end + +// Fix Casting: https://github.com/arichornlover/uYouEnhanced/issues/606#issuecomment-2098289942 +%group gFixCasting +%hook YTColdConfig +- (BOOL)cxClientEnableIosLocalNetworkPermissionReliabilityFixes { return YES; } +- (BOOL)cxClientEnableIosLocalNetworkPermissionUsingSockets { return NO; } +- (BOOL)cxClientEnableIosLocalNetworkPermissionWifiFixes { return YES; } +%end +%hook YTHotConfig +- (BOOL)isPromptForLocalNetworkPermissionsEnabled { return YES; } +%end +%end + +// Seek anywhere gesture - @bhackel +%hook YTColdConfig +- (BOOL)speedMasterArm2FastForwardWithoutSeekBySliding { + return IsEnabled(@"seekAnywhere_enabled") ? NO : %orig; +} +%end + +// New Settings UI - @bhackel +%hook YTColdConfig +- (BOOL)mainAppCoreClientEnableCairoSettings { + return IS_ENABLED(@"newSettingsUI_enabled"); +} +%end + +// Fullscreen to the Right (iPhone-Exclusive) - @arichornlover & @bhackel +// WARNING: Please turn off the “Portrait Fullscreen” or "iPad Layout" Option in YTLite while the option "Fullscreen to the Right" is enabled below. +%group gFullscreenToTheRight +%hook YTWatchViewController +- (UIInterfaceOrientationMask)allowedFullScreenOrientations { + UIInterfaceOrientationMask orientations = UIInterfaceOrientationMaskLandscapeRight; + return orientations; +} +%end +%end + +// YTTapToSeek - https://github.com/bhackel/YTTapToSeek +%group gYTTapToSeek + %hook YTInlinePlayerBarContainerView + - (void)didPressScrubber:(id)arg1 { + %orig; + // Get access to the seekToTime method + YTMainAppVideoPlayerOverlayViewController *mainAppController = [self.delegate valueForKey:@"_delegate"]; + YTPlayerViewController *playerViewController = [mainAppController valueForKey:@"parentViewController"]; + // Get the X position of this tap from arg1 + UIGestureRecognizer *gestureRecognizer = (UIGestureRecognizer *)arg1; + CGPoint location = [gestureRecognizer locationInView:self]; + CGFloat x = location.x; + // Get the associated proportion of time using scrubRangeForScrubX + double timestampFraction = [self scrubRangeForScrubX:x]; + // Get the timestamp from the fraction + double timestamp = [mainAppController totalTime] * timestampFraction; + // Jump to the timestamp + [playerViewController seekToTime:timestamp]; + } + %end +%end + +// Disable pull to enter vertical/portrait fullscreen gesture - @bhackel +// This was introduced in version 19.XX +// This does not apply to portrait videos +%group gDisablePullToFull +%hook YTWatchPullToFullController +- (BOOL)shouldRecognizeOverscrollEventsFromWatchOverscrollController:(id)arg1 { + // Get the current player orientation + YTWatchViewController *watchViewController = (YTWatchViewController *) self.playerViewSource; + NSUInteger allowedFullScreenOrientations = [watchViewController allowedFullScreenOrientations]; + // Check if the current player orientation is portrait + if (allowedFullScreenOrientations == UIInterfaceOrientationMaskAllButUpsideDown + || allowedFullScreenOrientations == UIInterfaceOrientationMaskPortrait + || allowedFullScreenOrientations == UIInterfaceOrientationMaskPortraitUpsideDown) { + return %orig; + } else { + return NO; + } +} +%end +%end + +// Always use remaining time in the video player - @bhackel +%hook YTPlayerBarController +// When a new video is played, enable time remaining flag +- (void)setActiveSingleVideo:(id)arg1 { + %orig; + if (IS_ENABLED(@"alwaysShowRemainingTime_enabled")) { + // Get the player bar view + YTInlinePlayerBarContainerView *playerBar = self.playerBar; + if (playerBar) { + // Enable the time remaining flag + playerBar.shouldDisplayTimeRemaining = YES; + } + } +} +%end + +// Disable toggle time remaining - @bhackel +%hook YTInlinePlayerBarContainerView +- (void)setShouldDisplayTimeRemaining:(BOOL)arg1 { + if (IS_ENABLED(@"disableRemainingTime_enabled")) { + // Set true if alwaysShowRemainingTime + if (IS_ENABLED(@"alwaysShowRemainingTime_enabled")) { + %orig(YES); + } else { + %orig(NO); + } + return; + } + %orig; +} +%end + +// Disable Ambient Mode - @bhackel +%hook YTWatchCinematicContainerController +- (BOOL)isCinematicLightingAvailable { + // Check if we are in fullscreen or not, then decide if ambient is disabled + YTWatchViewController *watchViewController = (YTWatchViewController *) self.parentResponder; + BOOL isFullscreen = watchViewController.fullscreen; + if (IsEnabled(@"disableAmbientModePortrait_enabled") && !isFullscreen) { + return NO; + } + if (IsEnabled(@"disableAmbientModeFullscreen_enabled") && isFullscreen) { + return NO; + } + return %orig; +} +%end + +%hook _ASDisplayView +- (void)didMoveToWindow { + %orig; + + // Hide the Comment Section Previews under the Video Player - @arichornlover + if ((IsEnabled(@"hidePreviewCommentSection_enabled")) && ([self.accessibilityIdentifier isEqualToString:@"id.ui.comments_entry_point_teaser"])) { + self.hidden = YES; + self.opaque = YES; + self.userInteractionEnabled = NO; + CGRect bounds = self.frame; + bounds.size.height = 0; + self.frame = bounds; + [self.superview layoutIfNeeded]; + [self setNeedsLayout]; + [self removeFromSuperview]; + } +} +%end + +// Hide Autoplay Mini Preview - @bhackel +%hook YTAutonavPreviewView +- (void)layoutSubviews { + %orig; + if (IsEnabled(@"hideAutoplayMiniPreview_enabled")) { + self.hidden = YES; + } +} +- (void)setHidden:(BOOL)arg1 { + if (IsEnabled(@"hideAutoplayMiniPreview_enabled")) { + %orig(YES); + } else { + %orig(arg1); + } +} +%end + +// Hide HUD Messages - @qnblackcat +%hook YTHUDMessageView +- (id)initWithMessage:(id)arg1 dismissHandler:(id)arg2 { + return IsEnabled(@"hideHUD_enabled") ? nil : %orig; +} +%end + +// Hide Video Player Collapse Button - @arichornlover +%hook YTMainAppControlsOverlayView +- (void)layoutSubviews { + %orig; + if (IsEnabled(@"disableCollapseButton_enabled")) { + if (self.watchCollapseButton) { + [self.watchCollapseButton removeFromSuperview]; + } + } +} +- (BOOL)watchCollapseButtonHidden { + if (IsEnabled(@"disableCollapseButton_enabled")) { + return YES; + } else { + return %orig; + } +} +- (void)setWatchCollapseButtonAvailable:(BOOL)available { + if (IsEnabled(@"disableCollapseButton_enabled")) { + } else { + %orig(available); + } +} +%end + +// Gestures - @bhackel +%group gPlayerGestures +%hook YTWatchLayerViewController +// invoked when the player view controller is either created or destroyed +- (void)watchController:(YTWatchController *)watchController didSetPlayerViewController:(YTPlayerViewController *)playerViewController { + if (playerViewController) { + // check to see if the pan gesture is already created + if (!playerViewController.YTLitePlusPanGesture) { + playerViewController.YTLitePlusPanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:playerViewController + action:@selector(YTLitePlusHandlePanGesture:)]; + playerViewController.YTLitePlusPanGesture.delegate = playerViewController; + [playerViewController.playerView addGestureRecognizer:playerViewController.YTLitePlusPanGesture]; + } + } + %orig; +} +%end + + +%hook YTPlayerViewController +// the pan gesture that will be created and added to the player view +%property (nonatomic, retain) UIPanGestureRecognizer *YTLitePlusPanGesture; +/** + * This method is called when the pan gesture is started, changed, or ended. It handles + * 12 different possible cases depending on the configuration: 3 zones with 4 choices + * for each zone. The zones are horizontal sections that divide the player into + * 3 equal parts. The choices are volume, brightness, seek, and disabled. + * There is also a deadzone that can be configured in the settings. + * There are 4 logical states: initial, changed in deadzone, changed, end. + */ +%new +- (void)YTLitePlusHandlePanGesture:(UIPanGestureRecognizer *)panGestureRecognizer { + // Haptic feedback generator + static UIImpactFeedbackGenerator *feedbackGenerator; + // Variables for storing initial values to be adjusted + static float initialVolume; + static float initialBrightness; + static CGFloat initialTime; + // Flag to determine if the pan gesture is valid + static BOOL isValidHorizontalPan = NO; + // Variable to store the section of the screen the gesture is in + static GestureSection gestureSection = GestureSectionInvalid; + // Variable to track the start location of the whole pan gesture + static CGPoint startLocation; + // Variable to track the X translation when exiting the deadzone + static CGFloat deadzoneStartingXTranslation; + // Variable to track the X translation of the pan gesture after exiting the deadzone + static CGFloat adjustedTranslationX; + // Variable used to smooth out the X translation + static CGFloat smoothedTranslationX = 0; + // Constant for the filter constant to change responsiveness + // static const CGFloat filterConstant = 0.1; + // Constant for the deadzone radius that can be changed in the settings + static CGFloat deadzoneRadius = (CGFloat)GetFloat(@"playerGesturesDeadzone"); + // Constant for the sensitivity factor that can be changed in the settings + static CGFloat sensitivityFactor = (CGFloat)GetFloat(@"playerGesturesSensitivity"); + // Objects for modifying the system volume + static MPVolumeView *volumeView; + static UISlider *volumeViewSlider; + // Get objects that should only be initialized once + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + volumeView = [[MPVolumeView alloc] init]; + for (UIView *view in volumeView.subviews) { + if ([view isKindOfClass:[UISlider class]]) { + volumeViewSlider = (UISlider *)view; + break; + } + } + feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium]; + }); + // Get objects used to seek nicely in the video player + static YTMainAppVideoPlayerOverlayViewController *mainVideoPlayerController = (YTMainAppVideoPlayerOverlayViewController *)self.childViewControllers.firstObject; + static YTPlayerBarController *playerBarController = mainVideoPlayerController.playerBarController; + static YTInlinePlayerBarContainerView *playerBar = playerBarController.playerBar; + +/***** Helper functions for adjusting player state *****/ + // Helper function to adjust brightness + void (^adjustBrightness)(CGFloat, CGFloat) = ^(CGFloat translationX, CGFloat initialBrightness) { + float brightnessSensitivityFactor = 3; + float newBrightness = initialBrightness + ((translationX / 1000.0) * sensitivityFactor * brightnessSensitivityFactor); + newBrightness = fmaxf(fminf(newBrightness, 1.0), 0.0); + [[UIScreen mainScreen] setBrightness:newBrightness]; + }; + + // Helper function to adjust volume + void (^adjustVolume)(CGFloat, CGFloat) = ^(CGFloat translationX, CGFloat initialVolume) { + float volumeSensitivityFactor = 3.0; + float newVolume = initialVolume + ((translationX / 1000.0) * sensitivityFactor * volumeSensitivityFactor); + newVolume = fmaxf(fminf(newVolume, 1.0), 0.0); + // Improve smoothness - ignore if the volume is within 0.01 of the current volume + CGFloat currentVolume = [[AVAudioSession sharedInstance] outputVolume]; + if (fabs(newVolume - currentVolume) < 0.01 && currentVolume > 0.01 && currentVolume < 0.99) { + return; + } + // https://stackoverflow.com/questions/50737943/how-to-change-volume-programmatically-on-ios-11-4 + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + volumeViewSlider.value = newVolume; + }); + }; + + // Helper function to adjust seek time + void (^adjustSeek)(CGFloat, CGFloat) = ^(CGFloat translationX, CGFloat initialTime) { + // Get the location in view for the current video time + CGFloat totalTime = self.currentVideoTotalMediaTime; + CGFloat videoFraction = initialTime / totalTime; + CGFloat initialTimeXPosition = [playerBar scrubXForScrubRange:videoFraction]; + // Calculate the new seek X position + CGFloat sensitivityFactor = 1; // Adjust this value to make seeking more/less sensitive + CGFloat newSeekXPosition = initialTimeXPosition + translationX * sensitivityFactor; + // Create a CGPoint using this new X position + CGPoint newSeekPoint = CGPointMake(newSeekXPosition, 0); + // Send this to a seek method in the player bar controller + [playerBarController didScrubToPoint:newSeekPoint]; + }; + + // Helper function to smooth out the X translation + // CGFloat (^applyLowPassFilter)(CGFloat) = ^(CGFloat newTranslation) { + // smoothedTranslationX = filterConstant * newTranslation + (1 - filterConstant) * smoothedTranslationX; + // return smoothedTranslationX; + // }; + +/***** Helper functions for running the selected gesture *****/ + // Helper function to run any setup for the selected gesture mode + void (^runSelectedGestureSetup)(NSString*) = ^(NSString *sectionKey) { + // Determine the selected gesture mode using the section key + GestureMode selectedGestureMode = (GestureMode)GetInteger(sectionKey); + // Handle the setup based on the selected mode + switch (selectedGestureMode) { + case GestureModeVolume: + initialVolume = [[AVAudioSession sharedInstance] outputVolume]; + break; + case GestureModeBrightness: + initialBrightness = [UIScreen mainScreen].brightness; + break; + case GestureModeSeek: + initialTime = self.currentVideoMediaTime; + // Start a seek action + [playerBarController startScrubbing]; + break; + case GestureModeDisabled: + // Do nothing if the gesture is disabled + break; + default: + // Show an alert if the gesture mode is invalid + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Invalid Gesture Mode" message:@"Please report this bug." preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; + [alertController addAction:okAction]; + [self presentViewController:alertController animated:YES completion:nil]; + break; + } + }; + + // Helper function to run the selected gesture action when the gesture changes + void (^runSelectedGestureChanged)(NSString*) = ^(NSString *sectionKey) { + // Determine the selected gesture mode using the section key + GestureMode selectedGestureMode = (GestureMode)GetInteger(sectionKey); + // Handle the gesture action based on the selected mode + switch (selectedGestureMode) { + case GestureModeVolume: + adjustVolume(adjustedTranslationX, initialVolume); + break; + case GestureModeBrightness: + adjustBrightness(adjustedTranslationX, initialBrightness); + break; + case GestureModeSeek: + adjustSeek(adjustedTranslationX, initialTime); + break; + case GestureModeDisabled: + // Do nothing if the gesture is disabled + break; + default: + // Show an alert if the gesture mode is invalid + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Invalid Gesture Mode" message:@"Please report this bug." preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; + [alertController addAction:okAction]; + [self presentViewController:alertController animated:YES completion:nil]; + break; + } + }; + + // Helper function to run the selected gesture action when the gesture ends + void (^runSelectedGestureEnded)(NSString*) = ^(NSString *sectionKey) { + // Determine the selected gesture mode using the section key + GestureMode selectedGestureMode = (GestureMode)GetInteger(sectionKey); + // Handle the gesture action based on the selected mode + switch (selectedGestureMode) { + case GestureModeVolume: + break; + case GestureModeBrightness: + break; + case GestureModeSeek: + [playerBarController endScrubbingForSeekSource:0]; + break; + case GestureModeDisabled: + break; + default: + // Show an alert if the gesture mode is invalid + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Invalid Gesture Mode" message:@"Please report this bug." preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; + [alertController addAction:okAction]; + [self presentViewController:alertController animated:YES completion:nil]; + break; + } + }; +/***** End of Helper functions *****/ + + // Handle gesture based on current gesture state + if (panGestureRecognizer.state == UIGestureRecognizerStateBegan) { + // Get the gesture's start position + startLocation = [panGestureRecognizer locationInView:self.view]; + CGFloat viewHeight = self.view.bounds.size.height; + // Determine the section based on the start position by dividing the view into thirds + if (startLocation.y <= viewHeight / 3.0) { + gestureSection = GestureSectionTop; + } else if (startLocation.y <= 2 * viewHeight / 3.0) { + gestureSection = GestureSectionMiddle; + } else if (startLocation.y <= viewHeight) { + gestureSection = GestureSectionBottom; + } else { + gestureSection = GestureSectionInvalid; + } + // Cancel the gesture if the chosen mode for this section is disabled + if ( ((gestureSection == GestureSectionTop) && (GetInteger(@"playerGestureTopSelection") == GestureModeDisabled)) + || ((gestureSection == GestureSectionMiddle) && (GetInteger(@"playerGestureMiddleSelection") == GestureModeDisabled)) + || ((gestureSection == GestureSectionBottom) && (GetInteger(@"playerGestureBottomSelection") == GestureModeDisabled))) { + panGestureRecognizer.state = UIGestureRecognizerStateCancelled; + return; + } + // Deactive the activity flag + isValidHorizontalPan = NO; + // Cancel this gesture if it has not activated after 1 second + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + if (!isValidHorizontalPan && panGestureRecognizer.state != UIGestureRecognizerStateEnded) { + // Cancel the gesture by setting its state to UIGestureRecognizerStateCancelled + panGestureRecognizer.state = UIGestureRecognizerStateCancelled; + } + }); + } + + // Handle changed gesture state by activating the gesture once it has exited the deadzone, + // and then adjusting the player based on the selected gesture mode + if (panGestureRecognizer.state == UIGestureRecognizerStateChanged) { + // Determine if the gesture is predominantly horizontal + CGPoint translation = [panGestureRecognizer translationInView:self.view]; + if (!isValidHorizontalPan) { + if (fabs(translation.x) > fabs(translation.y)) { + // Check if the touch has moved outside the deadzone + CGFloat distanceFromStart = hypot(translation.x, translation.y); + if (distanceFromStart < deadzoneRadius) { + // If within the deadzone, don't activate the pan gesture + return; + } + // If outside the deadzone, activate the pan gesture and store the initial values + isValidHorizontalPan = YES; + deadzoneStartingXTranslation = translation.x; + adjustedTranslationX = 0; + smoothedTranslationX = 0; + // Run the setup for the selected gesture mode + switch (gestureSection) { + case GestureSectionTop: + runSelectedGestureSetup(@"playerGestureTopSelection"); + break; + case GestureSectionMiddle: + runSelectedGestureSetup(@"playerGestureMiddleSelection"); + break; + case GestureSectionBottom: + runSelectedGestureSetup(@"playerGestureBottomSelection"); + break; + default: + // If the section is invalid, cancel the gesture + panGestureRecognizer.state = UIGestureRecognizerStateCancelled; + break; + } + // Provide haptic feedback to indicate a gesture start + if (IS_ENABLED(@"playerGesturesHapticFeedback_enabled")) { + [feedbackGenerator prepare]; + [feedbackGenerator impactOccurred]; + } + } else { + // Cancel the gesture if the translation is not horizontal + panGestureRecognizer.state = UIGestureRecognizerStateCancelled; + return; + } + } + + // Handle the gesture based on the identified section + if (isValidHorizontalPan) { + // Adjust the X translation based on the value hit after exiting the deadzone + adjustedTranslationX = translation.x - deadzoneStartingXTranslation; + // Smooth the translation value + // adjustedTranslationX = applyLowPassFilter(adjustedTranslationX); + // Pass the adjusted translation to the selected gesture + switch (gestureSection) { + case GestureSectionTop: + runSelectedGestureChanged(@"playerGestureTopSelection"); + break; + case GestureSectionMiddle: + runSelectedGestureChanged(@"playerGestureMiddleSelection"); + break; + case GestureSectionBottom: + runSelectedGestureChanged(@"playerGestureBottomSelection"); + break; + default: + // If the section is invalid, cancel the gesture + panGestureRecognizer.state = UIGestureRecognizerStateCancelled; + break; + } + } + } + + // Handle the gesture end state by running the selected gesture mode's end action + if (panGestureRecognizer.state == UIGestureRecognizerStateEnded && isValidHorizontalPan) { + switch (gestureSection) { + case GestureSectionTop: + runSelectedGestureEnded(@"playerGestureTopSelection"); + break; + case GestureSectionMiddle: + runSelectedGestureEnded(@"playerGestureMiddleSelection"); + break; + case GestureSectionBottom: + runSelectedGestureEnded(@"playerGestureBottomSelection"); + break; + default: + break; + } + // Provide haptic feedback upon successful gesture recognition + // [feedbackGenerator prepare]; + // [feedbackGenerator impactOccurred]; + } + +} +// allow the pan gesture to be recognized simultaneously with other gestures +%new +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { + if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { + // Do not allow this gesture to activate with the normal seek bar gesture + YTMainAppVideoPlayerOverlayViewController *mainVideoPlayerController = (YTMainAppVideoPlayerOverlayViewController *)self.childViewControllers.firstObject; + YTPlayerBarController *playerBarController = mainVideoPlayerController.playerBarController; + YTInlinePlayerBarContainerView *playerBar = playerBarController.playerBar; + if (otherGestureRecognizer == playerBar.scrubGestureRecognizer) { + return NO; + } + // Do not allow this gesture to activate with the fine scrubber gesture + YTFineScrubberFilmstripView *fineScrubberFilmstrip = playerBar.fineScrubberFilmstrip; + if (!fineScrubberFilmstrip) { + return YES; + } + YTFineScrubberFilmstripCollectionView *filmstripCollectionView = [fineScrubberFilmstrip valueForKey:@"_filmstripCollectionView"]; + if (filmstripCollectionView && otherGestureRecognizer == filmstripCollectionView.panGestureRecognizer) { + return NO; + } + + } + return YES; +} +%end +%end + +// BigYTMiniPlayer: https://github.com/Galactic-Dev/BigYTMiniPlayer +%group Main +%hook YTWatchMiniBarView +- (void)setWatchMiniPlayerLayout:(int)arg1 { + %orig(1); +} +- (int)watchMiniPlayerLayout { + return 1; +} +- (void)layoutSubviews { + %orig; + self.frame = CGRectMake(([UIScreen mainScreen].bounds.size.width - self.frame.size.width), self.frame.origin.y, self.frame.size.width, self.frame.size.height); +} +%end + +%hook YTMainAppVideoPlayerOverlayView +- (BOOL)isUserInteractionEnabled { + if([[self _viewControllerForAncestor].parentViewController.parentViewController isKindOfClass:%c(YTWatchMiniBarViewController)]) { + return NO; + } + return %orig; +} +%end +%end + +// Video player button in the navigation bar - @bhackel +// This code is based on the iSponsorBlock button code +%group gVideoPlayerButton +NSInteger pageStyle = 0; +%hook YTRightNavigationButtons +%property (retain, nonatomic) YTQTMButton *videoPlayerButton; +- (NSMutableArray *)buttons { + NSMutableArray *retVal = %orig.mutableCopy; + [self.videoPlayerButton removeFromSuperview]; + [self addSubview:self.videoPlayerButton]; + if (!self.videoPlayerButton || pageStyle != [%c(YTPageStyleController) pageStyle]) { + self.videoPlayerButton = [%c(YTQTMButton) iconButton]; + [self.videoPlayerButton enableNewTouchFeedback]; + self.videoPlayerButton.frame = CGRectMake(0, 0, 40, 40); + + if ([%c(YTPageStyleController) pageStyle]) { //dark mode + [self.videoPlayerButton setImage:[UIImage imageWithContentsOfFile:[tweakBundle pathForResource:@"YTLitePlusColored-128" ofType:@"png"]] forState:UIControlStateNormal]; + } + else { // light mode + [self.videoPlayerButton setImage:[UIImage imageWithContentsOfFile:[tweakBundle pathForResource:@"YTLitePlusColored-128" ofType:@"png"]] forState:UIControlStateNormal]; + // UIImage *image = [UIImage imageWithContentsOfFile:[tweakBundle pathForResource:@"YTLitePlusColored-128" ofType:@"png"]]; + // image = [image imageWithTintColor:UIColor.blackColor renderingMode:UIImageRenderingModeAlwaysTemplate]; + // [self.videoPlayerButton setImage:image forState:UIControlStateNormal]; + // [self.videoPlayerButton setTintColor:UIColor.blackColor]; + } + pageStyle = [%c(YTPageStyleController) pageStyle]; + + [self.videoPlayerButton addTarget:self action:@selector(videoPlayerButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + [retVal insertObject:self.videoPlayerButton atIndex:0]; + } + return retVal; +} +- (NSMutableArray *)visibleButtons { + NSMutableArray *retVal = %orig.mutableCopy; + + // fixes button overlapping yt logo on smaller devices + [self setLeadingPadding:-10]; + if (self.videoPlayerButton) { + [self.videoPlayerButton removeFromSuperview]; + [self addSubview:self.videoPlayerButton]; + [retVal insertObject:self.videoPlayerButton atIndex:0]; + } + return retVal; +} +// Method to handle the video player button press by showing a document picker +%new +- (void)videoPlayerButtonPressed:(UIButton *)sender { + // Traversing the responder chain to find the nearest UIViewController + UIResponder *responder = sender; + UIViewController *settingsViewController = nil; + while (responder) { + if ([responder isKindOfClass:[UIViewController class]]) { + settingsViewController = (UIViewController *)responder; + break; + } + responder = responder.nextResponder; + } + + if (settingsViewController) { + // Present the video picker + UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[(NSString *)UTTypeMovie.identifier, (NSString *)UTTypeVideo.identifier] inMode:UIDocumentPickerModeImport]; + documentPicker.delegate = (id)self; + documentPicker.allowsMultipleSelection = NO; + [settingsViewController presentViewController:documentPicker animated:YES completion:nil]; + } else { + NSLog(@"No view controller found for the sender button."); + } +} +// Delegate method to handle the picked video by showing the apple player +%new +- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { + NSURL *pickedURL = [urls firstObject]; + + if (pickedURL) { + // Use AVPlayerViewController to play the video + AVPlayer *player = [AVPlayer playerWithURL:pickedURL]; + AVPlayerViewController *playerViewController = [[AVPlayerViewController alloc] init]; + playerViewController.player = player; + + // Get the root view controller + UIViewController *presentingViewController = [UIApplication sharedApplication].keyWindow.rootViewController; + // Present the Video Player + if (presentingViewController) { + [presentingViewController presentViewController:playerViewController animated:YES completion:^{ + [player play]; + }]; + } else { + // Handle case where no view controller was found + NSLog(@"Error: No view controller found to present AVPlayerViewController."); + } + } +} +%end +%end + +// App Settings Overlay Options +%group gDisableAccountSection +%hook YTSettingsSectionItemManager +- (void)updateAccountSwitcherSectionWithEntry:(id)arg1 {} // Account +%end +%end + +%group gDisableAutoplaySection +%hook YTSettingsSectionItemManager +- (void)updateAutoplaySectionWithEntry:(id)arg1 {} // Autoplay +%end +%end + +%group gDisableTryNewFeaturesSection +%hook YTSettingsSectionItemManager +- (void)updatePremiumEarlyAccessSectionWithEntry:(id)arg1 {} // Try new features +%end +%end + +%group gDisableVideoQualityPreferencesSection +%hook YTSettingsSectionItemManager +- (void)updateVideoQualitySectionWithEntry:(id)arg1 {} // Video quality preferences +%end +%end + +%group gDisableNotificationsSection +%hook YTSettingsSectionItemManager +- (void)updateNotificationSectionWithEntry:(id)arg1 {} // Notifications +%end +%end + +%group gDisableManageAllHistorySection +%hook YTSettingsSectionItemManager +- (void)updateHistorySectionWithEntry:(id)arg1 {} // Manage all history +%end +%end + +%group gDisableYourDataInYouTubeSection +%hook YTSettingsSectionItemManager +- (void)updateYourDataSectionWithEntry:(id)arg1 {} // Your data in YouTube +%end +%end + +%group gDisablePrivacySection +%hook YTSettingsSectionItemManager +- (void)updatePrivacySectionWithEntry:(id)arg1 {} // Privacy +%end +%end + +%group gDisableLiveChatSection +%hook YTSettingsSectionItemManager +- (void)updateLiveChatSectionWithEntry:(id)arg1 {} // Live chat +%end +%end + +// Miscellaneous +%group giPadLayout // https://github.com/LillieH001/YouTube-Reborn +%hook UIDevice +- (UIUserInterfaceIdiom)userInterfaceIdiom { + return UIUserInterfaceIdiomPad; +} +%end +%hook UIStatusBarStyleAttributes +- (long long)idiom { + return NO; +} +%end +%hook UIKBTree +- (long long)nativeIdiom { + return NO; +} +%end +%hook UIKBRenderer +- (long long)assetIdiom { + return NO; +} +%end +%end + +%group giPhoneLayout // https://github.com/LillieH001/YouTube-Reborn +%hook UIDevice +- (UIUserInterfaceIdiom)userInterfaceIdiom { + return UIUserInterfaceIdiomPhone; +} +%end +%hook UIStatusBarStyleAttributes +- (long long)idiom { + return YES; +} +%end +%hook UIKBTree +- (long long)nativeIdiom { + if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait) { + return NO; + } else { + return YES; + } +} +%end +%hook UIKBRenderer +- (long long)assetIdiom { + if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait) { + return NO; + } else { + return YES; + } +} +%end +%end + +// YT startup animation +%hook YTColdConfig +- (BOOL)mainAppCoreClientIosEnableStartupAnimation { + return IsEnabled(@"ytStartupAnimation_enabled") ? YES : NO; +} +%end + +# pragma mark - ctor +%ctor { + %init; + // Access YouGroupSettings methods + dlopen([[NSString stringWithFormat:@"%@/Frameworks/YouGroupSettings.dylib", [[NSBundle mainBundle] bundlePath]] UTF8String], RTLD_LAZY); + // Access YouTube Plus methods + dlopen([[NSString stringWithFormat:@"%@/Frameworks/YTLite.dylib", [[NSBundle mainBundle] bundlePath]] UTF8String], RTLD_LAZY); + + if (IsEnabled(@"hideCastButton_enabled")) { + %init(gHideCastButton); + } + if (IsEnabled(@"iPadLayout_enabled")) { + %init(giPadLayout); + } + if (IsEnabled(@"iPhoneLayout_enabled")) { + %init(giPhoneLayout); + } + if (IsEnabled(@"bigYTMiniPlayer_enabled") && (UIDevice.currentDevice.userInterfaceIdiom != UIUserInterfaceIdiomPad)) { + %init(Main); + } + if (IsEnabled(@"hideVideoPlayerShadowOverlayButtons_enabled")) { + %init(gHideVideoPlayerShadowOverlayButtons); + } + if (IsEnabled(@"hideHeatwaves_enabled")) { + %init(gHideHeatwaves); + } + if (IsEnabled(@"ytNoModernUI_enabled")) { + %init(gYTNoModernUI); + } + if (IsEnabled(@"disableAccountSection_enabled")) { + %init(gDisableAccountSection); + } + if (IsEnabled(@"disableAutoplaySection_enabled")) { + %init(gDisableAutoplaySection); + } + if (IsEnabled(@"disableTryNewFeaturesSection_enabled")) { + %init(gDisableTryNewFeaturesSection); + } + if (IsEnabled(@"disableVideoQualityPreferencesSection_enabled")) { + %init(gDisableVideoQualityPreferencesSection); + } + if (IsEnabled(@"disableNotificationsSection_enabled")) { + %init(gDisableNotificationsSection); + } + if (IsEnabled(@"disableManageAllHistorySection_enabled")) { + %init(gDisableManageAllHistorySection); + } + if (IsEnabled(@"disableYourDataInYouTubeSection_enabled")) { + %init(gDisableYourDataInYouTubeSection); + } + if (IsEnabled(@"disablePrivacySection_enabled")) { + %init(gDisablePrivacySection); + } + if (IsEnabled(@"disableLiveChatSection_enabled")) { + %init(gDisableLiveChatSection); + } + if (IsEnabled(@"hideHomeTab_enabled")) { + %init(gHideHomeTab); + } + if (IsEnabled(@"fixCasting_enabled")) { + %init(gFixCasting); + } + if (IsEnabled(@"fullscreenToTheRight_enabled")) { + %init(gFullscreenToTheRight); + } + if (IsEnabled(@"YTTapToSeek_enabled")) { + %init(gYTTapToSeek); + } + if (IsEnabled(@"disablePullToFull_enabled")) { + %init(gDisablePullToFull); + } + if (IsEnabled(@"disableEngagementOverlay_enabled")) { + %init(gDisableEngagementOverlay); + } + if (IsEnabled(@"playerGestures_enabled")) { + %init(gPlayerGestures); + } + if (IsEnabled(@"videoPlayerButton_enabled")) { + %init(gVideoPlayerButton); + } + + // Change the default value of some options + NSArray *allKeys = [[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys]; + if (![allKeys containsObject:@"YTLPDidPerformFirstRunSetup"]) { + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"YTLPDidPerformFirstRunSetup"]; + // Set miscellaneous YTLitePlus features to enabled + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"RYD-ENABLED"]; + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"YouPiPEnabled"]; + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"newSettingsUI_enabled"]; + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"fixCasting_enabled"]; + // Default gestures as volume, brightness, seek + [[NSUserDefaults standardUserDefaults] setInteger:GestureModeVolume forKey:@"playerGestureTopSelection"]; + [[NSUserDefaults standardUserDefaults] setInteger:GestureModeBrightness forKey:@"playerGestureMiddleSelection"]; + [[NSUserDefaults standardUserDefaults] setInteger:GestureModeSeek forKey:@"playerGestureBottomSelection"]; + // Default gestures options + [[NSUserDefaults standardUserDefaults] setFloat:20.0 forKey:@"playerGesturesDeadzone"]; + [[NSUserDefaults standardUserDefaults] setFloat:1.0 forKey:@"playerGesturesSensitivity"]; + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"playerGesturesHapticFeedback_enabled"]; + } +} diff --git a/control b/control new file mode 100644 index 0000000..661e697 --- /dev/null +++ b/control @@ -0,0 +1,9 @@ +Package: com.yourusername.ytliteplus +Name: YTLitePlus +Version: 1.0.0 +Architecture: iphoneos-arm64 +Description: Custom YouTube tweak with themes, settings, and UI enhancements +Maintainer: Your Name +Author: Your Name +Section: Tweaks +Depends: mobilesubstrate (>= 0.9.5000), firmware (>= 14.0) diff --git a/lang/YTLitePlus.bundle/Back.png b/lang/YTLitePlus.bundle/Back.png new file mode 100644 index 0000000000000000000000000000000000000000..4719983941fb15369e7338e8e83c4d430aa73a9c GIT binary patch literal 3734 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 b2mk<_V!w^HLATrcVE}Cnef4Tp`>6i`bRXMP literal 0 HcmV?d00001 diff --git a/lang/YTLitePlus.bundle/YTLitePlusColored-1024.png b/lang/YTLitePlus.bundle/YTLitePlusColored-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..879a0b1ba3605bdc16226f7d7a53fee9c0dfabe7 GIT binary patch literal 10981 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 zcRijB1^@s60000000000000000000000000z&86Iv3mFYN+*+_00000NkvXXu0mjf DV!tr# literal 0 HcmV?d00001 diff --git a/lang/YTLitePlus.bundle/YTLitePlusDarkMode-1024.png b/lang/YTLitePlus.bundle/YTLitePlusDarkMode-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..cf0f7705de8193fb1adb574f7059d4a632ebeb25 GIT binary patch literal 11289 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 S2X;}@Ueo7HJLfLi{(k`6T*EH_ literal 0 HcmV?d00001 diff --git a/lang/YTLitePlus.bundle/YTLitePlusLightMode-1024.png b/lang/YTLitePlus.bundle/YTLitePlusLightMode-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..3c5093849840d6110723f4d1a8fc1c0d9ba8020c GIT binary patch literal 14797 zcmeHtc|25W{Qom!>}h1bTE;C~qEt#DGl~`=x|EXTs_a{(D9fDGZI6mn6cdS1*^&s; z;tE#?*_X0s-^0wD-{(yC`+fcX`u*|y`_Aj$SI#-l`8=QX{aK!K)zV^@gqWfjLP%oI zZsYw3QQ%Js5)p!bn9J8(fPX~&b{{&0khnDQKMBP=RDheLQ~P&qM=w4v8z9!Z8krj* zlo=t;`BM;~t3YR9 zOP2rQ;Ue9Ey>asm2OX0LcK`eRr-A=8@Sg_$e>A{JxreaSo26aDWfe0Tqr=nZ5-G@f z4?>Lf(PqaMbl(;YwI4Wa=XLWd&D&M-&5aGv-#amRGHDaG&OD+;Nmk>bA{C_Iz-K4_=+s8uD2Z9qZ{ zqh8gX>!ERvEUR8RSGBkJyb-Q*)tQ_OW@#@h1W$i-`>S||EQLV=n45KaVO>a{)mxYx zqHLRW?PUrz`e(mmyYI&wL>w4Qa#^4)MrO^vfglSYpmCx4FA}3rDc>cX{W%~6?Z|+=E{JLXQEv4?YV4|qx z&Vf?QC=gkjAmsJ-fz4d#e9^;D&)#!Bo8roY7iNY%x;10EbIn87tzGLgU6o%J6wP(b ztLU%`ixm<}D`Ex(EqJu{@VzhGCL_Pj?HS;3D=I2zs?XnC9nC6!JzTYY&&hY}RMLCSLa)Ge8{Q3%@LkjP z>e}47lk?3-XwTN76)_0e7P!xa7(5txI;u9qojTgIO;=lgb=;#ZRSxlwu2k)N;mY(a z0Nrkuyib*MVa6BFP7Ty1F)wF)v)JjbDzm%6=-t`*Rm#BBrX>jJZq|Jl5g-4V+kW;< z+1${pr&GZ-7lY4i_f=V@Ugo86YvJtXEpG4sI)eW6ED6{-5OJ$>a%eOX>og#x??&jD z`QE98fphzDuim~N2S;N?XE~leUslY@nYWT`BUBAO35r#EN~``OEU{cvD(2exz(TiB z`Ne3Gf~rr{Ugi1BA5G{;|1QtX(a}HatJ-hw&f#3+aExi#yp9n&n4!H?>7UC&cfGu? zNIFHmSR-r^=`C38PDcH=297ary-@o}Pugxeu2Fv6P^l-~`$|*U{M_7JK-%QwOJsSc2~!I73GEom zaEjuIP%OM@n4TzNt=!ndVLrIQo!TwUYq}beeYnG{Ki2cW0Lf`0sjy%nNWGfVN)fCa zjZiL1*{9ak;J;kthLk0^yNr+CKtvg@Vro}pQBZyQKc#M}v)LcISD>%SqU0ElFxRTM zZ95r$dcPGb|A}PG5&A2}j3Yd7?h7pv51N~aDB}za4A`6vO>fmrn=PLpIc@w$NMMPG zf#nwB9kVd(14Ra$Rq(-!nc@dGF68{Zhq-;d7^ zz8v`Zv+*JgHZ#{WJjiaa(Hy>q}DyL`X< z5(4ck>$wgKgINbK9ZKfi_5uwd3fq$*yeg6D8eSRrHxu$7JW%$7% z3CU!&X=&sYGcH!i^Z&`0_CwwZD~UH@>$byNfB(#{#mlmM*QwxqwiP-eWM5N6NKO*x zMDrr=bM`q{s2&Zd3EG)xb#DB-i5{K4520XRDe{xV#KfF}f|i-wnqx!L=L)UJKH#ze z89%z2zP_VBWD!=fm`bg!xVNsQeS_(Cl=q6R(z*7q8lL$bJo!{46JfG%&rNF-GV!8e zEpFMC%;g9Z-sJFQY_z|BcQ&0b_zk-oU_mv6V*i}@Yx42%($(Rw_WTu6qsZsg>x=Dr z%9Y{!$d9h-s55HD`=zZBX1XN#p)$&&My`9HtNO28RNVt2Bruk3a@oDT%ne0r=X#zAyQAcQaQDRi)b`if@Skf{9ku?gAx(~MQX?Wzyl(gq1w&1UQJERaAoE`5fo$9TO z&3*lG97`6p1&myCe`MX#?vWBg=qf8Fq_?5|e)Zwe(RKOD*Q~En`<<;$Iz&dc&Ku^K zx@Uju9VxlSOy|UM1W;=+1c(30lXuPp1jrZm_)DQ76{W#_qPA`u=4ej@Bk{5MX-`5+ z!cy1Qt#f$m7Q|C@6BD^Eik!|56yfpmZA_dOqGxDmfsnz&`<&91X4fwatXz!JT_#G# zClfYjtEs6iZCl;&OT3vAety+wG}!mb8ZFoXQwY0JCjWO@j?=VbEgfN%l3XNo;ax%t zEvT@)g~9=vr=pk7$uE44;t3#wqR`@a@T?ax0&6?n8229wBaZsw`@?-c!ShpP-8AI1 zBfXl+*a1(-$i?g+UJM$MH(NF*-{wj}jmv2CP&425w}<=}LS8m3Rb?QQtx94yb*ltr zcnvMkw`~K2^onjIkM&gCGqEE%PrJBEk!?#JPkNECXYO6aL*4k;ST%nilfQafUW+eq zO*EX=AZ1xJRhT%g;5-#sUQ$UWyNT@Xdxo&7g&$0Mz$kZNRuu)8-z3Fh92@CG!I)0yO=Dzxvu572qBsD=fAPMk^fbMSv&}3yG8_W<$GP7 z>z<8lE@k=eh~=5pgBSDW2kYep5ITLWuTb!z5W^K@h%p*Ts0RV^UoD$%rA3s&awWvt zs7f0uh`bj%64wRR`Qfq@!P010Is%t%V=cbLTKUJ9qDQcFZ)mx_*W5}3*;-2$6^QCF z5aZq=kP{a`{yPk2wcI#ETNrvqh@qyb$MY?7&_+n(RA^{Xf3yZlRYR;bS+ssFN2sZ- zKb^q*A0X_-rxDzDjtmsHgK0L9kp22k_SR7DR%`8@MEOaEH43w_>YMB66)2M`k{NE`Bj8FDefjf=39}-biPPvb;wVzbqXuyW~ z&yI^TmV`isDC91y&CLWo2y)ekUwKD>QqLlDu{KaiRvc{`?Nh>qGYcO*B4q% z7H|^EH($lb-;EfrRLi_)F6bK$*}x(etXYLvF5FfVa849vi2QFNEarG*OI17_05(Gq zd$9yrX2+kDCzeY#E0R=GZV@XAUK4CHuJpa{r{2xnwjyFdR0AlP)+X9zCD{>zx-|+h z?g8KR@5}J8D0ze}_86en@3Z_3_%4se3D?rD*u!_A!&^boSdtfswU>hGG-4Z@aVlbK zs*$Qy$jGC<%HAIggR&n|_yq65tOnU$1>gG%Y+`n&c}V!QazP9tBkd}n2R9Xu??V|7 z`wWY{f(*Km2@qAcBl(>@`vAI!I54&(vP=ZCf$yV}hvTH)|kOVwvwn8FyJyAWLl(->Dc&0TRxGL61eC9Gr%*&Ee!H;;^e~%M%kp z;L7_j85|(h-Q8g z8Pfoc_s|1s09;U7waBezQMlgxpz!TmGcc^#UNoj-BnVq77wD$-Tv&!sXhYWNoOl)) z*u3)`NdozdYthdWxfEGoA=35Db;1Y-YW9;@qI|BlCG(dA`WzV>$^#Gh3(4$54D%_3 zzKo3coSXh(L&jRFg|Qge`05J8Xl`$}pyQlV^AkOLn>#x8AwOx5+67dp4es-1upEC} zFwYIu)6>fr6Zr0mY)jymV3&)C;`&uTU$@Ts3~Pb6L9-*NdI&W)k{OjC!O8{emt^F& z=<)~uGiRQHxX%yE-HKCHe~7TdQ>O^e&30bHsHL%U33J0EW&WdMYtZ2tKhko!NL++8 zC5)Wfp*KhX-&F?}Azt(*q2`ZgutqsaE(s~+7|=bGcZ?JJ#eQlgBTea>q(nNyn$k~2 zm?~Chh8S8pB+AH>0jl50lg@UCak*r&E5Y@X$*z4nRuiu+MJWjRcxWrY8`US|Zl~P+ zwT~cTdzYNSQiQf_*|Hn?@2%K)jBfOff@FLRR;MHx3d20GzzF3%#pA>opxFH~m*cYy70Ie&j2;i>}V&*yQ&5ADwB;0>w$CTg|9?BI& zbnbtUF|E5#3_Di?PAVRob#8(;FMad74+R3KR{j1Th>(^b z=oQt(XCNwB3n@gDZb#PuwS>sXX;n!#eLDhGmmghXT`D9-Mw@r+H5sU-`~}?P z^MjCOY%b{pguAknq7+4ux_vHXaAINuDM}+1B`9`&xJ(U0*sd>9Xi8ef6fp+CBT`TM z`LXDTy~_P;T%7>sOuR)8utKQLIhW3cP$75z=|KsTrSYYO0!DH1%E0Q(P2lm^bS=a_ zV8I=P`tP_cR;WjyF#`n>GWl012u*o#zb%_B!N}7Pu&^2us}+f0`dp6n-5F^aP;Tbm zT_@-PZ9nL28kZCv9_}TM@{(YdL4lcq<%Xn#Xz$SI=pYsO?TtbEmy(c5Md(;6LO-p3 zd1yVLz)b)Qy@Vz?u$t9xh@lT*4ew@wVhazW;gup++6m9h@f1b5QpA?0i$b|E$aZb6 zfbk~AvZd-G2%85H4}ta|W-32so(EAlDM_!b=|<;4eaT7I;+UYzg22$oh$>C?ua5ZQsxCd#aaxjo-Syxw_WFU!1@7b}k)eh9rT82}fq%6ZvyuS)Fig9~ZABO-C@9JdU|^SI3+a0!`@)3_6w89> zANxtDgisC%jDJ-OqtXn4JQ5^sJzK+GDn+r zfNC0aa>N-&-a&IF7BJeLH0&+z!q^ndvRXz$Nk96C$|K{=r4N7V>Fd|hkh*EbWD<^?|1oCgyN2Hs(5gWe&vzs<|rjiS8ypy+iY`VRyVxCJ%^ zIQU;kWVeC2u$!{7h-MfJVg>40kXWkl{#`EHdkzy~Ihiw23mLl%Mkj8ofUC1o$X7ft zzAo97;LzB2V@Ir?3bBjQEkI3K_6H#``0vWrQqmxQ??%`{^+_=(7XAcNmdhdZ!dOz0 zA1Z{KB1`ZAc0s_X6dGGaT;BcW*WbwKG`dlGRE=aQ1L~akQ=Gj$n@fVQ7)ec{Vy^qI z5sGS}wnS51k)FCLzJyFzA4FpbIP0^UnjU~+h@JxVUmPCxhJf$a0XioaDWTV2ri8cX z>1Aml+dUPVYeU1r!0vZJ2K}avjs_Z3+%U#NkK|fN78Vw;A?qALby&HP3(1_#9|3()Z%Ejw=aUdUY6CNl5ZhF6A%cjq`j2Kpwxth1>cT zno~f4E{X3?Xw4O+&Y>YQ0)j!eDDON#I72sQD&~ipI#fpOosc+?Cdlo?Ny)}tIg;q;d7XI{gg1oDOS1?-Vao& z%*9efu7FAccN6;y%Na4Igox*tiTwhVVgFPjUWdIaKa%nsi8Z_AN`>McJsN=evkUh6vb4V*bIw58I1Ef?h%WoK{Vd zL@cGuVwhvBD-0Ze-%Z1oS`q$_sgFm5@T^9t=r!RkjeeCMI}GvpkGF4fL+SOJyh7O{ z?_3BS4AoZEChwDmGP9fuGr;F{#m?&-i_9*(Q-W1os8)zoD=TQoT1(Yz{dfcr`)VT1 z05vU1`cl)xbagH5rfvQ-Gc%K5cTZF-0mOC^fC0m5iOV|{IDZPfq98|UzD>riI498$ zaI31Sq*1FHFv(t(QAHy#0Zh%X`cCWNg-;)S@N@P_6mwUCF|RUKSG3WKP^0k4z`#J8 z5#R4s8Y`7GdPF{j7Oz7Xd5a(>bv!;M&fx4F(^w<#-zUM~V_bd_?$t=`_= z0D8)mnW32z)Tp!`8Kiyu>e1&QEg_=wu&RF*32B0kd&kjb%hkTNTBu1+>X~d z2Ck;~ugy%>e~Y+Uyr0=G2g#`#I&G(`>f^)|sJW?W{hTn%Rs(iC+}KBDXaThC1_&Cl zPoFAHEtX{xvCi63=rGz6Xu$iH52`qQC4_e(y<>d*1X;w+&hEO;YKa7OLP!3ERrGQ$ zOjuJ|X+OiE+}#dT{oB8N`)2c|_$U{pKx>8iHG$E7sZz8*)oR4K7)tyMv#6iNp~zR4PbWVF3)!QHQ4G!mLR}aGbqRA8FLYIr1?-(Kg&8D z3refLB1KHxp7vG6xAak4_f)LMEM@Ef7H`HPO_>GG@hO$FE&lXKR-T`SGF>b?4D?r&X z1aAq(5AcQA(Yp%*Yze45?yf*+x%2|3%eJ_ytBW#bY2dgYvUCz4h5tp^{;0>$GsYesp?>0-DXo;yBrkuoV{PX8<@LL0zM1d}ha#S7*{$Ei<>RdwP7qwkj z*hf=l`h0wRyc%{VM^pQz6rYLKrC?U4Q^XoAC@wUCVrxfcC}ZGojM;);B4q1pb%WDI zaL!0!_ii;Xc&jn1J1*>zQCL{mA5GnQtxE=_)=vB`!QOUl0}eOS?hvD}Mgz#yo6GHG zU{aax63;Z_4NnVuB$Bb1MsT>+Yau*_Jd~|4UxGr+A98goq4Ts5UgkX2a${9=-A4!$ z2<||qN<&Oz@v=*rmTiRf^7&;09S)s{7~T9yrCmYRj5e|{vq~DKe%xm{H;GG zw73{JG`^+`s?JJirROiKnHK(e9`P&bLo*j%t2`5Q5`_2;pAyLbEQ;!ON;6D*FUULH zj0xevz!2hh4`PH^LlHY9Jjw?;-ANb<>MUD446X;`A@v8z){yx49{!y=mKgm!DUl)w zmG>jHeR0aDxx|p~)r2=8{|g{181r>*t`s`^FJcMUzDOc>t|aQ)D9wfL<}c}jtXi0% zzbxl29~4GyFyc8zhN(FOxS#$xWC@jbJyZuru1n#KeY7<=QE^zFkq82jb0ptc7UIObVbS#;2ycY87cMit@qSsy`l8H^b z!6{hMRcJH#-NZGQcpS!Do8|-p2_GPcCAON7P_u8<4SDNZzIs*NYMU^ZI8~bn`cOmU0I16^U zX2QT7rhSKB`xh)YZx-GLyFQoFn_eZ;~JgamBBK; z2>}hcI-a>~FaYuz3N(O(t#0%fw)TlC=$An(9ln}RJc&Qa#7Y|`44#Q@Dgo>0ZG(bw z`HB?|cNg@(QduJV4-;J~R52{unK=QkwE%I%t`$NEW(u(o5l7=;f-jKqAU8Kxt_`@% z@MNShx*dvRL~d1%Mn;BiWRR)5_s-Y8?$i$eti zaVA!?-|7b`(iz53Lh}%9ga4QQPo^N&PH14>y*EVTof2aTF(>gYU#X^Yk*v11wvXKY z@~{#({7Z75!_8sDI9uDL@7uCid@Hofezyx!$S6>g$oC(eEXD<^T%+ zYP~fX>VcEsa4%bF{9-S4y1Oir;^+9wQO!9dZ2uOaTZ%XMbHv%-%Q;=Ui`D0J=Y)}I z0vRLjPebBgJu_Fz&!tDMOPbYIEc|i!Qwp_wzGTV6IQK)4(l>ny)&KkUMig*7p00T& zI5^lwLSV_Aou_36r+X^oNyrmy6!M%3q1- z{44jFxnh1ngHkTR6sA!KwH~rw1m`dSL&pV?$5C}SG&6;!jnoEYnAN=ftX{{#d<`P-@do2|{wrYQS=v4W8d29yI> z-W&2i6$}Zp0C&4?HIjgkNah#F7=3+xa0;D942%jMLLm6ZhCrjhtQNpDrr}@;O9_N z_WxYMkdUYk5d$huBIh38j)|a)K#9>n^!kzT7uo<+R9l4ktpwyq$X3lqde_4A3@#oK z^UpQV@4-brv}E2eIYj6a)Pu2Jg6wS9D#)sgdqApEJ-`ae7CukbNezP~au_X_WQ?4a z!p-XAz?lx{A4&)Q28tn{?OWU0;PeRDgFozSiUb+|rDQduj6Hw;oR9!&WREBbINve? zX8H+dF83kve346YN=7;{|K@oSQK3vK&rGv8mZ1vVc=7mZVZIfB4A# z`E&f}{LHYOhLQE570Z?>e_g{H6&nO}b*T~V?BSR|Ah6FFBUaW3Z`q0!a9n<#@|D1Jf= zy{ecY{TxV+mqHn>0|V~oxbvsKhZ^^Bnj_T1BNsti5WBgu1Lt-m7cUePFMOM4^7@#( zS^Q@N{*&hZfjd{lM|m z?FBwq%p07e^SbD~*Ew?()OueFS$kWpVsA_ThFp*sQDfCtv z2-*Mi$Yim$DB>Obb#znPTH=-`T}V8MhEo!JxPv!*ig=Xb@Ur2$Dwfz<$mMb&;RJka z$4yA=VopUdgs?|A>B*YVM>ljQ8B$Ve}-VK8K`_!oPKI!4>~voAZ~P&y=rB>?4zd2T^k z7aVc?_?k65>7Q>&sR#0#F2A{cTf|dH1X;d}zE8(3&ek$M@Nds&Q`WqF$#9zY(7ik;reJoYtpJp<&3S_M zJDUvL8pA2&@v3t-#jxmgP<4ydX0cDDaN^eYt0dAG` zYKR+AkAI*n2ipv04ZI|7o-Fx+ryYDL*OogQ|5*xEyVn*vCcg>dlh5gP>x0!Q2tg5w z>fzH2Z#!3V=W4RQxaFtGm}GRK^A4}At-H&*t8 zZm)*0ItM{XN97CeK$wg-&fhz}&e59|sAP11{9Mh=cqR{w4EG0JDt_QWX}A)?I?Rh@ z40}dO?v?p%0SkM@1s?EpZ#RfB=v%JJfaCZ}1Nlb(P!pbL=9iOUdJW^CW}o8SY*`Ze z=l+uq>*rnru&^XEch~NprlUd>mpjTihY}!*5*D}XBP8i zM>k0% zB;ynV5>^QK9#Om6SmbKYUETx>V$6!744)4EjdjQsx@s%9Efztyc#We>zYKqUJSdWB zO9dU)=(K!fe zDZ~)I@#SDL| z7;in~M379k&Y|hcyFeMcHa`~%+%z|S_V8mxCFle+NGnC$6ovji0W_BWn9lityOUSk zgkin$!vOs@xiP)x3Y3fQocwGg#-;}w{Qdov7r{ew%`?kXJ8QsMKYWz$_720KfX9Or zB=3?=Jn1CLJ2-2BkLRAa?+@LwBEr$!p~HM9y^Ucl^id?SM*UHId@}J_-;e33DHt=O zwv{kq)0L(yQhoVSwd;8P+-g>AASjZZ2sx^gJ!M*NrM8UiO{v?!CZ0>6_nmjwoeOa^%x z&B!R_qw=CHac@8Ft}VwD=;n^2=F7I;7Z-BbL^8V3=umx8u+no8e1suV*Hv(8Mz-_u zl$2%R8-~(n!MBrV&YzBz5R8e3GN4#o#AQ@~{q%l(U1{L_)W9fRch^L_e^zmJ_8(@u zSKw_$!E;&tGj8WTt{?kk4G!~faeL)yA6X!RAQ)0dYR$1GR^J!K_NU>Y+-|bnL|%sljs7+l*0)k zq)50ZJ`k47^xvARIO4a*1E>l)}Gw}1Je(_09z2C8iMzimVzX_VOqc!u^c7Che z7w0=4r1Pwz&2FYIFWa~Fgki|?CyPU={CrtZchl0f2HkOA z=NL-zE4yD*9?;JzTsK>lH+*u+}X_?=M-x z7rd_X8TAIi&%K&ovP1>n4~?$R{Sxu*-O$3bEA`eX2PT&<*?FqiF{ib7^4JQeT2UBf ze!A<>Zmi1q^cTh4+n3oJF|)A&{cKmCJT=*z&aQHZrCu=aBt-*Ur}w`gZ9nQFzN5dY z)gv^H`djXX2Kho-r(xxbxXV_(^FznDEnyHyr&Na#br<9zwo9(|MXSTv?n&Dd=>Y}9 z?Ov`~M^dUCPtpQyCnLwTJ>D=MOhnGMj&^XjsSY4Ga6r~BFZTZ#wc^u}G0Bs^zsqv^ zb0);@!Qh+O_Fg^BjfefV=??e=;U(8bmuoHbrKgFBVk6L#WXL6%TWmOAl5V51Y~KA% z>;Y3vF+(2@ds)Tnss*~6iYMN_V2)(XY8Cn(Uy7{-)x%URuFV>cPQ~?q!#9#5M)pCX zKq=sQ@PiMrh#!2;1wU*tFa>3%Fu*GS36z-s{r=Oye;W8t1OE>VRIkQqtj6L4nfDIE QffPb}Oe~CFY.lproj id. +For example: for Russia. For more details, visit: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes +3. Translate each line below after each = +4. Open a Pull Request + +- You can also add your localization to YTABConfig, YouPiP, YTUHD, Return YouTube Dislike. The process is the same: +https://github.com/PoomSmart/YTABConfig/tree/main/layout/Library/Application%20Support/YTABC.bundle +https://github.com/PoomSmart/YouPiP/tree/main/layout/Library/Application%20Support/YouPiP.bundle +https://github.com/PoomSmart/YTUHD/tree/master/layout/Library/Application%20Support/YTUHD.bundle +https://github.com/PoomSmart/Return-YouTube-Dislikes/tree/main/layout/Library/Application%20Support/RYD.bundle +*/ + +// Settings +"VERSION" = "YTLitePlus's version: %@"; +"VERSION_CHECK" = "Tap to check for update!"; + +"COPY_SETTINGS" = "Copy settings"; +"COPY_SETTINGS_DESC" = "Copy all current settings to the clipboard"; +"PASTE_SETTINGS" = "Paste settings"; +"PASTE_SETTINGS_DESC" = "Paste settings from clipboard and apply"; +"PASTE_SETTINGS_ALERT" = "Apply settings from clipboard?"; +"EXPORT_SETTINGS" = "Export settings"; +"EXPORT_SETTINGS_DESC" = "Exports all current settings into a .txt file"; +"IMPORT_SETTINGS" = "Import settings"; +"IMPORT_SETTINGS_DESC" = "Press to import settings (.txt)"; +"REPLACE_COPY_AND_PASTE_BUTTONS" = "Replace 'Copy settings' & 'Paste settings' Buttons"; +"REPLACE_COPY_AND_PASTE_BUTTONS_DESC" = "Replaces the Buttons to 'Export settings' and 'Import settings'"; + +"VIDEO_PLAYER" = "Video Player (Beta)"; +"VIDEO_PLAYER_DESC" = "Open a downloaded video in the Apple player"; + +// Player Gestures +"PLAYER_GESTURES_TOGGLE" = "Enable Player Gestures"; +"VOLUME" = "Volume"; +"BRIGHTNESS" = "Brightness"; +"SEEK" = "Seek"; +"DISABLED" = "Disabled"; +"DEADZONE" = "Deadzone"; +"DEADZONE_DESC" = "Minimum distance to move before a gesture is recognized"; +"SENSITIVITY" = "Sensitivity"; +"SENSITIVITY_DESC" = "Multiplier on volume and brightness gestures"; +"PLAYER_GESTURES_TITLE" = "Player Gestures"; +"PLAYER_GESTURES_DESC" = "Configure horizontal pan gestures for the player"; +"TOP_SECTION" = "Top Section"; +"MIDDLE_SECTION" = "Middle Section"; +"BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; + +// Video controls overlay options +"VIDEO_CONTROLS_OVERLAY_OPTIONS" = "Video Controls Overlay Options"; + +"ENABLE_SHARE_BUTTON" = "Enable Share Button"; +"ENABLE_SHARE_BUTTON_DESC" = "Enable the Share Button in video controls overlay."; + +"ENABLE_SAVE_TO_PLAYLIST_BUTTON" = "Enable 'Save To Playlist' Button"; +"ENABLE_SAVE_TO_PLAYLIST_BUTTON_DESC" = "Enable the 'Save To Playlist' Button in video controls overlay."; + +"HIDE_SHADOW_OVERLAY_BUTTONS" = "Hide Shadow Overlay Buttons"; +"HIDE_SHADOW_OVERLAY_BUTTONS_DESC" = "Hide the Shadow Overlay on the Play/Pause, Previous, Next, Forward & Rewind Buttons."; + +"HIDE_RIGHT_PANEL" = "Hide the right panel in fullscreen mode"; +"HIDE_RIGHT_PANEL_DESC" = "App restart is required."; + +"HIDE_HEATWAVES" = "Hide Heatwaves"; +"HIDE_HEATWAVES_DESC" = "Hides the Heatwaves in the video player. App restart is required."; + +"DISABLE_AMBIENT_PORTRAIT" = "Disable Ambient Mode (Portrait)"; +"DISABLE_AMBIENT_PORTRAIT_DESC" = "Disable lighting surrounding video title"; + +"DISABLE_AMBIENT_FULLSCREEN" = "Disable Ambient Mode (Fullscreen)"; +"DISABLE_AMBIENT_FULLSCREEN_DESC" = "Disable lighting surrouding video player"; + +"FULLSCREEN_TO_THE_RIGHT" = "Fullscreen to the Right"; +"FULLSCREEN_TO_THE_RIGHT_DESC" = "Always enter fullscreen with home button on the right side."; + +"SEEK_ANYWHERE" = "Seek Anywhere Gesture"; +"SEEK_ANYWHERE_DESC" = "Hold and drag on the video player to seek. You must disable YTLite - Hold to speed"; + +"ENABLE_TAP_TO_SEEK" = "Enable Tap To Seek"; +"ENABLE_TAP_TO_SEEK_DESC" = "Jump to anywhere in a video by single-tapping the seek bar"; + +"DISABLE_PULL_TO_FULLSCREEN_GESTURE" = "Disable pull-to-fullscreen gesture"; +"DISABLE_PULL_TO_FULLSCREEN_GESTURE_DESC" = "Disable the drag gesture to enter vertical fullscreen. Only applies to landscape videos."; + +"ALWAYS_USE_REMAINING_TIME" = "Always use remaining time"; +"ALWAYS_USE_REMAINING_TIME_DESC" = "Change the default to show time remaining in the player bar."; + +"DISABLE_TOGGLE_TIME_REMAINING" = "Disable toggle time remaining"; +"DISABLE_TOGGLE_TIME_REMAINING_DESC" = "Disables changing time elapsed to time remaining. Use with other setting to always show remaining time."; + +"DISABLE_ENGAGEMENT_OVERLAY" = "Disable fullscreen engagement overlay"; +"DISABLE_ENGAGEMENT_OVERLAY_DESC" = "Disable the swipe-up gesture and suggested videos list in fullscreen"; + +"HIDE_COMMENT_PREVIEWS_UNDER_PLAYER" = "Hide Comment previews under player"; +"HIDE_COMMENT_PREVIEWS_UNDER_PLAYER_DESC" = "Hide comment spoiler in comments button"; + +"HIDE_AUTOPLAY_MINI_PREVIEW" = "Hide autoplay mini preview"; +"HIDE_AUTOPLAY_MINI_PREVIEW_DESC" = "Hide the small suggested video box near the title in fullscreen"; + +"HIDE_HUD_MESSAGES" = "Hide HUD messages"; +"HIDE_HUD_MESSAGES_DESC" = "Hides snackbars that display for certain actions (e.g., CC turned on/off)"; + +"HIDE_COLLAPSE_BUTTON" = "Hide Collapse Button"; +"HIDE_COLLAPSE_BUTTON_DESC" = "Hides the Arrow Collapse Button that was shown in the Top Left of the Video Player."; + +"HIDE_SPEED_TOAST" = "Hide Speed Toast"; +"HIDE_SPEED_TOAST_DESC" = "Hide the 2X Speed popup when holding the player"; + +// App settings overlay options +"APP_SETTINGS_OVERLAY_OPTIONS" = "App Settings Overlay Options"; + +"HIDE_ACCOUNT_SECTION" = "Hide \"Account\" Section"; +"HIDE_AUTOPLAY_SECTION" = "Hide \"Autoplay\" Section"; +"HIDE_TRYNEWFEATURES_SECTION" = "Hide \"Try New Features\" Section"; +"HIDE_VIDEOQUALITYPREFERENCES_SECTION" = "Hide \"Video quality preferences\" Section"; +"HIDE_NOTIFICATIONS_SECTION" = "Hide \"Notifications\" Section"; +"HIDE_MANAGEALLHISTORY_SECTION" = "Hide \"Manage all history\" Section"; +"HIDE_YOURDATAINYOUTUBE_SECTION" = "Hide \"Your data in YouTube\" Section"; +"HIDE_PRIVACY_SECTION" = "Hide \"Privacy\" Section"; +"HIDE_LIVECHAT_SECTION" = "Hide \"Live Chat\" Section"; + +// Theme +"THEME_OPTIONS" = "Theme Options"; + +"OLED_DARK_THEME" = "OLED dark theme"; +"OLED_DARK_THEME_2" = "OLED dark theme"; +"OLED_DARK_THEME_DESC" = "True dark theme. Might not work correctly in some cases. App restart is required after you enable/disable this option."; + +"OLD_DARK_THEME" = "Old dark theme"; +"OLD_DARK_THEME_DESC" = "Old YouTube's dark theme (grey theme). App restart is required."; + +"DEFAULT_THEME" = "Default"; +"DEFAULT_THEME_DESC" = "Default dark(er) theme of YouTube. App restart is required."; + +"OLED_KEYBOARD" = "OLED Keyboard"; +"OLED_KEYBOARD_DESC" = "Might not work correctly in some cases. App restart is required."; + +"LOW_CONTRAST_MODE" = "Low Contrast Mode"; +"LOW_CONTRAST_MODE_DESC" = "This option will Low Contrast of the texts and buttons just like how the old YouTube Interface was. App restart is required."; +"LCM_SELECTOR" = "Low contrast mode selection"; +"DEFAULT_LOWCONTRASTMODE" = "(Default) LowContrastMode"; +"CUSTOM_LOWCONTRASTMODE" = "(Custom Color) LowContrastMode"; + +// Miscellaneous +"MISCELLANEOUS" = "Miscellaneous"; + +"PLAYBACK_IN_FEEDS" = "Playback in feeds"; +"PLAYBACK_IN_FEEDS_ALWAYS_ON" = "Always on"; +"PLAYBACK_IN_FEEDS_WIFI_ONLY" = "Wi-Fi only"; +"PLAYBACK_IN_FEEDS_OFF" = "Off"; + +"NEW_SETTINGS_UI" = "New Settings UI"; +"NEW_SETTINGS_UI_DESC" = "Use new grouped settings UI. May hide some settings"; + +"ENABLE_YT_STARTUP_ANIMATION" = "Enable YouTube startup animation"; +"ENABLE_YT_STARTUP_ANIMATION_DESC" = ""; + +"HIDE_MODERN_INTERFACE" = "Disable Modern YouTube Interface (YTNoModernUI)"; +"HIDE_MODERN_INTERFACE_DESC" = "This reverts the YouTube app to go back to a more traditional design from v17.38.10. This also removes some rounded elements, ambient mode, and other modern features. App restart is required."; + +"IPAD_LAYOUT" = "iPad Layout"; +"IPAD_LAYOUT_DESC" = "only use this if you want to have the iPad Layout on your current iPhone/iPod. App restart is required."; + +"IPHONE_LAYOUT" = "iPhone Layout"; +"IPHONE_LAYOUT_DESC" = "only use this if you want to have the iPhone Layout on your current iPad. App restart is required."; + +"CAST_CONFIRM" = "Confirm alert before casting (YTCastConfirm)"; +"CAST_CONFIRM_DESC" = "Show a confirm alert before casting to prevent accidentally hijacking TV."; +"CASTING" = "Castings"; +"MSG_ARE_YOU_SURE" = "Are you sure you want to start casting?"; +"MSG_YES" = "Yes"; +"MSG_CANCEL" = "Cancel"; + +"NEW_MINIPLAYER_STYLE" = "New mini player bar style (BigYTMiniPlayer)"; +"NEW_MINIPLAYER_STYLE_DESC" = "App restart is required."; + +"HIDE_CAST_BUTTON" = "Hide Cast button"; +"HIDE_CAST_BUTTON_DESC" = "App restart is required."; + +"VIDEO_PLAYER_BUTTON" = "Video Player Button"; +"VIDEO_PLAYER_BUTTON_DESC" = "Show a button in the navigation bar to open downloaded videos in the Apple player"; + +"HIDE_SPONSORBLOCK_BUTTON" = "Hide iSponsorBlock button in the Navigation bar"; +"HIDE_SPONSORBLOCK_BUTTON_DESC" = ""; + +"HIDE_HOME_TAB" = "Hide Home Tab"; +"HIDE_HOME_TAB_DESC" = "Be careful when hiding all tabs"; + +"FIX_CASTING" = "Fix Casting"; +"FIX_CASTING_DESC" = "Changes some AB flags to fix casting"; + +"ENABLE_FLEX" = "Enable FLEX"; +"ENABLE_FLEX_DESC" = "Enable FLEX for debugging (not recommended). Leave this off unless you know what you are doing."; + +// Version Spoofer +"APP_VERSION_SPOOFER_LITE" = "Enable App Version Spoofer (Lite)"; +"APP_VERSION_SPOOFER_LITE_DESC" = "Enable this to use the Version Spoofer (Lite) and select your perferred version below. App restart is required."; +"VERSION_SPOOFER_TITLE" = "Version Spoofer Picker"; + +// Other Localization +"APP_RESTART_DESC" = "App restart is required."; +"CHANGE_APP_ICON" = "Change App Icon"; diff --git a/lang/YTLitePlus.bundle/tr.lproj/Localizable.strings b/lang/YTLitePlus.bundle/tr.lproj/Localizable.strings new file mode 100644 index 0000000..c09ada0 --- /dev/null +++ b/lang/YTLitePlus.bundle/tr.lproj/Localizable.strings @@ -0,0 +1,190 @@ +// Settings +"VERSION" = "YTLitePlus'ın sürümü: %@"; +"VERSION_CHECK" = "Güncelleme kontrolü için dokun!"; + +"COPY_SETTINGS" = "Ayarları Kopyala"; +"COPY_SETTINGS_DESC" = "Tüm mevcut ayarları panoya kopyala"; +"PASTE_SETTINGS" = "Ayarları Yapıştır"; +"PASTE_SETTINGS_DESC" = "Panodaki ayarları yapıştır ve uygula"; +"PASTE_SETTINGS_ALERT" = "Panodan ayarları uygulamak istiyor musun?"; +"EXPORT_SETTINGS" = "Ayarları Dışa Aktar"; +"EXPORT_SETTINGS_DESC" = "Tüm mevcut ayarları bir .txt dosyasına dışa aktarır"; +"IMPORT_SETTINGS" = "Ayarları İçe Aktar"; +"IMPORT_SETTINGS_DESC" = "Ayarları içe aktarmak için basın (.txt)"; +"REPLACE_COPY_AND_PASTE_BUTTONS" = "'Ayarları Kopyala' ve 'Ayarları Yapıştır' Düğmelerini Değiştir"; +"REPLACE_COPY_AND_PASTE_BUTTONS_DESC" = "Düğmeleri 'Ayarları Dışa Aktar' ve 'Ayarları İçe Aktar' ile değiştirir"; + +"VIDEO_PLAYER" = "Video Oynatıcı (Beta)"; +"VIDEO_PLAYER_DESC" = "İndirilen bir videoyu Apple oynatıcısında aç"; + +// Player Gestures +"PLAYER_GESTURES_TOGGLE" = "Oynatıcı Hareketlerini Etkinleştir"; +"VOLUME" = "Ses"; +"BRIGHTNESS" = "Parlaklık"; +"SEEK" = "Arama"; +"DISABLED" = "Devre Dışı"; +"DEADZONE" = "Ölü Bölge"; +"DEADZONE_DESC" = "Bir hareketin tanınması için minimum mesafe"; +"SENSITIVITY" = "Hassasiyet"; +"SENSITIVITY_DESC" = "Ses ve parlaklık hareketleri için çarpan"; +"PLAYER_GESTURES_TITLE" = "Oynatıcı Hareketleri"; +"PLAYER_GESTURES_DESC" = "Oynatıcı için yatay kaydırma hareketlerini yapılandır"; +"TOP_SECTION" = "Üst Bölüm"; +"MIDDLE_SECTION" = "Orta Bölüm"; +"BOTTOM_SECTION" = "Alt Bölüm"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Haptik(titreşim) Geri Bildirimi Etkinleştir"; + +// Video controls overlay options +"VIDEO_CONTROLS_OVERLAY_OPTIONS" = "Video Kontrol Seç."; + +"ENABLE_SHARE_BUTTON" = "Paylaş Butonunu Aç"; +"ENABLE_SHARE_BUTTON_DESC" = "Video kontrolünde Paylaş Butonunu aç."; + +"ENABLE_SAVE_TO_PLAYLIST_BUTTON" = "'Listeye Kaydet' Butonunu Aç"; +"ENABLE_SAVE_TO_PLAYLIST_BUTTON_DESC" = "Video kontrolünde 'Listeye Kaydet' Butonunu aç."; + +"HIDE_SHADOW_OVERLAY_BUTTONS" = "Gölge Düğmelerini Gizle"; +"HIDE_SHADOW_OVERLAY_BUTTONS_DESC" = "Oynat/Duraklat, Önceki, Sonraki, İleri/Geri Düğmelerindeki gölgeyi gizle."; + +"HIDE_RIGHT_PANEL" = "Tam ekranda sağ paneli gizle"; +"HIDE_RIGHT_PANEL_DESC" = "Uyg. yeniden başlatılmalı."; + +"HIDE_HEATWAVES" = "Sıcak Dalgaları Gizle"; +"HIDE_HEATWAVES_DESC" = "Sıcak Dalgaları gizler. Uyg. yeniden başlatılmalı."; + +"DISABLE_AMBIENT_PORTRAIT" = "Ortam Modunu Kapat (Dikey)"; +"DISABLE_AMBIENT_PORTRAIT_DESC" = "Video başlığı çevresindeki aydınlatmayı kapat"; + +"DISABLE_AMBIENT_FULLSCREEN" = "Ortam Modunu Kapat (Tam Ekran)"; +"DISABLE_AMBIENT_FULLSCREEN_DESC" = "Video oynatıcının çevresindeki aydınlatmayı kapat"; + +"FULLSCREEN_TO_THE_RIGHT" = "Tam Ekran Sağda"; +"FULLSCREEN_TO_THE_RIGHT_DESC" = "Tam ekrana sağda ev düğmesi ile gir."; + +"SEEK_ANYWHERE" = "Her Yerde Ara"; +"SEEK_ANYWHERE_DESC" = "Video oynatıcıda basılı tutarak sürükleyin. YTLite - Hızlandırmak için basılı tutmayı kapatmanız gerek."; + +"ENABLE_TAP_TO_SEEK" = "Dokunarak Ara"; +"ENABLE_TAP_TO_SEEK_DESC" = "Arama çubuğuna tek dokunarak videonun herhangi bir yerine git"; + +"DISABLE_PULL_TO_FULLSCREEN_GESTURE" = "Tam ekran çekme hareketini kapat"; +"DISABLE_PULL_TO_FULLSCREEN_GESTURE_DESC" = "Dikey tam ekrana geçmek için çekme hareketini kapat. Sadece yatay videolar için geçerli."; + +"ALWAYS_USE_REMAINING_TIME" = "Her zaman kalan süreyi göster"; +"ALWAYS_USE_REMAINING_TIME_DESC" = "Oynatıcı çubuğunda kalan süreyi gösterir."; + +"DISABLE_TOGGLE_TIME_REMAINING" = "Kalan zamanı değiştirmeyi kapat"; +"DISABLE_TOGGLE_TIME_REMAINING_DESC" = "Geçen zamanı kalan zamanla değiştirmeyi kapatır."; + +"DISABLE_ENGAGEMENT_OVERLAY" = "Tam ekranda kaplamayı kapat"; +"DISABLE_ENGAGEMENT_OVERLAY_DESC" = "Tam ekranda yukarı kaydırma hareketini ve önerilen videoları kapat"; + +"HIDE_COMMENT_PREVIEWS_UNDER_PLAYER" = "Oynatıcının Altındaki Yorum Önizlemelerini Gizle"; +"HIDE_COMMENT_PREVIEWS_UNDER_PLAYER_DESC" = "Video oynatıcının altındaki yorum önizlemelerini gizler."; + +"HIDE_AUTOPLAY_MINI_PREVIEW" = "Otomatik Oynatma Mini Önizlemesini Gizle"; +"HIDE_AUTOPLAY_MINI_PREVIEW_DESC" = "Tam ekranda başlık yakınındaki küçük önerilen video kutusunu gizle"; + +"HIDE_HUD_MESSAGES" = "HUD mesajlarını gizle"; +"HIDE_HUD_MESSAGES_DESC" = "Belirli işlemler için görüntülenen bildirim çubuklarını gizler (ör. CC açıldı/kapandı)"; + +"HIDE_COLLAPSE_BUTTON" = "Daraltma Düğmesini Gizle"; +"HIDE_COLLAPSE_BUTTON_DESC" = "Video oynatıcıda sol üstte görünen ok ile daraltma düğmesini gizler."; + +"HIDE_SPEED_TOAST" = "Hız Bildirimini Gizle"; +"HIDE_SPEED_TOAST_DESC" = "Oynatıcıya basılı tutarken 2X Hız bildirimini gizle"; + +// App settings overlay options +"APP_SETTINGS_OVERLAY_OPTIONS" = "Uyg. Ayarları Kaplama Seç."; + +"HIDE_ACCOUNT_SECTION" = "\"Hesap\" Bölümünü Gizle"; +"HIDE_AUTOPLAY_SECTION" = "\"Otomatik Oynatma\" Bölümünü Gizle"; +"HIDE_TRYNEWFEATURES_SECTION" = "\"Yeni Özellikler\" Bölümünü Gizle"; +"HIDE_VIDEOQUALITYPREFERENCES_SECTION" = "\"Video Kalite\" Bölümünü Gizle"; +"HIDE_NOTIFICATIONS_SECTION" = "\"Bildirimler\" Bölümünü Gizle"; +"HIDE_MANAGEALLHISTORY_SECTION" = "\"Tüm Geçmişi Yönet\" Bölümünü Gizle"; +"HIDE_YOURDATAINYOUTUBE_SECTION" = "\"YouTube Verileriniz\" Bölümünü Gizle"; +"HIDE_PRIVACY_SECTION" = "\"Gizlilik\" Bölümünü Gizle"; +"HIDE_LIVECHAT_SECTION" = "\"Canlı Sohbet\" Bölümünü Gizle"; + +// Theme +"THEME_OPTIONS" = "Tema Seç"; + +"OLED_DARK_THEME" = "OLED koyu tema"; +"OLED_DARK_THEME_2" = "OLED koyu tema"; +"OLED_DARK_THEME_DESC" = "Gerçek koyu tema. Bazı durumlarda düzgün çalışmayabilir. Yeniden başlatılmalı."; + +"OLD_DARK_THEME" = "Eski koyu tema"; +"OLD_DARK_THEME_DESC" = "Eski YouTube koyu teması. Yeniden başlatılmalı."; + +"DEFAULT_THEME" = "Varsayılan"; +"DEFAULT_THEME_DESC" = "YouTube'un varsayılan koyu teması. Yeniden başlatılmalı."; + +"OLED_KEYBOARD" = "OLED Klavye (Deneysel)"; +"OLED_KEYBOARD_DESC" = "Bazı durumlarda düzgün çalışmayabilir. Yeniden başlatılmalı."; + +"LOW_CONTRAST_MODE" = "Düşük Kontrast Modu"; +"LOW_CONTRAST_MODE_DESC" = "Metinlerin ve düğmelerin kontrastını düşürür. Yeniden başlatılmalı."; +"LCM_SELECTOR" = "Düşük kontrast modu seçimi"; +"DEFAULT_LOWCONTRASTMODE" = "(Varsayılan) DüşükKontrastModu"; +"CUSTOM_LOWCONTRASTMODE" = "(Özel Renk) DüşükKontrastModu"; + +// Miscellaneous +"MISCELLANEOUS" = "Diğer"; + +"PLAYBACK_IN_FEEDS" = "Akışlarda Oto Oynatma"; +"PLAYBACK_IN_FEEDS_ALWAYS_ON" = "Her zaman açık"; +"PLAYBACK_IN_FEEDS_WIFI_ONLY" = "Yalnızca Wi-Fi"; +"PLAYBACK_IN_FEEDS_OFF" = "Kapalı"; + +"NEW_SETTINGS_UI" = "Yeni Ayarlar Arayüzü"; +"NEW_SETTINGS_UI_DESC" = "Yeni gruplandırılmış ayarlar arayüzünü kullan. Bazı ayarları gizleyebilir."; + +"ENABLE_YT_STARTUP_ANIMATION" = "YouTube açılış animasyonunu aç"; +"ENABLE_YT_STARTUP_ANIMATION_DESC" = ""; + +"HIDE_MODERN_INTERFACE" = "Modern Arayüzü Gizle (YTNoModernUI)"; +"HIDE_MODERN_INTERFACE_DESC" = "YouTube'un eklediği Modern Öğeleri gizlemek için aç. Yeniden başlatılmalı."; + +"IPAD_LAYOUT" = "iPad Düzeni"; +"IPAD_LAYOUT_DESC" = "Mevcut iPhone/iPod'unuzda iPad Düzenini kullanmak için aç. Yeniden başlatılmalı."; + +"IPHONE_LAYOUT" = "iPhone Düzeni"; +"IPHONE_LAYOUT_DESC" = "Mevcut iPad'inizde iPhone Düzenini kullanmak için aç. Yeniden başlatılmalı."; + +"CAST_CONFIRM" = "Yayın öncesi onay (YTCastConfirm)"; +"CAST_CONFIRM_DESC" = "TV'yi yanlışlıkla kontrol etmeyi önlemek için yayın öncesi onay göster."; +"CASTING" = "Yayınlar"; +"MSG_ARE_YOU_SURE" = "Yayın başlatmak istediğinizden emin misiniz?"; +"MSG_YES" = "Evet"; +"MSG_CANCEL" = "İptal"; + +"NEW_MINIPLAYER_STYLE" = "Yeni mini oynatıcı çubuğu (BigYTMiniPlayer)"; +"NEW_MINIPLAYER_STYLE_DESC" = "Yeniden başlatılmalı."; + +"HIDE_CAST_BUTTON" = "Yayınla düğmesini gizle"; +"HIDE_CAST_BUTTON_DESC" = "Yeniden başlatılmalı."; + +"VIDEO_PLAYER_BUTTON" = "Video Oynatıcı Butonu"; +"VIDEO_PLAYER_BUTTON_DESC" = "İndirilen videoları Apple oynatıcısında açmak için gezinme çubuğunda bir buton göster"; + +"HIDE_SPONSORBLOCK_BUTTON" = "Gezinme çubuğunda iSponsorBlock düğmesini gizle"; +"HIDE_SPONSORBLOCK_BUTTON_DESC" = ""; + +"HIDE_HOME_TAB" = "Ana Sekmeyi Gizle"; +"HIDE_HOME_TAB_DESC" = "Tüm sekmeleri gizlerken dikkatli olun"; + +"FIX_CASTING" = "Yayınlamayı Düzelt"; +"FIX_CASTING_DESC" = "Yayınlamayı düzeltmek için bazı ayarları değiştirir"; + +"ENABLE_FLEX" = "FLEX'i Aç"; +"ENABLE_FLEX_DESC" = "Hata ayıklama için FLEX'i aç (tavsiye edilmez). Ne yaptığınızı bilmiyorsanız kapalı tutun."; + +// Version Spoofer +"APP_VERSION_SPOOFER_LITE" = "Uyg. Sürüm Spoofer'ını Aç (Lite)"; +"APP_VERSION_SPOOFER_LITE_DESC" = "Sürüm Spoofer (Lite) kullanmak için açın ve sürümü seçin. Yeniden başlatılmalı."; +"VERSION_SPOOFER_TITLE" = "Sürüm Seçici"; + +// Other Localization +"APP_RESTART_DESC" = "Uyg. yeniden başlatılmalı."; +"CHANGE_APP_ICON" = "Uyg. Simgesini Değiştir"; diff --git a/lang/YTLitePlus.bundle/vi.lproj/Localizable.strings b/lang/YTLitePlus.bundle/vi.lproj/Localizable.strings new file mode 100644 index 0000000..17f779e --- /dev/null +++ b/lang/YTLitePlus.bundle/vi.lproj/Localizable.strings @@ -0,0 +1,190 @@ +// Settings +"VERSION" = "Phiên bản YTLitePlus: %@"; +"VERSION_CHECK" = "Nhấn để kiểm tra cập nhật!"; + +"COPY_SETTINGS" = "Sao chép cài đặt"; +"COPY_SETTINGS_DESC" = "Sao chép cài đặt hiện tại vào bảng nhớ tạm"; +"PASTE_SETTINGS" = "Dán cài đặt"; +"PASTE_SETTINGS_DESC" = "Dán cài đặt từ bảng nhớ tạm và áp dụng"; +"PASTE_SETTINGS_ALERT" = "Áp dụng các cài đặt?"; +"EXPORT_SETTINGS" = "Xuất cài đặt"; +"EXPORT_SETTINGS_DESC" = "Xuất tất cài đặt hiện tại vào tệp .txt"; +"IMPORT_SETTINGS" = "Nhập cài đặt"; +"IMPORT_SETTINGS_DESC" = "Nhấn để nhập cài đặt (.txt)"; +"REPLACE_COPY_AND_PASTE_BUTTONS" = "Thay thế nút 'Sao chép cài đặt' và 'Dán cài đặt'"; +"REPLACE_COPY_AND_PASTE_BUTTONS_DESC" = "Thay thế các nút thành 'Xuất cài đặt' và 'Nhập cài đặt'"; + +"VIDEO_PLAYER" = "Trình phát video (Beta)"; +"VIDEO_PLAYER_DESC" = "Mở video đã tải xuống trong trình phát Apple"; + +// Player Gestures +"PLAYER_GESTURES_TOGGLE" = "Bật cử chỉ trình phát"; +"VOLUME" = "Âm lượng"; +"BRIGHTNESS" = "Độ sáng"; +"SEEK" = "Tua"; +"DISABLED" = "Vô hiệu hóa"; +"DEADZONE" = "Vùng chết"; +"DEADZONE_DESC" = "Khoảng cách tối thiểu để nhận diện cử chỉ"; +"SENSITIVITY" = "Độ nhạy"; +"SENSITIVITY_DESC" = "Hệ số nhân cho cử chỉ âm lượng và độ sáng"; +"PLAYER_GESTURES_TITLE" = "Cử chỉ trình phát"; +"PLAYER_GESTURES_DESC" = "Cấu hình cử chỉ vuốt ngang cho trình phát"; +"TOP_SECTION" = "Phần trên"; +"MIDDLE_SECTION" = "Phần giữa"; +"BOTTOM_SECTION" = "Phần dưới"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Bật phản hồi xúc giác"; + +// Video controls overlay options +"VIDEO_CONTROLS_OVERLAY_OPTIONS" = "Lớp phủ video"; + +"ENABLE_SHARE_BUTTON" = "Bật nút chia sẻ"; +"ENABLE_SHARE_BUTTON_DESC" = "Thêm nút chia sẻ trong trình phát video."; + +"ENABLE_SAVE_TO_PLAYLIST_BUTTON" = "Bật nút 'Lưu vào danh sách phát'"; +"ENABLE_SAVE_TO_PLAYLIST_BUTTON_DESC" = "Thêm nút 'Lưu vào danh sách phát' trong trình phát video."; + +"HIDE_SHADOW_OVERLAY_BUTTONS" = "Ẩn bóng các nút"; +"HIDE_SHADOW_OVERLAY_BUTTONS_DESC" = "Ẩn bóng các nút Phát/Tạm dừng, Trước/Tiếp theo, Tua tới/Tua lại."; + +"HIDE_RIGHT_PANEL" = "Ẩn bảng bên phải ở chế độ toàn màn hình"; +"HIDE_RIGHT_PANEL_DESC" = "Điều nãy sẽ khiến bạn không thể xem nội dung mô tả, bình luận, v.v ở chế độ toàn màn hình. Cần khởi động lại ứng dụng."; + +"HIDE_HEATWAVES" = "Ẩn sóng nhiệt"; +"HIDE_HEATWAVES_DESC" = "Ẩn sóng nhiệt khỏi trình phát video. Cần khởi động lại ứng dụng."; + +"DISABLE_AMBIENT_PORTRAIT" = "Tắt chế độ môi trường (Chân dung)"; +"DISABLE_AMBIENT_PORTRAIT_DESC" = "Tắt ánh sáng xung quanh tiêu đề video"; + +"DISABLE_AMBIENT_FULLSCREEN" = "Tắt chế độ môi trường (Toàn màn hình)"; +"DISABLE_AMBIENT_FULLSCREEN_DESC" = "Tắt ánh sáng xung quanh trình phát video"; + +"FULLSCREEN_TO_THE_RIGHT" = "Toàn màn hình bên phải"; +"FULLSCREEN_TO_THE_RIGHT_DESC" = "Luôn vào chế độ toàn màn hình với nút home ở bên phải."; + +"SEEK_ANYWHERE" = "Tua ở mọi nơi"; +"SEEK_ANYWHERE_DESC" = "Giữ và kéo bất kỳ đâu trên trình phát video để tua. Bạn phải tắt tùy chỉnh YTLite - Giữ để tua"; + +"ENABLE_TAP_TO_SEEK" = "Nhấn để tua"; +"ENABLE_TAP_TO_SEEK_DESC" = "Nhấn một lần vào thanh tua để tua đến bất kỳ vị trí nào trong video"; + +"DISABLE_PULL_TO_FULLSCREEN_GESTURE" = "Tắt cử chỉ kéo để vào toàn màn hình"; +"DISABLE_PULL_TO_FULLSCREEN_GESTURE_DESC" = "Tắt cử chỉ kéo để vào toàn màn hình dọc. Chỉ áp dụng cho video ngang."; + +"ALWAYS_USE_REMAINING_TIME" = "Hiển thị thời gian còn lại"; +"ALWAYS_USE_REMAINING_TIME_DESC" = "Luôn hiển thị thời gian còn lại trên thanh trình phát video."; + +"DISABLE_TOGGLE_TIME_REMAINING" = "Tắt chuyển đổi thời gian còn lại"; +"DISABLE_TOGGLE_TIME_REMAINING_DESC" = "Tắt chức năng chuyển đổi từ thời gian đã phát sang thời gian còn lại. Sử dụng với cài đặt khác để luôn hiển thị thời gian còn lại."; + +"DISABLE_ENGAGEMENT_OVERLAY" = "Tắt lớp phủ tương tác toàn màn hình"; +"DISABLE_ENGAGEMENT_OVERLAY_DESC" = "Tắt cử chỉ vuốt lên để xem video gợi ý ở chế độ toàn màn hình"; + +"HIDE_COMMENT_PREVIEWS_UNDER_PLAYER" = "Ẩn bản xem trước bình luận dưới trình phát"; +"HIDE_COMMENT_PREVIEWS_UNDER_PLAYER_DESC" = "Ngăn tiết lộ nội dung bình luận trong nút bình luận"; + +"HIDE_AUTOPLAY_MINI_PREVIEW" = "Ẩn bản xem trước tự động phát nhỏ"; +"HIDE_AUTOPLAY_MINI_PREVIEW_DESC" = "Ẩn hộp gợi ý video nhỏ gần tiêu đề ở chế độ toàn màn hình"; + +"HIDE_HUD_MESSAGES" = "Ẩn thông báo HUD"; +"HIDE_HUD_MESSAGES_DESC" = "Ẩn các thông báo xuất hiện khi thực hiện các hành động (ví dụ: bật/tắt CC)"; + +"HIDE_COLLAPSE_BUTTON" = "Ẩn nút thu gọn"; +"HIDE_COLLAPSE_BUTTON_DESC" = "Ẩn nút thu gọn (mũi tên) xuất hiện ở góc trái trên của trình phát Video."; + +"HIDE_SPEED_TOAST" = "Ẩn thông báo tốc độ"; +"HIDE_SPEED_TOAST_DESC" = "Ẩn thông báo tốc độ 2X khi giữ trình phát video"; + +// App settings overlay options +"APP_SETTINGS_OVERLAY_OPTIONS" = "Lớp phủ cài đặt"; + +"HIDE_ACCOUNT_SECTION" = "Ẩn cài đặt \"Chuyển đổi tài khoản\""; +"HIDE_AUTOPLAY_SECTION" = "Ẩn cài đặt \"Tự động phát\""; +"HIDE_TRYNEWFEATURES_SECTION" = "Ẩn cài đặt \"Thử các tính năng mới\""; +"HIDE_VIDEOQUALITYPREFERENCES_SECTION" = "Ẩn cài đặt \"Lựa chọn ưu tiên về chất lượng video\""; +"HIDE_NOTIFICATIONS_SECTION" = "Ẩn cài đặt \"Thông báo\""; +"HIDE_MANAGEALLHISTORY_SECTION" = "Ẩn cài đặt \"Quản lý toàn bộ nhật ký hoạt động\""; +"HIDE_YOURDATAINYOUTUBE_SECTION" = "Ẩn cài đặt \"Dữ liệu của bạn trong YouTube\""; +"HIDE_PRIVACY_SECTION" = "Ẩn cài đặt \"Quyền riêng tư\""; +"HIDE_LIVECHAT_SECTION" = "Ẩn cài đặt \"Trò chuyện trực tiếp\""; + +// Theme +"THEME_OPTIONS" = "Tùy chọn giao diện"; + +"OLED_DARK_THEME" = "Giao diện tối OLED"; +"OLED_DARK_THEME_2" = "Giao diện tối OLED"; +"OLED_DARK_THEME_DESC" = "Giao diện tối thực sự. Có thể không hoạt động đúng trong một số trường hợp. Cần khởi động lại ứng dụng sau khi bạn bật/tắt tùy chọn này."; + +"OLD_DARK_THEME" = "Giao diện tối cũ"; +"OLD_DARK_THEME_DESC" = "Giao diện tối cũ của YouTube (giao diện xám). Cần khởi động lại ứng dụng."; + +"DEFAULT_THEME" = "Mặc định"; +"DEFAULT_THEME_DESC" = "Giao diện mặc định của YouTube. Cần khởi động lại ứng dụng."; + +"OLED_KEYBOARD" = "Bàn phím OLED"; +"OLED_KEYBOARD_DESC" = "Có thể hoạt động không đúng trong một số trường hợp. Cần khởi động lại ứng dụng."; + +"LOW_CONTRAST_MODE" = "Chế độ tương phản thấp"; +"LOW_CONTRAST_MODE_DESC" = "Tùy chọn này sẽ giảm tương phản của văn bản và nút giống như giao diện YouTube cũ. Cần khởi động lại ứng dụng."; +"LCM_SELECTOR" = "Chọn chế độ tương phản thấp"; +"DEFAULT_LOWCONTRASTMODE" = "(Mặc định) Chế độ tương phản thấp"; +"CUSTOM_LOWCONTRASTMODE" = "(Màu tùy chỉnh) Chế độ tương phản thấp"; + +// Miscellaneous +"MISCELLANEOUS" = "Tùy chọn khác"; + +"PLAYBACK_IN_FEEDS" = "Phát trong các trang danh sách video"; +"PLAYBACK_IN_FEEDS_ALWAYS_ON" = "Luôn bật"; +"PLAYBACK_IN_FEEDS_WIFI_ONLY" = "Chỉ Wi-Fi"; +"PLAYBACK_IN_FEEDS_OFF" = "Tắt"; + +"NEW_SETTINGS_UI" = "Giao diện cài đặt mới"; +"NEW_SETTINGS_UI_DESC" = "Nhóm các giao diện cài đặt. Một số cài đặt có thể bị ẩn"; + +"ENABLE_YT_STARTUP_ANIMATION" = "Hoạt ảnh khởi động YouTube"; +"ENABLE_YT_STARTUP_ANIMATION_DESC" = "Hiển thị hoạt ảnh khi mở YouTube"; + +"HIDE_MODERN_INTERFACE" = "Giao diện YouTube cũ"; +"HIDE_MODERN_INTERFACE_DESC" = "Đưa giao diện cũ của YouTube trở lại từ phiên bản v17.38.10. Loại bỏ một số yếu tố bo tròn, chế độ môi trường, và các tính năng hiện đại khác. Cần khởi động lại ứng dụng."; + +"IPAD_LAYOUT" = "Bố cục iPad"; +"IPAD_LAYOUT_DESC" = "Chỉ sử dụng nếu muốn có bố cục iPad trên iPhone/iPod của mình. Cần khởi động lại ứng dụng."; + +"IPHONE_LAYOUT" = "Bố cục iPhone"; +"IPHONE_LAYOUT_DESC" = "Chỉ sử dụng nếu muốn có bố cục iPhone trên iPad của mình. Cần khởi động lại ứng dụng."; + +"CAST_CONFIRM" = "Xác nhận trước khi truyền"; +"CAST_CONFIRM_DESC" = "Hiển thị thông báo xác nhận trước khi truyền."; +"CASTING" = "Truyền"; +"MSG_ARE_YOU_SURE" = "Bạn có chắc chắn muốn bắt đầu truyền không?"; +"MSG_YES" = "Có"; +"MSG_CANCEL" = "Không"; + +"NEW_MINIPLAYER_STYLE" = "Trình phát thu nhỏ kiểu mới"; +"NEW_MINIPLAYER_STYLE_DESC" = "Thay thế trình phát thu nhỏ mặc định thành (BigYTMiniPlayer). Cần khởi động lại ứng dụng."; + +"HIDE_CAST_BUTTON" = "Ẩn nút truyền"; +"HIDE_CAST_BUTTON_DESC" = "Cần khởi động lại ứng dụng."; + +"VIDEO_PLAYER_BUTTON" = "Nút Trình phát video"; +"VIDEO_PLAYER_BUTTON_DESC" = "Hiển thị nút trong thanh điều hướng để mở video đã tải xuống trong trình phát Apple"; + +"HIDE_SPONSORBLOCK_BUTTON" = "Ẩn nút iSponsorBlock"; +"HIDE_SPONSORBLOCK_BUTTON_DESC" = "Ẩn cài đặt iSponsorBlock trên thanh điều hướng"; + +"HIDE_HOME_TAB" = "Ẩn tab Trang chủ"; +"HIDE_HOME_TAB_DESC" = "Hãy cẩn thận khi ẩn tất cả các tab"; + +"FIX_CASTING" = "Sửa truyền"; +"FIX_CASTING_DESC" = "Thay đổi một số cờ AB để sửa truyền"; + +"ENABLE_FLEX" = "Bật FLEX"; +"ENABLE_FLEX_DESC" = "Bật FLEX để gỡ lỗi (không khuyến khích). Để tùy chọn này tắt trừ khi bạn biết rõ bạn đang làm gì."; + +// Version Spoofer +"APP_VERSION_SPOOFER_LITE" = "Giả mạo phiên bản ứng dụng (Lite)"; +"APP_VERSION_SPOOFER_LITE_DESC" = "Bật tính năng này để làm giả phiên bản YouTube (Lite). Chọn phiên bản bạn ưa thích bên dưới. Cần khởi động lại ứng dụng."; +"VERSION_SPOOFER_TITLE" = "Chọn phiên bản giả mạo"; + +// Other Localization +"APP_RESTART_DESC" = "Cần khởi động lại ứng dụng."; +"CHANGE_APP_ICON" = "Đổi biểu tượng ứng dụng"; diff --git a/lang/YTLitePlus.bundle/zh_CN.lproj/Localizable.strings b/lang/YTLitePlus.bundle/zh_CN.lproj/Localizable.strings new file mode 100644 index 0000000..4c233dc --- /dev/null +++ b/lang/YTLitePlus.bundle/zh_CN.lproj/Localizable.strings @@ -0,0 +1,194 @@ +// Settings +"VERSION" = "YTLitePlus版本: %@"; +"VERSION_CHECK" = "点击检查更新!"; + +"COPY_SETTINGS" = "复制设置"; +"COPY_SETTINGS_DESC" = "将当前所有设置复制到剪贴板"; +"PASTE_SETTINGS" = "粘贴设置"; +"PASTE_SETTINGS_DESC" = "从剪贴板粘贴设置并应用"; +"PASTE_SETTINGS_ALERT" = "应用剪贴板中的设置?"; +"EXPORT_SETTINGS" = "导出设置"; +"EXPORT_SETTINGS_DESC" = "将当前所有设置导出为txt文件"; +"IMPORT_SETTINGS" = "导入设置"; +"IMPORT_SETTINGS_DESC" = "从txt文件导入设置"; +"REPLACE_COPY_AND_PASTE_BUTTONS" = "替换「复制设置」和「粘贴设置」按钮"; +"REPLACE_COPY_AND_PASTE_BUTTONS_DESC" = "将按钮替换为「导出设置」和「导入设置」"; + +"VIDEO_PLAYER" = "视频播放器(测试版)"; +"VIDEO_PLAYER_DESC" = "使用系统自带播放器打开下载的视频"; + +// Player Gestures +"PLAYER_GESTURES_TOGGLE" = "启用播放器手势"; +"VOLUME" = "音量"; +"BRIGHTNESS" = "亮度"; +"SEEK" = "快进"; +"DISABLED" = "禁用"; +"DEADZONE" = "盲区"; +"DEADZONE_DESC" = "识别手势前需要移动的最小距离"; +"SENSITIVITY" = "灵敏度"; +"SENSITIVITY_DESC" = "对音量和亮度手势的倍数"; +"PLAYER_GESTURES_TITLE" = "播放器手势"; +"PLAYER_GESTURES_DESC" = "配置播放器的水平滑动手势"; +"TOP_SECTION" = "顶部区域"; +"MIDDLE_SECTION" = "中间区域"; +"BOTTOM_SECTION" = "底部区域"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "启用触觉反馈"; + +// Video controls overlay options +"VIDEO_CONTROLS_OVERLAY_OPTIONS" = "视频控件覆盖选项"; + +"ENABLE_SHARE_BUTTON" = "启用分享按钮"; +"ENABLE_SHARE_BUTTON_DESC" = "在播放器覆盖按钮区域中启用分享按钮"; + +"ENABLE_SAVE_TO_PLAYLIST_BUTTON" = "启用「保存到播放列表」按钮"; +"ENABLE_SAVE_TO_PLAYLIST_BUTTON_DESC" = "在播放器覆盖按钮区域中启用「保存到播放列表」按钮"; + +"HIDE_SHADOW_OVERLAY_BUTTONS" = "隐藏按钮阴影效果"; +"HIDE_SHADOW_OVERLAY_BUTTONS_DESC" = "隐藏播放/暂停、上一个、下一个、前进和后退按钮上的阴影效果"; + +"HIDE_RIGHT_PANEL" = "隐藏全屏模式中的右侧面板"; +"HIDE_RIGHT_PANEL_DESC" = "需要重启应用"; + +"HIDE_HEATWAVES" = "隐藏Heatwaves"; +"HIDE_HEATWAVES_DESC" = "隐藏视频播放器中的Heatwaves,需要重启应用"; + +"DISABLE_AMBIENT_PORTRAIT" = "禁用环境模式(纵向)"; +"DISABLE_AMBIENT_PORTRAIT_DESC" = "禁用视频标题周围的光效"; + +"DISABLE_AMBIENT_FULLSCREEN" = "禁用环境模式(全屏)"; +"DISABLE_AMBIENT_FULLSCREEN_DESC" = "禁用视频播放器周围的光效"; + +"FULLSCREEN_TO_THE_RIGHT" = "全屏向右"; +"FULLSCREEN_TO_THE_RIGHT_DESC" = "进入全屏时始终使主页按钮在右侧"; + +"SEEK_ANYWHERE" = "任意位置快进手势"; +"SEEK_ANYWHERE_DESC" = "按住并拖动视频播放器以快进,您必须禁用YTLite的「Hold to speed(按住加速)」功能"; + +"ENABLE_TAP_TO_SEEK" = "启用点击快进"; +"ENABLE_TAP_TO_SEEK_DESC" = "通过单击进度条跳转到视频的任意位置"; + +"DISABLE_PULL_TO_FULLSCREEN_GESTURE" = "禁用拉动全屏手势"; +"DISABLE_PULL_TO_FULLSCREEN_GESTURE_DESC" = "禁用拖动手势进入垂直全屏,仅适用于横向视频"; + +"ALWAYS_USE_REMAINING_TIME" = "始终使用剩余时间"; +"ALWAYS_USE_REMAINING_TIME_DESC" = "将默认设置为在播放器栏中显示剩余时间"; + +"DISABLE_TOGGLE_TIME_REMAINING" = "禁用切换剩余时间"; +"DISABLE_TOGGLE_TIME_REMAINING_DESC" = "禁用将已用时间更改为剩余时间,与其他设置一起使用以始终显示剩余时间"; + +"DISABLE_ENGAGEMENT_OVERLAY" = "禁用全屏参与覆盖"; +"DISABLE_ENGAGEMENT_OVERLAY_DESC" = "禁用上滑手势和全屏模式中的建议视频列表"; + +"HIDE_COMMENT_PREVIEWS_UNDER_PLAYER" = "隐藏播放器下方的评论预览"; +"HIDE_COMMENT_PREVIEWS_UNDER_PLAYER_DESC" = "隐藏评论按钮中的评论预览"; + +"HIDE_AUTOPLAY_MINI_PREVIEW" = "隐藏自动播放迷你预览"; +"HIDE_AUTOPLAY_MINI_PREVIEW_DESC" = "隐藏全屏模式下标题附近的小型建议视频框"; + +"HIDE_HUD_MESSAGES" = "隐藏HUD消息"; +"HIDE_HUD_MESSAGES_DESC" = "隐藏某些操作(如开启/关闭字幕)时显示的通知"; + +"HIDE_COLLAPSE_BUTTON" = "隐藏折叠按钮"; +"HIDE_COLLAPSE_BUTTON_DESC" = "隐藏显示在视频播放器左上角的箭头折叠按钮"; + +"HIDE_SPEED_TOAST" = "隐藏速度提示"; +"HIDE_SPEED_TOAST_DESC" = "隐藏按住播放器时显示的2X速度弹窗"; + +// App settings overlay options +"APP_SETTINGS_OVERLAY_OPTIONS" = "隐藏设置中的选项"; + +"HIDE_ACCOUNT_SECTION" = "隐藏「帐户」"; +"HIDE_AUTOPLAY_SECTION" = "隐藏「自动播放」"; +"HIDE_TRYNEWFEATURES_SECTION" = "隐藏「试用新功能」"; +"HIDE_VIDEOQUALITYPREFERENCES_SECTION" = "隐藏「视频画质偏好」"; +"HIDE_NOTIFICATIONS_SECTION" = "隐藏「通知」"; +"HIDE_MANAGEALLHISTORY_SECTION" = "隐藏「管理所有历史记录」"; +"HIDE_YOURDATAINYOUTUBE_SECTION" = "隐藏「YouTube个人数据管理」"; +"HIDE_PRIVACY_SECTION" = "隐藏「隐私设置」"; +"HIDE_LIVECHAT_SECTION" = "隐藏「实时聊天」"; + +// Theme +"THEME_OPTIONS" = "主题选项"; + +"OLED_DARK_THEME" = "OLED暗色主题"; +"OLED_DARK_THEME_2" = "OLED暗色主题"; +"OLED_DARK_THEME_DESC" = "真正的暗色主题,在某些情况下可能无法正常工作,启用/禁用此选项后需要重启应用"; + +"OLD_DARK_THEME" = "旧暗色主题"; +"OLD_DARK_THEME_DESC" = "旧版YouTube的暗色主题(灰色主题),启用后需要重启应用"; + +"DEFAULT_THEME" = "默认"; +"DEFAULT_THEME_DESC" = "YouTube的默认深色主题,启用后需要重启应用"; + +"OLED_KEYBOARD" = "OLED键盘"; +"OLED_KEYBOARD_DESC" = "在某些情况下可能无法正常工作,启用后需要重启应用"; + +"LOW_CONTRAST_MODE" = "低对比度模式"; +"LOW_CONTRAST_MODE_DESC" = "此选项将降低文本和按钮的对比度,就像旧的YouTube界面一样,启用后需要重启应用"; +"LCM_SELECTOR" = "低对比度模式选择"; +"DEFAULT_LOWCONTRASTMODE" = "(默认) 低对比度模式"; +"CUSTOM_LOWCONTRASTMODE" = "(自定义颜色) 低对比度模式"; + +// Miscellaneous +"MISCELLANEOUS" = "其它"; + +"PLAYBACK_IN_FEEDS" = "Feeds后台播放"; +"PLAYBACK_IN_FEEDS_ALWAYS_ON" = "始终开启"; +"PLAYBACK_IN_FEEDS_WIFI_ONLY" = "仅限Wi-Fi"; +"PLAYBACK_IN_FEEDS_OFF" = "关闭"; + +"NEW_SETTINGS_UI" = "新的设置界面"; +"NEW_SETTINGS_UI_DESC" = "使用新的分组设置界面,可能会隐藏某些设置"; + +"ENABLE_YT_STARTUP_ANIMATION" = "启用YouTube启动动画"; +"ENABLE_YT_STARTUP_ANIMATION_DESC" = ""; + +"HIDE_MODERN_INTERFACE" = "禁用现代YouTube界面(YTNoModernUI)"; +"HIDE_MODERN_INTERFACE_DESC" = "这会将YouTube应用恢复到v17.38.10之前的传统设计,此操作还会移除某些圆角元素、环境模式和其他现代功能,启用后需要重启应用"; + +"IPAD_LAYOUT" = "iPad 布局"; +"IPAD_LAYOUT_DESC" = "仅当您希望在当前的 iPhone/iPod 上使用 iPad 布局时使用,需要重新启动应用程序"; + +"IPHONE_LAYOUT" = "iPhone 布局"; +"IPHONE_LAYOUT_DESC" = "仅当您希望在当前的 iPad 上使用 iPhone 布局时使用,需要重新启动应用程序"; + +"CAST_CONFIRM" = "投屏前确认警告 (YTCastConfirm)"; +"CAST_CONFIRM_DESC" = "投屏前显示确认警告,以防止意外劫持电视"; +"CASTING" = "投屏"; +"MSG_ARE_YOU_SURE" = "您确定要开始投屏吗?"; +"MSG_YES" = "确定"; +"MSG_CANCEL" = "取消"; + +"NEW_MINIPLAYER_STYLE" = "新的迷你播放器样式 (BigYTMiniPlayer)"; +"NEW_MINIPLAYER_STYLE_DESC" = "需要重新启动应用程序"; + +"HIDE_CAST_BUTTON" = "隐藏投屏按钮"; +"HIDE_CAST_BUTTON_DESC" = "需要重新启动应用程序"; + +"VIDEO_PLAYER_BUTTON" = "视频播放器按钮"; +"VIDEO_PLAYER_BUTTON_DESC" = "在导航栏中显示按钮,以便在系统自带播放器中打开已下载的视频"; + +"HIDE_SPONSORBLOCK_BUTTON" = "隐藏导航栏中的 iSponsorBlock 按钮"; +"HIDE_SPONSORBLOCK_BUTTON_DESC" = ""; + +"HIDE_HOME_TAB" = "隐藏首页标签栏"; +"HIDE_HOME_TAB_DESC" = "隐藏所有标签栏时请小心"; + +"FIX_CASTING" = "修复投屏"; +"FIX_CASTING_DESC" = "更改一些 AB 标志以修复投屏功能"; + +"ENABLE_FLEX" = "启用 FLEX"; +"ENABLE_FLEX_DESC" = "启用 FLEX 进行调试(不推荐),除非您了解操作,否则请保持关闭状态"; + +// Version Spoofer +"APP_VERSION_SPOOFER_LITE" = "修改应用程序版本"; +"APP_VERSION_SPOOFER_LITE_DESC" = "要修改YouTube版本,请在下方选择您偏好的版本号,重新启动应用程序生效"; +"VERSION_SPOOFER_TITLE" = "版本选择器"; + +// Other Localization +"APP_RESTART_DESC" = "需要重新启动应用程序"; +"CHANGE_APP_ICON" = "更改应用程序图标"; + + + + diff --git a/lang/YTLitePlus.bundle/zh_TW.lproj/Localizable.strings b/lang/YTLitePlus.bundle/zh_TW.lproj/Localizable.strings new file mode 100644 index 0000000..d573c76 --- /dev/null +++ b/lang/YTLitePlus.bundle/zh_TW.lproj/Localizable.strings @@ -0,0 +1,204 @@ + +// Settings +"VERSION" = "YTLitePlus 版本:%@"; +"VERSION_CHECK" = "檢查更新"; + +"COPY_SETTINGS" = "Copy Settings"; +"COPY_SETTINGS_DESC" = "Copy all current settings to the clipboard"; +"PASTE_SETTINGS" = "Paste Settings"; +"PASTE_SETTINGS_DESC" = "Paste settings from clipboard and apply"; +"PASTE_SETTINGS_ALERT" = "Apply settings from clipboard?"; +"EXPORT_SETTINGS" = "Export Settings"; +"EXPORT_SETTINGS_DESC" = "Exports all current settings into a .txt file"; +"IMPORT_SETTINGS" = "Import Settings"; +"IMPORT_SETTINGS_DESC" = "Press to import settings (.txt)"; +"REPLACE_COPY_AND_PASTE_BUTTONS" = "Replace 'Copy Settings' & 'Paste Settings' Buttons"; +"REPLACE_COPY_AND_PASTE_BUTTONS_DESC" = "Replaces the Buttons to 'Export Settings' and 'Import Settings'"; + +"VIDEO_PLAYER" = "Video Player (Beta)"; +"VIDEO_PLAYER_DESC" = "Open a downloaded video in the Apple player"; + +// Player Gestures +"PLAYER_GESTURES_TOGGLE" = "Enable Player Gestures"; +"VOLUME" = "Volume"; +"BRIGHTNESS" = "Brightness"; +"SEEK" = "Seek"; +"DISABLED" = "Disabled"; +"DEADZONE" = "Deadzone"; +"DEADZONE_DESC" = "Minimum distance to move before a gesture is recognized"; +"SENSITIVITY" = "Sensitivity"; +"SENSITIVITY_DESC" = "Multiplier on volume and brightness gestures"; +"PLAYER_GESTURES_TITLE" = "Player Gestures"; +"PLAYER_GESTURES_DESC" = "Configure horizontal pan gestures for the player"; +"TOP_SECTION" = "Top Section"; +"MIDDLE_SECTION" = "Middle Section"; +"BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; + +// Video controls overlay options +"VIDEO_CONTROLS_OVERLAY_OPTIONS" = "影片區覆蓋按鈕設定"; + +"ENABLE_SHARE_BUTTON" = "顯示分享按鈕"; +"ENABLE_SHARE_BUTTON_DESC" = "在影片覆蓋按鈕區域中顯示分享按鈕"; + +"ENABLE_SAVE_TO_PLAYLIST_BUTTON" = "顯示「儲存至播放清單」按鈕"; +"ENABLE_SAVE_TO_PLAYLIST_BUTTON_DESC" = "在播放器覆蓋按鈕區域中顯示「儲存至播放清單」"; + +"HIDE_SHADOW_OVERLAY_BUTTONS" = "停用按鈕影子效果"; +"HIDE_SHADOW_OVERLAY_BUTTONS_DESC" = "隱藏播放/暫停、上一首、下一首、快轉和倒轉按鈕影子。"; + +"HIDE_RIGHT_PANEL" = "在全螢幕中隱藏右面板"; +"HIDE_RIGHT_PANEL_DESC" = "需要重新啟動應用程式"; + +"HIDE_HEATWAVES" = "隱藏熱浪"; +"HIDE_HEATWAVES_DESC" = "在播放器中隱藏熱浪"; + +"DISABLE_AMBIENT_PORTRAIT" = "Disable Ambient Mode (Portrait)"; +"DISABLE_AMBIENT_PORTRAIT_DESC" = "Disable lighting surrounding video title"; + +"DISABLE_AMBIENT_FULLSCREEN" = "Disable Ambient Mode (Fullscreen)"; +"DISABLE_AMBIENT_FULLSCREEN_DESC" = "Disable lighting surrouding video player"; + +"FULLSCREEN_TO_THE_RIGHT" = "Fullscreen to the Right"; +"FULLSCREEN_TO_THE_RIGHT_DESC" = "Always enter fullscreen with home button on the right side."; + +"SEEK_ANYWHERE" = "Seek Anywhere Gesture"; +"SEEK_ANYWHERE_DESC" = "Hold and drag on the video player to seek. You must disable YTLite - Hold to speed"; + +"ENABLE_TAP_TO_SEEK" = "Enable Tap To Seek"; +"ENABLE_TAP_TO_SEEK_DESC" = "Jump to anywhere in a video by single-tapping the seek bar"; + +"DISABLE_PULL_TO_FULLSCREEN_GESTURE" = "Disable pull-to-fullscreen gesture"; +"DISABLE_PULL_TO_FULLSCREEN_GESTURE_DESC" = "Disable the drag gesture to enter vertical fullscreen. Only applies to landscape videos."; + +"ALWAYS_USE_REMAINING_TIME" = "Always use remaining time"; +"ALWAYS_USE_REMAINING_TIME_DESC" = "Change the default to show time remaining in the player bar."; + +"DISABLE_TOGGLE_TIME_REMAINING" = "Disable toggle time remaining"; +"DISABLE_TOGGLE_TIME_REMAINING_DESC" = "Disables changing time elapsed to time remaining. Use with other setting to always show remaining time."; + +"DISABLE_ENGAGEMENT_OVERLAY" = "Disable fullscreen engagement overlay"; +"DISABLE_ENGAGEMENT_OVERLAY_DESC" = "Disable the swipe-up gesture and suggested videos list in fullscreen"; + +"HIDE_COMMENT_PREVIEWS_UNDER_PLAYER" = "Hide Comment previews under player"; +"HIDE_COMMENT_PREVIEWS_UNDER_PLAYER_DESC" = "Hide comment spoiler in comments button"; + +"HIDE_AUTOPLAY_MINI_PREVIEW" = "Hide autoplay mini preview"; +"HIDE_AUTOPLAY_MINI_PREVIEW_DESC" = "Hide the small suggested video box near the title in fullscreen"; + +"HIDE_HUD_MESSAGES" = "隱藏 HUD 訊息"; +"HIDE_HUD_MESSAGES_DESC" = "例如:CC字幕、開啟/關閉、影片循環開啟等..."; + +"HIDE_COLLAPSE_BUTTON" = "Hide Collapse Button"; +"HIDE_COLLAPSE_BUTTON_DESC" = "Hides the Arrow Collapse Button that was shown in the Top Left of the Video Player."; + +"HIDE_SPEED_TOAST" = "Hide Speed Toast"; +"HIDE_SPEED_TOAST_DESC" = "Hide the 2X Speed popup when holding the player"; + +// App settings overlay options +"APP_SETTINGS_OVERLAY_OPTIONS" = "應用程式設定隱藏項目"; + +"HIDE_ACCOUNT_SECTION" = "購買會員與會員資格"; +"HIDE_AUTOPLAY_SECTION" = "自動播放"; +"HIDE_TRYNEWFEATURES_SECTION" = ""; +"HIDE_VIDEOQUALITYPREFERENCES_SECTION" = "影片畫質偏好設定"; +"HIDE_NOTIFICATIONS_SECTION" = "通知"; +"HIDE_MANAGEALLHISTORY_SECTION" = "管理所有紀錄"; +"HIDE_YOURDATAINYOUTUBE_SECTION" = "已連結的應用程式"; +"HIDE_PRIVACY_SECTION" = "隱私設定"; +"HIDE_LIVECHAT_SECTION" = "聊天室"; + +// Theme +"THEME_OPTIONS" = "外觀"; + +"OLED_DARK_THEME" = "OLED 暗黑主題"; +"OLED_DARK_THEME_2" = "OLED 暗黑主題"; +"OLED_DARK_THEME_DESC" = "全黑主題。此功能不穩定。重新啟動應用程式以套用變更。"; + +"OLD_DARK_THEME" = "懷舊亮灰主題"; +"OLD_DARK_THEME_DESC" = "YouTube 亮灰懷舊暗黑主題。重新啟動應用程式以套用變更。"; + +"DEFAULT_THEME" = "預設主題"; +"DEFAULT_THEME_DESC" = ""; + +"OLED_KEYBOARD" = "OLED 鍵盤"; +"OLED_KEYBOARD_DESC" = "此功能不穩定。重新啟動應用程式以套用變更。"; + +"LOW_CONTRAST_MODE" = "低對比模式"; +"LOW_CONTRAST_MODE_DESC" = "模擬舊式 YouTube。重新啟動應用程式以套用變更。"; +"LCM_SELECTOR" = "低對比模式樣是"; +"DEFAULT_LOWCONTRASTMODE" = "(預設)LowContrastMode"; +"CUSTOM_LOWCONTRASTMODE" = "(自訂顏色)LowContrastMode"; + +// Miscellaneous +"MISCELLANEOUS" = "更多"; + +"PLAYBACK_IN_FEEDS" = "Playback in feeds"; +"PLAYBACK_IN_FEEDS_ALWAYS_ON" = "Always on"; +"PLAYBACK_IN_FEEDS_WIFI_ONLY" = "Wi-Fi only"; +"PLAYBACK_IN_FEEDS_OFF" = "Off"; + +"NEW_SETTINGS_UI" = "New Settings UI"; +"NEW_SETTINGS_UI_DESC" = "Use new grouped settings UI. May hide some settings"; + +"ENABLE_YT_STARTUP_ANIMATION" = "啟用 Youtube 歡迎動畫"; +"ENABLE_YT_STARTUP_ANIMATION_DESC" = ""; + +"HIDE_MODERN_INTERFACE" = "懷舊 YouTube 介面(YTNoModernUI)"; +"HIDE_MODERN_INTERFACE_DESC" = "可移除微光效果、曲線按鈕邊緣等等。重新啟動應用程式以套用變更。"; + +"IPAD_LAYOUT" = "iPad 排版"; +"IPAD_LAYOUT_DESC" = "重新啟動應用程式以套用變更"; + +"IPHONE_LAYOUT" = "iPhone 排版"; +"IPHONE_LAYOUT_DESC" = "重新啟動應用程式以套用變更。"; + +"CAST_CONFIRM" = "在投放前確認(YTCastConfirm)"; +"CAST_CONFIRM_DESC" = "再三確認以避免被家人訐譙"; +"CASTING" = "投放"; +"MSG_ARE_YOU_SURE" = "是否投放影片?"; +"MSG_YES" = "確認"; +"MSG_CANCEL" = "取消"; + +"NEW_MINIPLAYER_STYLE" = "啟用應用程式內的新迷你播放器(BigYTMiniPlayer)"; +"NEW_MINIPLAYER_STYLE_DESC" = "重新啟動應用程式以套用變更。"; + +"HIDE_CAST_BUTTON" = "隱藏投放按鈕"; +"HIDE_CAST_BUTTON_DESC" = "重新啟動應用程式以套用變更。"; + +"VIDEO_PLAYER_BUTTON" = "Video Player Button"; +"VIDEO_PLAYER_BUTTON_DESC" = "Show a button in the navigation bar to open downloaded videos in the Apple player"; + +"HIDE_SPONSORBLOCK_BUTTON" = "隱藏 iSponsorBlock 按鈕"; +"HIDE_SPONSORBLOCK_BUTTON_DESC" = ""; + +"HIDE_HOME_TAB" = "Hide Home Tab"; +"HIDE_HOME_TAB_DESC" = "Be careful when hiding all tabs"; + +"FIX_CASTING" = "Fix Casting"; +"FIX_CASTING_DESC" = "Changes some AB flags to fix casting"; + +"ENABLE_FLEX" = "啟用 FLEX(不建議使用)"; +"ENABLE_FLEX_DESC" = "如果不知道這是什麼,請勿啟用"; + +// Version Spoofer +"APP_VERSION_SPOOFER_LITE" = "改變應用程式版本"; +"APP_VERSION_SPOOFER_LITE_DESC" = "請在底下選擇版本。重新啟動應用程式以套用變更。"; +"VERSION_SPOOFER_TITLE" = "版本選擇器"; + +// Other Localization +"APP_RESTART_DESC" = "重新啟動應用程式以套用變更。"; +"CHANGE_APP_ICON" = "Change App Icon"; + +// Newly added strings +"ENABLE_TAP_TO_SEEK_DESC" = "ENABLE_TAP_TO_SEEK_DESC"; +"ENABLE_TAP_TO_SEEK" = "ENABLE_TAP_TO_SEEK"; +"DISABLE_PULL_TO_FULLSCREEN_GESTURE_DESC" = "DISABLE_PULL_TO_FULLSCREEN_GESTURE_DESC"; +"ALWAYS_USE_REMAINING_TIME" = "ALWAYS_USE_REMAINING_TIME"; +"DISABLE_PULL_TO_FULLSCREEN_GESTURE" = "DISABLE_PULL_TO_FULLSCREEN_GESTURE"; +"DISABLE_TOGGLE_TIME_REMAINING" = "DISABLE_TOGGLE_TIME_REMAINING"; +"DISABLE_ENGAGEMENT_OVERLAY" = "DISABLE_ENGAGEMENT_OVERLAY"; +"DISABLE_TOGGLE_TIME_REMAINING_DESC" = "DISABLE_TOGGLE_TIME_REMAINING_DESC"; +"DISABLE_ENGAGEMENT_OVERLAY_DESC" = "DISABLE_ENGAGEMENT_OVERLAY_DESC"; +"ALWAYS_USE_REMAINING_TIME_DESC" = "ALWAYS_USE_REMAINING_TIME_DESC"; +"DEFAULT_THEME_DESC" = "DEFAULT_THEME_DESC";