diff --git a/.github/ISSUE_TEMPLATE/bug_report_en.yml b/.github/ISSUE_TEMPLATE/bug_report_en.yml new file mode 100644 index 00000000..3d79363d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report_en.yml @@ -0,0 +1,89 @@ +name: 🐞 Bug Report +description: Report an error, crash, or other issue with the module. +title: "[Bug] " +labels: ["bug", "needs triage"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! To help us resolve the issue faster, please provide as much detail as possible. + + - type: checkboxes + id: pre-check + attributes: + label: Pre-submission Checklist + description: Before submitting, please check all the following items. Issues that do not meet these requirements may be closed immediately. + options: + - label: I confirm that this issue also exists in the latest CI build. (You can get the latest version [here](https://t.me/OPatchC)) + required: true + - label: I confirm I have tested with all other (LSPosed/Magisk) modules disabled to ensure this issue is caused by the OShin module itself. + required: true + - label: I confirm I have searched the Issue Tracker and found no similar issue. + required: true + + - type: input + id: oshin-version + attributes: + label: OShin Module Version + description: Which version of the OShin module are you using? + placeholder: "e.g., v15.0.123" + validations: + required: true + + - type: input + id: device-model + attributes: + label: Device Model + description: What is your specific phone model? + placeholder: "e.g., OnePlus Ace 3" + validations: + required: true + + - type: input + id: os-version + attributes: + label: System Version + description: What are your ColorOS and Android versions? + placeholder: "e.g., ColorOS 15.0.0.830 / Android 15" + validations: + required: true + + - type: textarea + id: bug-description + attributes: + label: Bug Description + description: | + Provide a clear and concise description of the bug. + What happened? What did you expect to happen? + placeholder: "When I try to..., the app crashes / the feature... doesn't work as expected." + validations: + required: true + + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps to Reproduce + description: Please provide step-by-step instructions on how to reproduce this issue. + placeholder: | + 1. Go to '...' + 2. Click on '...' + 3. Scroll down to '...' + 4. See error + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant Logs (Logcat, LSPosed Log) + description: | + This is crucial for debugging! Please capture and upload the relevant Logcat or LSPosed logs right after the issue occurs. + **Please do not provide logs as screenshots. Paste the text here or upload it as a .txt file.** + placeholder: "Paste your logcat or LSPosed logs here..." + render: shell + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Is there anything else you think is relevant? e.g., screenshots, screen recordings, etc. diff --git a/.github/ISSUE_TEMPLATE/bug_report_zh.yml b/.github/ISSUE_TEMPLATE/bug_report_zh.yml new file mode 100644 index 00000000..db4b57a0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report_zh.yml @@ -0,0 +1,89 @@ +name: 🐞 错误报告 +description: 报告一个模块中出现的错误、崩溃或问题。 +title: "[Bug] <请在这里简要描述你遇到的问题>" +labels: ["bug", "需要确认"] +body: + - type: markdown + attributes: + value: | + 感谢你花时间来填写这份错误报告!为了帮助我们更快地定位和解决问题,请你尽可能详细地提供以下信息。 + + - type: checkboxes + id: pre-check + attributes: + label: 提交前确认 + description: 在提交此 Issue 之前,请确认并勾选以下所有项目。不符合要求的 Issue 将可能被直接关闭。 + options: + - label: 我确认,此问题在最新的 CI 构建版本中同样存在。 (你可以在 [这里](https://t.me/OPatchC) 获取最新版本) + required: true + - label: 我确认,我已经排查过其他所有已安装的 (LSPosed/Magisk) 模块,并确定此问题是由 O神 模块本身引起的。 + required: true + - label: 我确认,我已经在 Issue Tracker 中进行过搜索,没有发现与此问题重复的 Issue。 + required: true + + - type: input + id: oshin-version + attributes: + label: OShin 模块版本 + description: 你正在使用哪个版本的 O神 模块? + placeholder: "例如:v15.0.123" + validations: + required: true + + - type: input + id: device-model + attributes: + label: 设备型号 + description: 你的手机具体型号是什么? + placeholder: "例如:一加 Ace 3" + validations: + required: true + + - type: input + id: os-version + attributes: + label: 系统版本 + description: 你的 ColorOS 版本和 Android 版本是什么? + placeholder: "例如:ColorOS 15.0.0.830 / Android 15" + validations: + required: true + + - type: textarea + id: bug-description + attributes: + label: 问题描述 + description: | + 请清晰、详细地描述你遇到的问题。 + 发生了什么?你期望的结果是什么? + placeholder: "当我尝试...时,应用崩溃了 / ...功能没有按预期工作。" + validations: + required: true + + - type: textarea + id: steps-to-reproduce + attributes: + label: 复现步骤 + description: 请一步步地告诉我们如何复现这个问题。 + placeholder: | + 1. 打开 '...' 应用 + 2. 点击 '...' 按钮 + 3. 滚动到 '...' + 4. 问题出现 + validations: + required: true + + - type: textarea + id: logs + attributes: + label: 相关日志 (Logcat, LSPosed Log) + description: | + 这是定位问题的关键!请在问题发生后,立即捕获并上传相关的 Logcat 或 LSPosed 日志。 + **请不要以截图形式提供日志,而是将日志文本粘贴在此处或上传为 `.txt` 文件。** + placeholder: "在此处粘贴你的 logcat 或 LSPosed 日志..." + render: shell + + - type: textarea + id: additional-context + attributes: + label: 额外信息 + description: 是否有其他任何你认为相关的信息?例如截图、屏幕录制等。 diff --git a/.github/ISSUE_TEMPLATE/feature_request_en.yml b/.github/ISSUE_TEMPLATE/feature_request_en.yml new file mode 100644 index 00000000..f41b0f0c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request_en.yml @@ -0,0 +1,38 @@ +name: ✨ Feature Request +description: Suggest a completely new feature or enhancement. +title: "[Feature] " +labels: ["enhancement"] +body: + - type: textarea + id: problem-description + attributes: + label: What problem does this feature solve? + description: | + Please provide a clear description of the problem or inconvenience you want to solve with this new feature. + e.g., I'm always frustrated when... It would be great if there was a feature to... + validations: + required: true + + - type: textarea + id: solution-description + attributes: + label: What is your desired solution? + description: | + Please describe in detail how the feature you're suggesting should work. + Where should it appear? How should the user interact with it? + validations: + required: true + + - type: textarea + id: alternatives-considered + attributes: + label: Have you considered any alternative solutions? + description: If applicable, describe any other implementations or alternative solutions you've considered. + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: | + Add any other context, mockups, or screenshots about the feature request here. + For example, references to similar features in other apps or modules. diff --git a/.github/ISSUE_TEMPLATE/feature_request_zh.yml b/.github/ISSUE_TEMPLATE/feature_request_zh.yml new file mode 100644 index 00000000..e1c0b3cd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request_zh.yml @@ -0,0 +1,38 @@ +name: ✨ 功能请求 +description: 提出一个全新的功能或改进建议。 +title: "[Feature] <请简要描述你的功能建议>" +labels: ["enhancement"] +body: + - type: textarea + id: problem-description + attributes: + label: 这个功能解决了什么问题? + description: | + 请清晰地描述你希望通过这个新功能解决的痛点或遇到的不便。 + 例如:我总是因为...而感到困扰,如果有一个...功能就好了。 + validations: + required: true + + - type: textarea + id: solution-description + attributes: + label: 你期望的解决方案是怎样的? + description: | + 请详细描述你建议的功能应该如何工作。 + 它应该出现在哪里?用户应该如何操作它? + validations: + required: true + + - type: textarea + id: alternatives-considered + attributes: + label: 你是否考虑过其他替代方案? + description: 如果有的话,请描述一下你考虑过的其他实现方式或替代方案。 + + - type: textarea + id: additional-context + attributes: + label: 额外信息 + description: | + 可以在此处添加任何与该功能请求相关的其他背景信息或截图。 + 例如,其他App或模块中类似功能的参考。 diff --git a/.github/ISSUE_TEMPLATE/port_feature_en.yml b/.github/ISSUE_TEMPLATE/port_feature_en.yml new file mode 100644 index 00000000..0bce63aa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/port_feature_en.yml @@ -0,0 +1,38 @@ +name: 🚀 Port Feature +description: Request to port an existing feature from another system or module to OShin. +title: "[Port] " +labels: ["port", "enhancement"] +body: + - type: input + id: feature-source + attributes: + label: Feature Source + description: Where does this feature come from? + placeholder: "e.g., Stock Android 15, Flyme OS, a specific app, or an Xposed module" + validations: + required: true + + - type: input + id: source-repo + attributes: + label: Open-Source Repository URL (Optional) + description: If this feature comes from an open-source project (e.g., another Xposed module), please provide the link to its repository here (e.g., GitHub, GitLab). This greatly helps the developer understand how it is implemented. + placeholder: "https://github.com/user/repository" + + - type: textarea + id: feature-description + attributes: + label: Feature Description + description: | + Please describe in detail what this feature is and how it works. + Screenshots or screen recordings demonstrating the feature are highly recommended. + validations: + required: true + + - type: textarea + id: value-proposition + attributes: + label: Why is this feature valuable for OShin users? + description: Please explain why you think bringing this feature to ColorOS would be useful. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/port_feature_zh.yml b/.github/ISSUE_TEMPLATE/port_feature_zh.yml new file mode 100644 index 00000000..84cdbdc7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/port_feature_zh.yml @@ -0,0 +1,38 @@ +name: 🚀 移植功能 +description: 请求将其他系统或模块已有的功能移植到 O神。 +title: "[Port] <想要移植的功能名称>" +labels: ["port", "enhancement"] +body: + - type: input + id: feature-source + attributes: + label: 功能来源 + description: 这个功能来自哪里? + placeholder: "例如:原生 Android 15、Flyme 系统、某个特定 App 或 Xposed 模块" + validations: + required: true + + - type: input + id: source-repo + attributes: + label: 开源仓库地址 (选填) + description: 如果该功能来自一个开源项目(如另一个 Xposed 模块),请在此处提供其仓库链接 (例如 GitHub, GitLab)。这能极大地帮助开发者理解其实现方式。 + placeholder: "https://github.com/user/repository" + + - type: textarea + id: feature-description + attributes: + label: 功能描述 + description: | + 请详细描述这个功能是什么,以及它如何工作。 + 最好能提供截图或屏幕录制来展示该功能的效果。 + validations: + required: true + + - type: textarea + id: value-proposition + attributes: + label: 为什么这个功能对 O神 用户有价值? + description: 请说明为什么你认为将这个功能带到 ColorOS 会很有用。 + validations: + required: true diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1 @@ + diff --git a/.gitignore b/.gitignore index f7415cfd..d69356ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.iml .gradle /local.properties +/.idea /.idea/caches /.idea/libraries /.idea/modules.xml @@ -9,7 +10,14 @@ /.idea/assetWizardSettings.xml .DS_Store /build +/.kotlin /captures +/app/release +/app/debug .externalNativeBuild .cxx -local.properties \ No newline at end of file +local.properties +/app/all +/app/copy.ps1 +/app/old/ +.idea \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..0ad25db4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/README.md b/README.md new file mode 100644 index 00000000..b8d59c87 --- /dev/null +++ b/README.md @@ -0,0 +1,165 @@ +
+ +# O神 - ColorOS 辅助模块 + +icon + +**简体中文** | [English](./README_EN.md) | [在线文档](https://oshin.mikusignal.top/) + +
+ + Stars + + + GitHub Downloads + + + Xposed Repo Downloads + +
+ +

+ +![OShin Socialify Banner](https://socialify.git.ci/suqi8/OShin/image?font=Rokkitt&forks=1&issues=1&language=1&name=1&owner=1&pattern=Brick%20Wall&pulls=1&stargazers=1&theme=Auto) + +
+ +
+

+ O神 (原名 OPatch) 是一个专为 ColorOS 系统深度定制的辅助模块,旨在增强和优化您的操作系统体验。 +

+
+ + + + + + + + + + +
+

⚠️ 核心要求

+
    +
  • Root 方案: Magisk, KernelSU 或 APatch
  • +
  • Xposed 框架: LSPosed
  • +
+
+

⚙️ 支持版本 (Android 16)

+
    +
  • ColorOS 16
  • +
  • RealmeUI 7
  • +
  • OxygenOS 16
  • +
+
+

📥 下载

+
    +
  1. 推荐: 在 LSPosed 管理器中搜索 O神
  2. +
  3. 手动: 从 GitHub Releases 下载 APK
  4. +
+
+

🚀 激活

+
    +
  1. 在 LSPosed 中启用模块
  2. +
  3. 勾选作用域: 系统框架 (system, android)等
  4. +
  5. 重启设备
  6. +
+
+ + +--- + +### 🚫 功能更新与代码获取说明 +* **自 2025 年 11 月 12 日起,主仓库已转为私有,仅保留此镜像以供参考。** +* **后续将不再公开源码及功能实现方案。** +* **如何参与 OShin 开发?** 请联系:`3383787570@qq.com` +* **最新动态?** 请参阅下方“社区与贡献”部分。 +* **Issues 状态:** 仍可正常使用。 + +> **🔒 仓库中保留的代码仍遵守开源协议及下方的开源说明。** + +## 📜 开源说明 (Open Source Disclaimer) + +本项目为开源项目,欢迎贡献。 + +但在您修改或分发此程序时, **不应更改** 以下内容: + +- 「关于」页中的 **感谢** 与 **作者信息** +- **友盟 SDK** 相关代码 +- **中国地区启动验证** 模块 + +## 💬 社区与贡献 (Community & Contribution) + +
+

+ + Telegram Channel + + + Telegram Chat + + + Telegram Build + + + QQ Group + +

+

+ + GitHub Issues + + + Pull Requests + + + Crowdin + +

+
+ +## 📊 项目统计 (Project Stats) + +
+ + + + + + + + +
+ + + + Repo Card + + + + + + Top Langs + +
+ + + + Trophies + +
+ +Repobeats analytics image + +[![Star History Chart](https://api.star-history.com/svg?repos=suqi8/OShin&type=Date)](https://star-history.com/#suqi8/OShin&Date) + +
+ +## ✨ 贡献者 (Contributors) + +
+ + Contributors + +
diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 00000000..75796e8e --- /dev/null +++ b/README_EN.md @@ -0,0 +1,162 @@ +
+ +# OShin - ColorOS Auxiliary Module + +icon + +[简体中文](./README.md) | **English** | [Online Website](https://oshin.mikusignal.top/) + +
+ + Stars + + + GitHub Downloads + + + Xposed Repo Downloads + +
+ +

+ +![OShin Socialify Banner](https://socialify.git.ci/suqi8/OShin/image?font=Rokkitt&forks=1&issues=1&language=1&name=1&owner=1&pattern=Brick%20Wall&pulls=1&stargazers=1&theme=Auto) + +
+ +
+

+ OShin (formerly OPatch) is a deeply customized assistant module for ColorOS systems, designed to enhance and optimize your overall system experience. +

+
+ + + + + + + + + + +
+

⚠️ Core Requirements

+
    +
  • Root Solution: Magisk, KernelSU, or APatch
  • +
  • Xposed Framework: LSPosed
  • +
+
+

⚙️ Supported Versions (Android 16)

+
    +
  • ColorOS 16
  • +
  • RealmeUI 7
  • +
  • OxygenOS 16
  • +
+
+

📥 Download

+
    +
  1. Recommended: Search for OShin in the LSPosed Manager
  2. +
  3. Manual: Download the APK from GitHub Releases
  4. +
+
+

🚀 Activation

+
    +
  1. Enable the module in LSPosed
  2. +
  3. Select scope: System Framework (android) etc.
  4. +
  5. Reboot the device
  6. +
+
+ + +--- + +### 🚫 Feature Updates and Code Availability Notice +* **As of November 12, 2025, the main repository has been made private, and this mirror is retained for reference only.** +* **Source code and feature implementation plans will no longer be made public subsequently.** +* **How to participate in OShin development?** Please contact: `3383787570@qq.com` +* **Latest developments?** Please refer to the "Community & Contribution" section below. +* **Issues Status:** Still available for normal use. + +> **🔒 The code retained in the repository still adheres to the open-source protocol and the open-source disclaimer below.** + +## 📜 Open Source Disclaimer + +This is an open-source project and contributions are welcome. + +When you modify or distribute this program, **you must not alter** the following content: + +- The **Acknowledgments** and **Author Information** in the "About" page +- Code related to the **Umeng SDK** +- The **China region startup verification** module + +## 💬 Community & Contribution + +
+

+ + Telegram Channel + + + Telegram Chat + + + Telegram Build + +

+

+ + GitHub Issues + + + Pull Requests + + + Crowdin + +

+
+ +## 📊 Project Stats + +
+ + + + + + + + +
+ + + + Repo Card + + + + + + Top Langs + +
+ + + + Trophies + +
+ +Repobeats analytics image + +[![Star History Chart](https://api.star-history.com/svg?repos=suqi8/OShin&type=Date)](https://star-history.com/#suqi8/OShin&Date) + +
+ +## ✨ Contributors + +
+ + Contributors + +
\ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 00000000..deb87e92 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,258 @@ +// 声明项目构建所需的 Gradle 插件。 +plugins { + // 通过 Gradle 版本目录(Version Catalog)的别名方式引用插件,以实现集中管理。 + alias(libs.plugins.android.application) // Android 应用程序插件,用于构建 .apk 文件。 + alias(libs.plugins.kotlin.android) // 提供 Kotlin 语言在 Android 平台上的支持。 + alias(libs.plugins.ksp) // Kotlin 符号处理(KSP)插件,用于执行编译时代码生成。 + alias(libs.plugins.kotlin.compose) // Jetpack Compose 编译器插件,用于处理 @Composable 注解。 + id("com.google.dagger.hilt.android") +} + +/** + * Git 版本信息提供者。 + * + * 通过 Gradle 的 Provider API 实现构建信息的延迟化配置(Lazy Configuration)。 + * `git` 命令仅在配置属性(如 `versionCode`)被实际需要时执行, + * 以此优化 Gradle 在配置阶段(Configuration Phase)的性能。 + */ +// 获取当前 Git 提交的短哈希值。 +val gitCommitHashProvider = providers.exec { + commandLine("git", "rev-parse", "--short", "HEAD") +}.standardOutput.asText.map { it.trim() } + +// 获取从项目起始到当前 HEAD 的总提交次数。 +val gitCommitCountProvider = providers.exec { + commandLine("git", "rev-list", "--count", "HEAD") +}.standardOutput.asText.map { it.trim() } + +// Android 项目的核心配置。 +android { + namespace = "com.suqi8.oshin" // 定义应用的包名,用于生成 R 类和 Manifest。 + compileSdk = 36 // 指定用于编译应用的 Android API 版本。 + + // 默认配置,应用于所有的构建变体(Build Variant)。 + defaultConfig { + applicationId = "com.suqi8.oshin" // 应用程序在设备和应用商店上的唯一标识符。 + minSdk = 33 // 应用可以运行的最低 Android API 级别。 + targetSdk = 36 // 应用设计和测试所基于的目标 Android API 级别。 + + // 动态设置版本信息。 + // .getOrElse() 提供了一个回退值,确保在非 Git 环境下构建的健壮性。 + versionCode = gitCommitCountProvider.map { it.toInt() }.getOrElse(1) + versionName = gitCommitCountProvider.zip(gitCommitHashProvider) { count, hash -> + "16.2.$count.$hash" // 版本名格式:主版本.次版本.提交总数.提交哈希 + }.getOrElse("16.local") // 在 Git 不可用时使用的默认版本名。 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" // 指定仪器测试的运行器。 + vectorDrawables { + useSupportLibrary = true // 为 API 21 以下的设备启用对矢量图的支持。 + } + } + + // --- ABI 拆分配置 --- + // 此配置块用于指示 Gradle 为不同的 CPU 架构生成独立的 APK。 + splits { + abi { + isEnable = true // 1. 启用 ABI 拆分 + reset() // 2. 清除默认设置 (如 x86, mips 等) + include("arm64-v8a") // 3. 只包含 64 位 v8a 架构 + isUniversalApk = false // 4. 不再生成通用 (universal/all) APK + } + } + + // 配置 APK 输出文件名。 + // 注意:此处使用已废弃的 `applicationVariants.all` API。 + // 这是为了兼容当前构建环境,以确保 APK 文件名自定义功能的稳定性。 + applicationVariants.all { + val variant = this + variant.outputs.all { + val outputImpl = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl + val name = "OShin" + // 从输出过滤器中获取 ABI(Application Binary Interface)名称。 + val abi = outputImpl.filters.find { it.filterType == "ABI" }?.identifier ?: "all" + val version = variant.versionName + val versionCode = variant.versionCode + val outputFileName = "${name}_${abi}_v${version}(${versionCode}).apk" + outputImpl.outputFileName = outputFileName + } + } + + // 配置应用的签名信息。 + signingConfigs { + val keystoreFile = System.getenv("KEYSTORE_PATH") + val isCiBuild = keystoreFile != null + + // 若检测到 CI/CD 环境变量,则创建用于持续集成的签名配置。 + if (isCiBuild) { + create("ci") { + storeFile = file(keystoreFile) + storePassword = System.getenv("KEYSTORE_PASSWORD") + keyAlias = System.getenv("KEY_ALIAS") + keyPassword = System.getenv("KEY_PASSWORD") + enableV4Signing = true // 启用 APK 签名方案 v4,以支持增量安装等优化。 + } + } + // 创建一个通用的 "release" 签名配置,用于本地构建或在 CI/CD 环境之外的场景。 + create("release") { + enableV4Signing = true + } + } + + // 配置不同的构建类型,如 "release" 和 "debug"。 + buildTypes { + release { + val keystoreFile = System.getenv("KEYSTORE_PATH") + val isCiBuild = keystoreFile != null + // 根据是否存在 CI 环境变量来决定使用哪个签名配置。 + signingConfig = signingConfigs.getByName(if (isCiBuild) "ci" else "release") + + // 通过 `buildConfigField` 在 `BuildConfig.java` 中生成一个常量。 + val buildTag = if (isCiBuild) "CI Build" else "Release" + buildConfigField("String", "BUILD_TYPE_TAG", "\"$buildTag\"") + + isMinifyEnabled = true // 启用 R8/ProGuard 进行代码压缩、优化和混淆。 + isShrinkResources = true // 启用资源缩减,移除未被引用的资源文件。 + isDebuggable = false // 发布版本禁止调试。 + isJniDebuggable = false // 禁止对 JNI (C/C++) 代码进行调试。 + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + debug { + buildConfigField("String", "BUILD_TYPE_TAG", "\"Debug\"") + } + } + + // Java/Kotlin 编译选项。 + compileOptions { + sourceCompatibility = JavaVersion.VERSION_21 // 设置 Java 源代码的语言级别。 + targetCompatibility = JavaVersion.VERSION_21 // 设置生成的 Java 字节码的目标 JVM 版本。 + } + + // 配置 Kotlin 编译器选项。 + tasks.withType().configureEach { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21) // 设置 Kotlin 编译输出的 JVM 目标版本。 + freeCompilerArgs.addAll( + "-Xno-param-assertions", + "-Xno-call-assertions", + "-Xno-receiver-assertions" + ) + } + } + + // 启用或禁用特定的构建功能。 + buildFeatures { + buildConfig = true // 启用 `BuildConfig.java` 的自动生成。 + viewBinding = true // 启用视图绑定功能。 + compose = true // 启用 Jetpack Compose 支持。 + } + + // Jetpack Compose 相关的编译器配置。 + composeOptions { + kotlinCompilerExtensionVersion = "1.5.14" + } + + // APK 打包相关的配置。 + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + excludes += "/META-INF/{AL2.0,LGPL2.1}" + excludes += "DebugProbesKt.bin" + excludes += "kotlin-tooling-metadata.json" + } + } + + // Android 资源处理相关的配置。 + androidResources { + ignoreAssetsPattern = "!*.ttf:!*.json:!*.bin" + noCompress += listOf("zip", "txt", "raw", "png") + } + + kotlin { + jvmToolchain(21) + compilerOptions { + freeCompilerArgs.addAll( + "-Xcontext-parameters" + ) + } + } + + // Lint 静态代码分析工具的配置。 + lint { + baseline = file("lint-baseline.xml") // 设置一个基线文件,用于忽略已存在的 Lint 问题。 + } +} + +// 依赖项声明块 +dependencies { + // ------------------- AndroidX & Jetpack 核心库 ------------------- + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.navigation.runtime.ktx) + implementation(libs.androidx.palette.ktx) + implementation(libs.androidx.activity.compose) + implementation(libs.hilt.android) + implementation(libs.androidx.foundation.layout) + ksp(libs.hilt.android.compiler) + + // ------------------- Jetpack Compose UI ------------------- + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.ui.graphics) + debugImplementation(libs.androidx.compose.ui.tooling.preview) + implementation(libs.androidx.compose.material.icons.extended) + implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.hilt.navigation.compose) + + // ------------------- Compose 生态第三方库 ------------------- + //implementation(libs.accompanist.flowlayout) + implementation(libs.airbnb.lottie.compose) + implementation(libs.coil.compose) + //implementation(libs.haze) + //implementation(libs.shimmer.compose) + //implementation(libs.toolbar.compose) + //implementation(libs.expandablebottombar) + //implementation(libs.neumorphism.compose) + implementation(libs.coil.compose) + implementation(libs.coil.network.okhttp) + implementation(libs.capsule) + implementation(libs.multiplatform.markdown.renderer.android) + implementation(libs.multiplatform.markdown.renderer.coil3) + implementation(libs.multiplatform.markdown.renderer.code) + + // ------------------- 底层与工具库 ------------------- + implementation(libs.luckypray.dexkit) + implementation(libs.xxpermissions) + implementation(libs.squareup.okhttp) + implementation(libs.coil.network.okhttp) + implementation(libs.gson) + //implementation(libs.drawabletoolbox) + implementation(libs.miuix) + //implementation(libs.mmkv) + + // ------------------- Hook API 相关 ------------------- + implementation(libs.ezxhelper) + compileOnly(libs.xposed.api) + implementation(libs.yukihook.api) + ksp(libs.yukihook.ksp.xposed) + implementation(libs.kavaref.core) + implementation(libs.kavaref.extension) + + // ------------------- Room 数据库 ------------------- + runtimeOnly(libs.androidx.room.runtime) + ksp(libs.androidx.room.compiler) + + // ------------------- Umeng (友盟) SDK ------------------- + implementation(libs.umeng.common) + implementation(libs.umeng.asms) + implementation(libs.umeng.uyumao) + implementation(libs.union) + + // ------------------- 测试相关库 ------------------- + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.test.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.compose.ui.test.junit4) +} diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml new file mode 100644 index 00000000..cb6dca8f --- /dev/null +++ b/app/lint-baseline.xml @@ -0,0 +1,1388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/proguard-dict.txt b/app/proguard-dict.txt new file mode 100644 index 00000000..f464341c --- /dev/null +++ b/app/proguard-dict.txt @@ -0,0 +1,487 @@ +你说得对 +但是原神是由米哈游自主研发的一款全新开放世界冒险游戏 +游戏发生在一个被称作提瓦特的幻想世界 +在这里被神选中的人将被授予神之眼 +导引元素之力 +你将扮演一位名为旅行者的神秘角色 +在自由的旅行中邂逅性格各异能力独特的同伴们 +和他们一起击败强敌 +找回失散的亲人同时 +逐步发掘原神的真相 +但是烟神 +是由丁真珍珠自主研发的一款全新开放世界冒险游戏 +游戏发生在一个被称作理塘的幻想世界 +被神选中的人将被授予电子烟 +引导尼古丁之力 +你将扮演一位名为芙蓉王的神秘角色 +在自由的旅行中邂逅性格各异能力独特的动物朋友 +和它们一起击败强敌 +找回不存在的亲人的同时 +逐步发掘理塘的真相 +但是命运二 +是由棒鸡自主研发的一款全新开放世界冒险游戏 +游戏发生在一个被称作太阳系的幻想世界 +被旅行者选中的人将被授予机灵 +导引光能之力 +你将扮演一位名为守护者的神秘角色 +阻止暗影的侵袭同时 +逐步发掘光能与暗影的真相 +但是贴吧 +是由百度自主研发的一款全新嘴炮论坛喷人平台 +贴吧含有一个被称作孙笑川吧的贴吧世界 +被吧主选中的人将被授予米线 +引导团建之力 +你将扮演一位名为8u的神秘用户 +在自由的发帖中邂逅性格各异的贴吧老鼠们 +和它们一起无中生有 +找回被抄的游戏的同时 +逐步降低贴吧的素质 +但是CSGO +是由valve自主研发的一款全新竞技类FPS游戏 +游戏发生在一个被称作炙热沙城的幻想世界 +被FBI选中的人将被授予经济 +导引反恐之力 +你将扮演一位名为反恐精英的神秘角色 +在自由的对局中邂逅性格各异能力独特的同伴们 +找回挂B的亲m的同时 +逐步发掘急停与拉枪的真相 +但是鬼泣五 +是由卡普空自主研发的一款全新动作类冒险游戏 +游戏发生在一个被称作红墓市的幻想世界 +被斯巴达选中的人将被授予魔人 +导引恶魔之力 +你将扮演一位名为恶魔猎人的神秘角色 +在自由的旅行中邂逅性格各异能力独特同伴们 +阻止魔树侵袭的同时 +逐步发掘V的真相 +但是巫师三狂猎 +是由波兰蠢驴自主研发的一款全新RPG冒险游戏 +游戏发生在一个被称作北部王国的幻想世界 +被维瑟米尔选中的人将被授予青草试炼 +导引狩魔猎人之力 +你将扮演一位名为狩魔猎人的神秘角色 +和他们一起打昆特牌 +寻找失散女儿的同时 +逐步发掘狂猎的真相 +满身烟味的我 +走路带上点浮夸 +想买悦刻五代找我丁真就对了 +竖起中指王源他算个寄吧 +台上打饱嗝我在和雪豹比划 +丁真势力正在不断地扩大 +微博之夜掏裤裆我上上下下 +珈乐心理放线被我轻松击垮 +甜美微笑爆杀那些小题做题家 +脚叫做勾八 +八八八八八 +对你说藏话 +啦啦啦啦啦 +你不会回笼 +回家找你妈 +抽传统香烟 +我测你们码 +偶尔骑骑小马 +理塘走一走 +随手掏出悦刻 +你也来一口 +哥们不费力 +就住进了高楼 +理塘高速路都五档起步走 +去联合国学英文 +哥们LetsGo +回理塘实践 +哥们Lets抽 +哥们哇啦哇啦乱唱听感就拉满了 +你文化程度再高你也听不懂的 +感觉这首歌技术不如丁真 +因为你是肥猪 +体重要按吨 +我投资悦刻 +别他妈倒闭了 +有烟弹寄一个 +给礼堂的丁真 +我特么想抽烟 +抽死我个byd +(快给我抽) +快给我 +我要抽 +IGOT油我想抽 +IGOT油ALLMYMIND +抽不了兜着走 +走走走走走走走 +那些说唱都是一坨屎 +攻击性没有词汇还低能的要死 +但我心怀善念接纳悦刻五的每个孝子 +我的回笼技术能够气死那些黑子 +一眼丁真鉴定为纯纯的若智 +牙医丁真鉴定为纯纯的白齿 +经过A门的时候帮我把烟雾给封死 +王源不发龙狙证明他并没有素质 +打完狙我准备骑珍珠去抽一根哦 +但是珍珠不在了 +我只能骑着我的纠纠 +穿越整个四川找悦刻旗舰店 +为了芋泥啵啵我抽胖了双脸 +但是人们依然爱我爱我纯真双眼 +为了苦练烟嗓我抽烟好几年 +现在一唱歌哥们嗓子就漏电 +我爱抽烟出生到太平间 +我爱抽烟一天十根直到肺病变 +不要抽假的烟不要抽 +如果你不知道去哪里就来我直播间 +为了买烟我付出太多 +没有电子烟的人生就只是片荒漠 +没有悦刻的人生我也只是个喽啰 +看到不认识的烟杆我会去抚摸 +这一段我也不知道我在唱什么 +也许我也只是溜大了 +也许我只不过是溜大了 +就像你的人生糊弄糊弄不就完了么 +我的小马名字叫珍珠 +假烟发现就跑路 +臭要饭的别挡我财路 +whyYouAlwaysSoPoor +因为你没光顾我店铺 +你自己心里有数 +电子烟我这最靠谱 +给我看看你吞云吐雾哥们哥们哥们 +我只想给你尝尝所有的口味 +拜托我别给你推销了 +从理塘到了上海 +哥收获了好多money +都什么年代? +还在抽传统香烟? +我买的烟弹 +包含了理塘王子的魅力 +欢迎你们来我村庄然日卡玩 +我是一个马背上的康巴的汉子 +姐姐哥哥能不能帮我和啾啾撮合 +哥们名叫丁真 +格莱美是我的下一步 +外国人都别挡我路 +每天看四小时书 +文化先和初中生同步 +没有电子烟的人生就只是片荒漠逐步发掘原神的真相 +逐步降低贴吧的素质在自由的发帖中邂逅性格各异的贴吧老鼠们 +随手掏出悦刻从理塘到了上海 +但是烟神但是CSGO +珈乐心理放线被我轻松击垮IGOT油ALLMYMIND +还在抽传统香烟?你将扮演一位名为8u的神秘用户 +阻止魔树侵袭的同时你将扮演一位名为恶魔猎人的神秘角色 +贴吧含有一个被称作孙笑川吧的贴吧世界哥们哇啦哇啦乱唱听感就拉满了 +都什么年代?臭要饭的别挡我财路 +就像你的人生糊弄糊弄不就完了么游戏发生在一个被称作红墓市的幻想世界 +臭要饭的别挡我财路啦啦啦啦啦 +如果你不知道去哪里就来我直播间被斯巴达选中的人将被授予魔人 +在自由的旅行中邂逅性格各异能力独特的动物朋友和它们一起无中生有 +你将扮演一位名为旅行者的神秘角色你将扮演一位名为芙蓉王的神秘角色 +找回不存在的亲人的同时满身烟味的我 +给我看看你吞云吐雾哥们哥们哥们是由valve自主研发的一款全新竞技类FPS游戏 +哥们不费力对你说藏话 +丁真势力正在不断地扩大穿越整个四川找悦刻旗舰店 +阻止暗影的侵袭同时你将扮演一位名为芙蓉王的神秘角色 +有烟弹寄一个快给我 +也许我只不过是溜大了不要抽假的烟不要抽 +和它们一起无中生有在自由的旅行中邂逅性格各异能力独特的动物朋友 +逐步发掘急停与拉枪的真相IGOT油ALLMYMIND +引导尼古丁之力是由百度自主研发的一款全新嘴炮论坛喷人平台 +王源不发龙狙证明他并没有素质哥们哇啦哇啦乱唱听感就拉满了 +你文化程度再高你也听不懂的在自由的旅行中邂逅性格各异能力独特同伴们 +我投资悦刻一眼丁真鉴定为纯纯的若智 +你将扮演一位名为守护者的神秘角色游戏发生在一个被称作北部王国的幻想世界 +为了芋泥啵啵我抽胖了双脸甜美微笑爆杀那些小题做题家 +理塘高速路都五档起步走阻止暗影的侵袭同时 +在自由的旅行中邂逅性格各异能力独特的同伴们抽不了兜着走 +和它们一起击败强敌为了苦练烟嗓我抽烟好几年 +和他们一起打昆特牌我爱抽烟一天十根直到肺病变 +电子烟我这最靠谱给我看看你吞云吐雾哥们哥们哥们 +牙医丁真鉴定为纯纯的白齿快给我 +我爱抽烟出生到太平间是由百度自主研发的一款全新嘴炮论坛喷人平台 +但是珍珠不在了但是命运二 +走走走走走走走在自由的旅行中邂逅性格各异能力独特的动物朋友 +一眼丁真鉴定为纯纯的若智导引元素之力 +导引元素之力走路带上点浮夸 +八八八八八找回失散的亲人同时 +IGOT油ALLMYMIND我是一个马背上的康巴的汉子 +为了买烟我付出太多逐步发掘急停与拉枪的真相 +哥们Lets抽脚叫做勾八 +导引光能之力为了买烟我付出太多 +就像你的人生糊弄糊弄不就完了么没有电子烟的人生就只是片荒漠 +脚叫做勾八在自由的旅行中邂逅性格各异能力独特的同伴们 +王源不发龙狙证明他并没有素质但是贴吧 +导引狩魔猎人之力找回挂B的亲m的同时 +因为你没光顾我店铺如果你不知道去哪里就来我直播间 +在这里被神选中的人将被授予神之眼我爱抽烟一天十根直到肺病变 +穿越整个四川找悦刻旗舰店格莱美是我的下一步 +但是命运二逐步发掘狂猎的真相 +为了苦练烟嗓我抽烟好几年你文化程度再高你也听不懂的 +是由丁真珍珠自主研发的一款全新开放世界冒险游戏微博之夜掏裤裆我上上下下 +游戏发生在一个被称作太阳系的幻想世界那些说唱都是一坨屎 +和它们一起击败强敌和它们一起无中生有 +快给我我特么想抽烟 +抽死我个byd但是贴吧 +从理塘到了上海姐姐哥哥能不能帮我和啾啾撮合 +你将扮演一位名为反恐精英的神秘角色导引光能之力 +你将扮演一位名为狩魔猎人的神秘角色理塘走一走 +但是鬼泣五我特么想抽烟 +我特么想抽烟你自己心里有数 +(快给我抽)经过A门的时候帮我把烟雾给封死 +有烟弹寄一个游戏发生在一个被称作炙热沙城的幻想世界 +逐步发掘狂猎的真相感觉这首歌技术不如丁真 +就住进了高楼给我看看你吞云吐雾哥们哥们哥们 +我买的烟弹别他妈倒闭了 +一眼丁真鉴定为纯纯的若智那些说唱都是一坨屎 +被斯巴达选中的人将被授予魔人从理塘到了上海 +被FBI选中的人将被授予经济有烟弹寄一个 +导引光能之力和它们一起击败强敌 +找回被抄的游戏的同时微博之夜掏裤裆我上上下下 +导引狩魔猎人之力如果你不知道去哪里就来我直播间 +给我看看你吞云吐雾哥们哥们哥们导引元素之力 +被FBI选中的人将被授予经济但是原神是由米哈游自主研发的一款全新开放世界冒险游戏 +偶尔骑骑小马你自己心里有数 +被吧主选中的人将被授予米线台上打饱嗝我在和雪豹比划 +每天看四小时书包含了理塘王子的魅力 +游戏发生在一个被称作北部王国的幻想世界你文化程度再高你也听不懂的 +欢迎你们来我村庄然日卡玩没有电子烟的人生就只是片荒漠 +我只想给你尝尝所有的口味欢迎你们来我村庄然日卡玩 +电子烟我这最靠谱被旅行者选中的人将被授予机灵 +牙医丁真鉴定为纯纯的白齿走路带上点浮夸 +在这里被神选中的人将被授予神之眼假烟发现就跑路 +脚叫做勾八偶尔骑骑小马 +哥收获了好多money你自己心里有数 +你文化程度再高你也听不懂的你说得对 +因为你是肥猪文化先和初中生同步 +看到不认识的烟杆我会去抚摸但是鬼泣五 +我爱抽烟一天十根直到肺病变但是巫师三狂猎 +和它们一起无中生有理塘高速路都五档起步走 +是由棒鸡自主研发的一款全新开放世界冒险游戏臭要饭的别挡我财路 +抽不了兜着走去联合国学英文 +是由丁真珍珠自主研发的一款全新开放世界冒险游戏对你说藏话 +但是贴吧回理塘实践 +因为你没光顾我店铺你也来一口 +你不会回笼别他妈倒闭了 +哥们哇啦哇啦乱唱听感就拉满了打完狙我准备骑珍珠去抽一根哦 +穿越整个四川找悦刻旗舰店抽不了兜着走 +满身烟味的我别他妈倒闭了 +是由valve自主研发的一款全新竞技类FPS游戏姐姐哥哥能不能帮我和啾啾撮合 +回家找你妈对你说藏话 +我投资悦刻微博之夜掏裤裆我上上下下 +阻止魔树侵袭的同时我爱抽烟出生到太平间 +包含了理塘王子的魅力牙医丁真鉴定为纯纯的白齿 +格莱美是我的下一步我买的烟弹 +给礼堂的丁真文化先和初中生同步 +在自由的旅行中邂逅性格各异能力独特同伴们走走走走走走走 +游戏发生在一个被称作提瓦特的幻想世界你自己心里有数 +文化先和初中生同步电子烟我这最靠谱 +我买的烟弹为了芋泥啵啵我抽胖了双脸 +(快给我抽)假烟发现就跑路 +你说得对寻找失散女儿的同时 +想买悦刻五代找我丁真就对了抽死我个byd +走走走走走走走我爱抽烟一天十根直到肺病变 +理塘高速路都五档起步走就像你的人生糊弄糊弄不就完了么 +但我心怀善念接纳悦刻五的每个孝子逐步发掘V的真相 +导引恶魔之力在自由的旅行中邂逅性格各异能力独特的动物朋友 +在自由的旅行中邂逅性格各异能力独特的动物朋友导引光能之力 +被维瑟米尔选中的人将被授予青草试炼你将扮演一位名为旅行者的神秘角色 +找回失散的亲人同时我要抽 +理塘走一走臭要饭的别挡我财路 +丁真势力正在不断地扩大阻止暗影的侵袭同时 +我是一个马背上的康巴的汉子外国人都别挡我路 +导引元素之力游戏发生在一个被称作炙热沙城的幻想世界 +攻击性没有词汇还低能的要死是由波兰蠢驴自主研发的一款全新RPG冒险游戏 +但是贴吧是由卡普空自主研发的一款全新动作类冒险游戏 +你将扮演一位名为狩魔猎人的神秘角色就住进了高楼 +这一段我也不知道我在唱什么给礼堂的丁真 +游戏发生在一个被称作理塘的幻想世界没有电子烟的人生就只是片荒漠 +找回不存在的亲人的同时理塘高速路都五档起步走 +走路带上点浮夸电子烟我这最靠谱 +和他们一起击败强敌从理塘到了上海 +珈乐心理放线被我轻松击垮感觉这首歌技术不如丁真 +姐姐哥哥能不能帮我和啾啾撮合你不会回笼 +在自由的发帖中邂逅性格各异的贴吧老鼠们拜托我别给你推销了 +但是CSGO为了苦练烟嗓我抽烟好几年 +走路带上点浮夸也许我只不过是溜大了 +游戏发生在一个被称作北部王国的幻想世界姐姐哥哥能不能帮我和啾啾撮合 +但是巫师三狂猎是由卡普空自主研发的一款全新动作类冒险游戏 +从理塘到了上海在自由的旅行中邂逅性格各异能力独特同伴们 +在自由的旅行中邂逅性格各异能力独特的同伴们whyYouAlwaysSoPoor +微博之夜掏裤裆我上上下下和他们一起击败强敌 +你将扮演一位名为恶魔猎人的神秘角色但是鬼泣五 +满身烟味的我游戏发生在一个被称作太阳系的幻想世界 +体重要按吨哥们LetsGo +你将扮演一位名为芙蓉王的神秘角色一眼丁真鉴定为纯纯的若智 +你将扮演一位名为守护者的神秘角色你也来一口 +是由波兰蠢驴自主研发的一款全新RPG冒险游戏逐步发掘狂猎的真相 +找回挂B的亲m的同时满身烟味的我 +你也来一口丁真势力正在不断地扩大 +是由百度自主研发的一款全新嘴炮论坛喷人平台(快给我抽) +抽死我个byd游戏发生在一个被称作提瓦特的幻想世界 +被吧主选中的人将被授予米线给我看看你吞云吐雾哥们哥们哥们 +理塘走一走都什么年代? +感觉这首歌技术不如丁真是由棒鸡自主研发的一款全新开放世界冒险游戏 +那些说唱都是一坨屎你不会回笼 +外国人都别挡我路但是CSGO +IGOT油我想抽牙医丁真鉴定为纯纯的白齿 +逐步降低贴吧的素质一眼丁真鉴定为纯纯的若智 +微博之夜掏裤裆我上上下下走路带上点浮夸 +看到不认识的烟杆我会去抚摸我要抽 +还在抽传统香烟?逐步发掘理塘的真相 +逐步发掘理塘的真相因为你没光顾我店铺 +逐步发掘狂猎的真相没有电子烟的人生就只是片荒漠 +我要抽但是珍珠不在了 +哥们名叫丁真游戏发生在一个被称作提瓦特的幻想世界 +你将扮演一位名为反恐精英的神秘角色这一段我也不知道我在唱什么 +打完狙我准备骑珍珠去抽一根哦是由卡普空自主研发的一款全新动作类冒险游戏 +导引恶魔之力哥们名叫丁真 +游戏发生在一个被称作红墓市的幻想世界逐步发掘狂猎的真相 +导引反恐之力从理塘到了上海 +是由百度自主研发的一款全新嘴炮论坛喷人平台你将扮演一位名为8u的神秘用户 +不要抽假的烟不要抽我爱抽烟出生到太平间 +找回被抄的游戏的同时你将扮演一位名为守护者的神秘角色 +没有电子烟的人生就只是片荒漠抽不了兜着走 +抽不了兜着走为了苦练烟嗓我抽烟好几年 +引导团建之力牙医丁真鉴定为纯纯的白齿 +去联合国学英文都什么年代? +游戏发生在一个被称作炙热沙城的幻想世界但是珍珠不在了 +我特么想抽烟和他们一起打昆特牌 +寻找失散女儿的同时IGOT油ALLMYMIND +不要抽假的烟不要抽IGOT油ALLMYMIND +游戏发生在一个被称作红墓市的幻想世界逐步发掘光能与暗影的真相 +哥们LetsGo和它们一起无中生有 +现在一唱歌哥们嗓子就漏电别他妈倒闭了 +我只想给你尝尝所有的口味和它们一起无中生有 +是由波兰蠢驴自主研发的一款全新RPG冒险游戏是由valve自主研发的一款全新竞技类FPS游戏 +我测你们码脚叫做勾八 +逐步发掘光能与暗影的真相我特么想抽烟 +啦啦啦啦啦被神选中的人将被授予电子烟 +快给我啦啦啦啦啦 +臭要饭的别挡我财路脚叫做勾八 +寻找失散女儿的同时理塘走一走 +抽传统香烟在自由的对局中邂逅性格各异能力独特的同伴们 +竖起中指王源他算个寄吧导引光能之力 +哥们Lets抽假烟发现就跑路 +但是CSGO哥们不费力 +被旅行者选中的人将被授予机灵你将扮演一位名为狩魔猎人的神秘角色 +逐步发掘V的真相没有电子烟的人生就只是片荒漠 +回理塘实践满身烟味的我 +没有悦刻的人生我也只是个喽啰每天看四小时书 +哥们不费力那些说唱都是一坨屎 +外国人都别挡我路是由卡普空自主研发的一款全新动作类冒险游戏 +和他们一起打昆特牌哥们Lets抽 +回家找你妈为了芋泥啵啵我抽胖了双脸 +你将扮演一位名为芙蓉王的神秘角色你不会回笼 +IGOT油我想抽给我看看你吞云吐雾哥们哥们哥们 +就住进了高楼为了苦练烟嗓我抽烟好几年 +经过A门的时候帮我把烟雾给封死理塘走一走 +甜美微笑爆杀那些小题做题家游戏发生在一个被称作理塘的幻想世界 +是由卡普空自主研发的一款全新动作类冒险游戏对你说藏话 +现在一唱歌哥们嗓子就漏电啦啦啦啦啦 +你将扮演一位名为旅行者的神秘角色给我看看你吞云吐雾哥们哥们哥们 +被维瑟米尔选中的人将被授予青草试炼我特么想抽烟 +和他们一起击败强敌为了芋泥啵啵我抽胖了双脸 +但是珍珠不在了偶尔骑骑小马 +对你说藏话被FBI选中的人将被授予经济 +我的小马名字叫珍珠文化先和初中生同步 +导引反恐之力抽死我个byd +台上打饱嗝我在和雪豹比划抽死我个byd +哥们LetsGo走走走走走走走 +但是命运二去联合国学英文 +是由valve自主研发的一款全新竞技类FPS游戏台上打饱嗝我在和雪豹比划 +贴吧含有一个被称作孙笑川吧的贴吧世界随手掏出悦刻 +被神选中的人将被授予电子烟逐步发掘光能与暗影的真相 +找回失散的亲人同时但是鬼泣五 +在自由的对局中邂逅性格各异能力独特的同伴们哥们哇啦哇啦乱唱听感就拉满了 +哥们名叫丁真为了苦练烟嗓我抽烟好几年 +是由卡普空自主研发的一款全新动作类冒险游戏哥们哇啦哇啦乱唱听感就拉满了 +但是人们依然爱我爱我纯真双眼在自由的旅行中邂逅性格各异能力独特的同伴们 +在自由的对局中邂逅性格各异能力独特的同伴们偶尔骑骑小马 +别他妈倒闭了(快给我抽) +哥收获了好多money我投资悦刻 +没有悦刻的人生我也只是个喽啰逐步降低贴吧的素质 +逐步发掘理塘的真相(快给我抽) +你将扮演一位名为恶魔猎人的神秘角色因为你没光顾我店铺 +随手掏出悦刻但是巫师三狂猎 +八八八八八竖起中指王源他算个寄吧 +那些说唱都是一坨屎走走走走走走走 +给礼堂的丁真在自由的旅行中邂逅性格各异能力独特的动物朋友 +但是人们依然爱我爱我纯真双眼我的小马名字叫珍珠 +我的回笼技术能够气死那些黑子拜托我别给你推销了 +感觉这首歌技术不如丁真穿越整个四川找悦刻旗舰店 +假烟发现就跑路抽不了兜着走 +我只能骑着我的纠纠导引狩魔猎人之力 +去联合国学英文哥收获了好多money +文化先和初中生同步是由百度自主研发的一款全新嘴炮论坛喷人平台 +都什么年代?体重要按吨 +游戏发生在一个被称作太阳系的幻想世界哥们Lets抽 +也许我也只是溜大了体重要按吨 +打完狙我准备骑珍珠去抽一根哦被旅行者选中的人将被授予机灵 +我要抽你将扮演一位名为旅行者的神秘角色 +欢迎你们来我村庄然日卡玩现在一唱歌哥们嗓子就漏电 +但是巫师三狂猎每天看四小时书 +包含了理塘王子的魅力也许我只不过是溜大了 +逐步发掘V的真相为了芋泥啵啵我抽胖了双脸 +我的小马名字叫珍珠甜美微笑爆杀那些小题做题家 +但是原神是由米哈游自主研发的一款全新开放世界冒险游戏不要抽假的烟不要抽 +whyYouAlwaysSoPoor抽不了兜着走 +阻止暗影的侵袭同时这一段我也不知道我在唱什么 +这一段我也不知道我在唱什么对你说藏话 +你不会回笼拜托我别给你推销了 +经过A门的时候帮我把烟雾给封死有烟弹寄一个 +我爱抽烟一天十根直到肺病变别他妈倒闭了 +我是一个马背上的康巴的汉子竖起中指王源他算个寄吧 +你自己心里有数你文化程度再高你也听不懂的 +在自由的发帖中邂逅性格各异的贴吧老鼠们逐步发掘狂猎的真相 +你也来一口抽不了兜着走 +啦啦啦啦啦回家找你妈 +为了苦练烟嗓我抽烟好几年但是原神是由米哈游自主研发的一款全新开放世界冒险游戏 +你说得对我只能骑着我的纠纠 +格莱美是我的下一步给礼堂的丁真 +逐步发掘原神的真相那些说唱都是一坨屎 +台上打饱嗝我在和雪豹比划给礼堂的丁真 +假烟发现就跑路你将扮演一位名为反恐精英的神秘角色 +被旅行者选中的人将被授予机灵走走走走走走走 +甜美微笑爆杀那些小题做题家逐步发掘V的真相 +whyYouAlwaysSoPoor你将扮演一位名为守护者的神秘角色 +抽传统香烟但是贴吧 +也许我只不过是溜大了被FBI选中的人将被授予经济 +为了买烟我付出太多现在一唱歌哥们嗓子就漏电 +也许我也只是溜大了引导尼古丁之力 +攻击性没有词汇还低能的要死你自己心里有数 +引导团建之力外国人都别挡我路 +姐姐哥哥能不能帮我和啾啾撮合啦啦啦啦啦 +但是鬼泣五因为你没光顾我店铺 +我的回笼技术能够气死那些黑子被FBI选中的人将被授予经济 +被神选中的人将被授予电子烟你说得对 +找回挂B的亲m的同时但是巫师三狂猎 +逐步发掘急停与拉枪的真相看到不认识的烟杆我会去抚摸 +引导尼古丁之力为了苦练烟嗓我抽烟好几年 +你将扮演一位名为8u的神秘用户每天看四小时书 +游戏发生在一个被称作理塘的幻想世界导引元素之力 +逐步发掘光能与暗影的真相脚叫做勾八 +我只能骑着我的纠纠但是鬼泣五 +我爱抽烟出生到太平间为了买烟我付出太多 +每天看四小时书我买的烟弹 +体重要按吨但我心怀善念接纳悦刻五的每个孝子 +回理塘实践哥们名叫丁真 +是由棒鸡自主研发的一款全新开放世界冒险游戏逐步降低贴吧的素质 +哥们哇啦哇啦乱唱听感就拉满了是由valve自主研发的一款全新竞技类FPS游戏 +为了芋泥啵啵我抽胖了双脸就像你的人生糊弄糊弄不就完了么 +你将扮演一位名为8u的神秘用户哥们LetsGo +如果你不知道去哪里就来我直播间如果你不知道去哪里就来我直播间 +游戏发生在一个被称作提瓦特的幻想世界给礼堂的丁真 +因为你是肥猪感觉这首歌技术不如丁真 +但是烟神哥们哇啦哇啦乱唱听感就拉满了 +在自由的旅行中邂逅性格各异能力独特同伴们IGOT油我想抽 +你自己心里有数找回挂B的亲m的同时 +我测你们码我只能骑着我的纠纠 +被斯巴达选中的人将被授予魔人哥们名叫丁真 +想买悦刻五代找我丁真就对了抽传统香烟 +但是原神是由米哈游自主研发的一款全新开放世界冒险游戏被神选中的人将被授予电子烟 +对你说藏话现在一唱歌哥们嗓子就漏电 +偶尔骑骑小马为了买烟我付出太多 +IGOT油ALLMYMIND和他们一起击败强敌 +逐步发掘原神的真相你将扮演一位名为旅行者的神秘角色 +别他妈倒闭了外国人都别挡我路 +拜托我别给你推销了在自由的对局中邂逅性格各异能力独特的同伴们 +竖起中指王源他算个寄吧随手掏出悦刻 +游戏发生在一个被称作炙热沙城的幻想世界但是人们依然爱我爱我纯真双眼 diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..9592e807 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,59 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle.kts. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +# FreeReflection +-keep class me.weishu.reflection.** {*;} + +-assumenosideeffects class kotlin.jvm.internal.Intrinsics { + public static *** throwUninitializedProperty(...); + public static *** throwUninitializedPropertyAccessException(...); +} +-keep class * implements androidx.viewbinding.ViewBinding { + (); + *** inflate(android.view.LayoutInflater); +} +-keep class com.android.tools.desugar.runtime.** { *; } +-keep class com.umeng.** {*;} + +-keep class org.repackage.** {*;} + +-keep class com.uyumao.** { *; } + +-keepclassmembers class * { + public (org.json.JSONObject); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} +-dontwarn com.umeng.ccg.ActionInfo +-dontwarn java.lang.reflect.AnnotatedType +-dontwarn com.umeng.** +-dontwarn org.repackage.** +-keep class com.suqi8.oshin.ui.activity.func.StatusBarLayout.** { *; } +-keepattributes Signature, InnerClasses, EnclosingMethod +-dontusemixedcaseclassnames + +-classobfuscationdictionary proguard-dict.txt +-obfuscationdictionary proguard-dict.txt +-packageobfuscationdictionary proguard-dict.txt diff --git a/app/src/androidTest/java/io/github/suqi8/opatch/ExampleInstrumentedTest.kt b/app/src/androidTest/java/io/github/suqi8/opatch/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..9c14206d --- /dev/null +++ b/app/src/androidTest/java/io/github/suqi8/opatch/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package io.github.suqi8.opatch + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("io.github.suqi8.opatch", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..bbe32ad9 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/xposed_init b/app/src/main/assets/xposed_init new file mode 100644 index 00000000..eede96ca --- /dev/null +++ b/app/src/main/assets/xposed_init @@ -0,0 +1 @@ +com.suqi8.oshin.hook.oshin \ No newline at end of file diff --git a/app/src/main/ic_launcher1-playstore.png b/app/src/main/ic_launcher1-playstore.png new file mode 100644 index 00000000..3a7c52f0 Binary files /dev/null and b/app/src/main/ic_launcher1-playstore.png differ diff --git a/app/src/main/java/com/kyant/backdrop/Backdrop.kt b/app/src/main/java/com/kyant/backdrop/Backdrop.kt new file mode 100644 index 00000000..a617938e --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/Backdrop.kt @@ -0,0 +1,17 @@ +package com.kyant.backdrop + +import androidx.compose.ui.graphics.GraphicsLayerScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.unit.Density + +interface Backdrop { + + val isCoordinatesDependent: Boolean + + fun DrawScope.drawBackdrop( + density: Density, + coordinates: LayoutCoordinates?, + layerBlock: (GraphicsLayerScope.() -> Unit)? = null + ) +} diff --git a/app/src/main/java/com/kyant/backdrop/BackdropEffectScope.kt b/app/src/main/java/com/kyant/backdrop/BackdropEffectScope.kt new file mode 100644 index 00000000..a271d451 --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/BackdropEffectScope.kt @@ -0,0 +1,78 @@ +package com.kyant.backdrop + +import android.graphics.RenderEffect +import android.graphics.RuntimeShader +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection + +sealed interface BackdropEffectScope : Density, RuntimeShaderCache { + + val size: Size + + val layoutDirection: LayoutDirection + + val shape: Shape + + var padding: Float + + var renderEffect: RenderEffect? +} + +internal abstract class BackdropEffectScopeImpl : BackdropEffectScope, RuntimeShaderCache { + + override var density: Float = 1f + override var fontScale: Float = 1f + override var size: Size = Size.Unspecified + override var layoutDirection: LayoutDirection = LayoutDirection.Ltr + override var padding: Float = 0f + override var renderEffect: RenderEffect? = null + + private val runtimeShaderCache = RuntimeShaderCacheImpl() + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + override fun obtainRuntimeShader(key: String, string: String): RuntimeShader { + return runtimeShaderCache.obtainRuntimeShader(key, string) + } + + fun update(scope: DrawScope): Boolean { + val newDensity = scope.density + val newFontScale = scope.fontScale + val newSize = scope.size + val newLayoutDirection = scope.layoutDirection + + val changed = newDensity != density || + newFontScale != fontScale || + newSize != size || + newLayoutDirection != layoutDirection + + if (changed) { + density = newDensity + fontScale = newFontScale + size = newSize + layoutDirection = newLayoutDirection + } + + return changed + } + + fun apply(effects: BackdropEffectScope.() -> Unit) { + padding = 0f + renderEffect = null + effects() + } + + fun reset() { + density = 1f + fontScale = 1f + size = Size.Unspecified + layoutDirection = LayoutDirection.Ltr + padding = 0f + renderEffect = null + runtimeShaderCache.clear() + } +} diff --git a/app/src/main/java/com/kyant/backdrop/DrawBackdropModifier.kt b/app/src/main/java/com/kyant/backdrop/DrawBackdropModifier.kt new file mode 100644 index 00000000..14d5c889 --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/DrawBackdropModifier.kt @@ -0,0 +1,387 @@ +package com.kyant.backdrop + +import android.os.Build +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.neverEqualPolicy +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.GraphicsLayerScope +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.asComposeRenderEffect +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.layer.GraphicsLayer +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.GlobalPositionAwareModifierNode +import androidx.compose.ui.node.LayoutModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.ObserverModifierNode +import androidx.compose.ui.node.observeReads +import androidx.compose.ui.node.requireGraphicsContext +import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize +import com.kyant.backdrop.backdrops.LayerBackdrop +import com.kyant.backdrop.highlight.Highlight +import com.kyant.backdrop.highlight.HighlightElement +import com.kyant.backdrop.shadow.InnerShadow +import com.kyant.backdrop.shadow.InnerShadowElement +import com.kyant.backdrop.shadow.Shadow +import com.kyant.backdrop.shadow.ShadowElement + +private val DefaultHighlight = { Highlight.Default } +private val DefaultShadow = { Shadow.Default } +private val DefaultOnDrawBackdrop: DrawScope.(DrawScope.() -> Unit) -> Unit = { it() } + +fun Modifier.drawPlainBackdrop( + backdrop: Backdrop, + shape: () -> Shape, + effects: BackdropEffectScope.() -> Unit, + layerBlock: (GraphicsLayerScope.() -> Unit)? = null, + exportedBackdrop: LayerBackdrop? = null, + onDrawBehind: (DrawScope.() -> Unit)? = null, + onDrawBackdrop: DrawScope.(drawBackdrop: DrawScope.() -> Unit) -> Unit = DefaultOnDrawBackdrop, + onDrawSurface: (DrawScope.() -> Unit)? = null, + onDrawFront: (DrawScope.() -> Unit)? = null +): Modifier { + val shapeProvider = ShapeProvider(shape) + return this + .then( + if (layerBlock != null) { + Modifier.graphicsLayer(layerBlock) + } else { + Modifier + } + ) + .then( + DrawBackdropElement( + backdrop = backdrop, + shapeProvider = shapeProvider, + effects = effects, + layerBlock = layerBlock, + exportedBackdrop = exportedBackdrop, + onDrawBehind = onDrawBehind, + onDrawBackdrop = onDrawBackdrop, + onDrawSurface = onDrawSurface, + onDrawFront = onDrawFront + ) + ) +} + +fun Modifier.drawBackdrop( + backdrop: Backdrop, + shape: () -> Shape, + effects: BackdropEffectScope.() -> Unit, + highlight: (() -> Highlight?)? = DefaultHighlight, + shadow: (() -> Shadow?)? = DefaultShadow, + innerShadow: (() -> InnerShadow?)? = null, + layerBlock: (GraphicsLayerScope.() -> Unit)? = null, + exportedBackdrop: LayerBackdrop? = null, + onDrawBehind: (DrawScope.() -> Unit)? = null, + onDrawBackdrop: DrawScope.(drawBackdrop: DrawScope.() -> Unit) -> Unit = DefaultOnDrawBackdrop, + onDrawSurface: (DrawScope.() -> Unit)? = null, + onDrawFront: (DrawScope.() -> Unit)? = null +): Modifier { + val shapeProvider = ShapeProvider(shape) + return this + .then( + if (layerBlock != null) { + Modifier.graphicsLayer(layerBlock) + } else { + Modifier + } + ) + .then( + if (innerShadow != null) { + InnerShadowElement( + shapeProvider = shapeProvider, + shadow = innerShadow + ) + } else { + Modifier + } + ) + .then( + if (shadow != null) { + ShadowElement( + shapeProvider = shapeProvider, + shadow = shadow + ) + } else { + Modifier + } + ) + .then( + if (highlight != null) { + HighlightElement( + shapeProvider = shapeProvider, + highlight = highlight + ) + } else { + Modifier + } + ) + .then( + DrawBackdropElement( + backdrop = backdrop, + shapeProvider = shapeProvider, + effects = effects, + layerBlock = layerBlock, + exportedBackdrop = exportedBackdrop, + onDrawBehind = onDrawBehind, + onDrawBackdrop = onDrawBackdrop, + onDrawSurface = onDrawSurface, + onDrawFront = onDrawFront + ) + ) +} + +private class DrawBackdropElement( + val backdrop: Backdrop, + val shapeProvider: ShapeProvider, + val effects: BackdropEffectScope.() -> Unit, + val layerBlock: (GraphicsLayerScope.() -> Unit)?, + val exportedBackdrop: LayerBackdrop?, + val onDrawBehind: (DrawScope.() -> Unit)?, + val onDrawBackdrop: DrawScope.(drawBackdrop: DrawScope.() -> Unit) -> Unit, + val onDrawSurface: (DrawScope.() -> Unit)?, + val onDrawFront: (DrawScope.() -> Unit)? +) : ModifierNodeElement() { + + override fun create(): DrawBackdropNode { + return DrawBackdropNode( + backdrop = backdrop, + shapeProvider = shapeProvider, + effects = effects, + layerBlock = layerBlock, + exportedBackdrop = exportedBackdrop, + onDrawBehind = onDrawBehind, + onDrawBackdrop = onDrawBackdrop, + onDrawSurface = onDrawSurface, + onDrawFront = onDrawFront + ) + } + + override fun update(node: DrawBackdropNode) { + node.backdrop = backdrop + node.shapeProvider = shapeProvider + node.effects = effects + node.layerBlock = layerBlock + node.exportedBackdrop = exportedBackdrop + node.onDrawBehind = onDrawBehind + node.onDrawBackdrop = onDrawBackdrop + node.onDrawSurface = onDrawSurface + node.onDrawFront = onDrawFront + node.invalidateDrawCache() + } + + override fun InspectorInfo.inspectableProperties() { + name = "drawBackdrop" + properties["backdrop"] = backdrop + properties["shapeProvider"] = shapeProvider + properties["effects"] = effects + properties["layerBlock"] = layerBlock + properties["exportedBackdrop"] = exportedBackdrop + properties["onDrawBehind"] = onDrawBehind + properties["onDrawBackdrop"] = onDrawBackdrop + properties["onDrawSurface"] = onDrawSurface + properties["onDrawFront"] = onDrawFront + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is DrawBackdropElement) return false + + if (backdrop != other.backdrop) return false + if (shapeProvider != other.shapeProvider) return false + if (effects != other.effects) return false + if (layerBlock != other.layerBlock) return false + if (exportedBackdrop != other.exportedBackdrop) return false + if (onDrawBehind != other.onDrawBehind) return false + if (onDrawBackdrop != other.onDrawBackdrop) return false + if (onDrawSurface != other.onDrawSurface) return false + if (onDrawFront != other.onDrawFront) return false + + return true + } + + override fun hashCode(): Int { + var result = backdrop.hashCode() + result = 31 * result + shapeProvider.hashCode() + result = 31 * result + effects.hashCode() + result = 31 * result + (layerBlock?.hashCode() ?: 0) + result = 31 * result + (exportedBackdrop?.hashCode() ?: 0) + result = 31 * result + (onDrawBehind?.hashCode() ?: 0) + result = 31 * result + onDrawBackdrop.hashCode() + result = 31 * result + (onDrawSurface?.hashCode() ?: 0) + result = 31 * result + (onDrawFront?.hashCode() ?: 0) + return result + } +} + +private class DrawBackdropNode( + var backdrop: Backdrop, + var shapeProvider: ShapeProvider, + var effects: BackdropEffectScope.() -> Unit, + var layerBlock: (GraphicsLayerScope.() -> Unit)?, + var exportedBackdrop: LayerBackdrop?, + var onDrawBehind: (DrawScope.() -> Unit)?, + var onDrawBackdrop: DrawScope.(drawBackdrop: DrawScope.() -> Unit) -> Unit, + var onDrawSurface: (DrawScope.() -> Unit)?, + var onDrawFront: (DrawScope.() -> Unit)? +) : LayoutModifierNode, DrawModifierNode, GlobalPositionAwareModifierNode, ObserverModifierNode, Modifier.Node() { + + override val shouldAutoInvalidate: Boolean = false + + private val effectScope = + object : BackdropEffectScopeImpl() { + + override val shape: Shape get() = shapeProvider.innerShape + } + + private var graphicsLayer: GraphicsLayer? = null + + private val layoutLayerBlock: GraphicsLayerScope.() -> Unit = { + clip = true + shape = shapeProvider.shape + compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen + } + + private var layoutCoordinates: LayoutCoordinates? by mutableStateOf(null, neverEqualPolicy()) + + private var padding by mutableFloatStateOf(0f) + + private val recordBackdropBlock: (DrawScope.() -> Unit) = { + val canvas = drawContext.canvas + val padding = padding + + if (padding != 0f) { + canvas.translate(padding, padding) + } + onDrawBackdrop { + with(backdrop) { + drawBackdrop( + density = effectScope, + coordinates = layoutCoordinates, + layerBlock = layerBlock + ) + } + } + if (padding != 0f) { + canvas.translate(-padding, -padding) + } + } + + private val drawBackdropLayer: DrawScope.() -> Unit = { + val layer = graphicsLayer + if (layer != null) { + val padding = padding + + recordLayer( + layer, + size = IntSize( + size.width.toInt() + padding.toInt() * 2, + size.height.toInt() + padding.toInt() * 2 + ), + block = recordBackdropBlock + ) + + layer.topLeft = + if (padding != 0f) IntOffset(-padding.toInt(), -padding.toInt()) + else IntOffset.Zero + drawLayer(layer) + } + } + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints + ): MeasureResult { + val placeable = measurable.measure(constraints) + return layout(placeable.width, placeable.height) { + placeable.placeWithLayer(IntOffset.Zero, layerBlock = layoutLayerBlock) + } + } + + override fun ContentDrawScope.draw() { + if (effectScope.update(this)) { + updateEffects() + } + + onDrawBehind?.invoke(this) + drawBackdropLayer() + onDrawSurface?.invoke(this) + drawContent() + onDrawFront?.invoke(this) + + exportedBackdrop?.graphicsLayer?.let { layer -> + recordLayer(layer) { + onDrawBehind?.invoke(this) + drawBackdropLayer() + onDrawSurface?.invoke(this) + onDrawFront?.invoke(this) + } + } + } + + override fun onGloballyPositioned(coordinates: LayoutCoordinates) { + if (coordinates.isAttached) { + if (backdrop.isCoordinatesDependent) { + layoutCoordinates = coordinates + } else { + if (layoutCoordinates != null) { + layoutCoordinates = null + } + } + exportedBackdrop?.layerCoordinates = coordinates + } + } + + override fun onObservedReadsChanged() { + invalidateDrawCache() + } + + fun invalidateDrawCache() { + observeEffects() + } + + private fun observeEffects() { + observeReads { updateEffects() } + } + + private fun updateEffects() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + effectScope.apply(effects) + graphicsLayer?.renderEffect = effectScope.renderEffect?.asComposeRenderEffect() + padding = effectScope.padding + } + } + + override fun onAttach() { + val graphicsContext = requireGraphicsContext() + graphicsLayer = graphicsContext.createGraphicsLayer() + + observeEffects() + } + + override fun onDetach() { + val graphicsContext = requireGraphicsContext() + graphicsLayer?.let { layer -> + graphicsContext.releaseGraphicsLayer(layer) + graphicsLayer = null + } + + effectScope.reset() + layoutCoordinates = null + exportedBackdrop?.layerCoordinates = null + } +} diff --git a/app/src/main/java/com/kyant/backdrop/InverseLayerScope.kt b/app/src/main/java/com/kyant/backdrop/InverseLayerScope.kt new file mode 100644 index 00000000..25941d09 --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/InverseLayerScope.kt @@ -0,0 +1,130 @@ +package com.kyant.backdrop + +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.CompositingStrategy +import androidx.compose.ui.graphics.DefaultCameraDistance +import androidx.compose.ui.graphics.DefaultShadowColor +import androidx.compose.ui.graphics.GraphicsLayerScope +import androidx.compose.ui.graphics.Matrix +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.RenderEffect +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.drawscope.DrawTransform +import androidx.compose.ui.unit.Density +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin + +internal class InverseLayerScope : GraphicsLayerScope { + + override var size: Size = Size.Unspecified + override var density: Float = 1f + override var fontScale: Float = 1f + + override var scaleX: Float = 1f + override var scaleY: Float = 1f + override var alpha: Float = 0f + override var translationX: Float = 0f + override var translationY: Float = 0f + override var shadowElevation: Float = 0f + override var ambientShadowColor: Color = DefaultShadowColor + override var spotShadowColor: Color = DefaultShadowColor + override var rotationX: Float = 0f + override var rotationY: Float = 0f + override var rotationZ: Float = 0f + override var cameraDistance: Float = DefaultCameraDistance + override var transformOrigin: TransformOrigin = TransformOrigin.Center + override var shape: Shape = RectangleShape + override var clip: Boolean = false + override var renderEffect: RenderEffect? = null + override var blendMode: BlendMode = BlendMode.SrcOver + override var colorFilter: ColorFilter? = null + override var compositingStrategy: CompositingStrategy = CompositingStrategy.Auto + + private var matrix: Matrix? = null + + fun DrawTransform.inverseTransform( + density: Density, + layerBlock: GraphicsLayerScope.() -> Unit + ) { + this@InverseLayerScope.size = size + this@InverseLayerScope.density = density.density + fontScale = density.fontScale + + layerBlock() + + inverseTransformAtTopLeft( + rotationZ = rotationZ, + scaleX = scaleX, + scaleY = scaleY + ) + } + + fun reset() { + size = Size.Unspecified + density = 1f + fontScale = 1f + + scaleX = 1f + scaleY = 1f + alpha = 1f + translationX = 0f + translationY = 0f + shadowElevation = 0f + ambientShadowColor = DefaultShadowColor + spotShadowColor = DefaultShadowColor + rotationX = 0f + rotationY = 0f + rotationZ = 0f + cameraDistance = DefaultCameraDistance + transformOrigin = TransformOrigin.Center + shape = RectangleShape + clip = false + renderEffect = null + blendMode = BlendMode.SrcOver + colorFilter = null + compositingStrategy = CompositingStrategy.Auto + + matrix = null + } + + private fun DrawTransform.inverseTransformAtTopLeft( + rotationZ: Float = 0f, + scaleX: Float = 1f, + scaleY: Float = 1f + ) { + if (rotationZ == 0f) { + if (scaleX != 0f && scaleY != 0f) { + scale(1f / scaleX, 1f / scaleY, Offset.Zero) + } + return + } + + val matrix = matrix ?: Matrix().also { matrix = it } + if (matrix.values.size < 16) return + + val rz = rotationZ * (PI / 180.0) + val rsz = sin(rz).toFloat() + val rcz = cos(rz).toFloat() + + val a00 = rcz * scaleX + val a01 = rsz * scaleY + val a10 = -rsz * scaleX + val a11 = rcz * scaleY + + val det = a00 * a11 - a01 * a10 + if (det == 0f) return + val invDet = 1f / det + matrix[0, 0] = a11 * invDet + matrix[0, 1] = -a01 * invDet + matrix[1, 0] = -a10 * invDet + matrix[1, 1] = a00 * invDet + + transform(matrix) + } +} diff --git a/app/src/main/java/com/kyant/backdrop/LayerRecorder.kt b/app/src/main/java/com/kyant/backdrop/LayerRecorder.kt new file mode 100644 index 00000000..51ab435f --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/LayerRecorder.kt @@ -0,0 +1,31 @@ +package com.kyant.backdrop + +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.draw +import androidx.compose.ui.graphics.layer.GraphicsLayer +import androidx.compose.ui.node.DelegatableNode +import androidx.compose.ui.node.requireDensity +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.toIntSize + +context(node: DelegatableNode) +internal fun DrawScope.recordLayer( + layer: GraphicsLayer, + size: IntSize = this.size.toIntSize(), + block: DrawScope.() -> Unit +) { + layer.record( + density = node.requireDensity(), + layoutDirection = layoutDirection, + size = size + ) { + this@recordLayer.draw( + density = drawContext.density, + layoutDirection = drawContext.layoutDirection, + canvas = drawContext.canvas, + size = drawContext.size, + graphicsLayer = drawContext.graphicsLayer, + block = block + ) + } +} diff --git a/app/src/main/java/com/kyant/backdrop/Outline.kt b/app/src/main/java/com/kyant/backdrop/Outline.kt new file mode 100644 index 00000000..9220e462 --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/Outline.kt @@ -0,0 +1,18 @@ +package com.kyant.backdrop + +import androidx.compose.ui.graphics.Canvas +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path + +internal fun Canvas.clipOutline(outline: Outline, path: Path?) { + when (outline) { + is Outline.Rectangle -> clipRect(outline.rect) + is Outline.Rounded -> { + path!!.rewind() + path.addRoundRect(outline.roundRect) + clipPath(path) + } + + is Outline.Generic -> clipPath(outline.path) + } +} diff --git a/app/src/main/java/com/kyant/backdrop/RuntimeShaderCache.kt b/app/src/main/java/com/kyant/backdrop/RuntimeShaderCache.kt new file mode 100644 index 00000000..f4a953ba --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/RuntimeShaderCache.kt @@ -0,0 +1,29 @@ +package com.kyant.backdrop + +import android.graphics.RuntimeShader +import android.os.Build +import androidx.annotation.RequiresApi +import org.intellij.lang.annotations.Language + +sealed interface RuntimeShaderCache { + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + fun obtainRuntimeShader( + key: String, + @Language("AGSL") string: String + ): RuntimeShader +} + +internal class RuntimeShaderCacheImpl : RuntimeShaderCache { + + private val runtimeShaders = mutableMapOf() + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + override fun obtainRuntimeShader(key: String, string: String): RuntimeShader { + return runtimeShaders.getOrPut(key) { RuntimeShader(string) } + } + + fun clear() { + runtimeShaders.clear() + } +} diff --git a/app/src/main/java/com/kyant/backdrop/Shaders.kt b/app/src/main/java/com/kyant/backdrop/Shaders.kt new file mode 100644 index 00000000..4e5ad533 --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/Shaders.kt @@ -0,0 +1,217 @@ +/* + Copyright 2025 Kyant + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +package com.kyant.backdrop + +import org.intellij.lang.annotations.Language + +@Language("AGSL") +private const val RoundedRectSDF = """ +float radiusAt(float2 coord, float4 radii) { + if (coord.x >= 0.0) { + if (coord.y <= 0.0) return radii.y; + else return radii.z; + } else { + if (coord.y <= 0.0) return radii.x; + else return radii.w; + } +} + +float sdRoundedRect(float2 coord, float2 halfSize, float radius) { + float2 cornerCoord = abs(coord) - (halfSize - float2(radius)); + float outside = length(max(cornerCoord, 0.0)) - radius; + float inside = min(max(cornerCoord.x, cornerCoord.y), 0.0); + return outside + inside; +} + +float2 gradSdRoundedRect(float2 coord, float2 halfSize, float radius) { + float2 cornerCoord = abs(coord) - (halfSize - float2(radius)); + if (cornerCoord.x >= 0.0 || cornerCoord.y >= 0.0) { + return sign(coord) * normalize(max(cornerCoord, 0.0)); + } else { + float gradX = step(cornerCoord.y, cornerCoord.x); + return sign(coord) * float2(gradX, 1.0 - gradX); + } +}""" + +@Language("AGSL") +internal const val RoundedRectRefractionShaderString = """ +uniform shader content; + +uniform float2 size; +uniform float2 offset; +uniform float4 cornerRadii; +uniform float refractionHeight; +uniform float refractionAmount; +uniform float depthEffect; + +$RoundedRectSDF + +float circleMap(float x) { + return 1.0 - sqrt(1.0 - x * x); +} + +half4 main(float2 coord) { + float2 halfSize = size * 0.5; + float2 centeredCoord = (coord + offset) - halfSize; + float radius = radiusAt(coord, cornerRadii); + + float sd = sdRoundedRect(centeredCoord, halfSize, radius); + if (-sd >= refractionHeight) { + return content.eval(coord); + } + sd = min(sd, 0.0); + + float d = circleMap(1.0 - -sd / refractionHeight) * refractionAmount; + float gradRadius = min(radius * 1.5, min(halfSize.x, halfSize.y)); + float2 grad = normalize(gradSdRoundedRect(centeredCoord, halfSize, gradRadius) + depthEffect * normalize(centeredCoord)); + + float2 refractedCoord = coord + d * grad; + return content.eval(refractedCoord); +}""" + +@Language("AGSL") +internal val RoundedRectRefractionWithDispersionShaderString = """ +uniform shader content; + +uniform float2 size; +uniform float2 offset; +uniform float4 cornerRadii; +uniform float refractionHeight; +uniform float refractionAmount; +uniform float depthEffect; +uniform float chromaticAberration; + +$RoundedRectSDF + +float circleMap(float x) { + return 1.0 - sqrt(1.0 - x * x); +} + +half4 main(float2 coord) { + float2 halfSize = size * 0.5; + float2 centeredCoord = (coord + offset) - halfSize; + float radius = radiusAt(coord, cornerRadii); + + float sd = sdRoundedRect(centeredCoord, halfSize, radius); + if (-sd >= refractionHeight) { + return content.eval(coord); + } + sd = min(sd, 0.0); + + float d = circleMap(1.0 - -sd / refractionHeight) * refractionAmount; + float gradRadius = min(radius * 1.5, min(halfSize.x, halfSize.y)); + float2 grad = normalize(gradSdRoundedRect(centeredCoord, halfSize, gradRadius) + depthEffect * normalize(centeredCoord)); + + float2 refractedCoord = coord + d * grad; + float dispersionIntensity = chromaticAberration * ((centeredCoord.x * centeredCoord.y) / (halfSize.x * halfSize.y)); + float2 dispersedCoord = d * grad * dispersionIntensity; + + half4 color = half4(0.0); + + half4 red = content.eval(refractedCoord + dispersedCoord); + color.r += red.r / 3.5; + color.a += red.a / 7.0; + + half4 orange = content.eval(refractedCoord + dispersedCoord * (2.0 / 3.0)); + color.r += orange.r / 3.5; + color.g += orange.g / 7.0; + color.a += orange.a / 7.0; + + half4 yellow = content.eval(refractedCoord + dispersedCoord * (1.0 / 3.0)); + color.r += yellow.r / 3.5; + color.g += yellow.g / 3.5; + color.a += yellow.a / 7.0; + + half4 green = content.eval(refractedCoord); + color.g += green.g / 3.5; + color.a += green.a / 7.0; + + half4 cyan = content.eval(refractedCoord - dispersedCoord * (1.0 / 3.0)); + color.g += cyan.g / 3.5; + color.b += cyan.b / 3.0; + color.a += cyan.a / 7.0; + + half4 blue = content.eval(refractedCoord - dispersedCoord * (2.0 / 3.0)); + color.b += blue.b / 3.0; + color.a += blue.a / 7.0; + + half4 purple = content.eval(refractedCoord - dispersedCoord); + color.r += purple.r / 7.0; + color.b += purple.b / 3.0; + color.a += purple.a / 7.0; + + return color; +}""" + +@Language("AGSL") +internal const val DefaultHighlightShaderString = """ +uniform float2 size; +uniform float4 cornerRadii; +uniform float angle; +uniform float falloff; + +$RoundedRectSDF + +half4 main(float2 coord) { + float2 halfSize = size * 0.5; + float2 centeredCoord = coord - halfSize; + float radius = radiusAt(coord, cornerRadii); + + float gradRadius = min(radius * 1.5, min(halfSize.x, halfSize.y)); + float2 grad = gradSdRoundedRect(centeredCoord, halfSize, gradRadius); + float2 normal = float2(cos(angle), sin(angle)); + float d = dot(grad, normal); + float intensity = pow(abs(d), falloff); + return half4(1.0) * intensity; +}""" + +@Language("AGSL") +internal const val AmbientHighlightShaderString = """ +uniform float2 size; +uniform float4 cornerRadii; +uniform float angle; +uniform float falloff; + +$RoundedRectSDF + +half4 main(float2 coord) { + float2 halfSize = size * 0.5; + float2 centeredCoord = coord - halfSize; + float radius = radiusAt(coord, cornerRadii); + + float gradRadius = min(radius * 1.5, min(halfSize.x, halfSize.y)); + float2 grad = gradSdRoundedRect(centeredCoord, halfSize, gradRadius); + float2 normal = float2(cos(angle), sin(angle)); + float d = dot(grad, normal); + float intensity = pow(abs(d), falloff); + float t = step(0.0, d); + return half4(t, t, t, 1.0) * intensity; +}""" + +@Language("AGSL") +internal const val GammaAdjustmentShaderString = """ +uniform shader content; + +uniform float power; + +half4 main(float2 coord) { + half4 color = content.eval(coord); + color.r = pow(color.r, power); + color.g = pow(color.g, power); + color.b = pow(color.b, power); + return color; +}""" diff --git a/app/src/main/java/com/kyant/backdrop/ShapeProvider.kt b/app/src/main/java/com/kyant/backdrop/ShapeProvider.kt new file mode 100644 index 00000000..c79a7767 --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/ShapeProvider.kt @@ -0,0 +1,44 @@ +package com.kyant.backdrop + +import androidx.compose.runtime.Immutable +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection + +@Immutable +internal class ShapeProvider(val shapeBlock: () -> Shape) { + + private var _shape: Shape? = null + private var _outline: Outline? = null + private var _size: Size = Size.Unspecified + private var _layoutDirection: LayoutDirection? = null + private var _density: Float? = null + + val innerShape + get() = shapeBlock() + + val shape = object : Shape { + + override fun createOutline( + size: Size, + layoutDirection: LayoutDirection, + density: Density + ): Outline { + val shape = shapeBlock() + if (_shape != shape) { + _shape = shape + _outline = null + } + if (_outline == null || _size != size || _layoutDirection != layoutDirection || _density != density.density) { + _size = size + _layoutDirection = layoutDirection + _density = density.density + _outline = shape.createOutline(size, layoutDirection, density) + } + + return _outline!! + } + } +} diff --git a/app/src/main/java/com/kyant/backdrop/backdrops/Backdrop.kt b/app/src/main/java/com/kyant/backdrop/backdrops/Backdrop.kt new file mode 100644 index 00000000..92bf6eaa --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/backdrops/Backdrop.kt @@ -0,0 +1,37 @@ +package com.kyant.backdrop.backdrops + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.GraphicsLayerScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.unit.Density +import com.kyant.backdrop.Backdrop + +@Composable +fun rememberBackdrop( + backdrop: Backdrop, + onDraw: DrawScope.(drawBackdrop: DrawScope.() -> Unit) -> Unit +): Backdrop { + return remember(backdrop, onDraw) { + Backdrop(backdrop, onDraw) + } +} + +@Immutable +private class Backdrop( + val backdrop: Backdrop, + val onDraw: DrawScope.(drawBackdrop: DrawScope.() -> Unit) -> Unit +) : Backdrop { + + override val isCoordinatesDependent: Boolean = backdrop.isCoordinatesDependent + + override fun DrawScope.drawBackdrop( + density: Density, + coordinates: LayoutCoordinates?, + layerBlock: (GraphicsLayerScope.() -> Unit)? + ) { + onDraw { with(backdrop) { drawBackdrop(density, coordinates, layerBlock) } } + } +} diff --git a/app/src/main/java/com/kyant/backdrop/backdrops/CanvasBackdrop.kt b/app/src/main/java/com/kyant/backdrop/backdrops/CanvasBackdrop.kt new file mode 100644 index 00000000..74156ebf --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/backdrops/CanvasBackdrop.kt @@ -0,0 +1,35 @@ +package com.kyant.backdrop.backdrops + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.GraphicsLayerScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.unit.Density +import com.kyant.backdrop.Backdrop + +@Composable +fun rememberCanvasBackdrop( + onDraw: DrawScope.() -> Unit +): Backdrop { + return remember(onDraw) { + CanvasBackdrop(onDraw) + } +} + +@Immutable +private class CanvasBackdrop( + val onDraw: DrawScope.() -> Unit +) : Backdrop { + + override val isCoordinatesDependent: Boolean = false + + override fun DrawScope.drawBackdrop( + density: Density, + coordinates: LayoutCoordinates?, + layerBlock: (GraphicsLayerScope.() -> Unit)? + ) { + onDraw() + } +} diff --git a/app/src/main/java/com/kyant/backdrop/backdrops/CombinedBackdrop.kt b/app/src/main/java/com/kyant/backdrop/backdrops/CombinedBackdrop.kt new file mode 100644 index 00000000..943d3d77 --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/backdrops/CombinedBackdrop.kt @@ -0,0 +1,99 @@ +package com.kyant.backdrop.backdrops + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.GraphicsLayerScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.unit.Density +import com.kyant.backdrop.Backdrop + +@Composable +fun rememberCombinedBackdrop( + backdrop1: Backdrop, + backdrop2: Backdrop +): Backdrop { + return remember(backdrop1, backdrop2) { + Combined2Backdrops(backdrop1, backdrop2) + } +} + +@Composable +fun rememberCombinedBackdrop( + backdrop1: Backdrop, + backdrop2: Backdrop, + backdrop3: Backdrop +): Backdrop { + return remember(backdrop1, backdrop2, backdrop3) { + Combined3Backdrops(backdrop1, backdrop2, backdrop3) + } +} + +@Composable +fun rememberCombinedBackdrop(vararg backdrops: Backdrop): Backdrop { + return remember(*backdrops) { + CombinedBackdrops(*backdrops) + } +} + +@Immutable +private class Combined2Backdrops( + val backdrop1: Backdrop, + val backdrop2: Backdrop +) : Backdrop { + + override val isCoordinatesDependent: Boolean = + backdrop1.isCoordinatesDependent || backdrop2.isCoordinatesDependent + + override fun DrawScope.drawBackdrop( + density: Density, + coordinates: LayoutCoordinates?, + layerBlock: (GraphicsLayerScope.() -> Unit)? + ) { + with(backdrop1) { drawBackdrop(density, coordinates, layerBlock) } + with(backdrop2) { drawBackdrop(density, coordinates, layerBlock) } + } +} + +@Immutable +private class Combined3Backdrops( + val backdrop1: Backdrop, + val backdrop2: Backdrop, + val backdrop3: Backdrop +) : Backdrop { + + override val isCoordinatesDependent: Boolean = + backdrop1.isCoordinatesDependent || + backdrop2.isCoordinatesDependent || + backdrop3.isCoordinatesDependent + + override fun DrawScope.drawBackdrop( + density: Density, + coordinates: LayoutCoordinates?, + layerBlock: (GraphicsLayerScope.() -> Unit)? + ) { + with(backdrop1) { drawBackdrop(density, coordinates, layerBlock) } + with(backdrop2) { drawBackdrop(density, coordinates, layerBlock) } + with(backdrop3) { drawBackdrop(density, coordinates, layerBlock) } + } +} + +@Immutable +private class CombinedBackdrops( + vararg val backdrops: Backdrop +) : Backdrop { + + override val isCoordinatesDependent: Boolean = + backdrops.any { it.isCoordinatesDependent } + + override fun DrawScope.drawBackdrop( + density: Density, + coordinates: LayoutCoordinates?, + layerBlock: (GraphicsLayerScope.() -> Unit)? + ) { + backdrops.forEach { backdrop -> + with(backdrop) { drawBackdrop(density, coordinates, layerBlock) } + } + } +} diff --git a/app/src/main/java/com/kyant/backdrop/backdrops/EmptyBackdrop.kt b/app/src/main/java/com/kyant/backdrop/backdrops/EmptyBackdrop.kt new file mode 100644 index 00000000..7701a8ce --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/backdrops/EmptyBackdrop.kt @@ -0,0 +1,25 @@ +package com.kyant.backdrop.backdrops + +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.GraphicsLayerScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.unit.Density +import com.kyant.backdrop.Backdrop + +@Stable +fun emptyBackdrop(): Backdrop = EmptyBackdrop + +@Immutable +private object EmptyBackdrop : Backdrop { + + override val isCoordinatesDependent: Boolean = false + + override fun DrawScope.drawBackdrop( + density: Density, + coordinates: LayoutCoordinates?, + layerBlock: (GraphicsLayerScope.() -> Unit)? + ) { + } +} diff --git a/app/src/main/java/com/kyant/backdrop/backdrops/LayerBackdrop.kt b/app/src/main/java/com/kyant/backdrop/backdrops/LayerBackdrop.kt new file mode 100644 index 00000000..e457f593 --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/backdrops/LayerBackdrop.kt @@ -0,0 +1,68 @@ +package com.kyant.backdrop.backdrops + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.GraphicsLayerScope +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.withTransform +import androidx.compose.ui.graphics.layer.GraphicsLayer +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.graphics.rememberGraphicsLayer +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.unit.Density +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.InverseLayerScope + +private val DefaultOnDraw: ContentDrawScope.() -> Unit = { drawContent() } + +@Composable +fun rememberLayerBackdrop( + graphicsLayer: GraphicsLayer = rememberGraphicsLayer(), + onDraw: ContentDrawScope.() -> Unit = DefaultOnDraw +): LayerBackdrop { + return remember(graphicsLayer, onDraw) { + LayerBackdrop(graphicsLayer, onDraw) + } +} + +@Stable +class LayerBackdrop internal constructor( + val graphicsLayer: GraphicsLayer, + internal val onDraw: ContentDrawScope.() -> Unit +) : Backdrop { + + override val isCoordinatesDependent: Boolean = true + + internal var layerCoordinates: LayoutCoordinates? by mutableStateOf(null) + + private var inverseLayerScope: InverseLayerScope? = null + + override fun DrawScope.drawBackdrop( + density: Density, + coordinates: LayoutCoordinates?, + layerBlock: (GraphicsLayerScope.() -> Unit)? + ) { + val coordinates = coordinates ?: return + val layerCoordinates = layerCoordinates ?: return + withTransform({ + if (layerBlock != null) { + with(obtainInverseLayerScope()) { inverseTransform(density, layerBlock) } + } + val offset = coordinates.positionInWindow() - layerCoordinates.positionInWindow() + translate(-offset.x, -offset.y) + }) { + drawLayer(graphicsLayer) + } + } + + private fun obtainInverseLayerScope(): InverseLayerScope { + return inverseLayerScope?.apply { reset() } + ?: InverseLayerScope().also { inverseLayerScope = it } + } +} diff --git a/app/src/main/java/com/kyant/backdrop/backdrops/LayerBackdropModifier.kt b/app/src/main/java/com/kyant/backdrop/backdrops/LayerBackdropModifier.kt new file mode 100644 index 00000000..d1d78e6f --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/backdrops/LayerBackdropModifier.kt @@ -0,0 +1,68 @@ +package com.kyant.backdrop.backdrops + +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.GlobalPositionAwareModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.invalidateDraw +import androidx.compose.ui.platform.InspectorInfo +import com.kyant.backdrop.recordLayer + +fun Modifier.layerBackdrop(backdrop: LayerBackdrop): Modifier = + this then LayerBackdropElement(backdrop) + +private class LayerBackdropElement( + val backdrop: LayerBackdrop +) : ModifierNodeElement() { + + override fun create(): LayerBackdropNode { + return LayerBackdropNode(backdrop) + } + + override fun update(node: LayerBackdropNode) { + node.backdrop = backdrop + node.invalidateDraw() + } + + override fun InspectorInfo.inspectableProperties() { + name = "layerBackdrop" + properties["backdrop"] = backdrop + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is LayerBackdropElement) return false + + if (backdrop != other.backdrop) return false + + return true + } + + override fun hashCode(): Int { + return backdrop.hashCode() + } +} + +private class LayerBackdropNode( + var backdrop: LayerBackdrop +) : DrawModifierNode, GlobalPositionAwareModifierNode, Modifier.Node() { + + override val shouldAutoInvalidate: Boolean = false + + override fun ContentDrawScope.draw() { + drawContent() + recordLayer(backdrop.graphicsLayer) { backdrop.onDraw(this@draw) } + } + + override fun onGloballyPositioned(coordinates: LayoutCoordinates) { + if (coordinates.isAttached) { + backdrop.layerCoordinates = coordinates + } + } + + override fun onDetach() { + backdrop.layerCoordinates = null + } +} diff --git a/app/src/main/java/com/kyant/backdrop/effects/Blur.kt b/app/src/main/java/com/kyant/backdrop/effects/Blur.kt new file mode 100644 index 00000000..d3599f3b --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/effects/Blur.kt @@ -0,0 +1,39 @@ +package com.kyant.backdrop.effects + +import android.graphics.RenderEffect +import android.os.Build +import androidx.annotation.FloatRange +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.toAndroidTileMode +import com.kyant.backdrop.BackdropEffectScope + +fun BackdropEffectScope.blur( + @FloatRange(from = 0.0) radius: Float, + edgeTreatment: TileMode = TileMode.Clamp +) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return + if (radius <= 0f) return + + if (edgeTreatment != TileMode.Clamp || renderEffect != null) { + if (radius > padding) { + padding = radius + } + } + + val currentEffect = renderEffect + renderEffect = + if (currentEffect != null) { + RenderEffect.createBlurEffect( + radius, + radius, + currentEffect, + edgeTreatment.toAndroidTileMode() + ) + } else { + RenderEffect.createBlurEffect( + radius, + radius, + edgeTreatment.toAndroidTileMode() + ) + } +} diff --git a/app/src/main/java/com/kyant/backdrop/effects/ColorFilter.kt b/app/src/main/java/com/kyant/backdrop/effects/ColorFilter.kt new file mode 100644 index 00000000..57595eef --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/effects/ColorFilter.kt @@ -0,0 +1,110 @@ +package com.kyant.backdrop.effects + +import android.graphics.ColorFilter +import android.graphics.ColorMatrix +import android.graphics.ColorMatrixColorFilter +import android.graphics.RenderEffect +import android.os.Build +import androidx.annotation.FloatRange +import androidx.compose.ui.graphics.asAndroidColorFilter +import com.kyant.backdrop.BackdropEffectScope +import com.kyant.backdrop.GammaAdjustmentShaderString +import kotlin.math.pow + +fun BackdropEffectScope.colorFilter(colorFilter: ColorFilter) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return + + val currentEffect = renderEffect + renderEffect = + if (currentEffect != null) { + RenderEffect.createColorFilterEffect(colorFilter, currentEffect) + } else { + RenderEffect.createColorFilterEffect(colorFilter) + } +} + +fun BackdropEffectScope.colorFilter(colorFilter: androidx.compose.ui.graphics.ColorFilter) { + colorFilter(colorFilter.asAndroidColorFilter()) +} + +fun BackdropEffectScope.opacity(@FloatRange(from = 0.0, to = 1.0) alpha: Float) { + val colorMatrix = ColorMatrix( + floatArrayOf( + 1f, 0f, 0f, 0f, 0f, + 0f, 1f, 0f, 0f, 0f, + 0f, 0f, 1f, 0f, 0f, + 0f, 0f, 0f, alpha, 0f + ) + ) + colorFilter(ColorMatrixColorFilter(colorMatrix)) +} + +fun BackdropEffectScope.colorControls( + brightness: Float = 0f, + contrast: Float = 1f, + saturation: Float = 1f +) { + if (brightness == 0f && contrast == 1f && saturation == 1f) { + return + } + + colorFilter(colorControlsColorFilter(brightness, contrast, saturation)) +} + +fun BackdropEffectScope.vibrancy() { + colorFilter(VibrantColorFilter) +} + +private val VibrantColorFilter = colorControlsColorFilter(saturation = 1.5f) + +fun BackdropEffectScope.exposureAdjustment(ev: Float) { + val scale = 2f.pow(ev / 2.2f) + val colorMatrix = ColorMatrix( + floatArrayOf( + scale, 0f, 0f, 0f, 0f, + 0f, scale, 0f, 0f, 0f, + 0f, 0f, scale, 0f, 0f, + 0f, 0f, 0f, 1f, 0f + ) + ) + colorFilter(ColorMatrixColorFilter(colorMatrix)) +} + +fun BackdropEffectScope.gammaAdjustment(power: Float) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return + + val shader = obtainRuntimeShader("GammaAdjustment", GammaAdjustmentShaderString).apply { + setFloatUniform("power", power) + } + effect(RenderEffect.createRuntimeShaderEffect(shader, "content")) +} + +private fun colorControlsColorFilter( + brightness: Float = 0f, + contrast: Float = 1f, + saturation: Float = 1f +): ColorFilter { + val invSat = 1f - saturation + val r = 0.213f * invSat + val g = 0.715f * invSat + val b = 0.072f * invSat + + val c = contrast + val t = (0.5f - c * 0.5f + brightness) * 255f + val s = saturation + + val cr = c * r + val cg = c * g + val cb = c * b + val cs = c * s + + val colorMatrix = ColorMatrix( + floatArrayOf( + cr + cs, cg, cb, 0f, t, + cr, cg + cs, cb, 0f, t, + cr, cg, cb + cs, 0f, t, + 0f, 0f, 0f, 1f, 0f + ) + ) + return ColorMatrixColorFilter(colorMatrix) +} diff --git a/app/src/main/java/com/kyant/backdrop/effects/Lens.kt b/app/src/main/java/com/kyant/backdrop/effects/Lens.kt new file mode 100644 index 00000000..921ad461 --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/effects/Lens.kt @@ -0,0 +1,88 @@ +package com.kyant.backdrop.effects + +import android.graphics.RenderEffect +import android.os.Build +import androidx.annotation.FloatRange +import androidx.compose.foundation.shape.CornerBasedShape +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.util.fastCoerceAtLeast +import androidx.compose.ui.util.fastCoerceAtMost +import com.kyant.backdrop.BackdropEffectScope +import com.kyant.backdrop.RoundedRectRefractionShaderString +import com.kyant.backdrop.RoundedRectRefractionWithDispersionShaderString + +fun BackdropEffectScope.lens( + @FloatRange(from = 0.0) refractionHeight: Float, + @FloatRange(from = 0.0) refractionAmount: Float, + depthEffect: Boolean = false, + chromaticAberration: Boolean = false +) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return + if (refractionHeight <= 0f || refractionAmount <= 0f) return + + if (padding > 0f) { + padding = (padding - refractionHeight).fastCoerceAtLeast(0f) + } + + val cornerRadii = cornerRadii + val effect = + if (cornerRadii != null) { + val shader = + if (!chromaticAberration) { + obtainRuntimeShader( + "Refraction", + RoundedRectRefractionShaderString + ) + } else { + obtainRuntimeShader( + "RefractionWithDispersion", + RoundedRectRefractionWithDispersionShaderString + ) + } + shader.apply { + setFloatUniform("size", size.width, size.height) + setFloatUniform("offset", -padding, -padding) + setFloatUniform("cornerRadii", cornerRadii) + setFloatUniform("refractionHeight", refractionHeight) + setFloatUniform("refractionAmount", -refractionAmount) + setFloatUniform("depthEffect", if (depthEffect) 1f else 0f) + if (chromaticAberration) { + setFloatUniform("chromaticAberration", 1f) + } + } + RenderEffect.createRuntimeShaderEffect(shader, "content") + } else { + throwUnsupportedSDFException() + } + effect(effect) +} + +private val BackdropEffectScope.cornerRadii: FloatArray? + get() { + val shape = shape as? CornerBasedShape ?: return null + val size = size + val maxRadius = size.minDimension / 2f + val isLtr = layoutDirection == LayoutDirection.Ltr + val topLeft = + if (isLtr) shape.topStart.toPx(size, this) + else shape.topEnd.toPx(size, this) + val topRight = + if (isLtr) shape.topEnd.toPx(size, this) + else shape.topStart.toPx(size, this) + val bottomRight = + if (isLtr) shape.bottomEnd.toPx(size, this) + else shape.bottomStart.toPx(size, this) + val bottomLeft = + if (isLtr) shape.bottomStart.toPx(size, this) + else shape.bottomEnd.toPx(size, this) + return floatArrayOf( + topLeft.fastCoerceAtMost(maxRadius), + topRight.fastCoerceAtMost(maxRadius), + bottomRight.fastCoerceAtMost(maxRadius), + bottomLeft.fastCoerceAtMost(maxRadius) + ) + } + +private fun throwUnsupportedSDFException(): Nothing { + throw UnsupportedOperationException("Only CornerBasedShape is supported in lens effects.") +} diff --git a/app/src/main/java/com/kyant/backdrop/effects/RenderEffect.kt b/app/src/main/java/com/kyant/backdrop/effects/RenderEffect.kt new file mode 100644 index 00000000..bccbb90f --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/effects/RenderEffect.kt @@ -0,0 +1,23 @@ +package com.kyant.backdrop.effects + +import android.graphics.RenderEffect +import android.os.Build +import com.kyant.backdrop.BackdropEffectScope + +fun BackdropEffectScope.effect(effect: RenderEffect) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return + + val currentEffect = renderEffect + renderEffect = + if (currentEffect != null) { + RenderEffect.createChainEffect(effect, currentEffect) + } else { + effect + } +} + +fun BackdropEffectScope.effect(effect: androidx.compose.ui.graphics.RenderEffect) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return + + effect(effect.asAndroidRenderEffect()) +} diff --git a/app/src/main/java/com/kyant/backdrop/highlight/Highlight.kt b/app/src/main/java/com/kyant/backdrop/highlight/Highlight.kt new file mode 100644 index 00000000..eda956f0 --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/highlight/Highlight.kt @@ -0,0 +1,28 @@ +package com.kyant.backdrop.highlight + +import androidx.annotation.FloatRange +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Immutable +data class Highlight( + val width: Dp = 0.5f.dp, + val blurRadius: Dp = width / 2f, + @param:FloatRange(from = 0.0, to = 1.0) val alpha: Float = 1f, + val style: HighlightStyle = HighlightStyle.Default +) { + + companion object { + + @Stable + val Default: Highlight = Highlight() + + @Stable + val Ambient: Highlight = Highlight(style = HighlightStyle.Ambient) + + @Stable + val Plain: Highlight = Highlight(style = HighlightStyle.Plain) + } +} diff --git a/app/src/main/java/com/kyant/backdrop/highlight/HighlightModifier.kt b/app/src/main/java/com/kyant/backdrop/highlight/HighlightModifier.kt new file mode 100644 index 00000000..fdd66fab --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/highlight/HighlightModifier.kt @@ -0,0 +1,170 @@ +package com.kyant.backdrop.highlight + +import android.graphics.BlurMaskFilter +import android.os.Build +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.PaintingStyle +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.graphics.layer.GraphicsLayer +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.invalidateDraw +import androidx.compose.ui.node.requireGraphicsContext +import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.util.fastCoerceAtMost +import com.kyant.backdrop.RuntimeShaderCacheImpl +import com.kyant.backdrop.ShapeProvider +import com.kyant.backdrop.clipOutline +import kotlin.math.ceil + +internal class HighlightElement( + val shapeProvider: ShapeProvider, + val highlight: () -> Highlight? +) : ModifierNodeElement() { + + override fun create(): HighlightNode { + return HighlightNode(shapeProvider, highlight) + } + + override fun update(node: HighlightNode) { + node.shapeProvider = shapeProvider + node.highlight = highlight + node.invalidateDraw() + } + + override fun InspectorInfo.inspectableProperties() { + name = "highlight" + properties["shapeProvider"] = shapeProvider + properties["highlight"] = highlight + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is HighlightElement) return false + + if (shapeProvider != other.shapeProvider) return false + if (highlight != other.highlight) return false + + return true + } + + override fun hashCode(): Int { + var result = shapeProvider.hashCode() + result = 31 * result + highlight.hashCode() + return result + } +} + +internal class HighlightNode( + var shapeProvider: ShapeProvider, + var highlight: () -> Highlight? +) : DrawModifierNode, Modifier.Node() { + + override val shouldAutoInvalidate: Boolean = false + + private var highlightLayer: GraphicsLayer? = null + + private val paint = + Paint().apply { + style = PaintingStyle.Stroke + } + private var clipPath: Path? = null + + private val runtimeShaderCache = RuntimeShaderCacheImpl() + + private var prevStyle: HighlightStyle? = null + + override fun ContentDrawScope.draw() { + val highlight = highlight() + if (highlight == null || highlight.width.value <= 0f) { + return drawContent() + } + + drawContent() + + val highlightLayer = highlightLayer + if (highlightLayer != null) { + val size = size + val density: Density = this + val layoutDirection = layoutDirection + + val safeSize = + IntSize( + ceil(size.width).toInt() + 2, + ceil(size.height).toInt() + 2 + ) + + val outline = shapeProvider.shape.createOutline(size, layoutDirection, density) + val clipPath = + if (outline is Outline.Rounded) { + clipPath ?: Path().also { clipPath = it } + } else { + null + } + + configurePaint(highlight) + + highlightLayer.alpha = highlight.alpha + highlightLayer.blendMode = highlight.style.blendMode + highlightLayer.record(safeSize) { + translate(1f, 1f) { + val canvas = drawContext.canvas + canvas.save() + canvas.clipOutline(outline, clipPath) + canvas.drawOutline(outline, paint) + canvas.restore() + } + } + + translate(-1f, -1f) { + drawLayer(highlightLayer) + } + } + } + + override fun onAttach() { + val graphicsContext = requireGraphicsContext() + highlightLayer = graphicsContext.createGraphicsLayer() + } + + override fun onDetach() { + val graphicsContext = requireGraphicsContext() + highlightLayer?.let { layer -> + graphicsContext.releaseGraphicsLayer(layer) + highlightLayer = null + } + clipPath = null + runtimeShaderCache.clear() + prevStyle = null + } + + private fun DrawScope.configurePaint(highlight: Highlight) { + paint.color = highlight.style.color + paint.strokeWidth = + ceil(highlight.width.toPx().fastCoerceAtMost(size.minDimension / 2f)) * 2f + val blurRadius = highlight.blurRadius.toPx() + paint.asFrameworkPaint().maskFilter = + if (blurRadius > 0f) { + BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.NORMAL) + } else { + null + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + paint.shader = with(highlight.style) { + createShader( + shape = shapeProvider.shape, + runtimeShaderCache = runtimeShaderCache + ) + } + } + } +} diff --git a/app/src/main/java/com/kyant/backdrop/highlight/HighlightStyle.kt b/app/src/main/java/com/kyant/backdrop/highlight/HighlightStyle.kt new file mode 100644 index 00000000..5992ece1 --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/highlight/HighlightStyle.kt @@ -0,0 +1,145 @@ +package com.kyant.backdrop.highlight + +import android.os.Build +import androidx.annotation.FloatRange +import androidx.annotation.RequiresApi +import androidx.compose.foundation.shape.CornerBasedShape +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shader +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.util.fastCoerceAtMost +import com.kyant.backdrop.AmbientHighlightShaderString +import com.kyant.backdrop.DefaultHighlightShaderString +import com.kyant.backdrop.RuntimeShaderCache +import kotlin.math.PI + +@Immutable +interface HighlightStyle { + + val color: Color + + val blendMode: BlendMode + + @RequiresApi(Build.VERSION_CODES.S) + fun DrawScope.createShader( + shape: Shape, + runtimeShaderCache: RuntimeShaderCache + ): Shader? + + @Immutable + data class Plain( + override val color: Color = Color.White.copy(alpha = 0.38f), + override val blendMode: BlendMode = BlendMode.Plus + ) : HighlightStyle { + + @RequiresApi(Build.VERSION_CODES.S) + override fun DrawScope.createShader( + shape: Shape, + runtimeShaderCache: RuntimeShaderCache + ): Shader? = null + } + + @Immutable + data class Default( + @param:FloatRange(from = 0.0, to = 1.0) val intensity: Float = 0.5f, + val angle: Float = 45f, + @param:FloatRange(from = 0.0) val falloff: Float = 1f + ) : HighlightStyle { + + override val color: Color = Color.White.copy(alpha = intensity) + + override val blendMode: BlendMode = BlendMode.Plus + + @RequiresApi(Build.VERSION_CODES.S) + override fun DrawScope.createShader( + shape: Shape, + runtimeShaderCache: RuntimeShaderCache + ): Shader? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + runtimeShaderCache.obtainRuntimeShader( + "Default", + DefaultHighlightShaderString + ).apply { + setFloatUniform("size", size.width, size.height) + setFloatUniform("cornerRadii", getCornerRadii(shape)) + setFloatUniform("angle", angle * (PI / 180f).toFloat()) + setFloatUniform("falloff", falloff) + } + } else { + null + } + } + } + + @Immutable + data class Ambient( + @param:FloatRange(from = 0.0, to = 1.0) val intensity: Float = 0.38f + ) : HighlightStyle { + + override val color: Color = Color.White.copy(alpha = intensity) + + override val blendMode: BlendMode = DrawScope.DefaultBlendMode + + @RequiresApi(Build.VERSION_CODES.S) + override fun DrawScope.createShader( + shape: Shape, + runtimeShaderCache: RuntimeShaderCache + ): Shader? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + runtimeShaderCache.obtainRuntimeShader( + "Ambient", + AmbientHighlightShaderString + ).apply { + setFloatUniform("size", size.width, size.height) + setFloatUniform("cornerRadii", getCornerRadii(shape)) + setFloatUniform("angle", 45f * (PI / 180f).toFloat()) + setFloatUniform("falloff", 1f) + } + } else { + null + } + } + } + + companion object { + + @Stable + val Default: Default = Default() + + @Stable + val Ambient: Ambient = Ambient() + + @Stable + val Plain: Plain = Plain() + } +} + +private fun DrawScope.getCornerRadii(shape: Shape): FloatArray { + val size = size + val maxRadius = size.minDimension / 2f + val shape = shape as? CornerBasedShape ?: return FloatArray(4) { maxRadius } + val isLtr = layoutDirection == LayoutDirection.Ltr + val topLeft = + if (isLtr) shape.topStart.toPx(size, this) + else shape.topEnd.toPx(size, this) + val topRight = + if (isLtr) shape.topEnd.toPx(size, this) + else shape.topStart.toPx(size, this) + val bottomRight = + if (isLtr) shape.bottomEnd.toPx(size, this) + else shape.bottomStart.toPx(size, this) + val bottomLeft = + if (isLtr) shape.bottomStart.toPx(size, this) + else shape.bottomEnd.toPx(size, this) + return floatArrayOf( + topLeft.fastCoerceAtMost(maxRadius), + topRight.fastCoerceAtMost(maxRadius), + bottomRight.fastCoerceAtMost(maxRadius), + bottomLeft.fastCoerceAtMost(maxRadius) + ) +} diff --git a/app/src/main/java/com/kyant/backdrop/shadow/InnerShadow.kt b/app/src/main/java/com/kyant/backdrop/shadow/InnerShadow.kt new file mode 100644 index 00000000..1475bec6 --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/shadow/InnerShadow.kt @@ -0,0 +1,41 @@ +package com.kyant.backdrop.shadow + +import androidx.annotation.FloatRange +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.lerp +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.lerp +import androidx.compose.ui.util.lerp + +@Immutable +data class InnerShadow( + val radius: Dp = 24f.dp, + val offset: DpOffset = DpOffset(0f.dp, radius), + val color: Color = Color.Black.copy(alpha = 0.15f), + @param:FloatRange(from = 0.0, to = 1.0) val alpha: Float = 1f, + val blendMode: BlendMode = DrawScope.DefaultBlendMode +) { + + companion object { + + @Stable + val Default: InnerShadow = InnerShadow() + } +} + +@Stable +fun lerp(start: InnerShadow, stop: InnerShadow, fraction: Float): InnerShadow { + return InnerShadow( + radius = lerp(start.radius, stop.radius, fraction), + offset = lerp(start.offset, stop.offset, fraction), + color = lerp(start.color, stop.color, fraction), + alpha = lerp(start.alpha, stop.alpha, fraction), + blendMode = if (fraction < 0.5f) start.blendMode else stop.blendMode + ) +} diff --git a/app/src/main/java/com/kyant/backdrop/shadow/InnerShadowModifier.kt b/app/src/main/java/com/kyant/backdrop/shadow/InnerShadowModifier.kt new file mode 100644 index 00000000..4c65d288 --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/shadow/InnerShadowModifier.kt @@ -0,0 +1,166 @@ +package com.kyant.backdrop.shadow + +import android.os.Build +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.BlurEffect +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.layer.CompositingStrategy +import androidx.compose.ui.graphics.layer.GraphicsLayer +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.invalidateDraw +import androidx.compose.ui.node.requireGraphicsContext +import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.unit.Density +import com.kyant.backdrop.ShapeProvider +import com.kyant.backdrop.clipOutline + +internal class InnerShadowElement( + val shapeProvider: ShapeProvider, + val shadow: () -> InnerShadow? +) : ModifierNodeElement() { + + override fun create(): InnerShadowNode { + return InnerShadowNode(shapeProvider, shadow) + } + + override fun update(node: InnerShadowNode) { + node.shapeProvider = shapeProvider + node.shadow = shadow + node.invalidateDraw() + } + + override fun InspectorInfo.inspectableProperties() { + name = "innerShadow" + properties["shapeProvider"] = shapeProvider + properties["shadow"] = shadow + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is InnerShadowElement) return false + + if (shapeProvider != other.shapeProvider) return false + if (shadow != other.shadow) return false + + return true + } + + override fun hashCode(): Int { + var result = shapeProvider.hashCode() + result = 31 * result + shadow.hashCode() + return result + } +} + +internal class InnerShadowNode( + var shapeProvider: ShapeProvider, + var shadow: () -> InnerShadow? +) : DrawModifierNode, Modifier.Node() { + + override val shouldAutoInvalidate: Boolean = false + + private var shadowLayer: GraphicsLayer? = null + + private val paint = Paint() + private var clipPath: Path? = null + + private var prevRadius = Float.NaN + + override fun ContentDrawScope.draw() { + drawContent() + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return + + val shadow = shadow() ?: return + + val shadowLayer = shadowLayer + if (shadowLayer != null) { + val size = size + val density: Density = this + val layoutDirection = layoutDirection + + val radius = shadow.radius.toPx() + val offsetX = shadow.offset.x.toPx() + val offsetY = shadow.offset.y.toPx() + + val outline = shapeProvider.shape.createOutline(size, layoutDirection, density) + val clipPath = + if (outline is Outline.Rounded) { + clipPath ?: Path().also { clipPath = it } + } else { + null + } + + configurePaint(shadow) + + shadowLayer.alpha = shadow.alpha + shadowLayer.blendMode = shadow.blendMode + if (prevRadius != radius) { + shadowLayer.renderEffect = + if (radius > 0f) { + BlurEffect(radius, radius, TileMode.Decal) + } else { + null + } + prevRadius = radius + } + shadowLayer.record { + val canvas = drawContext.canvas + canvas.save() + canvas.clipOutline(outline, clipPath) + canvas.drawOutline(outline, paint) + canvas.translate(offsetX, offsetY) + canvas.drawOutline(outline, ShadowMaskPaint) + canvas.translate(-offsetX, -offsetY) + canvas.restore() + } + + val canvas = drawContext.canvas + canvas.save() + canvas.clipOutline(outline, clipPath) + drawLayer(shadowLayer) + canvas.restore() + } + } + + override fun onAttach() { + val graphicsContext = requireGraphicsContext() + shadowLayer = + graphicsContext.createGraphicsLayer().apply { + compositingStrategy = CompositingStrategy.Offscreen + } + } + + override fun onDetach() { + val graphicsContext = requireGraphicsContext() + shadowLayer?.let { layer -> + graphicsContext.releaseGraphicsLayer(layer) + shadowLayer = null + } + } + + private fun DrawScope.configurePaint(shadow: InnerShadow) { + paint.color = shadow.color + } + + private fun DrawScope.drawMaskedShadow(outline: Outline, layer: GraphicsLayer) { + val canvas = drawContext.canvas + canvas.save() + canvas.clipOutline(outline, clipPath) + drawLayer(layer) + canvas.restore() + } +} + +private val ShadowMaskPaint = Paint().apply { + blendMode = BlendMode.Clear +} diff --git a/app/src/main/java/com/kyant/backdrop/shadow/Shadow.kt b/app/src/main/java/com/kyant/backdrop/shadow/Shadow.kt new file mode 100644 index 00000000..24684f8b --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/shadow/Shadow.kt @@ -0,0 +1,27 @@ +package com.kyant.backdrop.shadow + +import androidx.annotation.FloatRange +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp + +@Immutable +data class Shadow( + val radius: Dp = 24f.dp, + val offset: DpOffset = DpOffset(0f.dp, radius / 6f), + val color: Color = Color.Black.copy(alpha = 0.1f), + @param:FloatRange(from = 0.0, to = 1.0) val alpha: Float = 1f, + val blendMode: BlendMode = DrawScope.DefaultBlendMode +) { + + companion object { + + @Stable + val Default: Shadow = Shadow() + } +} diff --git a/app/src/main/java/com/kyant/backdrop/shadow/ShadowModifier.kt b/app/src/main/java/com/kyant/backdrop/shadow/ShadowModifier.kt new file mode 100644 index 00000000..1d810dd8 --- /dev/null +++ b/app/src/main/java/com/kyant/backdrop/shadow/ShadowModifier.kt @@ -0,0 +1,143 @@ +package com.kyant.backdrop.shadow + +import android.graphics.BlurMaskFilter +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.graphics.layer.CompositingStrategy +import androidx.compose.ui.graphics.layer.GraphicsLayer +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.invalidateDraw +import androidx.compose.ui.node.requireGraphicsContext +import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.IntSize +import com.kyant.backdrop.ShapeProvider +import kotlin.math.ceil + +internal class ShadowElement( + val shapeProvider: ShapeProvider, + val shadow: () -> Shadow? +) : ModifierNodeElement() { + + override fun create(): ShadowNode { + return ShadowNode(shapeProvider, shadow) + } + + override fun update(node: ShadowNode) { + node.shapeProvider = shapeProvider + node.shadow = shadow + node.invalidateDraw() + } + + override fun InspectorInfo.inspectableProperties() { + name = "shadow" + properties["shapeProvider"] = shapeProvider + properties["shadow"] = shadow + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ShadowElement) return false + + if (shapeProvider != other.shapeProvider) return false + if (shadow != other.shadow) return false + + return true + } + + override fun hashCode(): Int { + var result = shapeProvider.hashCode() + result = 31 * result + shadow.hashCode() + return result + } +} + +internal class ShadowNode( + var shapeProvider: ShapeProvider, + var shadow: () -> Shadow? +) : DrawModifierNode, Modifier.Node() { + + override val shouldAutoInvalidate: Boolean = false + + private var shadowLayer: GraphicsLayer? = null + + private val paint = Paint() + + override fun ContentDrawScope.draw() { + val shadow = shadow() ?: return drawContent() + + val shadowLayer = shadowLayer + if (shadowLayer != null) { + val size = size + val density: Density = this + val layoutDirection = layoutDirection + + val radius = shadow.radius.toPx() + val offsetX = shadow.offset.x.toPx() + val offsetY = shadow.offset.y.toPx() + val shadowSize = IntSize( + ceil(size.width + radius * 4f + offsetX).toInt(), + ceil(size.height + radius * 4f + offsetY).toInt() + ) + val outline = shapeProvider.shape.createOutline(size, layoutDirection, density) + + configurePaint(shadow) + + shadowLayer.alpha = shadow.alpha + shadowLayer.blendMode = shadow.blendMode + shadowLayer.record(shadowSize) { + translate(radius * 2f + offsetX, radius * 2f + offsetY) { + val canvas = drawContext.canvas + canvas.drawOutline(outline, paint) + canvas.translate(-offsetX, -offsetY) + canvas.drawOutline(outline, ShadowMaskPaint) + canvas.translate(offsetX, offsetY) + } + } + + translate(-radius * 2f, -radius * 2f) { + drawLayer(shadowLayer) + } + } + + drawContent() + } + + override fun onAttach() { + val graphicsContext = requireGraphicsContext() + shadowLayer = + graphicsContext.createGraphicsLayer().apply { + compositingStrategy = CompositingStrategy.Offscreen + } + } + + override fun onDetach() { + val graphicsContext = requireGraphicsContext() + shadowLayer?.let { layer -> + graphicsContext.releaseGraphicsLayer(layer) + shadowLayer = null + } + } + + private fun DrawScope.configurePaint(shadow: Shadow) { + paint.color = shadow.color + val blurRadius = shadow.radius.toPx() + paint.asFrameworkPaint().maskFilter = + if (blurRadius > 0f) { + BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.NORMAL) + } else { + null + } + } +} + +private val ShadowMaskPaint = Paint().apply { + blendMode = BlendMode.Clear +} diff --git a/app/src/main/java/com/suqi8/oshin/MainActivity.kt b/app/src/main/java/com/suqi8/oshin/MainActivity.kt new file mode 100644 index 00000000..1426a605 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/MainActivity.kt @@ -0,0 +1,92 @@ +package com.suqi8.oshin + +import android.content.Context +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.SystemBarStyle +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import com.highcapable.yukihookapi.hook.factory.prefs +import com.suqi8.oshin.ui.mainscreen.AppNavHost +import com.suqi8.oshin.ui.mainscreen.LocalColorMode +import com.suqi8.oshin.ui.theme.AppTheme +import dagger.hilt.android.AndroidEntryPoint +import java.util.Locale + +const val TAG = "OShin" +private const val APP_LANGUAGE_PREF_KEY = "app_language" + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + override fun attachBaseContext(newBase: Context) { + // 从 SharedPreferences 中读取语言设置 + val languageCode = newBase.prefs("settings").getInt(APP_LANGUAGE_PREF_KEY, 0) + + // 使用 when 表达式直接返回 Locale 对象,更简洁 + val localeToSet = when (languageCode) { + 1 -> Locale.SIMPLIFIED_CHINESE + 2 -> Locale.ENGLISH + 3 -> Locale.JAPANESE + 4 -> Locale.forLanguageTag("ru") + 5 -> Locale.Builder().setLanguage("qaa").setExtension('x', "meme").build() + 6 -> Locale.KOREAN + else -> null // 使用 null 代表系统默认 + } + + // 如果 localeToSet 不为 null,则应用它 + val context = if (localeToSet != null) { + val config = newBase.resources.configuration + config.setLocale(localeToSet) + newBase.createConfigurationContext(config) + } else { + newBase + } + + super.attachBaseContext(context) + } + + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() + super.onCreate(savedInstanceState) + + setContent { + val context = LocalContext.current + val colorMode = remember { + mutableIntStateOf( + context.prefs("settings").getInt("color_mode", 0) + ) + } + + val darkMode = colorMode.intValue == 2 || (colorMode.intValue == 0 && isSystemInDarkTheme()) + + DisposableEffect(darkMode) { + enableEdgeToEdge( + statusBarStyle = SystemBarStyle.auto( + android.graphics.Color.TRANSPARENT, + android.graphics.Color.TRANSPARENT + ) { darkMode }, + navigationBarStyle = SystemBarStyle.auto( + android.graphics.Color.TRANSPARENT, + android.graphics.Color.TRANSPARENT + ) { darkMode }, + ) + onDispose {} + } + + window.isNavigationBarContrastEnforced = false + + AppTheme(colorMode = colorMode.intValue) { + CompositionLocalProvider(LocalColorMode provides colorMode) { + // 主内容现在只是一个对 AppNavHost 的简单调用 + AppNavHost() + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/Main_Function.kt b/app/src/main/java/com/suqi8/oshin/Main_Function.kt new file mode 100644 index 00000000..ebe0feff --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/Main_Function.kt @@ -0,0 +1,100 @@ +package com.suqi8.oshin + +import android.annotation.SuppressLint +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.suqi8.oshin.ui.activity.components.FunArrow +import com.suqi8.oshin.ui.activity.components.addline +import com.suqi8.oshin.ui.mainscreen.home.ModernSectionTitle +import top.yukonga.miuix.kmp.basic.Card +import top.yukonga.miuix.kmp.basic.ScrollBehavior +import top.yukonga.miuix.kmp.utils.overScrollVertical + +@OptIn(ExperimentalSharedTransitionApi::class) +@SuppressLint("UnrememberedMutableState") +@Composable +fun Main_Function( + topAppBarScrollBehavior: ScrollBehavior, + navController: NavController, + padding: PaddingValues, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .overScrollVertical() + .nestedScroll(topAppBarScrollBehavior.nestedScrollConnection) + ) { + item { + ModernSectionTitle( + title = stringResource(id = R.string.module), + modifier = Modifier + .displayCutoutPadding() + .padding(top = padding.calculateTopPadding() + 80.dp) + ) + } + item { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(vertical = 6.dp) + ) { + Column { + with(sharedTransitionScope) { + Box( + modifier = Modifier + .sharedBounds( + sharedContentState = rememberSharedContentState(key = "func\\cpu_freq"), + animatedVisibilityScope = animatedVisibilityScope + ) + .fillMaxWidth().wrapContentHeight() + ) { + FunArrow( + title = stringResource(id = R.string.cpu_freq_main), + onClick = { navController.navigate("func\\cpu_freq") } + ) + } + } + addline() + with(sharedTransitionScope) { + Box( + modifier = Modifier + .sharedBounds( + sharedContentState = rememberSharedContentState(key = "func\\romworkshop"), + animatedVisibilityScope = animatedVisibilityScope + ) + .fillMaxWidth().wrapContentHeight() + ) { + FunArrow( + title = stringResource(id = R.string.rom_workshop), + onClick = { navController.navigate("func\\romworkshop") } + ) + } + } + } + } + } + item { + Spacer(modifier = Modifier.padding(bottom = padding.calculateBottomPadding())) + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/application/DefaultApplication.kt b/app/src/main/java/com/suqi8/oshin/application/DefaultApplication.kt new file mode 100644 index 00000000..5cc13168 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/application/DefaultApplication.kt @@ -0,0 +1,32 @@ +package com.suqi8.oshin.application + +import androidx.appcompat.app.AppCompatDelegate +import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication +import com.umeng.commonsdk.UMConfigure +import com.umeng.union.UMUnionSdk +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class DefaultApplication : ModuleApplication() { + + override fun onCreate() { + super.onCreate() + /** + * 初始化 MMKV + * Initialize MMKV + */ + //MMKV.initialize(this) + + + UMConfigure.setLogEnabled(true) + UMConfigure.preInit(this, "67c7dea68f232a05f127781e", "android") + UMUnionSdk.init(this) + + /** + * 跟随系统夜间模式 + * Follow system night mode + */ + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + // Your code here. + } +} diff --git a/app/src/main/java/com/suqi8/oshin/data/repository/AppInfoProvider.kt b/app/src/main/java/com/suqi8/oshin/data/repository/AppInfoProvider.kt new file mode 100644 index 00000000..1abba382 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/data/repository/AppInfoProvider.kt @@ -0,0 +1,69 @@ +package com.suqi8.oshin.data.repository + +import android.content.Context +import android.content.pm.PackageManager +import androidx.compose.ui.graphics.asImageBitmap +import androidx.core.graphics.drawable.toBitmap +import com.suqi8.oshin.utils.AppInfo +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.util.concurrent.ConcurrentHashMap +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AppInfoProvider @Inject constructor( + @ApplicationContext private val context: Context +) { + private val pm: PackageManager = context.packageManager + + private val NULL_CACHE_ENTRY = Any() + + // 使用线程安全的 ConcurrentHashMap 作为缓存 + private val cache = ConcurrentHashMap() + + /** + * 高效获取应用信息(名称 + 图标) + * - 优先读缓存,失败则从系统加载 + * - 找不到应用会缓存 null,防止重复查询 + */ + suspend fun getInfo(packageName: String): AppInfo? { + // 1. 检查缓存 + val cachedValue = cache[packageName] + if (cachedValue != null) { + return if (cachedValue === NULL_CACHE_ENTRY) { + null // 这是一个缓存的 "未找到" 结果 + } else { + cachedValue as AppInfo // 这是一个缓存的 AppInfo + } + } + + // 2. 缓存未命中,在 IO 线程加载 + return withContext(Dispatchers.IO) { + try { + val appInfo = pm.getApplicationInfo(packageName, 0) + val name = pm.getApplicationLabel(appInfo).toString() + val icon = appInfo.loadIcon(pm).toBitmap().asImageBitmap() + + val result = AppInfo(name = name, icon = icon) + + // 3. 存入缓存 + cache[packageName] = result + result + } catch (e: PackageManager.NameNotFoundException) { + // 缓存 null 结果,避免重复查询不存在的应用 + cache[packageName] = NULL_CACHE_ENTRY + null + } + } + } + + /** + * 仅获取应用名称的便捷函数 + */ + suspend fun getAppName(packageName: String): String { + // 如果找不到应用,返回包名作为兜底 + return getInfo(packageName)?.name ?: packageName + } +} diff --git a/app/src/main/java/com/suqi8/oshin/data/repository/FeatureRepository.kt b/app/src/main/java/com/suqi8/oshin/data/repository/FeatureRepository.kt new file mode 100644 index 00000000..125030b2 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/data/repository/FeatureRepository.kt @@ -0,0 +1,83 @@ +package com.suqi8.oshin.data.repository + +import android.content.Context +import android.content.pm.PackageManager +import com.suqi8.oshin.features.FeatureRegistry +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PlainText +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Title +import com.suqi8.oshin.models.TitledScreenItem +import com.suqi8.oshin.ui.mainscreen.module.SearchableItem +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.withContext +import javax.inject.Inject +import javax.inject.Singleton + +// 1. 定义仓库接口 +interface FeatureRepository { + suspend fun getAllSearchableItems(): List +} + +@Singleton +class FeatureRepositoryImpl @Inject constructor( + @ApplicationContext private val context: Context +) : FeatureRepository { + + // 1. 缓存结果,初始为 null + private var cachedItems: List? = null + + override suspend fun getAllSearchableItems(): List { + // 2. 如果已有缓存,直接返回缓存,后续调用将非常快 + cachedItems?.let { return it } + + // 3. 如果没有缓存,则在后台线程执行一次耗时计算 + return withContext(Dispatchers.Default) { + val searchableItemsJobs = FeatureRegistry.screenMap.flatMap { (routeId, pageDef) -> + pageDef.items.filterIsInstance() + .flatMap { it.items } + .filterIsInstance() + .map { item -> + async { + SearchableItem( + title = resolveTitle(item.title), + summary = item.summary?.let { context.getString(it) } ?: "", + route = "feature/$routeId", + key = item.key + ) + } + } + } + + val items = searchableItemsJobs.awaitAll() + + // 4. 将计算结果存入缓存 + cachedItems = items + + // 5. 返回结果 + items + } + } + + private suspend fun resolveTitle(title: Title): String { + return when (title) { + is StringResource -> context.getString(title.id) + is PlainText -> title.text + is AppName -> getAppName(title.packageName) + } + } + + private suspend fun getAppName(packageName: String): String = withContext(Dispatchers.IO) { + try { + val pm = context.packageManager + val appInfo = pm.getApplicationInfo(packageName, 0) + pm.getApplicationLabel(appInfo).toString() + } catch (e: PackageManager.NameNotFoundException) { + packageName + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/di/AppModule.kt b/app/src/main/java/com/suqi8/oshin/di/AppModule.kt new file mode 100644 index 00000000..af39777d --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/di/AppModule.kt @@ -0,0 +1,20 @@ +package com.suqi8.oshin.di + +import com.suqi8.oshin.data.repository.FeatureRepository +import com.suqi8.oshin.data.repository.FeatureRepositoryImpl +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import jakarta.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class AppModule { + + @Binds + @Singleton + abstract fun bindFeatureRepository( + impl: FeatureRepositoryImpl + ): FeatureRepository +} diff --git a/app/src/main/java/com/suqi8/oshin/di/PrefsModule.kt b/app/src/main/java/com/suqi8/oshin/di/PrefsModule.kt new file mode 100644 index 00000000..8d1887e3 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/di/PrefsModule.kt @@ -0,0 +1,21 @@ +package com.suqi8.oshin.di + +import android.content.Context +import com.highcapable.yukihookapi.hook.factory.prefs +import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookPrefsBridge +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object PrefsModule { + @Provides + @Singleton + fun provideYukiHookPrefsBridge(@ApplicationContext context: Context): YukiHookPrefsBridge { + return context.prefs() + } +} diff --git a/app/src/main/java/com/suqi8/oshin/features/FeatureRegistry.kt b/app/src/main/java/com/suqi8/oshin/features/FeatureRegistry.kt new file mode 100644 index 00000000..6c8cc590 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/FeatureRegistry.kt @@ -0,0 +1,151 @@ +package com.suqi8.oshin.features + +import com.suqi8.oshin.features.android.OplusServices +import com.suqi8.oshin.features.android.PMS +import com.suqi8.oshin.features.android.SplitScreenMultiWindow +import com.suqi8.oshin.features.android.android +import com.suqi8.oshin.features.appdetail.appdetail +import com.suqi8.oshin.features.battery.battery +import com.suqi8.oshin.features.browser.browser +import com.suqi8.oshin.features.exsystemservice.exsystemservice +import com.suqi8.oshin.features.games.games +import com.suqi8.oshin.features.health.health +import com.suqi8.oshin.features.incallui.incallui +import com.suqi8.oshin.features.launcher.RecentTask +import com.suqi8.oshin.features.launcher.launcher +import com.suqi8.oshin.features.mihealth.mihealth +import com.suqi8.oshin.features.mms.mms +import com.suqi8.oshin.features.notificationmanager.notificationmanager +import com.suqi8.oshin.features.ocrscanner.ocrscanner +import com.suqi8.oshin.features.oplusphonemanager.oplusphonemanager +import com.suqi8.oshin.features.oshare.oshare +import com.suqi8.oshin.features.ota.ota +import com.suqi8.oshin.features.padconnect.padconnect +import com.suqi8.oshin.features.phone.phone +import com.suqi8.oshin.features.phonemanager.phonemanager +import com.suqi8.oshin.features.quicksearchbox.quicksearchbox +import com.suqi8.oshin.features.securepay.securepay +import com.suqi8.oshin.features.securitypermission.securitypermission +import com.suqi8.oshin.features.settings.settings +import com.suqi8.oshin.features.speechassist.speechassist +import com.suqi8.oshin.features.systemui.ControlCenter +import com.suqi8.oshin.features.systemui.HardwareIndicator +import com.suqi8.oshin.features.systemui.StatusBar +import com.suqi8.oshin.features.systemui.StatusBarClock +import com.suqi8.oshin.features.systemui.StatusBarWifi +import com.suqi8.oshin.features.systemui.notification +import com.suqi8.oshin.features.systemui.systemui +import com.suqi8.oshin.features.themestore.themestore +import com.suqi8.oshin.features.wallet.wallet +import com.suqi8.oshin.features.weather.weather +import com.suqi8.oshin.models.ModuleEntry +import com.suqi8.oshin.models.PageDefinition + +object FeatureRegistry { + /** + * 主模块页面的应用入口列表。 + */ + val moduleEntries = listOf( + ModuleEntry("android", "android"), + ModuleEntry("com.android.systemui", "systemui"), + ModuleEntry("com.android.incallui", "incallui"), + ModuleEntry("com.android.settings", "settings"), + ModuleEntry("com.android.phone", "phone"), + ModuleEntry("com.android.mms", "mms"), + ModuleEntry("com.android.launcher", "launcher"), + ModuleEntry("com.coloros.ocrscanner", "ocrscanner"), + ModuleEntry("com.coloros.oshare", "oshare"), + ModuleEntry("com.coloros.phonemanager", "phonemanager"), + ModuleEntry("com.coloros.securepay", "securepay"), + ModuleEntry("com.finshell.wallet", "wallet"), + ModuleEntry("com.heytap.health", "health"), + ModuleEntry("com.heytap.quicksearchbox", "quicksearchbox"), + ModuleEntry("com.heytap.speechassist", "speechassist"), + ModuleEntry("com.mi.health", "mihealth"), + ModuleEntry("com.oplus.appdetail", "appdetail"), + ModuleEntry("com.oplus.battery", "battery"), + ModuleEntry("com.oplus.exsystemservice", "exsystemservice"), + ModuleEntry("com.oplus.games", "games"), + ModuleEntry("com.oplus.notificationmanager", "notificationmanager"), + ModuleEntry("com.oplus.ota", "ota"), + ModuleEntry("com.oplus.padconnect", "padconnect"), + ModuleEntry("com.oplus.phonemanager", "oplusphonemanager"), + ModuleEntry("com.coloros.weather2", "weather"), + ModuleEntry("com.heytap.browser", "browser"), + ModuleEntry("com.oplus.securitypermission", "securitypermission"), + ModuleEntry("com.heytap.themestore", "themestore"), + ) + + /** + * 所有功能页面的详细定义。 + * Key: 页面路由ID (与 ModuleEntry.routeId 对应) + * Value: 页面的完整定义 + */ + val screenMap: Map = mapOf( + "android" to android.definition, + "android\\package_manager_services" to PMS.definition, + "android\\oplus_system_services" to OplusServices.definition, + "android\\split_screen_multi_window" to SplitScreenMultiWindow.definition, + + "systemui" to systemui.definition, + "systemui\\controlCenter" to ControlCenter.definition, + "systemui\\status_bar\\hardware_indicator" to HardwareIndicator.definition, + "systemui\\notification" to notification.definition, + "systemui\\status_bar\\status_bar_clock" to StatusBarClock.definition, + "systemui\\status_bar\\status_bar_wifi" to StatusBarWifi.definition, + "systemui\\status_bar" to StatusBar.definition, + + "incallui" to incallui.definition, + + "settings" to settings.definition, + + "phone" to phone.definition, + + "mms" to mms.definition, + + "launcher" to launcher.definition, + "launcher\\recent_task" to RecentTask.definition, + + "ocrscanner" to ocrscanner.definition, + + "oshare" to oshare.definition, + + "phonemanager" to phonemanager.definition, + + "securepay" to securepay.definition, + + "wallet" to wallet.definition, + + "health" to health.definition, + + "quicksearchbox" to quicksearchbox.definition, + + "speechassist" to speechassist.definition, + + "mihealth" to mihealth.definition, + + "appdetail" to appdetail.definition, + + "battery" to battery.definition, + + "exsystemservice" to exsystemservice.definition, + + "games" to games.definition, + + "notificationmanager" to notificationmanager.definition, + + "ota" to ota.definition, + + "padconnect" to padconnect.definition, + + "oplusphonemanager" to oplusphonemanager.definition, + + "weather" to weather.definition, + + "browser" to browser.definition, + + "securitypermission" to securitypermission.definition, + + "themestore" to themestore.definition, + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/android/OplusServices.kt b/app/src/main/java/com/suqi8/oshin/features/android/OplusServices.kt new file mode 100644 index 00000000..d232ee52 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/android/OplusServices.kt @@ -0,0 +1,26 @@ +package com.suqi8.oshin.features.android + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object OplusServices { + val definition = PageDefinition( + category = "android\\oplus_system_services", + appList = listOf("android"), + title = StringResource(R.string.oplus_system_services), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.oplus_root_check), + summary = R.string.oplus_root_check_summary, + key = "disable_root_check" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/android/PMS.kt b/app/src/main/java/com/suqi8/oshin/features/android/PMS.kt new file mode 100644 index 00000000..f2ac3cb7 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/android/PMS.kt @@ -0,0 +1,93 @@ +package com.suqi8.oshin.features.android + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object PMS { + val definition = PageDefinition( + category = "android\\package_manager_services", + appList = listOf("android"), + title = StringResource(R.string.package_manager_services), + // 2. 定义页面的卡片列表 + items = listOf( + CardDefinition( + titleRes = R.string.common_settings, + items = listOf( + Switch( + title = StringResource(R.string.allow_downgrade_title), + summary = R.string.allow_downgrade_summary, + key = "allow_downgrade" + ), + Switch( + title = StringResource(R.string.allow_signature_mismatch_on_update_title), + summary = R.string.allow_signature_mismatch_on_update_summary, + key = "allow_signature_mismatch_on_update" + ) + ) + ), + CardDefinition( + titleRes = R.string.verification_bypass_settings, + items = listOf( + Switch( + title = StringResource(R.string.disable_jar_verifier_title), + summary = R.string.disable_jar_verifier_summary, + key = "disable_jar_verifier" + ), + Switch( + title = StringResource(R.string.disable_message_digest_title), + summary = R.string.disable_message_digest_summary, + key = "disable_message_digest" + ), + Switch( + title = StringResource(R.string.bypass_arsc_uncompressed_check_title), + summary = R.string.bypass_arsc_uncompressed_check_summary, + key = "bypass_arsc_uncompressed_check" + ), + Switch( + title = StringResource(R.string.bypass_min_signature_version_check_title), + summary = R.string.bypass_min_signature_version_check_summary, + key = "bypass_min_signature_version_check" + ), + Switch( + title = StringResource(R.string.bypass_v1_signature_errors_title), + summary = R.string.bypass_v1_signature_errors_summary, + key = "bypass_v1_signature_errors" + ), + Switch( + title = StringResource(R.string.allow_mismatched_split_apk_signatures_title), + summary = R.string.allow_mismatched_split_apk_signatures_summary, + key = "allow_mismatched_split_apk_signatures" + ), + Switch( + title = StringResource(R.string.disable_install_verification_title), + summary = R.string.disable_install_verification_summary, + key = "disable_install_verification" + ) + ) + ), + CardDefinition( + titleRes = R.string.advanced_settings, + items = listOf( + Switch( + title = StringResource(R.string.allow_system_app_hidden_api_title), + summary = R.string.allow_system_app_hidden_api_summary, + key = "allow_system_app_hidden_api" + ), + Switch( + title = StringResource(R.string.allow_nonsystem_shared_uid_title), + summary = R.string.allow_nonsystem_shared_uid_summary, + key = "allow_nonsystem_shared_uid" + ), + Switch( + title = StringResource(R.string.pms_command_title), + summary = R.string.pms_command_summary, + key = "pms_command" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/android/SplitScreenMultiWindow.kt b/app/src/main/java/com/suqi8/oshin/features/android/SplitScreenMultiWindow.kt new file mode 100644 index 00000000..19de5486 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/android/SplitScreenMultiWindow.kt @@ -0,0 +1,65 @@ +package com.suqi8.oshin.features.android + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.Slider +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object SplitScreenMultiWindow { + val definition = PageDefinition( + category = "android\\split_screen_multi_window", + appList = listOf("android"), + title = StringResource(R.string.split_screen_multi_window), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.remove_all_small_window_restrictions), + key = "remove_all_small_window_restrictions" + ), + Switch( + title = StringResource(R.string.force_multi_window_mode), + key = "force_multi_window_mode" + ), + Slider( + title = StringResource(R.string.max_simultaneous_small_windows), + summary = R.string.default_value_hint_negative_one, + key = "max_simultaneous_small_windows", + defaultValue = -1f, + valueRange = -1f..30f, + decimalPlaces = 0 + ), + Slider( + title = StringResource(R.string.small_window_corner_radius), + summary = R.string.default_value_hint_negative_one, + key = "small_window_corner_radius", + defaultValue = -1f, + unit = "px", + valueRange = -1f..300f, + decimalPlaces = 0 + ), + Slider( + title = StringResource(R.string.small_window_focused_shadow), + summary = R.string.default_value_hint_negative_one, + key = "small_window_focused_shadow", + defaultValue = -1f, + unit = "px", + valueRange = -1f..300f, + decimalPlaces = 0 + ), + Slider( + key = "small_window_unfocused_shadow", + title = StringResource(R.string.small_window_unfocused_shadow), + summary = R.string.default_value_hint_negative_one, + defaultValue = -1f, + valueRange = -1f..300f, + unit = "px", + decimalPlaces = 0 + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/android/android.kt b/app/src/main/java/com/suqi8/oshin/features/android/android.kt new file mode 100644 index 00000000..af7046fa --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/android/android.kt @@ -0,0 +1,65 @@ +package com.suqi8.oshin.features.android + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.Action +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.RelatedLinks +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object android { + val definition = PageDefinition( + // 1. 定义整个页面的共享信息 + category = "android", // 所有 Switch 项将使用这个 category + appList = listOf("android"), + title = AppName("android"), + // 2. 定义页面的卡片列表 + items = listOf( + // --- 第一个 Card --- + CardDefinition( + // 这个卡片前没有 SmallTitle,所以 titleRes 为 null + items = listOf( + // 对应第一个 FunArrow + Action( + title = StringResource(R.string.package_manager_services), + route = "android\\package_manager_services" // 点击后跳转的页面路由ID + ), + // 对应第二个 FunArrow + Action( + title = StringResource(R.string.oplus_system_services), + route = "android\\oplus_system_services" + ), + // 对应第三个 FunArrow + Action( + title = StringResource(R.string.split_screen_multi_window), + route = "android\\split_screen_multi_window" + ) + ) + ), + + // --- 第二个 Card --- + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.disable_72h_verify), + key = "DisablePinVerifyPer72h" + ), + Switch( + title = StringResource(R.string.allow_untrusted_touch), + key = "AllowUntrustedTouch" + ) + ) + ), + RelatedLinks( + links = listOf( + RelatedLinks.Link( + titleRes = R.string.allow_turn_off_all_categories, + route = "notificationmanager" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/appdetail/appdetail.kt b/app/src/main/java/com/suqi8/oshin/features/appdetail/appdetail.kt new file mode 100644 index 00000000..f988da31 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/appdetail/appdetail.kt @@ -0,0 +1,42 @@ +package com.suqi8.oshin.features.appdetail + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object appdetail { + val definition = PageDefinition( + category = "appdetail", + appList = listOf("com.oplus.appdetail"), + title = AppName("com.oplus.appdetail"), + items = listOf( + CardDefinition( + items = listOf( + /*Switch( + title = StringResource(R.string.remove_recommendations), + key = "remove_recommendations" + ), + Switch( + title = StringResource(R.string.remove_installation_frequency_popup), + key = "remove_installation_frequency_popup" + ), + Switch( + title = StringResource(R.string.remove_attempt_installation_popup), + key = "remove_attempt_installation_popup" + ),*/ + Switch( + title = StringResource(R.string.remove_version_check), + key = "remove_version_check" + ), + Switch( + title = StringResource(R.string.remove_security_check), + key = "remove_security_check" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/battery/battery.kt b/app/src/main/java/com/suqi8/oshin/features/battery/battery.kt new file mode 100644 index 00000000..0dfe12cb --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/battery/battery.kt @@ -0,0 +1,39 @@ +package com.suqi8.oshin.features.battery + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.Slider +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object battery { + val definition = PageDefinition( + category = "battery", + appList = listOf("com.oplus.battery"), + title = AppName("com.oplus.battery"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.low_battery_fluid_cloud_off), + key = "low_battery_fluid_cloud" + ) + ) + ), + CardDefinition( + items = listOf( + Slider( + title = StringResource(R.string.auto_start_max_limit), + summary = R.string.auto_start_default_hint, + key = "auto_start_max_limit", + defaultValue = 5f, + valueRange = 0f..100f, + decimalPlaces = 0 + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/browser/browser.kt b/app/src/main/java/com/suqi8/oshin/features/browser/browser.kt new file mode 100644 index 00000000..ddc48f2a --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/browser/browser.kt @@ -0,0 +1,32 @@ +package com.suqi8.oshin.features.browser + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object browser { + val definition = PageDefinition( + // 1. 定义整个页面的共享信息 + category = "browser", // 所有 Switch 项将使用这个 category + appList = listOf("com.heytap.browser"), + title = AppName("com.heytap.browser"), + items = listOf( + CardDefinition( + titleRes = R.string.weather_detail, + items = listOf( + Switch( + title = StringResource(R.string.remove_weather_injected_ads), + key = "remove_weather_injected_ads", + ), + Switch( + title = StringResource(R.string.remove_weather_search_box), + key = "remove_weather_search_box", + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/exsystemservice/exsystemservice.kt b/app/src/main/java/com/suqi8/oshin/features/exsystemservice/exsystemservice.kt new file mode 100644 index 00000000..f632230e --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/exsystemservice/exsystemservice.kt @@ -0,0 +1,26 @@ +package com.suqi8.oshin.features.exsystemservice + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object exsystemservice { + val definition = PageDefinition( + category = "exsystemservice", + appList = listOf("com.oplus.exsystemservice"), + title = AppName("com.oplus.exsystemservice"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.remove_system_tamper_warning), + key = "remove_system_tamper_warning" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/games/games.kt b/app/src/main/java/com/suqi8/oshin/features/games/games.kt new file mode 100644 index 00000000..98f54318 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/games/games.kt @@ -0,0 +1,69 @@ +package com.suqi8.oshin.features.games + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object games { + val definition = PageDefinition( + category = "games", + appList = listOf("com.oplus.games"), + title = AppName("com.oplus.games"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.enable_ultra_combo), + key = "ultra_combo" + ), + Switch( + title = StringResource(R.string.feature_disable_cloud_control), + key = "feature_disable_cloud_control" + ), + Switch( + title = StringResource(R.string.remove_package_restriction), + key = "remove_package_restriction" + ), + Switch( + title = StringResource(R.string.enable_all_features), + summary = R.string.enable_all_features_warning, + key = "enable_all_features" + ), + Switch( + title = StringResource(R.string.remove_game_filter_root_detection), + key = "remove_game_filter_root_detection" + ), + Switch( + title = StringResource(R.string.enable_mlbb_ai_god_assist), + key = "enable_mlbb_ai_god_assist" + ), + Switch( + title = StringResource(R.string.enable_pubg_ai), + key = "pubg_ai" + ) + ) + ), + CardDefinition( + titleRes = R.string.hok, + items = listOf( + Switch( + title = StringResource(R.string.enable_hok_ai_v1), + key = "hok_ai_v1" + ), + Switch( + title = StringResource(R.string.enable_hok_ai_v2), + summary = R.string.realme_gt7pro_feature_unlock_device_restriction, + key = "hok_ai_v2" + ), + Switch( + title = StringResource(R.string.enable_hok_ai_v3), + key = "hok_ai_v3" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/health/health.kt b/app/src/main/java/com/suqi8/oshin/features/health/health.kt new file mode 100644 index 00000000..08546dc5 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/health/health.kt @@ -0,0 +1,26 @@ +package com.suqi8.oshin.features.health + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object health { + val definition = PageDefinition( + category = "health", + appList = listOf("com.heytap.health"), + title = AppName("com.heytap.health"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.disable_root_dialog), + key = "disable_root_dialog" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/incallui/incallui.kt b/app/src/main/java/com/suqi8/oshin/features/incallui/incallui.kt new file mode 100644 index 00000000..4f9df537 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/incallui/incallui.kt @@ -0,0 +1,26 @@ +package com.suqi8.oshin.features.incallui + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object incallui { + val definition = PageDefinition( + category = "incallui", + appList = listOf("com.android.incallui"), + title = AppName("com.android.incallui"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.hide_call_ringtone), + key = "hide_call_ringtone", + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/launcher/RecentTask.kt b/app/src/main/java/com/suqi8/oshin/features/launcher/RecentTask.kt new file mode 100644 index 00000000..45319b2a --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/launcher/RecentTask.kt @@ -0,0 +1,26 @@ +package com.suqi8.oshin.features.launcher + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object RecentTask { + val definition = PageDefinition( + category = "launcher\\recent_task", + appList = listOf("com.android.launcher"), + title = StringResource(R.string.recent_tasks), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.force_display_memory), + key = "force_display_memory", + defaultValue = false + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/launcher/launcher.kt b/app/src/main/java/com/suqi8/oshin/features/launcher/launcher.kt new file mode 100644 index 00000000..576cfd86 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/launcher/launcher.kt @@ -0,0 +1,95 @@ +package com.suqi8.oshin.features.launcher + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.Action +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.Dropdown +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.SimpleCondition +import com.suqi8.oshin.models.Slider +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object launcher { + val definition = PageDefinition( + category = "launcher", + appList = listOf("com.android.launcher"), + title = AppName("com.android.launcher"), + items = listOf( + CardDefinition( + items = listOf( + Action( + title = StringResource(id = R.string.recent_tasks), + route = "launcher\\recent_task" + ) + ) + ), + CardDefinition( + titleRes = R.string.desktop_settings, + items = listOf( + Switch( + title = StringResource(R.string.force_enable_fold_mode), + key = "force_enable_fold_mode" + ), + Dropdown( + title = StringResource(R.string.fold_mode), + key = "fold_mode", + optionsRes = R.array.fold_mode, + condition = SimpleCondition( + dependencyKey = "force_enable_fold_mode" + ) + ) + ) + ), + CardDefinition( + titleRes = R.string.dock_settings, + items = listOf( + Switch( + title = StringResource(R.string.force_enable_fold_dock), + key = "force_enable_fold_dock" + ), + Slider( + title = StringResource(R.string.adjust_dock_transparency), + key = "dock_transparency", + defaultValue = 1f, + unit = "f", + valueRange = 0f..10f, + decimalPlaces = 2 + ), + Switch( + title = StringResource(R.string.force_enable_dock_blur), + summary = R.string.force_enable_dock_blur_undevice, + key = "force_enable_dock_blur" + ) + ) + ), + CardDefinition( + titleRes = R.string.animation_and_layout_settings, + items = listOf( + Slider( + title = StringResource(R.string.set_anim_level), + key = "set_anim_level", + valueRange = -1f..4f, + defaultValue = -1f, + decimalPlaces = 0 + ), + Switch( + title = StringResource(R.string.add_more_desktop_layouts), + summary = R.string.add_more_desktop_layouts_desc, + key = "add_more_desktop_layouts" + ), + Slider( + title = StringResource(R.string.custom_blur_corner), + summary = R.string.default_value_hint_negative_one, + key = "custom_blur_corner", + valueRange = -1f..100f, + defaultValue = -1f, + unit = "dp", + decimalPlaces = 1 + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/mihealth/mihealth.kt b/app/src/main/java/com/suqi8/oshin/features/mihealth/mihealth.kt new file mode 100644 index 00000000..a54a2281 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/mihealth/mihealth.kt @@ -0,0 +1,27 @@ +package com.suqi8.oshin.features.mihealth + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object mihealth { + val definition = PageDefinition( + category = "mihealth", + appList = listOf("com.mi.health"), + title = AppName("com.mi.health"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.enable_alarm_reminder), + summary = R.string.alarm_reminder_description, + key = "enable_alarm_reminder" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/mms/mms.kt b/app/src/main/java/com/suqi8/oshin/features/mms/mms.kt new file mode 100644 index 00000000..af8e127e --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/mms/mms.kt @@ -0,0 +1,26 @@ +package com.suqi8.oshin.features.mms + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object mms { + val definition = PageDefinition( + category = "mms", + appList = listOf("com.android.mms"), + title = AppName("com.android.mms"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.remove_message_ads), + key = "remove_message_ads", + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/notificationmanager/notificationmanager.kt b/app/src/main/java/com/suqi8/oshin/features/notificationmanager/notificationmanager.kt new file mode 100644 index 00000000..45b46498 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/notificationmanager/notificationmanager.kt @@ -0,0 +1,27 @@ +package com.suqi8.oshin.features.notificationmanager + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object notificationmanager { + val definition = PageDefinition( + category = "notificationmanager", + appList = listOf("com.oplus.notificationmanager"), + title = AppName("com.oplus.notificationmanager"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.allow_turn_off_all_categories), + summary = R.string.enable_all_category_control_summary, + key = "allow_turn_off_all_categories" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/ocrscanner/ocrscanner.kt b/app/src/main/java/com/suqi8/oshin/features/ocrscanner/ocrscanner.kt new file mode 100644 index 00000000..f6ca7cda --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/ocrscanner/ocrscanner.kt @@ -0,0 +1,26 @@ +package com.suqi8.oshin.features.ocrscanner + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object ocrscanner { + val definition = PageDefinition( + category = "ocrscanner", + appList = listOf("com.coloros.ocrscanner"), + title = AppName("com.coloros.ocrscanner"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.remove_full_screen_translation_restriction), + key = "full_screen_translation" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/oplusphonemanager/oplusphonemanager.kt b/app/src/main/java/com/suqi8/oshin/features/oplusphonemanager/oplusphonemanager.kt new file mode 100644 index 00000000..82e3c5e0 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/oplusphonemanager/oplusphonemanager.kt @@ -0,0 +1,52 @@ +package com.suqi8.oshin.features.oplusphonemanager + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.Slider +import com.suqi8.oshin.models.StringInput +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object oplusphonemanager { + val definition = PageDefinition( + category = "oplusphonemanager", + appList = listOf("com.oplus.phonemanager"), + title = AppName("com.oplus.phonemanager"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.remove_all_popup_delays), + summary = R.string.remove_all_popup_delays_eg, + key = "remove_all_popup_delays" + ), + Slider( + title = StringResource(R.string.custom_score), + summary = R.string.default_value_hint_negative_one, + key = "custom_score", + defaultValue = -1f, + valueRange = -1f..100f, + decimalPlaces = 0 + ), + StringInput( + title = StringResource(R.string.custom_prompt_content), + key = "custom_prompt_content", + defaultValue = "", + nullable = true + ), + Slider( + title = StringResource(R.string.custom_animation_duration), + summary = R.string.default_value_hint_negative_one, + key = "custom_animation_duration", + defaultValue = -1f, + unit = "ms", + valueRange = -1f..10000f, + decimalPlaces = 0 + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/oshare/oshare.kt b/app/src/main/java/com/suqi8/oshin/features/oshare/oshare.kt new file mode 100644 index 00000000..3e23f2ca --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/oshare/oshare.kt @@ -0,0 +1,26 @@ +package com.suqi8.oshin.features.oshare + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object oshare { + val definition = PageDefinition( + category = "oshare", + appList = listOf("com.coloros.oshare"), + title = AppName("com.coloros.oshare"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.remove_oshare_auto_off), + key = "remove_oshare_auto_off" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/ota/ota.kt b/app/src/main/java/com/suqi8/oshin/features/ota/ota.kt new file mode 100644 index 00000000..91166238 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/ota/ota.kt @@ -0,0 +1,52 @@ +package com.suqi8.oshin.features.ota + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object ota { + val definition = PageDefinition( + category = "ota", + appList = listOf("com.oplus.ota"), + title = AppName("com.oplus.ota"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.remove_system_update_dialog), + key = "remove_system_update_dialog" + ), + Switch( + title = StringResource(R.string.remove_system_update_notification), + key = "remove_system_update_notification" + ), + Switch( + title = StringResource(R.string.remove_wlan_auto_download_dialog), + key = "remove_wlan_auto_download_dialog" + ), + Switch( + title = StringResource(R.string.remove_unlock_and_dmverity_check), + key = "remove_unlock_and_dmverity_check" + ), + Switch( + title = StringResource(R.string.bypass_preinstall_checks), + summary = R.string.bypass_preinstall_checks_summary, + key = "bypass_preinstall_checks" + ), + Switch( + title = StringResource(R.string.force_show_local_install), + key = "force_show_local_install" + ), + Switch( + title = StringResource(R.string.force_download_last_update_package), + summary = R.string.force_download_last_update_package_summary, + key = "force_download_last_update_package" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/padconnect/padconnect.kt b/app/src/main/java/com/suqi8/oshin/features/padconnect/padconnect.kt new file mode 100644 index 00000000..7e805be6 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/padconnect/padconnect.kt @@ -0,0 +1,27 @@ +package com.suqi8.oshin.features.padconnect + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object padconnect { + val definition = PageDefinition( + category = "padconnect", + appList = listOf("com.oplus.padconnect"), + title = AppName("com.oplus.padconnect"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.bypass_same_account_unlock_safety_check), + summary = R.string.bypass_same_account_unlock_safety_check_summary, + key = "bypass_same_account_unlock_safety_check" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/phone/phone.kt b/app/src/main/java/com/suqi8/oshin/features/phone/phone.kt new file mode 100644 index 00000000..a7392e87 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/phone/phone.kt @@ -0,0 +1,68 @@ +package com.suqi8.oshin.features.phone + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.SimpleCondition +import com.suqi8.oshin.models.StringInput +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object phone { + val definition = PageDefinition( + category = "phone", + appList = listOf("com.android.phone"), + title = AppName("com.android.phone"), + // 2. 定义页面的卡片列表 + items = listOf( + // --- 第一个 Card --- + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.sms_verification_code), + key = "sms_verification_code" + ) + ) + ), + CardDefinition( + items = listOf( + StringInput( + title = StringResource(R.string.sms_code_keyword), + key = "SMSCodeRule", + defaultValue = "验证码|校验码|检验码|确认码|激活码|动态码|安全码|验证代码|校验代码|检验代码|激活代码|确认代码|动态代码|安全代码|登入码|认证码|识别码|短信口令|动态密码|交易码|上网密码|随机码|动态口令|驗證碼|校驗碼|檢驗碼|確認碼|激活碼|動態碼|驗證代碼|校驗代碼|檢驗代碼|確認代碼|激活代碼|動態代碼|登入碼|認證碼|識別碼|Code|code|CODE|Код|код|КОД|Пароль|пароль|ПАРОЛЬ|Kod|kod|KOD|Ma|Mã|OTP" + ), + Switch( + title = StringResource(R.string.show_verification_toast), + key = "showCodeToast" + ), + Switch( + title = StringResource(R.string.show_verification_notification), + key = "showCodeNotification" + ), + Switch( + title = StringResource(R.string.copy_verification_to_clipboard), + key = "copyCode" + ), + Switch( + title = StringResource(R.string.auto_input_verification_code), + key = "inputCode", + defaultValue = true + ) + ), + condition = SimpleCondition( + dependencyKey = "sms_verification_code" + ) + ), + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.remove_sim_name_length_limit), + summary = R.string.remove_sim_name_length_limit_summary, + key = "remove_sim_name_length_limit" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/phonemanager/phonemanager.kt b/app/src/main/java/com/suqi8/oshin/features/phonemanager/phonemanager.kt new file mode 100644 index 00000000..21341b9e --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/phonemanager/phonemanager.kt @@ -0,0 +1,52 @@ +package com.suqi8.oshin.features.phonemanager + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.Slider +import com.suqi8.oshin.models.StringInput +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object phonemanager { + val definition = PageDefinition( + category = "phonemanager", + appList = listOf("com.coloros.phonemanager"), + title = AppName("com.coloros.phonemanager"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.remove_all_popup_delays), + summary = R.string.remove_all_popup_delays_eg, + key = "remove_all_popup_delays" + ), + Slider( + title = StringResource(R.string.custom_score), + summary = R.string.default_value_hint_negative_one, + key = "custom_score", + defaultValue = -1f, + valueRange = -1f..100f, + decimalPlaces = 0 + ), + StringInput( + title = StringResource(R.string.custom_prompt_content), + key = "custom_prompt_content", + defaultValue = "", + nullable = true + ), + Slider( + title = StringResource(R.string.custom_animation_duration), + summary = R.string.default_value_hint_negative_one, + key = "custom_animation_duration", + defaultValue = -1f, + unit = "ms", + valueRange = -1f..10000f, + decimalPlaces = 0 + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/quicksearchbox/quicksearchbox.kt b/app/src/main/java/com/suqi8/oshin/features/quicksearchbox/quicksearchbox.kt new file mode 100644 index 00000000..9dc0f30e --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/quicksearchbox/quicksearchbox.kt @@ -0,0 +1,26 @@ +package com.suqi8.oshin.features.quicksearchbox + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object quicksearchbox { + val definition = PageDefinition( + category = "quicksearchbox", + appList = listOf("com.heytap.quicksearchbox"), + title = AppName("com.heytap.quicksearchbox"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.remove_app_recommendation_ads), + key = "remove_app_recommendation_ads" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/securepay/securepay.kt b/app/src/main/java/com/suqi8/oshin/features/securepay/securepay.kt new file mode 100644 index 00000000..0fabb411 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/securepay/securepay.kt @@ -0,0 +1,26 @@ +package com.suqi8.oshin.features.securepay + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object securepay { + val definition = PageDefinition( + category = "securepay", + appList = listOf("com.coloros.securepay"), + title = AppName("com.coloros.securepay"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.security_payment_remove_risky_fluid_cloud), + key = "security_payment_remove_risky_fluid_cloud" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/securitypermission/securitypermission.kt b/app/src/main/java/com/suqi8/oshin/features/securitypermission/securitypermission.kt new file mode 100644 index 00000000..213ed7ab --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/securitypermission/securitypermission.kt @@ -0,0 +1,31 @@ +package com.suqi8.oshin.features.securitypermission + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object securitypermission { + val definition = PageDefinition( + category = "securitypermission", + appList = listOf("com.oplus.securitypermission"), + title = AppName("com.oplus.securitypermission"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.app_start_dialog_legacy_mode_title), + summary = R.string.app_start_dialog_legacy_mode_summary, + key = "app_start_dialog_legacy_mode" + ), + Switch( + title = StringResource(R.string.app_start_dialog_always_allow), + key = "app_start_dialog_always_allow" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/settings/settings.kt b/app/src/main/java/com/suqi8/oshin/features/settings/settings.kt new file mode 100644 index 00000000..21f4e62e --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/settings/settings.kt @@ -0,0 +1,129 @@ +package com.suqi8.oshin.features.settings + +import android.os.Environment +import com.suqi8.oshin.R +import com.suqi8.oshin.models.Action +import com.suqi8.oshin.models.AndCondition +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.AppSelection +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.Picture +import com.suqi8.oshin.models.RelatedLinks +import com.suqi8.oshin.models.SimpleCondition +import com.suqi8.oshin.models.Slider +import com.suqi8.oshin.models.StringInput +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object settings { + val definition = PageDefinition( + title = AppName("com.android.settings"), + category = "settings", + appList = listOf("com.android.settings"), + items = listOf( + CardDefinition( + items = listOf( + Action( + title = StringResource(R.string.oplus_settings_features), + route = "settings\\oplus_settings" + ) + ) + ), + CardDefinition( + items = listOf( + StringInput( + key = "custom_display_model", + title = StringResource(R.string.custom_display_model), + summary = R.string.hint_empty_content_default, + nullable = true + ), + Switch( + key = "enable_ota_card_bg", + title = StringResource(R.string.enable_ota_card_bg) + ), + // --- OTA 卡片背景设置 (仅在 ota_card_bg 开启时显示) --- + Picture( + key = "ota_card_bg_image", + title = StringResource(R.string.select_background_btn), + targetPath = "${Environment.getExternalStorageDirectory()}/.OShin/settings/ota_card.png", + condition = SimpleCondition("enable_ota_card_bg", requiredValue = true) + ), + Slider( + key = "ota_corner_radius", + title = StringResource(R.string.corner_radius_title), + valueRange = 0f..300f, unit = "px", decimalPlaces = 1, + condition = SimpleCondition("enable_ota_card_bg", requiredValue = true) + ), + Switch( + key = "force_show_nfc_security_chip", + title = StringResource(R.string.force_show_nfc_security_chip), + summary = R.string.confirm_privacy_password_is_not_set + ) + ) + ), + + // --- 第二个设置卡片 (无障碍相关) --- + CardDefinition( + items = listOf( + // “授予”开关:!jump && !autoauth + Switch( + key = "auth", + title = StringResource(R.string.accessibility_service_authorize), + condition = AndCondition( + listOf( + SimpleCondition("jump", requiredValue = false), + SimpleCondition("autoauth", requiredValue = false) + ) + ) + ), + // “直接跳转”开关:!auth && !autoauth + Switch( + key = "jump", + title = StringResource(R.string.accessibility_service_direct), + condition = AndCondition( + listOf( + SimpleCondition("auth", requiredValue = false), + SimpleCondition("autoauth", requiredValue = false) + ) + ) + ), + // “智能授权”开关:!auth && !jump + Switch( + key = "autoauth", + title = StringResource(R.string.smart_accessibility_service), + summary = R.string.whitelist_app_auto_authorization, + condition = AndCondition( + listOf( + SimpleCondition("auth", requiredValue = false), + SimpleCondition("jump", requiredValue = false) + ) + ) + ), + // “白名单”应用选择器:autoauth && !auth && !jump + AppSelection( + key = "autoauthwhite", + title = StringResource(R.string.accessibility_whitelist), + condition = AndCondition( + listOf( + SimpleCondition("autoauth", requiredValue = true), + SimpleCondition("auth", requiredValue = false), + SimpleCondition("jump", requiredValue = false) + ) + ) + ) + ) + ), + + // --- 独立的 WantFind/RelatedLinks 卡片 --- + RelatedLinks( + links = listOf( + RelatedLinks.Link( + titleRes = R.string.auto_start_max_limit, + route = "battery" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/speechassist/speechassist.kt b/app/src/main/java/com/suqi8/oshin/features/speechassist/speechassist.kt new file mode 100644 index 00000000..779f6fea --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/speechassist/speechassist.kt @@ -0,0 +1,26 @@ +package com.suqi8.oshin.features.speechassist + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object speechassist { + val definition = PageDefinition( + category = "speechassist", + appList = listOf("com.heytap.speechassist"), + title = AppName("com.heytap.speechassist"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.force_enable_xiaobu_call), + key = "ai_call" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/systemui/ControlCenter.kt b/app/src/main/java/com/suqi8/oshin/features/systemui/ControlCenter.kt new file mode 100644 index 00000000..0a5b5798 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/systemui/ControlCenter.kt @@ -0,0 +1,39 @@ +package com.suqi8.oshin.features.systemui + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AndCondition +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.Condition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.SimpleCondition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object ControlCenter { + val definition = PageDefinition( + category = "systemui\\controlCenter", + appList = listOf("com.android.systemui"), + title = StringResource(R.string.control_center), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.enlarge_media_cover), + summary = R.string.media_cover_background_description, + key = "enlarge_media_cover", + defaultValue = false, + ), + Switch( + title = StringResource(R.string.qs_media_auto_color_label), + key = "qs_media_auto_color_label", + defaultValue = true, + condition = SimpleCondition( + dependencyKey = "enlarge_media_cover", + requiredValue = true + ) + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/systemui/HardwareIndicator.kt b/app/src/main/java/com/suqi8/oshin/features/systemui/HardwareIndicator.kt new file mode 100644 index 00000000..78be62db --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/systemui/HardwareIndicator.kt @@ -0,0 +1,156 @@ +package com.suqi8.oshin.features.systemui + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.Dropdown +import com.suqi8.oshin.models.NoEnable +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.SimpleCondition +import com.suqi8.oshin.models.Slider +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object HardwareIndicator { + val definition = PageDefinition( + title = StringResource(R.string.hardware_indicator), + category = "systemui\\status_bar\\hardware_indicator", + appList = listOf("com.android.systemui"), + items = listOf( + // --- 电量消耗指示器 --- + CardDefinition(items = listOf( + Switch(key = "power_indicator_enabled", title = StringResource(R.string.power_consumption_indicator)) + )), + NoEnable(condition = SimpleCondition("power_indicator_enabled", requiredValue = false)), + + // --- 电量指示器设置 (显示内容) --- + CardDefinition( + titleRes = R.string.display_content, + condition = SimpleCondition("power_indicator_enabled", requiredValue = true), + items = listOf( + Switch(key = "power_indicator_dual_row", title = StringResource(R.string.dual_row_title)), + Dropdown( + key = "power_indicator_line1_content", + title = StringResource(R.string.first_line_content), + optionsRes = R.array.hardware_indicator_display_options + ), + Dropdown( + key = "power_indicator_line2_content", + title = StringResource(R.string.second_line_content), + optionsRes = R.array.hardware_indicator_display_options, + condition = SimpleCondition("power_indicator_dual_row", requiredValue = true) + ) + ) + ), + // --- 电量指示器设置 (外观和更新) --- + CardDefinition( + titleRes = R.string.appearance_and_update, + condition = SimpleCondition("power_indicator_enabled", requiredValue = true), + items = listOf( + Switch(key = "power_indicator_bold", title = StringResource(R.string.bold_text)), + Dropdown( + key = "power_indicator_alignment", + title = StringResource(R.string.alignment), + optionsRes = R.array.hardware_indicator_gravity_options + ), + Slider( + key = "power_indicator_update_interval", + title = StringResource(R.string.update_time), + defaultValue = 1000f, valueRange = 0f..2000f, unit = "ms", decimalPlaces = 0 + ), + Slider( + key = "power_indicator_font_size", + title = StringResource(R.string.font_size), + defaultValue = 8f, valueRange = 0f..20f, unit = "sp", decimalPlaces = 1 + ) + ) + ), + + // --- 温度指示器 --- + CardDefinition(items = listOf( + Switch(key = "temp_indicator_enabled", title = StringResource(R.string.temperature_indicator)) + )), + NoEnable(condition = SimpleCondition("temp_indicator_enabled", requiredValue = false)), + + // --- 温度指示器设置 (显示内容) --- + CardDefinition( + titleRes = R.string.display_content, + condition = SimpleCondition("temp_indicator_enabled", requiredValue = true), + items = listOf( + Switch(key = "temp_indicator_dual_row", title = StringResource(R.string.dual_row_title)), + Dropdown( + key = "temp_indicator_line1_content", + title = StringResource(R.string.first_line_content), + optionsRes = R.array.hardware_indicator_display_options + ), + Dropdown( + key = "temp_indicator_line2_content", + title = StringResource(R.string.second_line_content), + optionsRes = R.array.hardware_indicator_display_options, + condition = SimpleCondition("temp_indicator_dual_row", requiredValue = true) + ) + ) + ), + // --- 温度指示器设置 (外观和更新) --- + CardDefinition( + titleRes = R.string.appearance_and_update, + condition = SimpleCondition("temp_indicator_enabled", requiredValue = true), + items = listOf( + Switch(key = "temp_indicator_bold", title = StringResource(R.string.bold_text)), + Dropdown( + key = "temp_indicator_alignment", + title = StringResource(R.string.alignment), + optionsRes = R.array.hardware_indicator_gravity_options + ), + Slider( + key = "temp_indicator_update_interval", + title = StringResource(R.string.update_time), + defaultValue = 1000f, valueRange = 0f..2000f, unit = "ms", decimalPlaces = 0 + ), + Slider( + key = "temp_indicator_font_size", + title = StringResource(R.string.font_size), + defaultValue = 8f, valueRange = 0f..20f, unit = "sp", decimalPlaces = 1 + ) + ) + ), + + // --- 全局数据源设置 --- + CardDefinition( + titleRes = R.string.data_source_settings, + items = listOf( + Switch(key = "data_power_dual_cell", title = StringResource(R.string.dual_cell)), + Switch(key = "data_power_absolute_current", title = StringResource(R.string.absolute_value)), + Slider( + key = "data_temp_cpu_source", + title = StringResource(R.string.change_cpu_temp_source), + summary = R.string.enter_thermal_zone_number, + valueRange = 0f..100f, + decimalPlaces = 0 + ), + Slider( + key = "data_freq_cpu_source", + title = StringResource(R.string.change_cpu_freq_source), + summary = R.string.enter_cpu_core_number, + valueRange = 0f..15f, + decimalPlaces = 0 + ) + ) + ), + + // --- 全局单位设置 --- + CardDefinition( + titleRes = R.string.unit_display_settings, + items = listOf( + Switch(key = "unit_hide_power", title = StringResource(R.string.power)), + Switch(key = "unit_hide_current", title = StringResource(R.string.current)), + Switch(key = "unit_hide_voltage", title = StringResource(R.string.voltage)), + Switch(key = "unit_hide_temp_battery", title = StringResource(R.string.battery_temperature)), + Switch(key = "unit_hide_temp_cpu", title = StringResource(R.string.cpu_temperature)), + Switch(key = "unit_hide_cpu_frequency", title = StringResource(R.string.cpu_frequency)), + Switch(key = "unit_hide_cpu_usage", title = StringResource(R.string.cpu_usage)), + Switch(key = "unit_hide_ram_usage", title = StringResource(R.string.ram_usage)) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/systemui/StatusBar.kt b/app/src/main/java/com/suqi8/oshin/features/systemui/StatusBar.kt new file mode 100644 index 00000000..48d0428f --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/systemui/StatusBar.kt @@ -0,0 +1,52 @@ +package com.suqi8.oshin.features.systemui + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.Action +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object StatusBar { + val definition = PageDefinition( + category = "systemui\\status_bar", + appList = listOf("com.android.systemui"), + title = StringResource(R.string.status_bar), + items = listOf( + CardDefinition( + items = listOf( + Action( + title = StringResource(id = R.string.status_bar_clock), + route = "systemui\\status_bar\\status_bar_clock" + ), + Action( + title = StringResource(id = R.string.network_speed_indicator), + route = "systemui\\status_bar\\status_bar_wifi" + ), + Action( + title = StringResource(id = R.string.hardware_indicator), + route = "systemui\\status_bar\\hardware_indicator" + ), + Action( + title = StringResource(id = R.string.status_bar_layout), + route = "systemui\\status_bar\\StatusBarLayout" + ) + ) + ), + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.hide_status_bar), + key = "hide_status_bar", + defaultValue = false + ), + Switch( + title = StringResource(R.string.show_real_battery), + summary = R.string.show_real_battery_summary, + key = "show_real_battery" + ) + ) + ), + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/systemui/StatusBarClock.kt b/app/src/main/java/com/suqi8/oshin/features/systemui/StatusBarClock.kt new file mode 100644 index 00000000..c4dcbf81 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/systemui/StatusBarClock.kt @@ -0,0 +1,191 @@ +package com.suqi8.oshin.features.systemui + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AndCondition +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.Dropdown +import com.suqi8.oshin.models.NoEnable +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.SimpleCondition +import com.suqi8.oshin.models.Slider +import com.suqi8.oshin.models.StringInput +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch +import com.suqi8.oshin.models.UrlAction + +object StatusBarClock { + val definition = PageDefinition( + title = StringResource(R.string.status_bar_clock), + category = "systemui\\status_bar\\status_bar_clock", + appList = listOf("com.android.systemui"), + items = listOf( + // --- 主开关 --- + CardDefinition( + items = listOf( + Switch( + key = "status_bar_clock", + title = StringResource(R.string.status_bar_clock) + ) + ) + ), + NoEnable(condition = SimpleCondition("status_bar_clock", requiredValue = false)), + + // --- 主要设置 (仅在主开关开启时显示) --- + CardDefinition( + condition = SimpleCondition("status_bar_clock", requiredValue = true), + items = listOf( + Dropdown( + key = "ClockStyleSelectedOption", + title = StringResource(R.string.clock_style), + optionsRes = R.array.clock_style_options // "预设", "极客" + ), + Slider( + key = "ClockSize", + title = StringResource(R.string.clock_size), + summary = R.string.clock_size_summary, + valueRange = 0f..30f, unit = "dp", decimalPlaces = 1 + ), + Slider( + key = "ClockUpdateSpeed", + title = StringResource(R.string.clock_update_time_title), + summary = R.string.clock_update_time_summary, + defaultValue = -1f, valueRange = -1f..2000f, unit = "ms", decimalPlaces = 0 + ) + ) + ), + // --- 时钟边距 (仅在主开关开启时显示) --- + CardDefinition( + titleRes = R.string.clock_margin, + condition = SimpleCondition("status_bar_clock", requiredValue = true), + items = listOf( + Slider( + key = "TopPadding", + title = StringResource(R.string.clock_top_margin), + valueRange = 0f..30f, + unit = "dp", + decimalPlaces = 0 + ), + Slider( + key = "BottomPadding", + title = StringResource(R.string.clock_bottom_margin), + valueRange = 0f..30f, + unit = "dp", + decimalPlaces = 0 + ), + Slider( + key = "LeftPadding", + title = StringResource(R.string.clock_left_margin), + valueRange = 0f..30f, + unit = "dp", + decimalPlaces = 0 + ), + Slider( + key = "RightPadding", + title = StringResource(R.string.clock_right_margin), + valueRange = 0f..30f, + unit = "dp", + decimalPlaces = 0 + ) + ) + ), + + // --- “预设”风格选项 --- + CardDefinition( + condition = AndCondition( + listOf( // <-- 使用 AndCondition + SimpleCondition("status_bar_clock", requiredValue = true), + SimpleCondition("ClockStyleSelectedOption", requiredValue = 0) + ) + ), + items = listOf( + Switch( + title = StringResource(R.string.show_years_title), + summary = R.string.show_years_summary, + key = "ShowYears", + defaultValue = false + ), + Switch( + title = StringResource(R.string.show_month_title), + summary = R.string.show_month_summary, + key = "ShowMonth", + defaultValue = false + ), + Switch( + title = StringResource(R.string.show_day_title), + summary = R.string.show_day_summary, + key = "ShowDay", + defaultValue = false + ), + Switch( + title = StringResource(R.string.show_week_title), + summary = R.string.show_week_summary, + key = "ShowWeek", + defaultValue = false + ), + Switch( + title = StringResource(R.string.show_cn_hour_title), + summary = R.string.show_cn_hour_summary, + key = "ShowCNHour", + defaultValue = false + ), + Switch( + title = StringResource(R.string.showtime_period_title), + summary = R.string.showtime_period_summary, + key = "Showtime_period", + defaultValue = false + ), + Switch( + title = StringResource(R.string.show_seconds_title), + summary = R.string.show_seconds_summary, + key = "ShowSeconds", + defaultValue = true + ), + Switch( + title = StringResource(R.string.show_millisecond_title), + summary = R.string.show_millisecond_summary, + key = "ShowMillisecond", + defaultValue = false + ), + Switch( + title = StringResource(R.string.hide_space_title), + summary = R.string.hide_space_summary, + key = "HideSpace", + defaultValue = false + ), + Switch( + title = StringResource(R.string.dual_row_title), + summary = R.string.dual_row_summary, + key = "DualRow", + defaultValue = false + ) + ) + ), + + // --- “极客”风格选项 --- + CardDefinition( + condition = AndCondition( + listOf( // <-- 使用 AndCondition + SimpleCondition("status_bar_clock", requiredValue = true), + SimpleCondition("ClockStyleSelectedOption", requiredValue = 1) + ) + ), + items = listOf( + Dropdown( + key = "alignment", + title = StringResource(R.string.alignment), + optionsRes = R.array.hardware_indicator_gravity_options + ), + StringInput( + key = "CustomClockStyle", + title = StringResource(R.string.clock_format), + defaultValue = "HH:mm" + ), + UrlAction( + title = StringResource(R.string.clock_format_example), + url = "https://oshin.mikusignal.top/docs/timeformat.html" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/systemui/StatusBarWifi.kt b/app/src/main/java/com/suqi8/oshin/features/systemui/StatusBarWifi.kt new file mode 100644 index 00000000..abb14baf --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/systemui/StatusBarWifi.kt @@ -0,0 +1,253 @@ +package com.suqi8.oshin.features.systemui + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AndCondition +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.Dropdown +import com.suqi8.oshin.models.NoEnable +import com.suqi8.oshin.models.Operator +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.SimpleCondition +import com.suqi8.oshin.models.Slider +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +/** + * 状态栏网速指示器配置页面 + * + * 页面结构: + * 1. 主开关卡片 + * 2. 基础设置卡片 (样式、字体、对齐) + * 3. 字体大小设置卡片 + * 4. 显示控制卡片 (慢速隐藏、阈值) + * 5. 高级设置卡片 (箭头、单位、交换) + */ +object StatusBarWifi { + val definition = PageDefinition( + title = StringResource(R.string.network_speed_indicator), + category = "systemui\\status_bar\\status_bar_wifi", + appList = listOf("com.android.systemui"), + items = listOf( + // ==================== 1. 主开关卡片 ==================== + CardDefinition( + items = listOf( + Switch( + key = "status_bar_wifi", + title = StringResource(R.string.network_speed_indicator) + ) + ) + ), + + // 当主开关关闭时,禁用后续所有设置 + NoEnable(condition = SimpleCondition("status_bar_wifi", requiredValue = false)), + + // ==================== 2. 基础设置卡片 ==================== + CardDefinition( + titleRes = R.string.basic_settings, // 基础设置 + condition = SimpleCondition("status_bar_wifi", requiredValue = true), + items = listOf( + // 显示样式选择 + Dropdown( + key = "StyleSelectedOption", + title = StringResource(R.string.network_speed_style), + optionsRes = R.array.network_speed_style_options + // 选项: 0=默认(增强原生), 1=上下行分离(完全自定义) + ), + + // 使用系统字体开关 + Switch( + key = "use_system_font", + title = StringResource(R.string.use_system_font) + ), + + // 对齐方式选择 + Dropdown( + key = "alignment", + title = StringResource(R.string.network_speed_alignment), + optionsRes = R.array.network_speed_alignment_options + // 选项: 0=居中, 1=左对齐, 3=右对齐 + ) + ) + ), + + // ==================== 3. 字体大小设置卡片 ==================== + // 3.1 默认样式的字体设置 + CardDefinition( + titleRes = R.string.font_size_settings, // 字体大小设置 + condition = AndCondition(listOf( + SimpleCondition("status_bar_wifi", requiredValue = true), + SimpleCondition("StyleSelectedOption", requiredValue = 0), // 默认样式 + SimpleCondition("use_system_font", requiredValue = false) // 未使用系统字体 + )), + items = listOf( + // 速度数字字体大小 + Slider( + key = "speed_font_size", + title = StringResource(R.string.speed_font_size), + summary = R.string.default_value_hint_negative_one, + defaultValue = -1f, + valueRange = -1f..20f, + unit = "sp", + decimalPlaces = 0 + // -1 表示使用系统默认值 + ), + + // 单位字体大小 + Slider( + key = "unit_font_size", + title = StringResource(R.string.unit_font_size), + summary = R.string.default_value_hint_negative_one, + defaultValue = -1f, + valueRange = -1f..20f, + unit = "sp", + decimalPlaces = 0 + ) + ) + ), + + // 3.2 上下行分离样式的字体设置 + CardDefinition( + titleRes = R.string.font_size_settings, // 字体大小设置 + condition = AndCondition(listOf( + SimpleCondition("status_bar_wifi", requiredValue = true), + SimpleCondition("StyleSelectedOption", requiredValue = 1), // 上下行分离样式 + SimpleCondition("use_system_font", requiredValue = false) // 未使用系统字体 + )), + items = listOf( + // 上传速度字体大小 + Slider( + key = "upload_font_size", + title = StringResource(R.string.upload_font_size), + summary = R.string.default_value_hint_negative_one, + defaultValue = -1f, + valueRange = -1f..20f, + unit = "sp", + decimalPlaces = 0 + ), + + // 下载速度字体大小 + Slider( + key = "download_font_size", + title = StringResource(R.string.download_font_size), + summary = R.string.default_value_hint_negative_one, + defaultValue = -1f, + valueRange = -1f..20f, + unit = "sp", + decimalPlaces = 0 + ) + ) + ), + + // ==================== 4. 显示控制卡片 ==================== + CardDefinition( + titleRes = R.string.display_control, // 显示控制 + condition = SimpleCondition("status_bar_wifi", requiredValue = true), + items = listOf( + // 慢速阈值设置 + Slider( + key = "slow_speed_threshold", + title = StringResource(R.string.slow_speed_threshold), + defaultValue = 20f, + valueRange = 0f..1024f, + unit = "KB/S", + decimalPlaces = 0 + // 说明: 低于此速度时视为慢速 + ), + + // 慢速时隐藏 + Switch( + key = "hide_on_slow", + title = StringResource(R.string.hide_on_slow) + // 说明: 当速度低于阈值时隐藏网速显示 + ), + + // 上下行都慢时隐藏 (仅上下行分离样式) + Switch( + key = "hide_when_both_slow", + title = StringResource(R.string.hide_when_both_slow), + condition = AndCondition(listOf( + SimpleCondition("hide_on_slow", requiredValue = true), + SimpleCondition("StyleSelectedOption", requiredValue = 1) + )) + // 说明: 仅当上传和下载速度都低于阈值时才隐藏 + ) + ) + ), + + // ==================== 5. 高级设置卡片 (仅上下行分离样式) ==================== + CardDefinition( + titleRes = R.string.advanced_settings, // 高级设置 + condition = AndCondition(listOf( + SimpleCondition("status_bar_wifi", requiredValue = true), + SimpleCondition("StyleSelectedOption", requiredValue = 1) // 仅上下行分离样式 + )), + items = listOf( + // === 箭头指示器设置 === + // 箭头样式选择 + Dropdown( + key = "icon_indicator", + title = StringResource(R.string.icon_indicator), + optionsRes = R.array.icon_indicator_options + // 选项: 0=无, 1=三角形(动态), 2=三角形2(动态), 3=将棋符号(动态), 4=简单箭头, 5=双线箭头 + ), + + // 箭头位置前置 + Switch( + key = "position_speed_indicator_front", + title = StringResource(R.string.position_speed_indicator_front), + condition = SimpleCondition( + dependencyKey = "icon_indicator", + operator = Operator.NOT_EQUALS, + requiredValue = 0 + ) + // 说明: 将箭头显示在速度数字前面而不是后面 + // 仅当选择了箭头样式时显示此选项 + ), + + // === 单位显示设置 === + // 隐藏数字和单位之间的空格 + Switch( + key = "hide_space", + title = StringResource(R.string.hide_space) + // 示例: "1.5 MB/s" -> "1.5MB/s" + ), + + // 隐藏单位 (仅显示前缀) + Switch( + key = "hide_bs", + title = StringResource(R.string.hide_bs) + // 示例: "1.5 MB/s" -> "1.5 M" + ), + + // 隐藏 "/s" 后缀 + Switch( + key = "hide_per_second", + title = StringResource(R.string.hide_per_second), + summary = R.string.hide_per_second_summary, + condition = SimpleCondition("hide_bs", requiredValue = false) + // 示例: "1.5 MB/s" -> "1.5 MB" + // 仅当未隐藏单位时显示此选项 + ), + + // 使用大写 B (Byte) + Switch( + key = "use_uppercase_b", + title = StringResource(R.string.use_uppercase_b), + summary = R.string.use_uppercase_b_summary, + condition = SimpleCondition("hide_bs", requiredValue = false) + // 示例: "1.5 Mb/s" -> "1.5 MB/s" + // 仅当未隐藏单位时显示此选项 + ), + + // === 布局设置 === + // 交换上传下载位置 + Switch( + key = "swap_upload_download", + title = StringResource(R.string.swap_upload_download) + // 说明: 将上传速度和下载速度的显示位置互换 + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/systemui/notification.kt b/app/src/main/java/com/suqi8/oshin/features/systemui/notification.kt new file mode 100644 index 00000000..76b1d453 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/systemui/notification.kt @@ -0,0 +1,46 @@ +package com.suqi8.oshin.features.systemui + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object notification { + val definition = PageDefinition( + category = "systemui\\notification", + appList = listOf("com.android.systemui"), + title = StringResource(R.string.status_bar_notification), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.remove_developer_options_notification), + key = "remove_developer_options_notification", + defaultValue = false + ), + Switch( + title = StringResource(R.string.remove_and_do_not_disturb_notification), + key = "remove_and_do_not_disturb_notification", + defaultValue = false + ), + Switch( + title = StringResource(R.string.remove_active_vpn_notification), + summary = R.string.reboot_required_to_take_effect, + key = "remove_active_vpn_notification", + defaultValue = false + ), + Switch( + title = StringResource(R.string.remove_charging_complete_notification), + key = "remove_charging_complete_notification", + defaultValue = false + ) + ) + ), + CardDefinition( + titleRes = R.string.notification_restriction_message, + items = listOf() + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/systemui/systemui.kt b/app/src/main/java/com/suqi8/oshin/features/systemui/systemui.kt new file mode 100644 index 00000000..8d87c5e1 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/systemui/systemui.kt @@ -0,0 +1,90 @@ +package com.suqi8.oshin.features.systemui + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.Action +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.Operator +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.RelatedLinks +import com.suqi8.oshin.models.SimpleCondition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object systemui { + val definition = PageDefinition( + category = "systemui", + appList = listOf("com.android.systemui"), + title = AppName("com.android.systemui"), + items = listOf( + CardDefinition( + items = listOf( + Action( + title = StringResource(id = R.string.status_bar), + route = "systemui\\status_bar" + ), + Action( + title = StringResource(id = R.string.status_bar_notification), + route = "systemui\\notification" + ), + Action( + title = StringResource(id = R.string.control_center), + route = "systemui\\controlCenter" + ) + ) + ), + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.enable_all_day_screen_off), + key = "enable_all_day_screen_off" + ), + Switch( + title = StringResource(R.string.force_trigger_ltpo), + key = "force_trigger_ltpo", + defaultValue = true, + condition = SimpleCondition( + dependencyKey = "enable_all_day_screen_off", // 它依赖的项的 key + operator = Operator.EQUALS, // 依赖项的值必须等于 + requiredValue = true // true + ) + ) + ) + ), + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.disable_data_transfer_auth), + key = "disable_data_transfer_auth", + defaultValue = false + ), + Switch( + title = StringResource(R.string.usb_default_file_transfer), + key = "usb_default_file_transfer", + defaultValue = false + ), + Switch( + title = StringResource(R.string.remove_usb_selection_dialog), + key = "remove_usb_selection_dialog", + defaultValue = false + ), + Switch( + title = StringResource(R.string.toast_force_show_app_icon), + summary = R.string.toast_icon_source_module, + key = "toast_force_show_app_icon", + defaultValue = false + ) + ) + ), + RelatedLinks( + links = listOf( + RelatedLinks.Link( + R.string.security_payment_remove_risky_fluid_cloud, + "securepay" + ), + RelatedLinks.Link(R.string.low_battery_fluid_cloud_off, "battery") + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/template/templateMain.kt b/app/src/main/java/com/suqi8/oshin/features/template/templateMain.kt new file mode 100644 index 00000000..9d657bce --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/template/templateMain.kt @@ -0,0 +1,28 @@ +package com.suqi8.oshin.features.template + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.Action +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource + +object templateMain { + val definition = PageDefinition( + category = "template", + appList = listOf("com.template.main"), + title = AppName("com.template.main"), + // 2. 定义页面的卡片列表 + items = listOf( + // --- 第一个 Card --- + CardDefinition( + items = listOf( + Action( + title = StringResource(R.string.app_name), + route = "template" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/themestore/themestore.kt b/app/src/main/java/com/suqi8/oshin/features/themestore/themestore.kt new file mode 100644 index 00000000..b0710266 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/themestore/themestore.kt @@ -0,0 +1,42 @@ +package com.suqi8.oshin.features.themestore + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object themestore { + val definition = PageDefinition( + category = "themestore", + appList = listOf("com.heytap.themestore"), + title = AppName("com.heytap.themestore"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.unlock_themestore_vip_features), + key = "unlock_themestore_vip_features" + ) + ) + ), + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.remove_themestore_splash_ads), + key = "remove_themestore_splash_ads" + ) + ) + ), + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.remove_themestore_upgrade), + key = "remove_themestore_upgrade" + ) + ) + ), + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/wallet/wallet.kt b/app/src/main/java/com/suqi8/oshin/features/wallet/wallet.kt new file mode 100644 index 00000000..b2c01ed1 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/wallet/wallet.kt @@ -0,0 +1,27 @@ +package com.suqi8.oshin.features.wallet + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object wallet { + val definition = PageDefinition( + category = "wallet", + appList = listOf("com.finshell.wallet"), + title = AppName("com.finshell.wallet"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.remove_swipe_page_ads), + summary = R.string.clear_wallet_data_notice, + key = "remove_swipe_page_ads" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/features/weather/weather.kt b/app/src/main/java/com/suqi8/oshin/features/weather/weather.kt new file mode 100644 index 00000000..7b86cd09 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/features/weather/weather.kt @@ -0,0 +1,43 @@ +package com.suqi8.oshin.features.weather + +import com.suqi8.oshin.R +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.RelatedLinks +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch + +object weather { + val definition = PageDefinition( + category = "weather", + appList = listOf("com.coloros.weather2"), + title = AppName("com.coloros.weather2"), + items = listOf( + CardDefinition( + items = listOf( + Switch( + title = StringResource(R.string.remove_second_page_ads), + key = "remove_second_page_ads" + ), + Switch( + title = StringResource(R.string.prevent_system_browser_redirect), + key = "prevent_system_browser_redirect" + ) + ) + ), + RelatedLinks( + links = listOf( + RelatedLinks.Link( + titleRes = R.string.remove_weather_injected_ads, + route = "browser" + ), + RelatedLinks.Link( + titleRes = R.string.remove_weather_search_box, + route = "browser" + ) + ) + ) + ) + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/HookEntry.kt b/app/src/main/java/com/suqi8/oshin/hook/HookEntry.kt new file mode 100644 index 00000000..7b5c6e54 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/HookEntry.kt @@ -0,0 +1,93 @@ +package com.suqi8.oshin.hook + +import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed +import com.highcapable.yukihookapi.hook.factory.configs +import com.highcapable.yukihookapi.hook.factory.encase +import com.highcapable.yukihookapi.hook.xposed.bridge.event.YukiXposedEvent +import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit +import com.suqi8.oshin.hook.android.android +import com.suqi8.oshin.hook.appdetail.appdetail +import com.suqi8.oshin.hook.battery.battery +import com.suqi8.oshin.hook.browser.browser +import com.suqi8.oshin.hook.exsystemservice.exsystemservice +import com.suqi8.oshin.hook.games.games +import com.suqi8.oshin.hook.health.health +import com.suqi8.oshin.hook.incallui.incallui +import com.suqi8.oshin.hook.launcher.launcher +import com.suqi8.oshin.hook.launcher.recent_task +import com.suqi8.oshin.hook.mihealth.mihealth +import com.suqi8.oshin.hook.mms.mms +import com.suqi8.oshin.hook.notificationmanager.NotificationManager +import com.suqi8.oshin.hook.ocrscanner.ocrscanner +import com.suqi8.oshin.hook.oshare.oshare +import com.suqi8.oshin.hook.ota.ota +import com.suqi8.oshin.hook.padconnect.padconnect +import com.suqi8.oshin.hook.phone.phone +import com.suqi8.oshin.hook.phonemanager.oplusphonemanager +import com.suqi8.oshin.hook.phonemanager.phonemanager +import com.suqi8.oshin.hook.quicksearchbox.quicksearchbox +import com.suqi8.oshin.hook.securepay.securepay +import com.suqi8.oshin.hook.securitypermission.securitypermission +import com.suqi8.oshin.hook.settings.settings +import com.suqi8.oshin.hook.speechassist.speechassist +import com.suqi8.oshin.hook.systemui.systemui +import com.suqi8.oshin.hook.themestore.themestore +import com.suqi8.oshin.hook.wallet.wallet +import com.suqi8.oshin.hook.weather.weather +import de.robv.android.xposed.IXposedHookZygoteInit +import de.robv.android.xposed.callbacks.XC_LoadPackage + + +@InjectYukiHookWithXposed(entryClassName = "oshin", isUsingResourcesHook = true) +class HookEntry : IYukiHookXposedInit { + + override fun onInit() = configs { + debugLog { + tag = "OShin" + } + isDebug = false + } + + override fun onHook() = encase { + try { System.loadLibrary("dexkit") } catch (t: Throwable) {} + loadApp(hooker = android()) + loadApp(hooker = systemui()) + loadApp(hooker = battery()) + loadApp(hooker = speechassist()) + loadApp(hooker = games()) + loadApp(hooker = ocrscanner()) + loadApp(hooker = settings()) + loadApp(hooker = wallet()) + loadApp(hooker = launcher()) + loadApp(hooker = phonemanager()) + loadApp(hooker = oplusphonemanager()) + loadApp(hooker = mms()) + loadApp(hooker = securepay()) + loadApp(hooker = health()) + loadApp(hooker = appdetail()) + loadApp(hooker = quicksearchbox()) + loadApp(hooker = mihealth()) + loadApp(hooker = ota()) + loadApp(hooker = oshare()) + loadApp(hooker = incallui()) + loadApp(hooker = NotificationManager()) + loadApp(hooker = themestore()) + loadHooker(exsystemservice()) + loadHooker(phone()) + loadHooker(padconnect()) + loadHooker(weather()) + loadHooker(browser()) + loadHooker(securitypermission()) + } + + override fun onXposedEvent() { + YukiXposedEvent.onHandleLoadPackage { lpparam: XC_LoadPackage.LoadPackageParam -> + run { + } + } + YukiXposedEvent.onInitZygote { startupParam: IXposedHookZygoteInit.StartupParam -> + run { + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/android/AllowUntrustedTouch.kt b/app/src/main/java/com/suqi8/oshin/hook/android/AllowUntrustedTouch.kt new file mode 100644 index 00000000..85d55c36 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/android/AllowUntrustedTouch.kt @@ -0,0 +1,25 @@ +package com.suqi8.oshin.hook.android + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.java.IntType + +class AllowUntrustedTouch : YukiBaseHooker() { + override fun onHook() { + loadSystem { + if (prefs("android").getBoolean("AllowUntrustedTouch", false)) { + "com.android.server.wm.WindowState".toClass().apply { + method { + name = "getTouchOcclusionMode" + emptyParam() + returnType = IntType + }.hook { + before { + result = 2 + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/android/DisablePinVerifyPer72h.kt b/app/src/main/java/com/suqi8/oshin/hook/android/DisablePinVerifyPer72h.kt new file mode 100644 index 00000000..b55a882d --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/android/DisablePinVerifyPer72h.kt @@ -0,0 +1,25 @@ +package com.suqi8.oshin.hook.android + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.java.IntType +import com.highcapable.yukihookapi.hook.type.java.LongType +import com.highcapable.yukihookapi.hook.type.java.UnitType + +class DisablePinVerifyPer72h : YukiBaseHooker() { + override fun onHook() { + loadSystem { + if (prefs("android").getBoolean("DisablePinVerifyPer72h", false)) { + "com.android.server.locksettings.LockSettingsStrongAuth".toClass().apply { + method { + name = "rescheduleStrongAuthTimeoutAlarm" + param(LongType, IntType) + returnType = UnitType + }.hook { + replaceUnit { } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/android/OplusRootCheck.kt b/app/src/main/java/com/suqi8/oshin/hook/android/OplusRootCheck.kt new file mode 100644 index 00000000..d0a29ef1 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/android/OplusRootCheck.kt @@ -0,0 +1,23 @@ +package com.suqi8.oshin.hook.android + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method + +class OplusRootCheck : YukiBaseHooker() { + override fun onHook() { + loadSystem { + if (prefs("android\\oplus_system_services").getBoolean("disable_root_check", false)) { + "com.android.server.oplus.heimdall.HeimdallService".toClass().apply { + method { + name = "isRootEnable" + emptyParam() + }.hook { + after { + result = false + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/android/PackageManagerServices.kt b/app/src/main/java/com/suqi8/oshin/hook/android/PackageManagerServices.kt new file mode 100644 index 00000000..0c0f4bb7 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/android/PackageManagerServices.kt @@ -0,0 +1,352 @@ +package com.suqi8.oshin.hook.android + +import android.content.pm.ApplicationInfo +import android.content.pm.Signature +import com.github.kyuubiran.ezxhelper.paramTypes +import com.highcapable.kavaref.KavaRef.Companion.asResolver +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.log.YLog +import com.highcapable.yukihookapi.hook.param.PackageParam +import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookPrefsBridge +import java.io.PrintWriter + +class PackageManagerServices : YukiBaseHooker() { + + override fun onHook() { + loadSystem { + val prefs = prefs("android\\package_manager_services") + hookDowngradeChecks(prefs) + hookCoreSignatureChecks(prefs) + hookInstallationChecks(prefs) + hookSystemApiChecks(prefs) + hookDebugCommands(prefs) + } + } + + /** + * Hook 允许应用降级安装的功能 + */ + private fun PackageParam.hookDowngradeChecks(prefs: YukiHookPrefsBridge) { + if (!prefs.getBoolean("allow_downgrade", false)) return + "com.android.server.pm.PackageManagerServiceUtils".toClass().resolve() + .firstMethod { name = "checkDowngrade" }.hook { + before { result = null } + } + } + + /** + * Hook 核心签名验证 + */ + private fun PackageParam.hookCoreSignatureChecks(prefs: YukiHookPrefsBridge) { + if (prefs.getBoolean("disable_jar_verifier", false)) { + "android.util.jar.StrictJarVerifier".toClass().resolve().apply { + method { name = "verifyMessageDigest" }.forEach { it.hook { before { result = true } } } + method { name = "verify" }.forEach { it.hook { before { result = true } } } + } + } + if (prefs.getBoolean("disable_message_digest", false)) { + "java.security.MessageDigest".toClass().resolve().method { name = "isEqual" }.forEach { + it.hook { before { result = true } } + } + } + if (prefs.getBoolean("disable_jar_verifier", false)) { + "android.util.jar.StrictJarVerifier".toClass().resolve().constructor { }.hookAll { + after { + instance.asResolver().firstField { name = "signatureSchemeRollbackProtectionsEnforced" }.set(false) + } + } + } + } + + /** + * Hook 安装过程中的各种检查 + */ + private fun PackageParam.hookInstallationChecks(prefs: YukiHookPrefsBridge) { + // 绕过 resources.arsc 校验 + if (prefs.getBoolean("bypass_arsc_uncompressed_check", false)) { + "android.content.res.AssetManager".toClass().resolve().method { name = "containsAllocatedTable" }.forEach { + it.hook { before { result = false } } + } + } + + // 绕过最小签名版本检查 + if (prefs.getBoolean("bypass_min_signature_version_check", false)) { + "android.util.apk.ApkSignatureVerifier".toClass().resolve() + .firstMethod { + name = "getMinimumSignatureSchemeVersionForTargetSdk" + parameters(Int::class) + }.hook { + before { result = 0 } + } + "com.android.server.pm.ScanPackageUtils".toClass().resolve().method { + name = "assertMinSignatureSchemeIsValid" + }.hookAll { + after { result = null } // 设为 null 以跳过异常 + } + } + + // 覆盖安装签名不一致处理 + if (prefs.getBoolean("allow_signature_mismatch_on_update", false)) { + "android.content.pm.SigningDetails".toClass().resolve().apply { + method { name = "checkCapability" }.forEach { + it.hook { + before { + val capability = args(1).int() + if (capability != 4 && capability != 16) result = true + } + } + } + } + val shouldBypassKeySet = ThreadLocal() + "com.android.server.pm.KeySetManagerService".toClass().resolve().apply { + method { name = "shouldCheckUpgradeKeySetLocked" }.hookAll { + after { + val stackTrace = Thread.currentThread().stackTrace + // 检查是否在安装包的堆栈调用中 + val isFromPreparePackage = stackTrace.any { it.methodName.startsWith("preparePackage") } + if (isFromPreparePackage) { + shouldBypassKeySet.set(true) + result = true // 告诉系统“不需要检查” + } else { + shouldBypassKeySet.set(false) + } + } + } + method { name = "checkUpgradeKeySetLocked" }.hookAll { + after { + if (shouldBypassKeySet.get() == true) { + result = true // 告诉系统“检查通过” + } + } + } + } + "com.android.server.pm.InstallPackageHelper".toClass().resolve().method { + name = "doesSignatureMatchForPermissions" + parameters(String::class.java, "com.android.internal.pm.parsing.pkg.ParsedPackage", Int::class.java) + }.hookAll { + after { + // 如果系统判定签名不匹配 (false) + if (result == false) { + val parsedPackage = args(1) + val targetPackageName = args(0).string() + // 检查包名是否一致,如果一致,则强行改为 true + val pPname = parsedPackage.asResolver().firstMethod { name = "getPackageName" }.invoke() + if (pPname == targetPackageName) { + result = true + } + } + } + } + } + + // 禁用安装验证 + if (prefs.getBoolean("disable_install_verification", false)) { + "com.android.server.pm.VerifyingSession".toClass().resolve().method { name = "isVerificationEnabled" }.forEach { + it.hook { before { result = false } } + } + } + + if (prefs.getBoolean("bypass_v1_signature_errors", false)) { + hookV1SignatureErrors() + } + + // 允许 Split APK 签名不一致 + if (prefs.getBoolean("allow_mismatched_split_apk_signatures", false)) { + "android.content.pm.SigningDetails".toClass().resolve().method { + name = "signaturesMatchExactly" + }.hookAll { + before { result = true } + } + } + } + + private fun PackageParam.hookV1SignatureErrors() { + val COREPATCH_SIGNATURE = "308203c6308202aea003020102021426d148b7c65944abcf3a683b4c3dd3b139c4ec85300d06092a864886f70d01010b05003074310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e205669657731143012060355040a130b476f6f676c6520496e632e3110300e060355040b1307416e64726f69643110300e06035504031307416e64726f6964301e170d3139303130323138353233385a170d3439303130323138353233385a3074310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e205669657731143012060355040a130b476f6f676c6520496e632e3110300e060355040b1307416e64726f69643110300e06035504031307416e64726f696430820122300d06092a864886f70d01010105000382010f003082010a028201010087fcde48d9beaeba37b733a397ae586fb42b6c3f4ce758dc3ef1327754a049b58f738664ece587994f1c6362f98c9be5fe82c72177260c390781f74a10a8a6f05a6b5ca0c7c5826e15526d8d7f0e74f2170064896b0cf32634a388e1a975ed6bab10744d9b371cba85069834bf098f1de0205cdee8e715759d302a64d248067a15b9beea11b61305e367ac71b1a898bf2eec7342109c9c5813a579d8a1b3e6a3fe290ea82e27fdba748a663f73cca5807cff1e4ad6f3ccca7c02945926a47279d1159599d4ecf01c9d0b62e385c6320a7a1e4ddc9833f237e814b34024b9ad108a5b00786ea15593a50ca7987cbbdc203c096eed5ff4bf8a63d27d33ecc963990203010001a350304e300c0603551d13040530030101ff301d0603551d0e04160414a361efb002034d596c3a60ad7b0332012a16aee3301f0603551d23041830168014a361efb002034d596c3a60ad7b0332012a16aee3300d06092a864886f70d01010b0500038201010022ccb684a7a8706f3ee7c81d6750fd662bf39f84805862040b625ddf378eeefae5a4f1f283deea61a3c7f8e7963fd745415153a531912b82b596e7409287ba26fb80cedba18f22ae3d987466e1fdd88e440402b2ea2819db5392cadee501350e81b8791675ea1a2ed7ef7696dff273f13fb742bb9625fa12ce9c2cb0b7b3d94b21792f1252b1d9e4f7012cb341b62ff556e6864b40927e942065d8f0f51273fcda979b8832dd5562c79acf719de6be5aee2a85f89265b071bf38339e2d31041bc501d5e0c034ab1cd9c64353b10ee70b49274093d13f733eb9d3543140814c72f8e003f301c7a00b1872cc008ad55e26df2e8f07441002c4bcb7dc746745f0db" + + val packageParserExceptionClass = "android.content.pm.PackageParser.PackageParserException".toClass() + val errorField = packageParserExceptionClass.resolve().firstField { name = "error" } + + "android.util.apk.ApkSignatureVerifier".toClass().resolve().method { + name = "verifyV1Signature" + }.hookAll { + after { + val throwable = this.throwable ?: return@after + + var isV1Error = false + + // 检查 throwable 本身 + if (throwable::class.java == packageParserExceptionClass) { + if (errorField.get() as Int == -103) isV1Error = true + } + + // 检查 throwable 的 cause + if (!isV1Error) { + val cause = throwable.cause + if (cause != null && cause::class.java == packageParserExceptionClass) { + if (errorField.get() as Int == -103) isV1Error = true + } + } + + if (!isV1Error) return@after + + // 创建签名 + val fakeSigs = arrayOf(Signature(COREPATCH_SIGNATURE)) + val newInstance = "android.content.pm.SigningDetails".toClass().resolve() + .firstConstructor { + paramTypes(Array::class.java, Int::class.java) + }.create(fakeSigs, 1) + + result = newInstance + } + } + } + + /** + * Hook 系统 API 权限相关的检查 + */ + private fun PackageParam.hookSystemApiChecks(prefs: YukiHookPrefsBridge) { + // 允许系统应用使用隐藏 API + if (prefs.getBoolean("allow_system_app_hidden_api", false)) { + ApplicationInfo::class.java.resolve().firstMethod { + name = "isPackageWhitelistedForHiddenApis" + }.hook { + before { + val info = instance() + val FLAG_SYSTEM = 1 + val FLAG_UPDATED_SYSTEM_APP = 128 + if ((info.flags and FLAG_SYSTEM) != 0 || (info.flags and FLAG_UPDATED_SYSTEM_APP) != 0) { + result = true + } + } + } + } + + // 共享用户ID签名逻辑 + if (prefs.getBoolean("allow_nonsystem_shared_uid", false)) { + "com.android.server.pm.ReconcilePackageUtils".toClass().resolve().firstField { + name = "ALLOW_NON_PRELOADS_SYSTEM_SHAREDUIDS" + }.set(true) + } + } + + /** + * Hook adb shell 调试命令 + */ + private fun PackageParam.hookDebugCommands(prefs: YukiHookPrefsBridge) { + if (prefs.getBoolean("pms_command", false)) { + var mPMS: Any? = null + + // Hook PMS 的构造函数,以获取其实例 + "com.android.server.pm.PackageManagerService".toClass().resolve().constructor { }.hookAll { + after { + mPMS = instance + } + } + + // Hook adb shell pm 命令的入口 + "com.android.server.pm.PackageManagerShellCommand".toClass().resolve() + .firstMethod { + name = "onCommand" + parameters(String::class) + }.hook { + before { + val cmd = args(0).string() + if (cmd != "pms" || mPMS == null) return@before + + result = 0 // 阻止原始方法执行 + val shellCommandInstance = instance + try { + val localPms = mPMS + + val pw = shellCommandInstance.asResolver().firstMethod { name = "getOutPrintWriter" }.invoke() as PrintWriter + val type = shellCommandInstance.asResolver().firstMethod { name = "getNextArgRequired" }.invoke() as String + val settings = localPms?.asResolver()?.firstField { name = "mSettings" } + ?.get() + + if (settings == null) { + pw.println("Error: Could not get mSettings from PMS.") + return@before + } + + when (type) { + "p", "package" -> { + val packageName = shellCommandInstance.asResolver().firstMethod { name = "getNextArgRequired" }.invoke() as String + val packageSetting = settings.asResolver().firstMethod { name = "getPackageLPr"; parameters(String::class) }.invoke(packageName) + if (packageSetting != null) { + dumpPackageSetting(packageSetting, pw, settings) + } else { + pw.println("no package $packageName found") + } + } + "su", "shareduser" -> { + val name = shellCommandInstance.asResolver().firstMethod { name = "getNextArgRequired" }.invoke() as String + val su = getSharedUser(name, settings) + if (su != null) { + dumpSharedUserSetting(su, pw) + } else { + pw.println("no shared user $name found") + } + } + else -> pw.println("usage: ") + } + } catch (t: Throwable) { + YLog.error("CorePatch command failed", t) + instance.asResolver().firstMethod { name = "getErrPrintWriter" }.invoke()?.println(t) + } + } + } + } + } + + /** + * 调试功能所需的辅助方法 + */ + private fun dumpPackageSetting(packageSetting: Any, pw: PrintWriter, settings: Any) { + val signingDetails = getSigningDetailsFromSetting(packageSetting) + pw.println("signing for package $packageSetting") + if (signingDetails != null) dumpSigningDetails(signingDetails, pw) + + val pkg = packageSetting.asResolver().firstField { name = "pkg" }.get() + if (pkg == null) { + pw.println("android package is null!") + return + } + val sharedUserId = pkg.asResolver().firstMethod { name = "getSharedUserId" }.invoke() + pw.println("shared user id: $sharedUserId") + if (sharedUserId != null) { + getSharedUser(sharedUserId, settings)?.let { dumpSharedUserSetting(it, pw) } + } + } + + private fun getSharedUser(id: String, settings: Any): Any? { + val sharedUserSettings = settings.asResolver().firstField { name = "mSharedUsers" }.get() + return sharedUserSettings?.asResolver()?.firstMethod { name = "get"; parameters(Any::class) } + ?.invoke(id) + } + + private fun dumpSharedUserSetting(sharedUser: Any, pw: PrintWriter) { + val signingDetails = getSigningDetailsFromSetting(sharedUser) + pw.println("signing for shared user $sharedUser") + if (signingDetails != null) dumpSigningDetails(signingDetails, pw) + } + + private fun getSigningDetailsFromSetting(pkgOrSharedUser: Any): Any? { + // 路径: .signatures.mSigningDetails + return pkgOrSharedUser.asResolver().firstField { name = "signatures" }.get() + ?.asResolver()?.firstField { name = "mSigningDetails" }?.get() + } + + private fun dumpSigningDetails(signingDetails: Any, pw: PrintWriter) { + // 在 Android T (API 33) 及以上, signatures 是方法调用 + val signatures = signingDetails.asResolver().firstMethod { name = "getSignatures" }.invoke>() + + if (signatures == null) { + pw.println("Could not get signatures.") + return + } + signatures.forEachIndexed { i, sign -> + pw.println("${i + 1}: ${sign.toCharsString()}") + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/android/Package_Installer.kt b/app/src/main/java/com/suqi8/oshin/hook/android/Package_Installer.kt new file mode 100644 index 00000000..1e972c35 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/android/Package_Installer.kt @@ -0,0 +1,101 @@ +package com.suqi8.oshin.hook.android + +import android.content.Intent +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.field +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.log.YLog +import com.highcapable.yukihookapi.hook.type.java.IntType + +class Package_Installer : YukiBaseHooker() { + override fun onHook() { + loadSystem { + if (prefs("android").getBoolean("", false)) { + } + loadSystem { + // Hook系统级Activity启动服务 + //hookSystemServices() + } + loadApp("com.android.packageinstaller") { + //hookInstallerActivities() // Hook安装器Activity + } + } + } + + /** + * Hook系统服务核心方法 + * 目标:拦截ActivityStarter的intent处理 + */ + private fun hookSystemServices() { + "com.android.server.wm.ActivityStarter".toClass().apply { + method { + name = "execute" + emptyParam() + returnType = IntType + }.hook { + before { + // 获取 com.android.server.wm.ActivityStarter\$Request 类的 Class 对象 + val requestClass = "com.android.server.wm.ActivityStarter\$Request".toClass() + // 确保 requestClass 不为 null + requestClass.let { clazz -> + // 获取当前实例的 mRequest 字段值 + val mRequest = instance.javaClass.field { + name = "mRequest" + type = clazz + }.get() + + // 确保 mRequest 不为 null + mRequest.let { requestInstance -> + // 获取 intent 字段的值 + val intent = requestInstance.javaClass.field { + name = "intent" + type = Intent::class.java + }.get() + + // 确保 intent 不为 null + intent.let { intentInstance -> + YLog.info("$intentInstance") + // 调用 handleIntentRedirect 方法处理 intent + //handleIntentRedirect(intentInstance) + } + } + } + //YLog.info("befintent: $intent mRequest: ${mRequest}") + // 从ActivityStarter$Request对象获取原始intent + /*handleIntentRedirect( + intent + )*/ + } + after { + "com.android.server.wm.ActivityStarter\$Request".toClass().apply { + field { + name = "intent" + type = "android.content.Intent" + }.get(this).cast()?.apply { + YLog.info("$this") + } + } + //YLog.info("befintent: $intent mRequest: ${mRequest}") + // 从ActivityStarter$Request对象获取原始intent + /*handleIntentRedirect( + intent + )*/ + } + } + } + } + /** + * 核心Intent处理逻辑 + * @param intent 系统原始intent + */ + private fun handleIntentRedirect(intent: Intent?) { + intent?.apply { + when { + // 处理卸载请求 + //isUninstallIntent() -> handleUninstall() + // 处理安装请求 + //isInstallIntent() -> handleInstall() + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/android/SplitScreenMultiWindow.kt b/app/src/main/java/com/suqi8/oshin/hook/android/SplitScreenMultiWindow.kt new file mode 100644 index 00000000..1b6d1ac1 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/android/SplitScreenMultiWindow.kt @@ -0,0 +1,168 @@ +package com.suqi8.oshin.hook.android + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.java.BooleanType +import com.highcapable.yukihookapi.hook.type.java.IntType + +class SplitScreenMultiWindow : YukiBaseHooker() { + override fun onHook() { + loadSystem { + if (prefs("android\\split_screen_multi_window").getBoolean("remove_all_small_window_restrictions", false)) { + "com.android.server.wm.FlexibleWindowUtils".toClass().apply { + method { + name = "isUnSupportCallerFlexibleWindow" + param("java.lang.String") + returnType = BooleanType + }.hook { + after { + result = true + } + } + } + "com.android.server.wm.FlexibleWindowUtils".toClass().apply { + method { + name = "isSupportFlexibleWindow" + param("java.lang.String", "java.lang.String") + returnType = BooleanType + }.hook { + after { + result = true + } + } + } + "com.android.server.wm.FlexibleWindowUtils".toClass().apply { + method { + name = "isInFlexibleWindowBlackList" + param("java.lang.String", "java.lang.String") + returnType = BooleanType + }.hook { + after { + result = false + } + } + } + "com.android.server.wm.FlexibleWindowUtils".toClass().apply { + method { + name = "isInMultiWindowFlexibleBlackList" + param("java.lang.String") + returnType = BooleanType + }.hook { + after { + result = false + } + } + } + "com.android.server.wm.FlexibleWindowUtils".toClass().apply { + method { + name = "isSupportFlexibleWindow" + param("android.content.Intent", "android.content.pm.ActivityInfo") + returnType = BooleanType + }.hook { + after { + result = true + } + } + } + "com.android.server.wm.FlexibleWindowUtils".toClass().apply { + method { + name = "isSupportFlexibleWindow" + param("com.android.server.wm.Task") + returnType = BooleanType + }.hook { + after { + result = true + } + } + } + "com.android.server.wm.FlexibleWindowUtils".toClass().apply { + method { + name = "isFlexibleTaskInPSBlackList" + param("android.content.Intent", "android.content.pm.ActivityInfo") + returnType = BooleanType + }.hook { + after { + result = false + } + } + } + "com.android.server.wm.FlexibleWindowUtils".toClass().apply { + method { + name = "getUnSupportRatiosInFlexibleTask" + param("java.lang.String") + returnType = "java.lang.String" + }.hook { + after { + result = false + } + } + } + } + if (prefs("android\\split_screen_multi_window").getBoolean("force_multi_window_mode", false)) { + "com.android.server.wm.FlexibleWindowUtils".toClass().apply { + method { + name = "isSupportMultiMode" + emptyParam() + returnType = BooleanType + }.hook { + after { + result = true + } + } + } + } + if (prefs("android\\split_screen_multi_window").getFloat("max_simultaneous_small_windows", -1f) != -1f) { + "com.android.server.wm.FlexibleWindowManagerService".toClass().apply { + method { + name = "getMaxWinNum" + param(IntType) + returnType = IntType + }.hook { + after { + result = prefs("android\\split_screen_multi_window").getFloat("max_simultaneous_small_windows", -1f).toInt() + } + } + } + } + if (prefs("android\\split_screen_multi_window").getFloat("small_window_corner_radius", -1f) != -1f) { + "com.android.server.wm.FlexibleWindowManagerService".toClass().apply { + method { + name = "getCornerRadius" + param(IntType) + returnType = IntType + }.hook { + after { + result = prefs("android\\split_screen_multi_window").getFloat("small_window_corner_radius", -1f).toInt() + } + } + } + } + if (prefs("android\\split_screen_multi_window").getFloat("small_window_focused_shadow", -1f) != -1f) { + "com.android.server.wm.FlexibleWindowManagerService".toClass().apply { + method { + name = "getShadowRadiusFocused" + param(IntType) + returnType = IntType + }.hook { + after { + result = prefs("android\\split_screen_multi_window").getFloat("small_window_focused_shadow", -1f).toInt() + } + } + } + } + if (prefs("android\\split_screen_multi_window").getFloat("small_window_unfocused_shadow", -1f) != -1f) { + "com.android.server.wm.FlexibleWindowManagerService".toClass().apply { + method { + name = "getShadowRadiusUnfocused" + param(IntType) + returnType = IntType + }.hook { + after { + result = prefs("android\\split_screen_multi_window").getFloat("small_window_unfocused_shadow", -1f).toInt() + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/android/android.kt b/app/src/main/java/com/suqi8/oshin/hook/android/android.kt new file mode 100644 index 00000000..952636c1 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/android/android.kt @@ -0,0 +1,14 @@ +package com.suqi8.oshin.hook.android + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker + +class android : YukiBaseHooker() { + override fun onHook() { + loadApp(hooker = OplusRootCheck()) + loadApp(hooker = SplitScreenMultiWindow()) + loadApp(hooker = DisablePinVerifyPer72h()) + loadApp(hooker = AllowUntrustedTouch()) + loadHooker(PackageManagerServices()) + //loadApp(hooker = Package_Installer()) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/appdetail/appdetail.kt b/app/src/main/java/com/suqi8/oshin/hook/appdetail/appdetail.kt new file mode 100644 index 00000000..16c791ed --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/appdetail/appdetail.kt @@ -0,0 +1,185 @@ +package com.suqi8.oshin.hook.appdetail + +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import org.luckypray.dexkit.DexKitBridge +import java.lang.reflect.Modifier + +class appdetail: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.oplus.appdetail") { + val prefs = prefs("appdetail") + /*if (prefs("appdetail").getBoolean("remove_recommendations", false)) { + "com.oplus.appdetail.model.install.view.InstallPageContent\$initLiveDataObserver\$1".toClass().apply { + method { + name = "invoke" + param("com.heytap.cdo.card.domain.dto.CardDto") + returnType = UnitType + }.hook { + replaceUnit { } + } + } + "com.oplus.appdetail.model.uninstall.UiDataObserverHelper\$initLiveDataObserver\$1".toClass().apply { + method { + name = "invoke" + param("com.heytap.cdo.card.domain.dto.CardDto") + returnType = UnitType + }.hook { + replaceUnit { } + } + } + } + var installation_frequency_methodName = "" + var attempt_installation_method = "" + var attempt_installation_callMethod = "" + var security_check_method = "" + DexKitBridge.create(this.appInfo.sourceDir).use { + installation_frequency_methodName = it.findMethod { + searchPackages("com.oplus.appdetail.model.entrance") + matcher { + modifiers = Modifier.PRIVATE + paramTypes = listOf() + returnType("void") + usingStrings("1") + } + }.singleOrNull()?.methodName.toString() + attempt_installation_method = it.findMethod { + searchPackages("com.oplus.appdetail.model.entrance") + matcher { + modifiers = Modifier.PRIVATE + paramTypes = listOf() + returnType("void") + usingStrings("channel_risk_dialog") + } + }.singleOrNull()?.methodName.toString() + attempt_installation_callMethod = it.findMethod { + searchPackages("com.oplus.appdetail.model.entrance") + matcher { + modifiers = Modifier.PRIVATE + paramTypes = listOf() + returnType("void") + usingStrings("oplus_extra_app_op_mode") + } + }.singleOrNull()?.methodName.toString() + it.findClass { + searchPackages("com.oplus.appdetail.model.guide.viewModel") + matcher { + source("GuideShareViewModel.kt") + } + }.also { + security_check_method = it.findMethod { + matcher { + modifiers = Modifier.PUBLIC + paramTypes = listOf() + returnType("boolean") + usingNumbers(0) + invokeMethods { + add { + name = "getPackageUri" + } + } + } + }.singleOrNull()?.methodName.toString() + } + } + //安装频繁 + if (prefs("appdetail").getBoolean("remove_installation_frequency_popup", false)) { + "com.oplus.appdetail.model.entrance.ChannelBarrageActivity".toClass().apply { + method { + name = installation_frequency_methodName + emptyParam() + returnType = UnitType + }.hook { + replaceUnit { + method { + name = attempt_installation_method + emptyParam() + returnType = UnitType + }.get(instance).call() + } + } + } + } + //尝试安装应用 + if (prefs("appdetail").getBoolean("remove_attempt_installation_popup", false)) { + "com.oplus.appdetail.model.entrance.ChannelBarrageActivity".toClass().apply { + method { + name = attempt_installation_method + emptyParam() + returnType = UnitType + }.hook { + replaceUnit { + method { + name = attempt_installation_callMethod + emptyParam() + returnType = UnitType + }.get(instance).call() + } + } + } + } + //移除版本号检测 + if (prefs("appdetail").getBoolean("remove_version_check", false)) { + "com.nearme.common.util.AppUtil".toClass().apply { + method { + name = "getAppVersionCode" + param("android.content.Context", "java.lang.String") + returnType = IntType + }.hook { + before { + result = -1 + } + } + } + }*/ + val bridge = DexKitBridge.create(this.appInfo.sourceDir) + var remove_security_checkClassName = "" + var remove_security_checkMethodName = "" + bridge.apply { + findClass { + searchPackages("com.oplus.appdetail.common.utils") + matcher { + usingStrings("com.heytap.market") + usingStrings("oplus.intent.action.settings.SCREEN_LOCK","oplus.intent.action.settings.BIOMETRIC_ENROLL_GUIDE") + } + }.findMethod { + matcher { + modifiers = Modifier.STATIC or Modifier.PUBLIC + returnType = "boolean" + paramTypes("android.content.Context") + } + }.singleOrNull()?.apply { + remove_security_checkClassName = className + remove_security_checkMethodName = methodName + } + } + //移除安装前安全检测 + if (prefs.getBoolean("remove_security_check", false)) { + remove_security_checkClassName.toClass().resolve().firstMethod { + modifiers(Modifiers.PUBLIC, Modifiers.STATIC) + name = remove_security_checkMethodName + parameters("android.content.Context") + returnType = Boolean::class + }.hook { + replaceToFalse() + } + } + //移除版本检测 + if (prefs.getBoolean("remove_version_check",false)) { + "com.nearme.common.util.AppUtil".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC, Modifiers.STATIC, Modifiers.FINAL) + name = "getAppVersionCode" + parameters("android.content.Context", String::class) + returnType = Int::class + }.hook { + before { + result = -1 + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/battery/battery.kt b/app/src/main/java/com/suqi8/oshin/hook/battery/battery.kt new file mode 100644 index 00000000..a0517c8c --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/battery/battery.kt @@ -0,0 +1,57 @@ +package com.suqi8.oshin.hook.battery + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method +import org.luckypray.dexkit.DexKitBridge +import java.lang.reflect.Modifier + +class battery: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.oplus.battery") { + DexKitBridge.create(this.appInfo.sourceDir).use { + if (prefs("battery").getBoolean("low_battery_fluid_cloud", false)) { + it.findClass { + matcher { + addMethod { + usingStrings("serviceInstanceId","isSupportMultiInstance") + } + addMethod { + usingStrings("createCallBack: resultCode = ") + } + addMethod { + usingStrings("createCallBack,action = ") + } + } + }.singleOrNull()?.also { + it.name.toClass().method { name = "sendSeedling" }.hook { before { result = 0 } } + } + } + if (prefs("battery").getInt("auto_start_max_limit", 5) != 5) { + it.findClass { + matcher { + addMethod { + usingStrings(" ready for first page: isAutoStart:"," stop async work when loadIcon: ") + } + } + }.findMethod { + matcher { + modifiers = Modifier.PUBLIC + returnType = "int" + paramTypes() + invokeMethods { + add { + modifiers = Modifier.PUBLIC + returnType = "int" + paramTypes() + usingNumbers(5,20) + } + } + } + }.singleOrNull()?.also { + it.className.toClass().method { name = it.methodName }.hook { replaceTo(prefs("battery").getInt("auto_start_max_limit", 5)) } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/browser/browser.kt b/app/src/main/java/com/suqi8/oshin/hook/browser/browser.kt new file mode 100644 index 00000000..9a337eac --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/browser/browser.kt @@ -0,0 +1,69 @@ +package com.suqi8.oshin.hook.browser + +import android.view.View +import android.widget.RelativeLayout +import com.highcapable.kavaref.KavaRef.Companion.asResolver +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import org.luckypray.dexkit.DexKitBridge +import java.lang.reflect.Modifier + +class browser: YukiBaseHooker() { + override fun onHook() { + val prefs = prefs("browser") + loadApp("com.heytap.browser") { + val bridge = DexKitBridge.create(this.appInfo.sourceDir) + if (prefs.getBoolean("remove_weather_injected_ads", false)) { + bridge.findClass { + matcher { + className = "com.heytap.browser.business.weather.js.ThirdCommonJsHook" + } + }.findMethod { + matcher { + modifiers = Modifier.PUBLIC + paramCount = 0 + returnType = "void" + usingNumbers(4) + } + }.singleOrNull()?.also { + it.className.toClass().asResolver().firstMethod { + name = it.methodName + }.hook { + replaceUnit { } + } + } + } + if (prefs.getBoolean("remove_weather_search_box", false)) { + val TitleBarCommonClass = bridge.findClass { + matcher { + className = "com.heytap.browser.business.weather.titlebar.TitleBarCommon" + } + } + val searchBarMethodName = TitleBarCommonClass.findMethod { + matcher { + usingStrings("findViewById(this, R.id.search_layout_common)") + } + }.singleOrNull()?.methodName + val searchBarField = TitleBarCommonClass.findField { + matcher { + type = "com.heytap.browser.business.weather.titlebar.ThirdSearchLayout" + } + }.singleOrNull()?.fieldName + "com.heytap.browser.business.weather.titlebar.TitleBarCommon".toClass().resolve().apply { + firstMethod { + name = searchBarMethodName + }.hook { + after { + val titleBarInstance = instance + + val searchLayoutField = titleBarInstance.asResolver().firstField { name = searchBarField } + val searchLayout = searchLayoutField.get() as RelativeLayout + + searchLayout.visibility = View.GONE + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/exsystemservice/RemoveSystemTamperWarning.kt b/app/src/main/java/com/suqi8/oshin/hook/exsystemservice/RemoveSystemTamperWarning.kt new file mode 100644 index 00000000..d9321123 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/exsystemservice/RemoveSystemTamperWarning.kt @@ -0,0 +1,23 @@ +package com.suqi8.oshin.hook.exsystemservice + +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import org.luckypray.dexkit.DexKitBridge + +class RemoveSystemTamperWarning: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.oplus.exsystemservice") { + DexKitBridge.create(this.appInfo.sourceDir).use { + if (prefs("exsystemservice").getBoolean("remove_system_tamper_warning", false)) { + it.findMethod { + matcher { + usingStrings("OplusAntiRootDialogService","displayDialog uid = ","phone","power") + } + }.singleOrNull()?.also { + it.className.toClass().resolve().firstMethod { name = it.methodName }.hook { replaceUnit { } } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/exsystemservice/exsystemservice.kt b/app/src/main/java/com/suqi8/oshin/hook/exsystemservice/exsystemservice.kt new file mode 100644 index 00000000..2b2e7e66 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/exsystemservice/exsystemservice.kt @@ -0,0 +1,9 @@ +package com.suqi8.oshin.hook.exsystemservice + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker + +class exsystemservice: YukiBaseHooker() { + override fun onHook() { + loadHooker(RemoveSystemTamperWarning()) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/games/games.kt b/app/src/main/java/com/suqi8/oshin/hook/games/games.kt new file mode 100644 index 00000000..83a9dc98 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/games/games.kt @@ -0,0 +1,168 @@ +package com.suqi8.oshin.hook.games + +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import org.luckypray.dexkit.DexKitBridge +import java.lang.reflect.Modifier + +class games: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.oplus.games") { + DexKitBridge.create(this.appInfo.sourceDir).use { + //游戏AI + it.findMethod { + searchPackages("business.module.aiplay") + matcher { + modifiers = Modifier.PRIVATE + returnType = "boolean" + usingStrings("feature.support.game.AI_PLAY") + } + }.forEach { + it.className.toClass().resolve().apply { + if (prefs("games").getBoolean("hok_ai_v1", false) && it.className in "sgame") { + firstMethod { + name = it.methodName + returnType = Boolean::class + }.hook { before { result = true } } + } + if (prefs("games").getBoolean("pubg_ai", false) && it.className in "pubg") { + firstMethod { + name = it.methodName + returnType = Boolean::class + }.hook { before { result = true } } + } + if (prefs("games").getBoolean("enable_mlbb_ai_god_assist", false) && it.className in "mlbb") { + firstMethod { + name = it.methodName + returnType = Boolean::class + }.hook { before { result = true } } + } + } + } + //全部功能 + if (prefs("games").getBoolean("enable_all_features", false)) { + it.findMethod { + matcher { + modifiers = Modifier.PUBLIC + returnType = "boolean" + paramTypes("android.content.ContentResolver", "java.lang.String") + } + }.singleOrNull()?.also { + //YLog.info("methodName:"+it.methodName + " className:" + it.className) + it.className.toClass().resolve().firstMethod { name = it.methodName }.hook { before { result = true } } + } + } + //超神连招 + if (prefs("games").getBoolean("ultra_combo", false)) { + it.findMethod { + searchPackages("business.module.assistkey") + matcher { + modifiers = Modifier.PRIVATE + returnType = "boolean" + usingStrings("feature.support.game.ASSIST_KEY") + } + }.singleOrNull()?.also { + it.className.toClass().resolve().firstMethod { name = it.methodName }.hook { before { result = true } } + } + } + //云控 + if (prefs("games").getBoolean("feature_disable_cloud_control", false)) { + it.findMethod { + searchPackages("com.coloros.gamespaceui.config.cloud") + matcher { + modifiers = Modifier.PUBLIC + returnType = "boolean" + usingStrings("cloudKey") + paramTypes("java.lang.String", "java.util.Map") + } + }.singleOrNull()?.also { + it.className.toClass().resolve().firstMethod { name = it.methodName }.hook { before { result = true } } + } + } + //去除报名限制 + if (prefs("games").getBoolean("remove_package_restriction", false)) { + it.findMethod { + searchPackages("com.coloros.gamespaceui.config.cloud") + matcher { + modifiers = Modifier.PRIVATE + returnType = "boolean" + paramTypes("java.util.Set", "java.util.Map") + } + }.singleOrNull()?.also { + it.className.toClass().resolve().firstMethod { name = it.methodName }.hook { before { result = true } } + } + } + //root检测 + if (prefs("games").getBoolean("remove_game_filter_root_detection", false)) { + it.findMethod { + searchPackages("business.module.gamefilter") + matcher { + modifiers = Modifier.PUBLIC + returnType = "java.lang.Integer" + } + }.forEach { + it.className.toClass().resolve().firstMethod { name = it.methodName }.hook { after { + val Rresult = result + //YLog.info(it.methodName+"Root返回模式:$Rresult") + if (Rresult == 1) result = 0 + } } + } + } + if (prefs("games").getBoolean("hok_ai_v2", false)) { + it.findMethod { + searchPackages("business.module.aiplay.sgame") + matcher { + modifiers = Modifier.PUBLIC + returnType = "boolean" + usingStrings("feature.support.game.AI_PLAY_version2") + } + }.singleOrNull()?.also { + it.className.toClass().resolve().firstMethod { name = it.methodName }.hook { before { result = true } } + } + } + if (prefs("games").getBoolean("hok_ai_v3", false)) { + it.findMethod { + searchPackages("business.module.aiplay.sgame") + matcher { + modifiers = Modifier.PUBLIC + returnType = "boolean" + usingStrings("feature.support.game.AI_PLAY_version3") + } + }.singleOrNull()?.also { + it.className.toClass().resolve().firstMethod { name = it.methodName }.hook { before { result = true } } + } + } + /*it.findClass { + searchPackages("com.coloros.gamespaceui.config") + matcher { + usingStrings("ServerConfigManager","parseJsonToUnit() error, json: ") + usingStrings("setCloudControlRecord key: ") + usingStrings("getApplicationContext(...)") + } + }.findMethod { + matcher { + paramTypes = emptyList() + returnType = "java.util.Map" + } + }.singleOrNull()?.also { + it.className.toClass().resolve().firstMethod { + name = it.methodName + }.hook { + after { + val res = result + if (res !is Map<*, *>) { + return@after + } + val newMap = res.toMutableMap() + if (newMap.containsKey("game_assist_key_back_list_key")) { + newMap["game_assist_key_back_list_key"] = "[\\n {\\n \\\"conditionSet\\\": [\\n {\\n \\\"name\\\": \\\"supportedGames\\\",\\n \\\"logic\\\": 4,\\n \\\"value\\\": [\\n \\\"com.tencent.tmgp.sg\\\"\\n ]\\n }\\n ],\\n \\\"result\\\": {\\n \\\"functionEnabled\\\": 1\\n }\\n },\\n {\\n \\\"conditionSet\\\": [\\n ],\\n \\\"result\\\": {\\n \\\"functionEnabled\\\": 0\\n }\\n }\\n]" + } + YLog.info(newMap.toMap()) + result = newMap.toMap() + } + } + }*/ + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/health/health.kt b/app/src/main/java/com/suqi8/oshin/hook/health/health.kt new file mode 100644 index 00000000..722e00a3 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/health/health.kt @@ -0,0 +1,22 @@ +package com.suqi8.oshin.hook.health + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method + +class health: YukiBaseHooker() { + override fun onHook() { + if (prefs("health").getBoolean("disable_root_dialog", false)) { + loadApp(name = "com.heytap.health") { + "com.heytap.health.safety.safetycheck.SafetyCheckManager\$check\$1".toClass().apply { + method { + name = "invokeSuspend" + }.hook { + before { + args[0] = null + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/incallui/call_ringtone.kt b/app/src/main/java/com/suqi8/oshin/hook/incallui/call_ringtone.kt new file mode 100644 index 00000000..02e45cc3 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/incallui/call_ringtone.kt @@ -0,0 +1,32 @@ +package com.suqi8.oshin.hook.incallui + +import android.annotation.SuppressLint +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.java.BooleanType + +class call_ringtone : YukiBaseHooker() { + @SuppressLint("PrivateApi") + override fun onHook() { + if (prefs("incallui").getBoolean("hide_call_ringtone", false)) { + loadApp(name = "com.android.incallui") { + "com.android.incallui.Call".toClass().apply { + method { + name = "getVideoCall" + emptyParam() + returnType = "android.telecom.InCallService\$VideoCall" + }.hook { + before { result = null } + } + method { + name = "getIsVideoRingTone" + emptyParam() + returnType = BooleanType + }.hook { + replaceToFalse() + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/incallui/incallui.kt b/app/src/main/java/com/suqi8/oshin/hook/incallui/incallui.kt new file mode 100644 index 00000000..19296754 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/incallui/incallui.kt @@ -0,0 +1,9 @@ +package com.suqi8.oshin.hook.incallui + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker + +class incallui: YukiBaseHooker() { + override fun onHook() { + loadApp(hooker = call_ringtone()) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/launcher/launcher.kt b/app/src/main/java/com/suqi8/oshin/hook/launcher/launcher.kt new file mode 100644 index 00000000..51aa21fd --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/launcher/launcher.kt @@ -0,0 +1,193 @@ +package com.suqi8.oshin.hook.launcher + +import android.annotation.SuppressLint +import android.util.Pair +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.java.BooleanType +import com.highcapable.yukihookapi.hook.type.java.FloatType +import com.highcapable.yukihookapi.hook.type.java.UnitType +import java.util.List + +class launcher: YukiBaseHooker() { + @SuppressLint("UseCompatLoadingForDrawables", "SetTextI18n") + override fun onHook() { + loadHooker(hooker = recent_task()) + loadApp("com.android.launcher"){ + val prefs = prefs("launcher") + val set_anim_level = prefs.getFloat("set_anim_level", -1f) + if (set_anim_level != -1f) { + "com.android.common.util.PlatformLevelUtils\$animationLevelOS14\$2".toClass().apply { + method { + name = "invoke" + emptyParam() + returnType = "java.lang.Integer" + }.hook { + before { + result = set_anim_level.toInt() + } + } + } + } + if (prefs.getBoolean("force_enable_fold_mode", false)) { + if (prefs.getInt("fold_mode",0) == 0) { + "com.android.common.util.ScreenUtils".toClass().apply { + method { + name = "isFoldScreenExpanded" + emptyParam() + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + } else { + "com.android.common.util.ScreenUtils".toClass().apply { + method { + name = "isFoldScreenFolded" + emptyParam() + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + } + "com.android.common.util.ScreenUtils".toClass().apply { + method { + name = "isSupportDockerExpandScreen" + emptyParam() + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + } + if (prefs.getBoolean("add_more_desktop_layouts")) { + var cachedLayoutList: List>>? = null + "com.android.launcher.UiConfig".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PRIVATE, Modifiers.STATIC) + name = "getLayoutNormal" + emptyParameters() + returnType = "java.util.List" + }.hook { + replaceAny { + val cached = cachedLayoutList + if (cached != null) { + // 3. 如果缓存存在,直接返回它 + return@replaceAny cached + } + + val list = java.util.ArrayList>>() + + for (cols in 2..20) { // 列数 (从 2 到 20) + for (rows in 2..20) { // 行数 (从 2 到 20) + + val layoutPair = Pair( + cols, + Pair(rows, rows) + ) + list.add(layoutPair) + } + } + + cachedLayoutList = list as List>>? + + return@replaceAny list + } + } + } + } + if (prefs.getBoolean("force_enable_fold_dock", false)) { + "com.android.launcher3.OplusHotseat".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "onDraw" + parameters("android.graphics.Canvas") + returnType = Void.TYPE + }.hook { + after { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "setDockerBackground" + emptyParameters() + returnType = Void.TYPE + }.of(instance).invoke() + } + } + } + } + if (prefs.getFloat("dock_transparency", 1f) != 1f) { + "com.android.launcher3.OplusHotseat".toClass().apply { + method { + name = "setBackgroundAlpha" + param(FloatType) + returnType = UnitType + }.hook { + before { + args[0] = prefs.getFloat("dock_transparency", 1f) + } + } + } + } + if (prefs.getBoolean("force_enable_dock_blur", false)) { + "com.android.launcher3.uioverrides.states.blurdrawable.OplusBlurProperties".toClass().apply { + method { + name = "isSupportNewBlur" + param("android.content.Context", BooleanType) + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + "com.android.common.util.ScreenUtils".toClass().apply { + method { + name = "hasLargeDisplayFeatures" + emptyParam() + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + } + + val customRadiusDp = prefs.getFloat("custom_blur_corner", -1f) + if (customRadiusDp != -1f) { + "com.android.launcher3.uioverrides.states.blurdrawable.OplusBlurProperties".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC, Modifiers.FINAL) + name = "setBlurCornerRadius" + parameters(Float::class, Boolean::class, "com.android.launcher3.model.data.ItemInfo") + returnType = "com.android.launcher3.uioverrides.states.blurdrawable.OplusBlurProperties" + }.hook { + before { + val contextRef = firstField { + modifiers(Modifiers.PRIVATE, Modifiers.FINAL) + name = "contextRef" + type = "java.lang.ref.WeakReference" + }.of(instance).get() as java.lang.ref.WeakReference + val displayMetrics = contextRef.get()!!.resources.displayMetrics + val customRadiusPx = android.util.TypedValue.applyDimension( + android.util.TypedValue.COMPLEX_UNIT_DIP, + customRadiusDp, + displayMetrics + ) + args[0] = customRadiusPx + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/launcher/recent_task.kt b/app/src/main/java/com/suqi8/oshin/hook/launcher/recent_task.kt new file mode 100644 index 00000000..e201df92 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/launcher/recent_task.kt @@ -0,0 +1,38 @@ +package com.suqi8.oshin.hook.launcher + +import android.annotation.SuppressLint +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.java.BooleanType + +class recent_task: YukiBaseHooker() { + @SuppressLint("UseCompatLoadingForDrawables", "SetTextI18n") + override fun onHook() { + loadApp("com.android.launcher"){ + if (prefs("launcher\\recent_task").getBoolean("force_display_memory", false)) { + "com.oplus.quickstep.memory.MemoryInfoManager".toClass().apply { + method { + name = "isAllowMemoryInfoDisplay" + emptyParam() + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + "com.oplus.quickstep.memory.MemoryInfoManager".toClass().apply { + method { + name = "needMemoryDetail" + emptyParam() + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/mihealth/mihealth.kt b/app/src/main/java/com/suqi8/oshin/hook/mihealth/mihealth.kt new file mode 100644 index 00000000..e518e41a --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/mihealth/mihealth.kt @@ -0,0 +1,125 @@ +package com.suqi8.oshin.hook.mihealth + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.java.BooleanType +import com.highcapable.yukihookapi.hook.type.java.UnitType + +class mihealth: YukiBaseHooker() { + override fun onHook() { + if (prefs("mihealth").getBoolean("enable_alarm_reminder", false)) { + loadApp(name = "com.mi.health") { + "com.xiaomi.fitness.devicesettings.base.clock.AlarmClockViewModel".toClass().apply { + method { + name = "hasClockInstalled" + param("android.content.pm.PackageManager") + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + "com.xiaomi.fitness.common.utils.RomUtils".toClass().apply { + method { + name = "isXiaomi" + emptyParam() + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + "com.xiaomi.fitness.common.utils.AppUtil".toClass().apply { + method { + name = "isPlayChannel" + emptyParam() + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + "com.xiaomi.fitness.common.utils.RomUtils".toClass().apply { + method { + name = "isOppo" + emptyParam() + returnType = BooleanType + }.hook { + before { + result = false + } + } + } + "com.xiaomi.fitness.common.utils.RomUtils".toClass().apply { + method { + name = "isMiui" + emptyParam() + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + "com.xiaomi.fitness.common.utils.RomUtils".toClass().apply { + method { + name = "isMIUIRom" + param("android.content.Context") + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + "com.xiaomi.fitness.common.utils.RomUtils".toClass().apply { + method { + name = "checkIsMiui" + param(BooleanType) + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + "com.xiaomi.fitness.common.utils.RomUtils\$RomInfo".toClass().apply { + method { + name = "component5" + emptyParam() + returnType = "java.lang.String" + }.hook { + before { + result = "25010PN30C" + } + } + } + "com.xiaomi.fitness.common.utils.RomUtils\$RomInfo".toClass().apply { + method { + name = "getName" + emptyParam() + returnType = "java.lang.String" + }.hook { + before { + result = "xiaomi" + } + } + } + "com.xiaomi.fitness.common.utils.RomUtils\$RomInfo".toClass().apply { + method { + name = "setModel" + param("java.lang.String") + returnType = UnitType + }.hook { + before { + args[0] = "25010PN30C" + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/mms/mms.kt b/app/src/main/java/com/suqi8/oshin/hook/mms/mms.kt new file mode 100644 index 00000000..ea7860f0 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/mms/mms.kt @@ -0,0 +1,34 @@ +package com.suqi8.oshin.hook.mms + +import android.annotation.SuppressLint +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import org.luckypray.dexkit.DexKitBridge + +class mms: YukiBaseHooker() { + @SuppressLint("UseCompatLoadingForDrawables", "SetTextI18n") + override fun onHook() { + loadApp("com.android.mms"){ + if (prefs("mms").getBoolean("remove_message_ads", false)) { + DexKitBridge.create(this.appInfo.sourceDir).use { + it.findClass { + matcher { + usingStrings("e = ","TedCardUtil") + usingStrings("isParsedSmsEntity: isSyncingMessage = ") + usingStrings("isVerificationCode = ") + usingStrings("getSmsEntity: JSONException") + } + } .findMethod { + matcher { + paramTypes(null, "java.util.HashMap", "java.util.HashMap") + returnType = "java.util.List" + usingStrings("\\s") + } + }.singleOrNull()?.also { + it.className.toClass().resolve().firstMethod { name = it.methodName }.hook { before { result = emptyList() } } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/notificationmanager/NotificationCategoryControl.kt b/app/src/main/java/com/suqi8/oshin/hook/notificationmanager/NotificationCategoryControl.kt new file mode 100644 index 00000000..bfe48ef6 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/notificationmanager/NotificationCategoryControl.kt @@ -0,0 +1,40 @@ +package com.suqi8.oshin.hook.notificationmanager + +import android.app.NotificationChannel +import com.highcapable.kavaref.KavaRef.Companion.asResolver +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker + +class NotificationCategoryControl : YukiBaseHooker() { + override fun onHook() { + if (prefs("notificationmanager").getBoolean("allow_turn_off_all_categories", false)) { + NotificationChannel::class.java.resolve().apply { + constructor { + optional() + }.hookAll { + after { + instance.asResolver().firstField { name = "mBlockableSystem" }.set(true) + instance.asResolver().firstField { name = "mImportanceLockedDefaultApp" }.set(false) + } + } + firstMethod { name = "isBlockable" }.hook { replaceToTrue() } + firstMethod { name = "setBlockable" }.hook { replaceUnit { } } + firstMethod { name = "isImportanceLockedByCriticalDeviceFunction" }.hook { replaceAny { false } } + firstMethod { name = "setImportanceLockedByCriticalDeviceFunction" }.hook { replaceUnit { } } + } + loadApp(name = "com.oplus.notificationmanager") { + "com.oplus.notificationmanager.property.uicontroller.BooleanController".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "isEnabled" + emptyParameters() + returnType = Boolean::class + }.hook { + replaceToTrue() + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/notificationmanager/NotificationManager.kt b/app/src/main/java/com/suqi8/oshin/hook/notificationmanager/NotificationManager.kt new file mode 100644 index 00000000..4d83d5dd --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/notificationmanager/NotificationManager.kt @@ -0,0 +1,9 @@ +package com.suqi8.oshin.hook.notificationmanager + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker + +class NotificationManager: YukiBaseHooker() { + override fun onHook() { + loadHooker(NotificationCategoryControl()) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/ocrscanner/ocrscanner.kt b/app/src/main/java/com/suqi8/oshin/hook/ocrscanner/ocrscanner.kt new file mode 100644 index 00000000..f8bb8a56 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/ocrscanner/ocrscanner.kt @@ -0,0 +1,47 @@ +package com.suqi8.oshin.hook.ocrscanner + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.java.BooleanType + +class ocrscanner: YukiBaseHooker() { + override fun onHook() { + if (prefs("ocrscanner").getBoolean("full_screen_translation", false)) { + loadApp(name = "com.coloros.ocrscanner") { + "com.oplus.scanner.screentrans.ui.ScreenTranslationRootView".toClass().apply { + method { + name = "s0" + param("com.oplus.scanner.screentrans.business.k") + returnType = BooleanType + }.hook { + before { + result = false + } + } + } + "com.oplus.scanner.screentrans.ui.ScreenTranslationRootView\$onNotSupportApp$1".toClass().apply { + method { + name = "invokeSuspend" + param("java.lang.Object") + returnType = "java.lang.Object" + }.hook { + before { + result = false + } + } + } + "com.oplus.scanner.screentrans.ui.ScreenTranslationToolCapsule".toClass().apply { + method { + name = "O0" + emptyParam() + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/oshare/oshare.kt b/app/src/main/java/com/suqi8/oshin/hook/oshare/oshare.kt new file mode 100644 index 00000000..4300f86a --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/oshare/oshare.kt @@ -0,0 +1,57 @@ +package com.suqi8.oshin.hook.oshare + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method +import org.luckypray.dexkit.DexKitBridge +import java.lang.reflect.Modifier + +class oshare: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.coloros.oshare") { + if (prefs("oshare").getBoolean("remove_oshare_auto_off", false)) { + DexKitBridge.create(this.appInfo.sourceDir).use { + it.findClass { + matcher { + addMethod { + usingStrings("last_radar_signal_state") + } + addMethod { + usingStrings("updateLastTurnOnTime time = ") + } + addMethod { + usingStrings("cta_dialog_should_show") + } + } + }.findMethod { + matcher { + usingStrings("updateLastTurnOnTime time = ") + } + }.singleOrNull()?.also { + it.className.toClass().method { name = it.methodName }.hook { before { + //val addtime = (prefs("oshare").getInt("transfer_time_modify", 10) * 1000 * 60).toLong() + args[1] = 0L//args[1] as Long + addtime + } } + } + it.findClass { + matcher { + addMethod { + usingStrings("commonOShareSwitch = ","RomUpdateListManager","testAllList assertCheckResult successful! And you must check other logs.") + } + } + }.singleOrNull()?.findMethod { + matcher { + modifiers = Modifier.PUBLIC + paramTypes("android.content.Context") + returnType = "long" + } + }?.singleOrNull()?.also { + it.className.toClass().method { name = it.methodName }.hook { after { + //val addtime = prefs("oshare").getInt("transfer_time_modify", 10).toLong() + result = 0L//result as Long + addtime + } } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/ota/ota.kt b/app/src/main/java/com/suqi8/oshin/hook/ota/ota.kt new file mode 100644 index 00000000..ad7bb8f3 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/ota/ota.kt @@ -0,0 +1,215 @@ +package com.suqi8.oshin.hook.ota + +import android.content.ContentResolver +import android.provider.Settings +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method +import org.luckypray.dexkit.DexKitBridge + +class ota : YukiBaseHooker() { + override fun onHook() { + val prefs = prefs("ota") + loadApp(name = "com.oplus.ota") { + val bridge = DexKitBridge.create(this.appInfo.sourceDir) + bridge.also { + it.findClass { + matcher { + addMethod { + usingStrings( + "upgrade_show_download_dialog_time_interval", + "entry ui is not exist!!" + ) + } + addMethod { + usingStrings( + "upgrade_show_install_dialog_time_interval", + "global_dialog_install_delay" + ) + } + } + }.singleOrNull()?.also { + if (prefs.getBoolean("remove_system_update_dialog", false)) { + it.findMethod { + matcher { + usingStrings("There are no overlays right to showNotifyDownloadDialog, so return") + } + }.singleOrNull()?.also { + it.className.toClass().method { name = it.methodName } + .hook { replaceUnit { } } + } + } + if (prefs.getBoolean("remove_wlan_auto_download_dialog", false)) { + it.findMethod { + matcher { + usingStrings( + "upgrade_show_download_dialog_time_interval", + "OTA_NoticeAlertDialog" + ) + } + }.singleOrNull()?.also { + it.className.toClass().method { name = it.methodName } + .hook { replaceUnit { } } + } + } + } + if (prefs.getBoolean("remove_system_update_notification", false)) { + it.findClass { + matcher { + addMethod { + usingStrings( + "ota_notify_new_channel_default_id", + "ota_notify_new_channel_id" + ) + } + addMethod { + usingStrings( + "NotificationHelper notifyABFinalizingProgress", + "NotificationHelper initABFinalizingNotificationBuilder" + ) + } + } + }.singleOrNull()?.also { + it.findMethod { + matcher { + usingStrings( + "notifyNewVersionUpdate false, big version upgrade and not has enough space", + "notifyNewVersionUpdate false, has disable download and install remind" + ) + } + }.singleOrNull()?.also { + it.className.toClass().method { name = it.methodName } + .hook { replaceUnit { } } + } + } + } + if (prefs.getBoolean("remove_wlan_auto_download_dialog", false)) { + it.findClass { + searchPackages("com.oplus.common") + matcher { + addMethod { + usingStrings( + "initStmapAndVersionType", + "can not get oplus_custom_ota_version_info " + ) + } + addMethod { + usingStrings( + "OTA_AUTO_DOWNLOAD_STATUS should check the alarm now", + "User change switch to Wlan, so set alarm" + ) + } + } + }.singleOrNull()?.also { + it.findMethod { + matcher { + usingStrings("ro.boot.veritymode", "ro.boot.vbmeta.device_state") + } + }.singleOrNull()?.also { + it.className.toClass().method { name = it.methodName } + .hook { before { result = false } } + } + } + } + } + if (prefs.getBoolean("bypass_preinstall_checks", false)) { + val className = bridge.findClass { + matcher { + usingStrings("forbid_ota_local_update") + } + } + className.findMethod { + matcher { + usingStrings("forbid_ota_local_update") + } + }.forEach { + it.className.toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC, Modifiers.FINAL) + name = it.methodName + parameters("android.content.Context", String::class) + returnType = Int::class + }.hook { + after { + className.findField { + matcher { + type = "boolean" + } + }.forEach { + firstField { + modifiers(Modifiers.PUBLIC) + name = it.name + type = Boolean::class + }.of(instance).set(false) + } + } + } + } + } + } + if (prefs.getBoolean("force_show_local_install", false)) { + Settings.Global::class.java.resolve().firstMethod { + name = "getInt" + parameters(ContentResolver::class, String::class, Int::class) + }.hook { + before { + if ("development_settings_enabled" == args[1]) result = 1 + } + } + } + + if (prefs.getBoolean("force_download_last_update_package", false)) { + val className = bridge.findClass { + matcher { + usingStrings("ro.build.display.id.show") + } + } + className.findMethod { + matcher { + usingStrings("ro.build.display.id.show") + } + }.singleOrNull()?.also { + it.className.toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC, Modifiers.STATIC) + name = it.methodName + emptyParameters() + returnType = String::class + }.hook { + after { + // 匹配 PLK110_16.0.0.001(CN01) 末尾三位数字并替换为 001 + val origin = result as? String ?: "" + val replaced = origin.replace(Regex("(\\.\\d{3}\\()"), ".001(") + result = replaced + } + } + } + } + + className.findMethod { + matcher { + usingStrings("ro.build.version.ota") + } + }.singleOrNull()?.also { + it.className.toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC, Modifiers.STATIC) + name = it.methodName + emptyParameters() + returnType = String::class + }.hook { + after { + // 匹配 _xxxx_yyyyyyyyyyyy 为 _0001_197001010001 + val origin = result as? String ?: "" + val replaced = + origin.replace(Regex("_(\\d{4})_(\\d{12})")) { "_0001_197001010001" } + result = replaced + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/padconnect/BypassSameAccountUnlockCheck.kt b/app/src/main/java/com/suqi8/oshin/hook/padconnect/BypassSameAccountUnlockCheck.kt new file mode 100644 index 00000000..b55187bc --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/padconnect/BypassSameAccountUnlockCheck.kt @@ -0,0 +1,38 @@ +package com.suqi8.oshin.hook.padconnect + +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.log.YLog +import org.luckypray.dexkit.DexKitBridge + +class BypassSameAccountUnlockCheck: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.oplus.padconnect") { + DexKitBridge.create(this.appInfo.sourceDir).use { + if (prefs("padconnect").getBoolean("bypass_same_account_unlock_safety_check", false)) { + it.findClass { + searchPackages("com.oplus.sdp") + matcher { + usingStrings("deviceNotSupportDialog != null && deviceNotSupportDialog.isShowing()") + } + }.singleOrNull()?.apply { + name.toClass().resolve().apply { + firstConstructor { + modifiers(Modifiers.PUBLIC) + parameters("com.oplus.padconnect.nfc.unlock.ui.NFCUnlockDevicePreference", Int::class) + }.hook { + before { + //YLog.debug("原始参数 args[1] = ${args[1]}") + if (args[1] == 0) { + args[1] = 1 + } + } + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/padconnect/padconnect.kt b/app/src/main/java/com/suqi8/oshin/hook/padconnect/padconnect.kt new file mode 100644 index 00000000..0d0c4672 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/padconnect/padconnect.kt @@ -0,0 +1,9 @@ +package com.suqi8.oshin.hook.padconnect + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker + +class padconnect: YukiBaseHooker() { + override fun onHook() { + loadHooker(BypassSameAccountUnlockCheck()) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/Action.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/Action.kt new file mode 100644 index 00000000..3ae896fd --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/Action.kt @@ -0,0 +1,13 @@ +package com.suqi8.oshin.hook.phone + +/** + * 定义了一个名为 Action 的通用接口。 + * 是一个泛型参数,表示这个动作可以返回任何类型的结果。 + */ +interface Action { + /** + * 声明所有实现此接口的类,都必须拥有一个名为 action 的方法。 + * 这个方法负责执行具体的操作,并可以返回一个 T 类型的结果,或者返回 null。 + */ + fun action(): T? +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/AutoInputAction.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/AutoInputAction.kt new file mode 100644 index 00000000..1456ff0f --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/AutoInputAction.kt @@ -0,0 +1,29 @@ +package com.suqi8.oshin.hook.phone + +import android.content.Context +import android.os.Bundle + +/** + * 一个具体的操作类,负责执行“自动输入验证码”的逻辑。 + */ +class AutoInputAction( + pluginContext: Context?, + phoneContext: Context?, + smsMsg: SmsMsg? +) : CallableAction(pluginContext, phoneContext, smsMsg) { + + /** + * 当此操作被调度执行时,这是入口点。 + */ + override fun action(): Bundle? { + prepareAutoInputCode(mSmsMsg?.smsCode) + return null + } + + /** + * 准备自动输入,会先检查当前应用是否在黑名单中。 + */ + private fun prepareAutoInputCode(code: String?) { + code?.let { InputHelper.sendText(it) } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/CallableAction.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/CallableAction.kt new file mode 100644 index 00000000..1caf73fd --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/CallableAction.kt @@ -0,0 +1,29 @@ +package com.suqi8.oshin.hook.phone + +import android.content.Context +import android.os.Bundle +import java.util.concurrent.Callable + +/** + * 一个通用的抽象基类,结合了自定义的 Action 接口和 Java 的 Callable 接口。 + * 它为所有需要在后台线程执行并可能返回结果的任务提供了一个统一的模板。 + */ +abstract class CallableAction( + // 将所有子类都可能用到的通用数据和上下文作为受保护成员,方便子类直接访问 + protected var mPluginContext: Context?, + protected var mPhoneContext: Context?, + protected var mSmsMsg: SmsMsg? +) : Action, Callable { // 实现自定义的 Action 接口和 Java 的 Callable 接口 + + /** + * 实现了 Callable 接口的 call 方法。 + * 这是任务在线程池中被执行的入口。 + */ + override fun call(): Bundle? { + return try { + action() + } catch (t: Throwable) { + null + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/CancelNotifyAction.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/CancelNotifyAction.kt new file mode 100644 index 00000000..e42e01ad --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/CancelNotifyAction.kt @@ -0,0 +1,51 @@ +package com.suqi8.oshin.hook.phone + +import android.app.NotificationManager +import android.content.Context +import android.os.Bundle + +/** + * 一个具体的操作类,负责执行“自动清除通知”的逻辑。 + */ +class CancelNotifyAction( + pluginContext: Context?, + phoneContext: Context?, + smsMsg: SmsMsg? +) : CallableAction(pluginContext, phoneContext, smsMsg) { + // 保存要清除的通知的 ID + private var mNotificationId: Int = NOTIFICATION_NONE + + /** + * 设置此任务要清除的通知 ID。 + */ + fun setNotificationId(notificationId: Int) { + mNotificationId = notificationId + } + + /** + * 当此操作被调度执行时,这是入口点。 + */ + override fun action(): Bundle? { + cancelNotification() + return null + } + + /** + * 执行清除通知的具体操作。 + */ + private fun cancelNotification() { + // 确保我们有一个有效的通知 ID + if (mNotificationId != NOTIFICATION_NONE) { + // 获取系统的通知管理器 + val manager = + mPhoneContext?.let { it.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager? } + + manager?.cancel(mNotificationId) + } + } + + companion object { // 定义常量 + // 一个无效的通知 ID,用作默认值 + private const val NOTIFICATION_NONE = -0xff + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/ClipboardUtils.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/ClipboardUtils.kt new file mode 100644 index 00000000..d738e1c8 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/ClipboardUtils.kt @@ -0,0 +1,17 @@ +package com.suqi8.oshin.hook.phone + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context + +object ClipboardUtils { + @JvmStatic + fun copyToClipboard(context: Context, text: String?) { + val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager? + if (cm == null) { + return + } + val clipData = ClipData.newPlainText("Copy text", text) + cm.setPrimaryClip(clipData) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/CodeWorker.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/CodeWorker.kt new file mode 100644 index 00000000..60b51e77 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/CodeWorker.kt @@ -0,0 +1,113 @@ +package com.suqi8.oshin.hook.phone + +import android.content.Context +import android.content.Intent +import android.os.Handler +import android.os.Looper +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit + +/** + * 验证码处理的核心工作类。 + * 负责接收短信意图,解析验证码,并调度一系列后续操作(如复制、通知、自动输入等)。 + */ +class CodeWorker internal constructor( + private val mPluginContext: Context?, // 本模块的上下文 + private val mPhoneContext: Context?, // "电话"应用的上下文 + smsIntent: Intent? // 包含短信内容的 Intent +) { + // 包含短信数据的 Intent + private val mSmsIntent: Intent? = smsIntent + + // 用于在主线程(UI线程)执行操作,如显示Toast + private val mUIHandler: Handler = Handler(Looper.getMainLooper()) + + // 一个单线程的线程池,用于执行后台和延迟任务 + private val mScheduledExecutor: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor() + + /** + * 主要的解析和调度方法 + */ + fun parse(): Boolean? { + // --- 第一步:异步解析短信,判断是否为验证码 --- + val smsParseAction = SmsParseAction(mPluginContext, mPhoneContext, null) + smsParseAction.setSmsIntent(mSmsIntent) + // 将解析任务提交到线程池,并获取 Future 对象以便等待结果 + val smsParseFuture = mScheduledExecutor.schedule(smsParseAction, 0, TimeUnit.MILLISECONDS) + + val smsMsg: SmsMsg? + try { + val parseBundle = smsParseFuture.get() // 等待解析任务完成并获取结果 + if (parseBundle == null) { + // 如果解析结果为空,说明不是验证码短信,直接退出 + return null + } + + // 检查是否是重复的短信 + val duplicated = parseBundle.getBoolean(SmsParseAction.SMS_DUPLICATED, false) + if (duplicated) { + return buildParseResult() // 如果是重复短信,也直接退出,但需要返回拦截状态 + } + + // 从结果中获取解析出的 SmsMsg 对象 + smsMsg = parseBundle.getParcelable(SmsParseAction.SMS_MSG, SmsMsg::class.java) + } catch (e: Exception) { + return null + } + + // --- 第二步:成功解析出验证码后,调度一系列后续操作 --- + + // 复制验证码到剪贴板 (在UI线程执行) + mUIHandler.post(CopyToClipboardAction(mPluginContext, mPhoneContext, smsMsg)) + + // 显示“已复制”提示 (在UI线程执行) + mUIHandler.post(ToastAction(mPluginContext, mPhoneContext, smsMsg)) + + // 如果开启了自动输入功能 + if (inputCode) { + val autoInputAction = AutoInputAction(mPluginContext, mPhoneContext, smsMsg) + val autoInputDelay = 0L * 1000L + // 延迟指定时间后,执行自动输入任务 + mScheduledExecutor.schedule(autoInputAction, autoInputDelay, TimeUnit.MILLISECONDS) + } + + // 显示“新验证码”通知 + val notifyAction = NotifyAction(mPluginContext, mPhoneContext, smsMsg) + val notificationFuture = mScheduledExecutor.schedule(notifyAction, 0, TimeUnit.MILLISECONDS) + + // 延迟3秒后,将短信标为已读或删除 + val operateSmsAction = OperateSmsAction(mPluginContext, mPhoneContext, smsMsg) + mScheduledExecutor.schedule(operateSmsAction, 3000, TimeUnit.MILLISECONDS) + + // 延迟4秒后,执行清理任务 (如关闭线程池) + /*val action = KillMeAction(mPluginContext, mPhoneContext, smsMsg, xsp) + mScheduledExecutor.schedule(action, 4000, TimeUnit.MILLISECONDS) +*/ + // --- 第三步:处理通知的自动清除 --- + try { + // 等待通知任务执行完毕,获取通知ID和设置的保留时间 + val bundle = notificationFuture.get() + if (bundle != null && bundle.containsKey(NotifyAction.NOTIFY_RETENTION_TIME)) { + val delay = bundle.getLong(NotifyAction.NOTIFY_RETENTION_TIME, 0L) + val notificationId = bundle.getInt(NotifyAction.NOTIFY_ID, 0) + // 创建一个清除通知的任务 + val cancelNotifyAction = CancelNotifyAction(mPluginContext, mPhoneContext, smsMsg) + cancelNotifyAction.setNotificationId(notificationId) + + // 延迟指定时间后,自动清除这条通知 + mScheduledExecutor.schedule(cancelNotifyAction, delay, TimeUnit.MILLISECONDS) + } + } catch (e: Exception) {} + + // --- 第四步:返回最终结果 --- + return buildParseResult() + } + + /** + * 构建最终结果,告诉调用者(SmsHandlerHook)是否要拦截原始短信 + */ + private fun buildParseResult(): Boolean { + return true + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/CopyCodeReceiver.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/CopyCodeReceiver.kt new file mode 100644 index 00000000..5335e1d2 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/CopyCodeReceiver.kt @@ -0,0 +1,117 @@ +package com.suqi8.oshin.hook.phone + +import android.annotation.SuppressLint +import android.content.BroadcastReceiver +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.widget.Toast +import androidx.core.content.ContextCompat +import com.suqi8.oshin.BuildConfig +import com.suqi8.oshin.R + +/** + * 一个广播接收器,负责处理通知栏上的“复制验证码”点击事件。 + */ +class CopyCodeReceiver : BroadcastReceiver() { + /** + * 使用 Holder 模式实现懒加载的单例,确保全局只有一个接收器实例。 + */ + private object CopyCodeReceiverHolder { + @SuppressLint("StaticFieldLeak") + val INSTANCE = CopyCodeReceiver() + } + + // 缓存本模块的上下文,用于获取资源 + private var mPluginContext: Context? = null + + /** + * 接收到广播时执行的核心方法。 + */ + override fun onReceive(phoneContext: Context, intent: Intent) { + val action = intent.action + // 只处理我们自己定义的“复制验证码”广播 + if (ACTION_COPY_CODE == action) { + // 从广播的 Intent 中获取验证码 + val smsCode = intent.getStringExtra(EXTRA_KEY_CODE) + // 将验证码复制到剪贴板 + copyToClipboard(phoneContext, smsCode) + + // 显示一个“已复制”的提示 + val pluginContext = createSmsCodeAppContext(phoneContext) + showToast(pluginContext, phoneContext, smsCode) + } + } + + fun copyToClipboard(context: Context, text: String?) { + val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager? + if (cm == null) return + val clipData = ClipData.newPlainText("Copy text", text) + cm.setPrimaryClip(clipData) + } + + /** + * 创建并缓存本模块的上下文,用于获取字符串等资源。 + */ + private fun createSmsCodeAppContext(phoneContext: Context): Context? { + if (mPluginContext == null) { + mPluginContext = phoneContext.createPackageContext( + BuildConfig.APPLICATION_ID, + Context.CONTEXT_IGNORE_SECURITY + ) + } + return mPluginContext + } + + /** + * 显示 Toast 提示。 + */ + private fun showToast(pluginContext: Context?, phoneContext: Context?, smsCode: String?) { + if (pluginContext != null && phoneContext != null) { + // 从模块的资源中获取提示文本,并显示 + val text = pluginContext.getString(R.string.prompt_sms_code_copied, smsCode) + Toast.makeText(phoneContext, text, Toast.LENGTH_LONG).show() + } + } + + companion object { // 定义常量和静态辅助方法 + // 自定义广播 Action,确保唯一性 + private const val ACTION_COPY_CODE = BuildConfig.APPLICATION_ID + ".ACTION_COPY_CODE" + // Intent 中传递验证码的 Key + private const val EXTRA_KEY_CODE = "extra_key_code" + + /** + * 获取接收器的单例实例。 + */ + fun newInstance(): CopyCodeReceiver { + return CopyCodeReceiverHolder.INSTANCE + } + + /** + * 创建一个能触发本接收器的 Intent。 + */ + @JvmStatic + fun createIntent(smsCode: String?): Intent { + val intent = Intent(ACTION_COPY_CODE) + intent.putExtra(EXTRA_KEY_CODE, smsCode) + return intent + } + + /** + * 将此接收器动态注册到系统中。 + */ + fun registerMe(context: Context) { + val receiver: CopyCodeReceiver = newInstance() + val filter = IntentFilter() + filter.addAction(ACTION_COPY_CODE) // 设置只接收指定 Action 的广播 + ContextCompat.registerReceiver( + context, + receiver, + filter, + ContextCompat.RECEIVER_NOT_EXPORTED // 设置为非导出,更安全 + ) + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/CopyToClipboardAction.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/CopyToClipboardAction.kt new file mode 100644 index 00000000..daab79d6 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/CopyToClipboardAction.kt @@ -0,0 +1,40 @@ +package com.suqi8.oshin.hook.phone + +import android.content.Context +import android.os.Bundle + +/** + * 一个具体的操作类,负责执行“复制验证码到剪贴板”的逻辑。 + * + * @param pluginContext 模块上下文。 + * @param phoneContext 电话应用上下文。 + * @param smsMsg 解析后的短信对象。 + * @param xsp Xposed 共享设置。 + */ +class CopyToClipboardAction( + pluginContext: Context?, + phoneContext: Context?, + smsMsg: SmsMsg? +) : RunnableAction(pluginContext, phoneContext, smsMsg) { + + /** + * 当此操作被调度执行时,这是入口点。 + */ + override fun action(): Bundle? { + // 首先检查用户是否在设置中开启了“自动复制”功能。 + // 假设 xsp 是从父类 RunnableAction 继承的属性。 + if (copyCode) { + copyToClipboard() + } + return null // 此操作不返回任何结果。 + } + + /** + * 调用工具类,执行实际的复制操作。 + */ + private fun copyToClipboard() { + mSmsMsg?.smsCode?.let { code -> + mPluginContext?.let { ClipboardUtils.copyToClipboard(it, code) } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/EntityStoreManager.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/EntityStoreManager.kt new file mode 100644 index 00000000..0d837ce0 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/EntityStoreManager.kt @@ -0,0 +1,112 @@ +package com.suqi8.oshin.hook.phone + +import android.annotation.SuppressLint +import android.os.Environment +import com.google.gson.GsonBuilder +import com.highcapable.yukihookapi.hook.log.YLog +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.nio.charset.StandardCharsets + +/** + * 实体存储管理器。 + * 使用 Kotlin 的 `object` 关键字,这会自动实现单例模式,确保全局只有一个实例。 + * 主要功能是将各种数据实体(如短信规则、拦截应用列表等)以 JSON 格式存储到应用私有文件中,或从文件中读取。 + */ +object EntityStoreManager { + /** + * 将一个实体对象列表序列化为 JSON 并存储到文件中。 + * @param T 泛型参数,代表实体的类型。 + * @param entityType 要存储的实体类型,用于决定文件名。 + * @param entities 要存储的实体对象列表。 + * @return 操作成功返回 `true`,失败返回 `false`。 + */ + fun storeEntitiesToFile(entities: List?): Boolean { + return try { + val baseDir = File(Environment.getDataDirectory(), "data/com.android.phone").resolve("files") + if (!baseDir.exists()) baseDir.mkdirs() + val storeFile = baseDir.resolve("prev_code_record") + // 使用 Kotlin 的扩展函数 `writer()` 和 `use` 块。 + // `writer(StandardCharsets.UTF_8)` 创建一个 OutputStreamWriter。 + // `use` 会在代码块执行完毕后自动关闭流,无需手动写 finally 和 close(),代码更简洁安全。 + FileOutputStream(storeFile).writer(StandardCharsets.UTF_8).use { writer -> + GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().toJson(entities, writer) + } + + setFileWorldWritable(storeFile, 0) + true // 操作成功 + } catch (e: Exception) { + YLog.error("",e) + false // 发生异常,操作失败 + } + } + + @JvmStatic + @SuppressLint("SetWorldWritable", "SetWorldReadable") + fun setFileWorldWritable(startFile: File?, parentDepth: Int) { + if (startFile == null || !startFile.exists()) { + return + } + var currentFile: File? = startFile + repeat(parentDepth + 1) { + currentFile?.apply { + setExecutable(true, false) + setWritable(true, false) + setReadable(true, false) + } + currentFile = currentFile?.parentFile + } + } + + /** + * 从文件中读取 JSON 数据并反序列化为实体对象列表。 + * @param T 泛型参数,代表实体的类型。 + * @param entityType 要加载的实体类型。 + * @param entityClass 实体的 Class 对象,用于 JSON 反序列化。 + * @return 返回一个包含实体对象的列表。如果文件不存在或发生错误,返回一个空列表。 + */ + fun loadEntitiesFromFile(entityClass: Class): List { + val baseDir = File(Environment.getDataDirectory(), "data/com.android.phone").resolve("files") + if (!baseDir.exists()) baseDir.mkdirs() + val storeFile = baseDir.resolve("prev_code_record") + // 如果文件不存在,直接返回空列表,避免不必要的 IO 操作 + if (!storeFile.exists()) { + return emptyList() + } + + return try { + // 同样使用 `reader()` 和 `use` 块来自动管理资源 + FileInputStream(storeFile).reader(StandardCharsets.UTF_8).use { reader -> + GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().fromJson(reader, ListOfJson(entityClass)) + } + } catch (e: Exception) { + YLog.error("",e) + emptyList() + } + } + + private class ListOfJson(private val wrapped: Class) : ParameterizedType { + // 使用主构造函数属性和单表达式函数,代码更简洁 + override fun getActualTypeArguments(): Array = arrayOf(wrapped) + override fun getRawType(): Type = List::class.java + override fun getOwnerType(): Type? = null + } + + /** + * 从文件中加载单个实体的便捷方法。 + * 内部调用 `loadEntitiesFromFile` 并返回列表中的第一个元素。 + * @param T 泛型参数,代表实体的类型。 + * @param entityType 要加载的实体类型。 + * @param entityClass 实体的 Class 对象。 + * @return 如果成功加载且列表不为空,则返回第一个实体对象;否则返回 `null`。 + */ + fun loadEntityFromFile(entityClass: Class): T? { + val entities = loadEntitiesFromFile(entityClass) + // 使用 Kotlin 集合的扩展函数 `firstOrNull()`,它会安全地返回列表的第一个元素, + // 如果列表为空,则返回 null。这比 Java 的 `if (list != null && !list.isEmpty())` 检查更简洁。 + return entities.firstOrNull() + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/InputHelper.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/InputHelper.kt new file mode 100644 index 00000000..1b6f6cf9 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/InputHelper.kt @@ -0,0 +1,82 @@ +package com.suqi8.oshin.hook.phone + +import android.annotation.SuppressLint +import android.hardware.input.InputManager +import android.view.InputDevice +import android.view.KeyCharacterMap +import android.view.KeyEvent +import de.robv.android.xposed.XposedHelpers + +/** + * 模拟输入法输入字符的辅助工具类。

+ * 参考了 com.android.commands.input.Input 的实现。 + */ +object InputHelper { + /** + * 模拟输入一段指定的文本。 + * @param text 要输入的文本 + * @throws Throwable 如果没有 INJECT_EVENTS 权限会抛出异常 + */ + @Throws(Throwable::class) + fun sendText(text: String) { + val source = InputDevice.SOURCE_KEYBOARD // 定义输入源为键盘 + + val sb = kotlin.text.StringBuilder(text) + + // 处理特殊转义字符,例如将 %s 转换为空格 (这是模仿了 adb shell input 命令的行为) + var escapeFlag = false + var i = 0 + while (i < sb.length) { + if (escapeFlag) { + escapeFlag = false + if (sb[i] == 's') { + sb.setCharAt(i, ' ') + sb.deleteCharAt(--i) + } + } + if (sb[i] == '%') { + escapeFlag = true + } + i++ + } + + val chars = sb.toString().toCharArray() + + // 获取一个虚拟键盘的按键映射表 + val kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD) + // 将字符串转换成一个按键事件的序列 (例如,输入'A'需要 SHIFT_DOWN, A_DOWN, A_UP, SHIFT_UP) + val events = kcm.getEvents(chars) + // 遍历并注入每一个按键事件 + for (keyEvent in events) { + if (source != keyEvent.source) { + keyEvent.source = source + } + injectKeyEvent(keyEvent) + } + } + + /** + * 将单个按键事件注入到系统中。 + */ + @SuppressLint("PrivateApi") + @Throws(Throwable::class) + private fun injectKeyEvent(keyEvent: KeyEvent?) { + // 通过反射获取系统输入管理器 InputManager + val inputManager = + XposedHelpers.callStaticMethod(InputManager::class.java, "getInstance") as InputManager? + + // 获取注入模式:等待事件处理完成后再继续,这样可以保证输入的顺序和稳定性 + val INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = + XposedHelpers.getStaticIntField( + InputManager::class.java, + "INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH" + ) + + val paramTypes = + arrayOf(KeyEvent::class.java, Int::class.javaPrimitiveType) + val args = arrayOf(keyEvent, INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH) + + // 调用 InputManager 的内部方法 injectInputEvent 来实现注入 + XposedHelpers.callMethod(inputManager, "injectInputEvent", paramTypes, *args) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/NotificationConst.java b/app/src/main/java/com/suqi8/oshin/hook/phone/NotificationConst.java new file mode 100644 index 00000000..7d574dfe --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/NotificationConst.java @@ -0,0 +1,9 @@ +package com.suqi8.oshin.hook.phone; + +/** + * Notification Constants + */ +public interface NotificationConst { + String CHANNEL_ID_SMSCODE_NOTIFICATION = "oshin_smscode"; + String GROUP_KEY_SMSCODE_NOTIFICATION = "group_key_smscode_notification"; +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/NotifyAction.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/NotifyAction.kt new file mode 100644 index 00000000..369b05a8 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/NotifyAction.kt @@ -0,0 +1,105 @@ +package com.suqi8.oshin.hook.phone + +import android.annotation.SuppressLint +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.graphics.BitmapFactory +import android.os.Bundle +import android.text.TextUtils +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat +import com.suqi8.oshin.R +import com.suqi8.oshin.hook.phone.CopyCodeReceiver.Companion.createIntent + +/** + * 一个具体的操作类,负责创建并显示“新验证码”通知。 + */ +class NotifyAction( + pluginContext: Context?, + phoneContext: Context?, + smsMsg: SmsMsg? +) : CallableAction(pluginContext, phoneContext, smsMsg) { + + /** + * 当此操作被调度执行时,这是入口点。 + */ + override fun action(): Bundle? { + // 检查用户是否在设置中开启了“显示通知”功能 + if (showCodeNotification) { + return mSmsMsg?.let { showCodeNotification(it) } // 如果开启了,就执行显示通知的操作 + } + return null + } + + /** + * 创建并显示通知的核心逻辑。 + * @return 如果设置了自动清除,则返回包含通知ID和延迟时间的Bundle。 + */ + @SuppressLint("UnspecifiedImmutableFlag", "NotificationPermission", "LaunchActivityFromNotification") + private fun showCodeNotification(smsMsg: SmsMsg): Bundle? { + // 获取系统的通知管理器 + val manager = + mPhoneContext?.let { it.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager? } + if (manager == null) { + return null + } + + // 准备通知的标题和内容 + val company = smsMsg.company + val smsCode = smsMsg.smsCode + val title = if (TextUtils.isEmpty(company)) smsMsg.sender else company + val content = mPluginContext?.getString(R.string.code_notification_content, smsCode) + + // 使用短信的哈希码作为通知ID,确保唯一性 + val notificationId = smsMsg.hashCode() + + // 创建一个点击通知时要执行的意图(在这里是复制验证码) + val copyCodeIntent = createIntent(smsCode) + // 创建一个 PendingIntent,它封装了 copyCodeIntent,以便在另一个进程(系统UI)中安全地执行 + // 这里的 FLAG_MUTABLE 和 FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT 是为了兼容新版安卓的安全策略 + val contentIntent = PendingIntent.getBroadcast( + mPhoneContext, + 0, + copyCodeIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT + ) + + // 使用 NotificationCompat.Builder 来构建通知 + val notification = mPluginContext?.let { context -> + NotificationCompat.Builder(context, NotificationConst.CHANNEL_ID_SMSCODE_NOTIFICATION).apply { + //setSmallIcon(R.drawable.icon) // 设置小图标 + setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.icon)) // 设置大图标 + setWhen(System.currentTimeMillis()) // 设置时间戳 + setContentTitle(title) // 设置标题 + setContentText(content) // 设置内容 + setContentIntent(contentIntent) // 设置点击事件 + setAutoCancel(true) // 点击后自动清除通知 + setColor(ContextCompat.getColor(context, R.color.colorPrimaryAccent)) // 设置颜色 + setGroup(NotificationConst.GROUP_KEY_SMSCODE_NOTIFICATION) // 将通知分组 + }.build() + } + + // 显示通知 + manager.notify(notificationId, notification) + + // 如果用户开启了“自动清除通知”功能 + /*if (XSPUtils.autoCancelCodeNotification(xsp)) { + // 获取用户设置的通知保留时间 + val retentionTime = XSPUtils.getNotificationRetentionTime(xsp) * 1000L + // 将通知ID和保留时间打包返回,以便后续的清除任务使用 + val bundle = Bundle() + bundle.putLong(NOTIFY_RETENTION_TIME, retentionTime) + bundle.putInt(NOTIFY_ID, notificationId) + return bundle + }*/ + return null + } + + companion object { // 定义常量 + // 用于在 Bundle 中传递通知保留时间的 Key + const val NOTIFY_RETENTION_TIME: String = "notify_retention_time" + // 用于在 Bundle 中传递通知ID的 Key + const val NOTIFY_ID: String = "notify_id" + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/OperateSmsAction.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/OperateSmsAction.kt new file mode 100644 index 00000000..9de16bab --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/OperateSmsAction.kt @@ -0,0 +1,139 @@ +package com.suqi8.oshin.hook.phone + +import android.Manifest +import android.content.ContentValues +import android.content.Context +import android.content.pm.PackageManager +import android.database.Cursor +import android.os.Bundle +import android.provider.Telephony +import androidx.annotation.IntDef +import androidx.core.content.ContextCompat +import com.highcapable.yukihookapi.hook.log.YLog + +/** + * 一个具体的操作类,负责将验证码短信删除或标记为已读。 + */ +class OperateSmsAction( + pluginContext: Context?, + phoneContext: Context?, + smsMsg: SmsMsg? +) : CallableAction(pluginContext, phoneContext, smsMsg) { + // 定义一个注解,限定操作类型只能是删除或标记已读 + @IntDef(OP_DELETE, OP_MARK_AS_READ) + private annotation class SmsOp + + /** + * 当此操作被调度执行时,这是入口点。 + */ + override fun action(): Bundle? { + val sender = mSmsMsg?.sender + val body = mSmsMsg?.body + // 根据用户的配置,决定是删除短信还是标记为已读 + if (false) { + body?.let { deleteSms(sender, it) } + } else if (true) { + body?.let { markSmsAsRead(sender, it) } + } + return null + } + + /** + * 将短信标记为已读。 + */ + private fun markSmsAsRead(sender: String?, body: String) { + YLog.info("正在将短信标记为已读...") + val result = operateSms(sender, body, OP_MARK_AS_READ) + if (result) { + YLog.info("成功将短信标记为已读") + } else { + YLog.info("标记短信为已读失败") + } + } + + /** + * 删除短信。 + */ + private fun deleteSms(sender: String?, body: String) { + YLog.info("正在删除短信...") + val result = operateSms(sender, body, OP_DELETE) + if (result) { + YLog.info("成功删除短信") + } else { + YLog.info("删除短信失败") + } + } + + + /** + * 根据指定的操作类型(删除或标记已读)来处理短信。 + * 这是核心的数据库操作方法。 + */ + private fun operateSms(sender: String?, body: String, @SmsOp smsOp: Int): Boolean { + var cursor: Cursor? = null + try { + // 检查是否有读写短信的权限 + mPluginContext?.let { + if (ContextCompat.checkSelfPermission(it, Manifest.permission.READ_SMS) + != PackageManager.PERMISSION_GRANTED + ) { + YLog.info("没有读写短信的权限") + return false + } + } + // 定义要查询的数据库列 + val projection = arrayOf( + Telephony.Sms._ID, + Telephony.Sms.ADDRESS, + Telephony.Sms.BODY, + Telephony.Sms.READ, + Telephony.Sms.DATE + ) + // 只查询最近的5条短信,并按时间倒序排列,以提高效率 + val sortOrder = Telephony.Sms.DATE + " desc limit 5" + val uri = Telephony.Sms.CONTENT_URI + val resolver = mPluginContext?.contentResolver + if (resolver != null) { + cursor = resolver.query(uri, projection, null, null, sortOrder) + } + if (cursor == null) { + YLog.info("数据库查询游标为空") + return false + } + // 遍历查询结果 + while (cursor.moveToNext()) { + val curAddress = cursor.getString(cursor.getColumnIndexOrThrow("address")) + val curRead = cursor.getInt(cursor.getColumnIndexOrThrow("read")) + val curBody = cursor.getString(cursor.getColumnIndexOrThrow("body")) + // 寻找那条发件人相同、内容匹配且未读的短信 + if (curAddress == sender && curRead == 0 && curBody.startsWith(body)) { + val smsMessageId = cursor.getString(cursor.getColumnIndexOrThrow("_id")) + val where = Telephony.Sms._ID + " = ?" + val selectionArgs = arrayOf(smsMessageId) + if (smsOp == OP_DELETE) { + // 执行删除操作 + val rows = resolver?.delete(uri, where, selectionArgs) + rows?.let { if (it > 0) return true } + } else if (smsOp == OP_MARK_AS_READ) { + // 执行更新(标记已读)操作 + val values = ContentValues() + values.put(Telephony.Sms.READ, true) + val rows = resolver?.update(uri, values, where, selectionArgs) + rows?.let { if (it > 0) return true } + } + } + } + } catch (e: Exception) { + YLog.info("操作短信失败: ", e) + } finally { + // 确保游标被关闭,防止内存泄漏 + cursor?.close() + } + return false + } + + companion object { // 定义常量 + private const val OP_DELETE = 0 // 删除操作的代号 + private const val OP_MARK_AS_READ = 1 // 标记已读操作的代号 + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/RemoveSimNameLengthLimit.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/RemoveSimNameLengthLimit.kt new file mode 100644 index 00000000..0d809cc9 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/RemoveSimNameLengthLimit.kt @@ -0,0 +1,38 @@ +package com.suqi8.oshin.hook.phone + +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import org.luckypray.dexkit.DexKitBridge + + +class RemoveSimNameLengthLimit: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.android.phone") { + val prefs = prefs("phone") + val bridge = DexKitBridge.create(this.appInfo.sourceDir) + if (prefs.getBoolean("remove_sim_name_length_limit")) { + bridge.findClass { + searchPackages("com.android.simsettings.utils") + matcher { + usingStrings("save sim name keep = ","SIMS_SimNameHelper") + } + }.singleOrNull()?.also { + it.name.toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC, Modifiers.FINAL) + name = "filter" + parameters("java.lang.CharSequence", Int::class, Int::class, "android.text.Spanned", Int::class, Int::class) + returnType = "java.lang.CharSequence" + }.hook { + before { + result = null + } + } + } + } + + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/RunnableAction.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/RunnableAction.kt new file mode 100644 index 00000000..c0bf758f --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/RunnableAction.kt @@ -0,0 +1,36 @@ +package com.suqi8.oshin.hook.phone + +import android.content.Context + +/** + * 在 [CallableAction] 的基础上,实现了 [Runnable] 接口。 + * + * 此类的主要目的是将一个可调用、有返回值的任务 ([CallableAction]) 封装成一个 + * 可以在 UI 主线程执行,但不需要处理其返回值的任务 ([Runnable])。 + * + * @param pluginContext 模块上下文。 + * @param phoneContext 电话应用上下文。 + * @param smsMsg 解析后的短信对象。 + * @param xsp Xposed 共享设置。 + */ +abstract class RunnableAction( + pluginContext: Context?, + phoneContext: Context?, + smsMsg: SmsMsg? +) : CallableAction(pluginContext, phoneContext, smsMsg), Runnable { + + /** + * 实现了 [Runnable] 接口的 `run` 方法。 + * + * 当此任务被提交到 Handler(例如,UI 主线程的 Handler)时,此方法会被调用。 + * 它直接调用父类 [CallableAction] 的 [call] 方法,从而复用统一的 + * 任务执行和异常处理逻辑,同时忽略其返回值。 + * + * 此方法被标记为 `final`,以确保子类不会意外地覆盖它,而是应该去实现 [action] 方法。 + */ + final override fun run() { + // 直接调用 call(),其返回值在这里被忽略。 + // 父类的 call() 方法已经处理了 action() 的调用和异常捕获。 + call() + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/SMSCode.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/SMSCode.kt new file mode 100644 index 00000000..99683502 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/SMSCode.kt @@ -0,0 +1,164 @@ +package com.suqi8.oshin.hook.phone + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Binder +import android.provider.Telephony +import com.highcapable.kavaref.KavaRef.Companion.asResolver +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.log.YLog +import com.highcapable.yukihookapi.hook.param.PackageParam +import com.suqi8.oshin.BuildConfig.APPLICATION_ID +import com.suqi8.oshin.R + +var smsRule = "验证码|校验码|检验码|确认码|激活码|动态码|安全码|验证代码|校验代码|检验代码|激活代码|确认代码|动态代码|安全代码|登入码|认证码|识别码|短信口令|动态密码|交易码|上网密码|随机码|动态口令|驗證碼|校驗碼|檢驗碼|確認碼|激活碼|動態碼|驗證代碼|校驗代碼|檢驗代碼|確認代碼|激活代碼|動態代碼|登入碼|認證碼|識別碼|Code|code|CODE|Код|код|КОД|Пароль|пароль|ПАРОЛЬ|Kod|kod|KOD|Ma|Mã|OTP" +var showCodeToast = false +var showCodeNotification = false +var copyCode = false +var inputCode = true +class SMSCode: YukiBaseHooker() { + // "电话"应用的上下文,用于创建通知、注册广播等 + private var mPhoneContext: Context? = null + + // 本模块的上下文,用于获取模块自身的资源(如字符串) + private var mPluginContext: Context? = null + + private val pluginContext: Context? + get() { + if (mPluginContext == null) { + try { + mPluginContext = mPhoneContext!!.createPackageContext( + APPLICATION_ID, + Context.CONTEXT_IGNORE_SECURITY + ) + } catch (e: Exception) {} + } + return mPluginContext + } + + override fun onHook() { + loadApp(name = "com.android.phone") { + if (!prefs("phone").getBoolean("sms_verification_code", false)) return + + smsRule = prefs("phone").getString("SMSCodeRule", smsRule) + showCodeToast = prefs("phone").getBoolean("showCodeToast", false) + showCodeNotification = prefs("phone").getBoolean("showCodeNotification", false) + copyCode = prefs("phone").getBoolean("copyCode", false) + inputCode = prefs("phone").getBoolean("inputCode", true) + + hookConstructor(this) // Hook 构造方法,用于初始化 + hookDispatchIntent(this) // Hook 短信分发方法,用于拦截短信 + } + } + + fun hookConstructor(param: PackageParam) = param.apply { + "com.android.internal.telephony.InboundSmsHandler".toClass().resolve().constructor { }.hookAll { + after { + val context = args[1] as Context? + if (mPhoneContext == null) { // 确保只初始化一次 + mPhoneContext = context + // 创建本模块的上下文 + mPluginContext = mPhoneContext!!.createPackageContext( + APPLICATION_ID, + Context.CONTEXT_IGNORE_SECURITY + ) + val channelName = pluginContext?.getString(R.string.channel_name_smscode_notification) + createNotificationChannel(mPhoneContext!!, + "oshin_smscode", channelName, NotificationManager.IMPORTANCE_HIGH) + CopyCodeReceiver.registerMe(mPhoneContext!!) + } + } + } + } + + private fun hookDispatchIntent(param: PackageParam) = param.apply { + val inboundSmsHandlerClass = "com.android.internal.telephony.InboundSmsHandler".toClass() + + // 通过遍历的方式模糊查找 dispatchIntent 方法,提高兼容性 + var exactMethod: String? = null + val DISPATCH_INTENT = "dispatchIntent" + var receiverIndex = 0 // 记录 BroadcastReceiver 参数的位置 + for (method in inboundSmsHandlerClass.declaredMethods) { + if (DISPATCH_INTENT == method.name) { + exactMethod = method.name + // 顺便找到 BroadcastReceiver 类型参数的索引 + val parameterTypes = method.parameterTypes + for (i in parameterTypes.indices) { + if (BroadcastReceiver::class.java.isAssignableFrom(parameterTypes[i]!!)) { + receiverIndex = i + } + } + break + } + } + + if (exactMethod == null) return@apply + + // 对找到的方法进行 Hook + inboundSmsHandlerClass.resolve().firstMethod { + name = exactMethod + }.hook { + before { + val intent = args[0] as Intent + // 只关心收到的短信(SMS_DELIVER_ACTION) + if (Telephony.Sms.Intents.SMS_DELIVER_ACTION != intent.action) return@before + + // 使用 CodeWorker 来解析短信,提取验证码 + val parseResult = CodeWorker(pluginContext, mPhoneContext, intent).parse() + if (parseResult != null) { // 如果成功解析出验证码 + if (parseResult) { // 如果配置了要拦截此短信 + // 从系统数据库中删除这条短信,并通知系统处理完毕 + deleteRawTableAndSendMessage(instance, args[receiverIndex]) + // 阻止原始方法继续执行,从而实现拦截效果 + resultNull() + } + } + } + } + } + + private fun deleteRawTableAndSendMessage(inboundSmsHandler: Any, smsReceiver: Any?) { + val token = Binder.clearCallingIdentity() // 临时获取系统权限 + try { + deleteFromRawTable(inboundSmsHandler, smsReceiver) // 从数据库删除 + } catch (e: Throwable) { + YLog.error("删除短信失败: ", e) + } finally { + Binder.restoreCallingIdentity(token) // 恢复原始身份 + } + inboundSmsHandler.asResolver().firstMethod { + name = "sendMessage" + parameters(Int::class.java) + superclass() + }.invoke(3) + } + + @Throws(ReflectiveOperationException::class) + private fun deleteFromRawTable(inboundSmsHandler: Any, smsReceiver: Any?) { + // 通过反射获取删除短信所需的条件和参数 + val deleteWhere = smsReceiver?.asResolver()?.firstField { name = "mDeleteWhere" } + ?.get() as String? + @Suppress("UNCHECKED_CAST") + val deleteWhereArgs = smsReceiver?.asResolver()?.firstField { name = "mDeleteWhereArgs" } + ?.get() as Array? + val MARK_DELETED = 2 + inboundSmsHandler.asResolver().firstMethod { + name = "deleteFromRawTable" + parameters(String::class, Array::class.java, Int::class) + superclass() + }.invoke(deleteWhere, deleteWhereArgs, MARK_DELETED) + } + + fun createNotificationChannel( + context: Context, channelId: String?, + channelName: String?, importance: Int + ) { + val channel = NotificationChannel(channelId, channelName, importance) + val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager? + manager?.createNotificationChannel(channel) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/SmsCodeUtils.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/SmsCodeUtils.kt new file mode 100644 index 00000000..cd8f4f7d --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/SmsCodeUtils.kt @@ -0,0 +1,133 @@ +package com.suqi8.oshin.hook.phone + +import kotlin.math.abs + +/** + * 一个核心工具对象,封装了所有从短信中解析验证码和公司名的复杂逻辑。 + */ +object SmsCodeUtils { + + // --- 定义验证码的匹配优先级 --- + private const val LEVEL_DIGITAL_6 = 4 // 6位纯数字,最优先 + private const val LEVEL_DIGITAL_4 = 3 // 4位纯数字 + private const val LEVEL_DIGITAL_OTHERS = 2// 其他长度的纯数字 + private const val LEVEL_TEXT = 1 // 数字+字母 混合 + private const val LEVEL_CHARACTER = 0 // 纯字母,最末 + private const val LEVEL_NONE = -1 + + private val CHINESE_REGEX = "[\u4e00-\u9fa5]|。".toRegex() + private val WHITESPACE_REGEX = "\\s*".toRegex() + private val COMPANY_REGEX = "((?<=【)(.*?)(?=】))|((?<=\\[)(.*?)(?=]))".toRegex() + + /** + * 解析验证码的总入口。它会先尝试自定义规则,如果失败再使用默认规则。 + */ + @JvmStatic + fun parseSmsCodeIfExists(content: String): String { + return parseByDefaultRule(content) + } + + /** + * 从短信内容中解析出公司或应用名(通常在【】或[]中)。 + */ + fun parseCompany(content: String): String { + return COMPANY_REGEX.findAll(content) + .map { it.value } + .joinToString(" ") + } + + /** + * 使用通用的默认规则来解析验证码。 + */ + private fun parseByDefaultRule(content: String, ): String { + // 加载用于匹配的“关键字”列表(如“验证码”、“code”等) + val keywordsRegex = smsRule + // 在短信中找到第一个匹配的关键字,如果找不到则直接返回空字符串 + val keyword = keywordsRegex.toRegex().find(content)?.value ?: return "" + + // 根据短信是否包含中文,调用不同的处理方法 + return if (content.contains(CHINESE_REGEX)) { + getSmsCodeCN(keyword, content) + } else { + getSmsCodeEN(keyword, content) + } + } + + /** + * 处理中文短信,验证码可能是字母和数字的组合。 + */ + private fun getSmsCodeCN(keyword: String, content: String): String { + val codeRegex = "(? + distanceToKeyword(keyword, code, content) <= 30 + }.ifEmpty { possibleCodes } + + + // 3. 根据格式和距离,给每个候选码打分,选出最优的那个 + var bestCode = "" + var maxMatchLevel = LEVEL_NONE + var minDistance = content.length + + for (code in filteredCodes) { + val currentLevel = getMatchLevel(code) + if (currentLevel > maxMatchLevel) { + maxMatchLevel = currentLevel + minDistance = distanceToKeyword(keyword, code, content) + bestCode = code + } else if (currentLevel == maxMatchLevel) { + val currentDistance = distanceToKeyword(keyword, code, content) + if (currentDistance < minDistance) { + minDistance = currentDistance + bestCode = code + } + } + } + return bestCode + } + + /** + * 根据字符串格式,判断其作为验证码的“匹配等级”或“优先级”。 + */ + private fun getMatchLevel(matchedStr: String): Int = when { + matchedStr.matches("^[0-9]{6}$".toRegex()) -> LEVEL_DIGITAL_6 + matchedStr.matches("^[0-9]{4}$".toRegex()) -> LEVEL_DIGITAL_4 + matchedStr.matches("^[0-9]*$".toRegex()) -> LEVEL_DIGITAL_OTHERS + matchedStr.matches("^[a-zA-Z]*$".toRegex()) -> LEVEL_CHARACTER + else -> LEVEL_TEXT + } + + /** + * 计算候选码和关键字之间的距离。 + */ + private fun distanceToKeyword(keyword: String, possibleCode: String, content: String): Int { + val keywordIdx = content.indexOf(keyword) + val possibleCodeIdx = content.indexOf(possibleCode) + return abs(keywordIdx - possibleCodeIdx) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/SmsMessageUtils.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/SmsMessageUtils.kt new file mode 100644 index 00000000..49501f6f --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/SmsMessageUtils.kt @@ -0,0 +1,15 @@ +package com.suqi8.oshin.hook.phone + +import android.content.Intent +import android.provider.Telephony +import android.telephony.SmsMessage + +object SmsMessageUtils { + fun fromIntent(intent: Intent): Array? { + return Telephony.Sms.Intents.getMessagesFromIntent(intent) + } + + fun getMessageBody(messageParts: Array): String { + return messageParts.joinToString("") { it.displayMessageBody } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/SmsMsg.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/SmsMsg.kt new file mode 100644 index 00000000..d4abba78 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/SmsMsg.kt @@ -0,0 +1,54 @@ +package com.suqi8.oshin.hook.phone + +import android.content.Intent +import android.telephony.SmsMessage +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName +import java.io.Serializable +import java.text.Normalizer + +data class SmsMsg( + @SerializedName("id") + var id: Long? = null, + + // 发件人 + @Expose + @SerializedName("sender") + var sender: String? = null, + + // 短信内容 + @Expose + @SerializedName("body") + var body: String? = null, + + // 接收日期 + @Expose + @SerializedName("date") + var date: Long = 0, + + // 公司/服务商 + @Expose + @SerializedName("company") + var company: String? = null, + + // 验证码 + @Expose + @SerializedName("code") + var smsCode: String? = null +) : Serializable { + companion object { + fun fromIntent(intent: Intent): SmsMsg? { + val smsMessageParts: Array? = SmsMessageUtils.fromIntent(intent) + if (smsMessageParts.isNullOrEmpty()) { + return null + } + val sender = smsMessageParts[0].displayOriginatingAddress?.let { + Normalizer.normalize(it, Normalizer.Form.NFC) + } + val body = SmsMessageUtils.getMessageBody(smsMessageParts).let { + Normalizer.normalize(it, Normalizer.Form.NFC) + } + return SmsMsg(sender = sender, body = body) + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/SmsParseAction.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/SmsParseAction.kt new file mode 100644 index 00000000..c854def7 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/SmsParseAction.kt @@ -0,0 +1,86 @@ +package com.suqi8.oshin.hook.phone + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.text.TextUtils +import com.suqi8.oshin.hook.phone.EntityStoreManager.storeEntitiesToFile +import kotlin.math.abs + +/** + * 一个具体的操作类,负责从收到的短信中解析出验证码。 + * 这是整个处理流程的第一步,也是最关键的一步。 + */ +class SmsParseAction( + pluginContext: Context?, + phoneContext: Context?, + smsMsg: SmsMsg? +) : CallableAction(pluginContext, phoneContext, smsMsg) { + // 包含原始短信数据的 Intent + private var mSmsIntent: Intent? = null + + fun setSmsIntent(smsIntent: Intent?) { + mSmsIntent = smsIntent + } + + /** + * 当此操作被调度执行时,这是入口点。 + */ + override fun action(): Bundle? { + return parseSmsMsg() + } + + /** + * 解析短信的核心逻辑。 + * @return 如果是验证码短信,则返回包含解析结果的 Bundle,否则返回 null。 + */ + private fun parseSmsMsg(): Bundle? { + // 1. 从 Intent 中提取 SmsMsg 对象,如果为空则提前返回 + val sms = mSmsIntent?.let { SmsMsg.fromIntent(it) } ?: return null + + // 2. 获取发件人和内容,如果为空则提前返回 + val sender = sms.sender + val body = sms.body + if (sender == null || body == null) return null + + // 4. 解析验证码,如果解析失败则提前返回 + val smsCode = mPluginContext?.let { SmsCodeUtils.parseSmsCodeIfExists(body) } + if (TextUtils.isEmpty(smsCode)) { + return null + } + + // 5. 使用 apply 更新 SmsMsg 对象的属性,代码更集中 + val timestamp = System.currentTimeMillis() + sms.apply { + this.smsCode = smsCode + this.company = SmsCodeUtils.parseCompany(body) + this.date = timestamp + } + + // 6. 处理短信去重逻辑 + var duplicated = false + val prevSmsMsg = EntityStoreManager.loadEntityFromFile(SmsMsg::class.java) + if (prevSmsMsg != null) { + val timeDiff = abs(timestamp - prevSmsMsg.date) + if (timeDiff <= 15000) { + // 时间接近,再检查内容是否一致 + if ((sender == prevSmsMsg.sender && smsCode == prevSmsMsg.smsCode) || body == prevSmsMsg.body) { + duplicated = true + } + } + } + // 无论是否重复,都保存当前短信记录以备下次比较 + storeEntitiesToFile(listOf(sms)) + + // 7. 将最终结果打包到 Bundle 中返回 + return Bundle().apply { + putSerializable(SMS_MSG, sms) + putBoolean(SMS_DUPLICATED, duplicated) + } + } + + companion object { // 定义常量,用于在 Bundle 中传递数据 + const val SMS_MSG: String = "sms_msg" + const val SMS_DUPLICATED: String = "sms_duplicated" + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/ToastAction.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/ToastAction.kt new file mode 100644 index 00000000..004adde1 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/ToastAction.kt @@ -0,0 +1,39 @@ +package com.suqi8.oshin.hook.phone + +import android.content.Context +import android.os.Bundle +import android.widget.Toast +import com.suqi8.oshin.R + +/** + * 一个具体的操作类,负责显示一个包含验证码的 Toast 提示。 + */ +class ToastAction( + pluginContext: Context?, // 本模块的上下文 + phoneContext: Context?, // "电话"应用的上下文 + smsMsg: SmsMsg? // 包含验证码信息的对象 +) : RunnableAction(pluginContext, phoneContext, smsMsg) { + + /** + * 当此操作被调度执行时,这是入口点。 + */ + override fun action(): Bundle? { + // 检查用户是否在设置中开启了“显示Toast”功能 + if (showCodeToast) { + showCodeToast() // 如果开启了,就显示 Toast + } + return null + } + + /** + * 执行显示 Toast 的具体逻辑。 + */ + private fun showCodeToast() { + // 从本模块的资源中加载提示文本,并将验证码填入 + val text = mSmsMsg?.let { mPluginContext?.getString(R.string.code_notification_content, it.smsCode) } + // 在"电话"应用的上下文中创建并显示 Toast + if (mPhoneContext != null) { + Toast.makeText(mPhoneContext, text, Toast.LENGTH_LONG).show() + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phone/phone.kt b/app/src/main/java/com/suqi8/oshin/hook/phone/phone.kt new file mode 100644 index 00000000..99487079 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phone/phone.kt @@ -0,0 +1,11 @@ +package com.suqi8.oshin.hook.phone + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker + + +class phone: YukiBaseHooker() { + override fun onHook() { + loadApp(hooker = SMSCode()) + loadHooker(RemoveSimNameLengthLimit()) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phonemanager/oplusphonemanager.kt b/app/src/main/java/com/suqi8/oshin/hook/phonemanager/oplusphonemanager.kt new file mode 100644 index 00000000..29f2b0c6 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phonemanager/oplusphonemanager.kt @@ -0,0 +1,77 @@ +package com.suqi8.oshin.hook.phonemanager + +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import org.luckypray.dexkit.DexKitBridge +import java.lang.reflect.Modifier + +class oplusphonemanager: YukiBaseHooker() { + override fun onHook() { + val prefs = prefs("oplusphonemanager") + loadApp(name = "com.oplus.phonemanager") { + if (prefs.getBoolean("remove_all_popup_delays", false)) { + "com.oplus.phonemanager.common.DialogCrossActivity\$f".toClass().resolve().apply { + firstMethod { + name = "onTick" + }.hook { + before { + args[0] = 0 + } + } + } + } + DexKitBridge.create(this.appInfo.sourceDir).use { + if (prefs.getString("custom_prompt_content", "") != "") { + it.findMethod { + searchPackages("com.oplus.phonemanager.newrequest.delegate") + matcher { + modifiers = Modifier.PUBLIC + usingStrings("updateScanResult manualOptItems: ", "main_entry_summary") + } + }.singleOrNull()?.also { + //YLog.info("methodName:"+it.methodName + " className:" + it.className) + it.className.toClass().resolve().firstMethod { name = it.methodName }.hook { + before { + result = + prefs.getString("custom_prompt_content", "") + } + } + } + } + it.findMethod { + searchPackages("com.oplus.phonemanager.common.view") + matcher { + modifiers = Modifier.PUBLIC + returnType = "void" + usingStrings(" not change, skip", "[setScore] ") + } + }.singleOrNull()?.also { + var triggerCount = 0 + it.className.toClass().resolve().firstMethod { name = it.methodName }.hook { + before { + val customScore = prefs.getFloat("custom_score", -1f) + val customAnimationDuration = + prefs.getFloat("custom_animation_duration", -1f) + + val isSecondPassAnimation = args[1] as Boolean + + if (customScore != -1f && isSecondPassAnimation) { + triggerCount += 1 // 条件触发次数加 1 + + // 仅在第二次触发时才应用自定义分数 + if (triggerCount == 2) { + args[0] = customScore.toInt() + } + } + + if (customAnimationDuration != -1f) { + // args[2] 对应的是动画时长参数 + args[2] = customAnimationDuration.toLong() + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/phonemanager/phonemanager.kt b/app/src/main/java/com/suqi8/oshin/hook/phonemanager/phonemanager.kt new file mode 100644 index 00000000..30649597 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/phonemanager/phonemanager.kt @@ -0,0 +1,77 @@ +package com.suqi8.oshin.hook.phonemanager + +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import org.luckypray.dexkit.DexKitBridge +import java.lang.reflect.Modifier + +class phonemanager : YukiBaseHooker() { + override fun onHook() { + val prefs = prefs("phonemanager") + loadApp(name = "com.coloros.phonemanager") { + if (prefs.getBoolean("remove_all_popup_delays", false)) { + "com.oplus.phonemanager.common.DialogCrossActivity\$f".toClass().resolve().apply { + firstMethod { + name = "onTick" + }.hook { + before { + args[0] = 0 + } + } + } + } + DexKitBridge.create(this.appInfo.sourceDir).use { + if (prefs.getString("custom_prompt_content", "") != "") { + it.findMethod { + searchPackages("com.oplus.phonemanager.newrequest.delegate") + matcher { + modifiers = Modifier.PUBLIC + usingStrings("updateScanResult manualOptItems: ", "main_entry_summary") + } + }.singleOrNull()?.also { + //YLog.info("methodName:"+it.methodName + " className:" + it.className) + it.className.toClass().resolve().firstMethod { name = it.methodName }.hook { + before { + result = + prefs.getString("custom_prompt_content", "") + } + } + } + } + it.findMethod { + searchPackages("com.oplus.phonemanager.common.view") + matcher { + modifiers = Modifier.PUBLIC + returnType = "void" + usingStrings(" not change, skip", "[setScore] ") + } + }.singleOrNull()?.also { + var triggerCount = 0 + it.className.toClass().resolve().firstMethod { name = it.methodName }.hook { + before { + val customScore = prefs.getFloat("custom_score", -1f) + val customAnimationDuration = + prefs.getFloat("custom_animation_duration", -1f) + + val isSecondPassAnimation = args[1] as Boolean + + if (customScore != -1f && isSecondPassAnimation) { + triggerCount += 1 // 条件触发次数加 1 + + // 仅在第二次触发时才应用自定义分数 + if (triggerCount == 2) { + args[0] = customScore.toInt() + } + } + + if (customAnimationDuration != -1f) { + // args[2] 对应的是动画时长参数 + args[2] = customAnimationDuration.toLong() + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/quicksearchbox/quicksearchbox.kt b/app/src/main/java/com/suqi8/oshin/hook/quicksearchbox/quicksearchbox.kt new file mode 100644 index 00000000..0e4ccc40 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/quicksearchbox/quicksearchbox.kt @@ -0,0 +1,35 @@ +package com.suqi8.oshin.hook.quicksearchbox + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.java.BooleanType +import com.highcapable.yukihookapi.hook.type.java.UnitType + +class quicksearchbox: YukiBaseHooker() { + override fun onHook() { + if (prefs("quicksearchbox").getBoolean("remove_app_recommendation_ads", false)) { + loadApp(name = "com.heytap.quicksearchbox") { + /*DexKitBridge.create(this.appInfo.sourceDir).use { + it.findMethod { + searchPackages("com.heytap.quicksearchbox.ui.widget.advicesub") + matcher { + modifiers = Modifier.PUBLIC + paramTypes(null, "java.lang.Boolean") + } + }.forEach { + YLog.info("methodName:"+it.methodName + " className:" + it.className) + } + }*/ + "com.heytap.quicksearchbox.ui.widget.advicesub.AliveAppRecommendView".toClass().apply { + method { + name = "o" + param("java.util.List", BooleanType) + returnType = UnitType + }.hook { + replaceUnit { } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/securepay/securepay.kt b/app/src/main/java/com/suqi8/oshin/hook/securepay/securepay.kt new file mode 100644 index 00000000..37a460fb --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/securepay/securepay.kt @@ -0,0 +1,36 @@ +package com.suqi8.oshin.hook.securepay + +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import org.luckypray.dexkit.DexKitBridge + +class securepay: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.coloros.securepay") { + if (prefs("securepay").getBoolean("security_payment_remove_risky_fluid_cloud", false)) { + DexKitBridge.create(this.appInfo.sourceDir).also { + it.findClass { + matcher { + usingStrings("[updateFixingCard] ","pages/fix") + usingStrings("showProgress") + usingStrings("entrancePackageNameKey") + usingStrings("pantanal.intent.business.app.system.PAY_SCAN") + } + }.findMethod { + matcher { + usingStrings("pantanal.intent.business.app.system.PAY_SCAN") + returnType = "void" + paramTypes("java.util.List") + } + }.singleOrNull()?.also { + it.className.toClass().resolve().firstMethod { + name = it.methodName + }.hook { + replaceUnit { } + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/securitypermission/securitypermission.kt b/app/src/main/java/com/suqi8/oshin/hook/securitypermission/securitypermission.kt new file mode 100644 index 00000000..f2b2e152 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/securitypermission/securitypermission.kt @@ -0,0 +1,120 @@ +package com.suqi8.oshin.hook.securitypermission + +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import org.luckypray.dexkit.DexKitBridge + +class securitypermission : YukiBaseHooker() { + override fun onHook() { + val prefs = prefs("securitypermission") + loadApp("com.oplus.securitypermission") { + val bridge = DexKitBridge.create(this.appInfo.sourceDir) + if (prefs.getBoolean("app_start_dialog_legacy_mode", false)) { + bridge.findClass { + matcher { + usingStrings( + "callerPackage", + "callerName", + "calleePackage", + "calleeName", + "sourceIntent" + ) + } + }.singleOrNull()?.also { + it.name.toClass().resolve().apply { + firstConstructor { + modifiers(Modifiers.PUBLIC) + parameters( + String::class, + String::class, + String::class, + String::class, + "android.content.Intent", + Int::class, + Int::class, + Int::class, + "d9.a\$b", + Int::class + ) + }.hook { + before { + args[9] = 3 + } + } + } + } + } + if (prefs.getBoolean("app_start_dialog_always_allow", false)) { + bridge.findClass { + matcher { + usingStrings( + "remove ignored activity: callerPackage=", + ", targetActivity=", + "ignored_activity" + ) + usingStrings("valid_time", "user set, s=") + } + }.findMethod { + matcher { + paramTypes("long") + } + }.singleOrNull()?.also { + it.className.toClass().resolve().firstMethod { name = it.methodName }.hook { + before { + result = null + } + } + } + + bridge.findClass { + matcher { + usingStrings( + "COUIAlertDialogBuilder", + "customImageview is error; Need to check whether the application has a layout" + ) + } + }.singleOrNull()?.let { cls -> + cls.name.toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "t0" + parameters( + Int::class, + "android.content.DialogInterface\$OnClickListener", + Boolean::class + ) + returnType = cls.name + }.hook { + before { + "com.oplus.securitypermission.R\$string".toClass().resolve().apply { + val allow30Res = firstField { + modifiers( + Modifiers.PUBLIC, + Modifiers.STATIC, + Modifiers.FINAL + ) + name = "app_start_dialog_allow_30" + type = Int::class + }.get() + val alwaysAllowRes = firstField { + modifiers( + Modifiers.PUBLIC, + Modifiers.STATIC, + Modifiers.FINAL + ) + name = "app_start_dialog_always_allow" + type = Int::class + }.get() + if (args[0] == allow30Res) { + args[0] = alwaysAllowRes + } + } + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/settings/Accessibility.kt b/app/src/main/java/com/suqi8/oshin/hook/settings/Accessibility.kt new file mode 100644 index 00000000..10ca269d --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/settings/Accessibility.kt @@ -0,0 +1,183 @@ +package com.suqi8.oshin.hook.settings + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.provider.Settings +import android.view.accessibility.AccessibilityManager +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.android.BundleClass + +class Accessibility : YukiBaseHooker() { + @SuppressLint("PrivateApi") + override fun onHook() { + loadApp(name = "com.android.settings") { + val auth = prefs("settings").getBoolean("auth", false) + val jump = prefs("settings").getBoolean("jump", false) + val autoauth = prefs("settings").getBoolean("autoauth", false) + if (!auth && !jump && !autoauth) return + val autoauthwhite = prefs("settings").getString("autoauthwhite", "") + "com.android.settings.SettingsActivity".toClass().apply { + method { + name = "onCreate" + param(BundleClass) + }.hook { + after { + val activity = instance as Activity // 获取当前 Activity 实例 + val intent = activity.intent // 获取启动该 Activity 的 Intent + val action = intent.action // 获取 Intent 的 Action + //YLog.info("Activity: $activity") + + // 检查是否是无障碍服务设置的请求 + if (action == "android.settings.ACCESSIBILITY_SETTINGS") { + val packageManager = + activity.packageManager // 获取 PackageManager 以查询应用信息 + val referrer: Uri? = activity.referrer // 获取打开此 Activity 的应用包名 + val packageName: String? = referrer?.host // 提取包名 + if (packageName.isNullOrEmpty()) return@after + + var accessibilityService: String? = null // 无障碍服务类名 + var summary: String? = null // 无障碍服务描述信息 + + // 获取 AccessibilityManager 以查询已安装的无障碍服务 + val accessibilityManager = + activity.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager + val appName = packageManager.getApplicationLabel( + packageManager.getApplicationInfo( + packageName, + 0 + ) + ).toString() + + // 遍历已安装的无障碍服务,查找对应包名的服务 + for (serviceInfo in accessibilityManager.installedAccessibilityServiceList) { + val service = serviceInfo.resolveInfo.serviceInfo + if (packageName == service.packageName) { + accessibilityService = service.name + summary = + serviceInfo.loadDescription(packageManager) // 获取无障碍服务的描述信息 + break + } + } + + // 如果未找到对应的无障碍服务,记录日志并退出 + if (accessibilityService == null) { + //YLog.error("错误:$packageName 没有对应的无障碍服务!") + return@after + } + + val serviceName = "$packageName/$accessibilityService" // 生成完整的无障碍服务名称 + + if (auth) { + // 读取当前启用的无障碍服务列表 + val enabledServices = Settings.Secure.getString( + activity.contentResolver, + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES + )?.split(":")?.toMutableList() ?: mutableListOf() + + // 先移除可能已存在的相同服务,避免重复添加 + enabledServices.remove(serviceName) + enabledServices.add(serviceName) // 添加目标无障碍服务 + + // 更新系统无障碍服务设置 + Settings.Secure.putString( + activity.contentResolver, + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + enabledServices.joinToString(":") + ) + activity.finish() // 关闭当前 Activity + return@after + } + + if (jump) { + val intentOpenSub = Intent( + activity, + classLoader?.loadClass("com.android.settings.SubSettings") + ) + intentOpenSub.action = "android.intent.action.MAIN" + intentOpenSub.putExtra(":settings:show_fragment_title", appName) + intentOpenSub.putExtra(":settings:source_metrics", 0) + intentOpenSub.putExtra(":settings:show_fragment_title_resid", -1) + intentOpenSub.putExtra( + ":settings:show_fragment", + "com.android.settings.accessibility.VolumeShortcutToggleAccessibilityServicePreferenceFragment" + ) + + val bundle = Bundle().apply { + putParcelable( + "component_name", + ComponentName(packageName, accessibilityService) + ) + putString("package", packageName) + putString( + "preference_key", + "$packageName/$accessibilityService" + ) + putString("summary", summary) + } + intentOpenSub.putExtra(":settings:show_fragment_args", bundle) + activity.startActivity(intentOpenSub) + } + + if (autoauth) { + if (packageName in autoauthwhite.split(",")) { + // 读取当前启用的无障碍服务列表 + val enabledServices = Settings.Secure.getString( + activity.contentResolver, + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES + )?.split(":")?.toMutableList() ?: mutableListOf() + + // 先移除可能已存在的相同服务,避免重复添加 + enabledServices.remove(serviceName) + enabledServices.add(serviceName) // 添加目标无障碍服务 + + // 更新系统无障碍服务设置 + Settings.Secure.putString( + activity.contentResolver, + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + enabledServices.joinToString(":") + ) + activity.finish() // 关闭当前 Activity + return@after + } else { + val intentOpenSub = Intent( + activity, + classLoader?.loadClass("com.android.settings.SubSettings") + ) + intentOpenSub.action = "android.intent.action.MAIN" + intentOpenSub.putExtra(":settings:show_fragment_title", appName) + intentOpenSub.putExtra(":settings:source_metrics", 0) + intentOpenSub.putExtra(":settings:show_fragment_title_resid", -1) + intentOpenSub.putExtra( + ":settings:show_fragment", + "com.android.settings.accessibility.VolumeShortcutToggleAccessibilityServicePreferenceFragment" + ) + + val bundle = Bundle().apply { + putParcelable( + "component_name", + ComponentName(packageName, accessibilityService) + ) + putString("package", packageName) + putString( + "preference_key", + "$packageName/$accessibilityService" + ) + putString("summary", summary) + } + intentOpenSub.putExtra(":settings:show_fragment_args", bundle) + activity.startActivity(intentOpenSub) + } + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/settings/SettingsFeature.kt b/app/src/main/java/com/suqi8/oshin/hook/settings/SettingsFeature.kt new file mode 100644 index 00000000..890c284e --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/settings/SettingsFeature.kt @@ -0,0 +1,92 @@ +package com.suqi8.oshin.hook.settings + +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.log.YLog +import com.highcapable.yukihookapi.hook.param.PackageParam +import org.luckypray.dexkit.DexKitBridge + +class SettingsFeature : YukiBaseHooker() { + + companion object { + const val OPLUS_SETTINGS_PREFS_NAME = "settings\\feature" + const val KEY_GET_METHODS = "get_oplus_feature_methods" + const val KEY_RETURN_METHODS = "return_oplus_feature_methods" + const val KEY_CACHED_METHODS = "cached_oplus_feature_methods" + + const val TARGET_CLASS_DEVICE_INFO = "com.oplus.settings.feature.deviceinfo.DeviceInfoUtils" + const val TARGET_CLASS_CUSTOMIZE = "com.oplus.settings.utils.CustomizeFeatureUtils" + const val TARGET_CLASS_SYS = "com.oplus.settings.utils.SysFeatureUtils" + } + + override fun onHook() { + loadApp(name = "com.android.settings") { + + // 监听 UI 请求 + dataChannel.wait(KEY_GET_METHODS) { + YLog.info("接收到特性方法扫描请求") + Thread { + try { + DexKitBridge.create(appInfo.sourceDir).use { bridge -> + YLog.info("开始扫描 Oplus Settings 功能方法...") + + fun findBooleanMethods(className: String, returnType: String): List { + val methods = bridge.findMethod { + matcher { + declaredClass { this.className = className } + this.returnType = returnType + } + } + YLog.info("在 $className 中找到 ${methods.size} 个返回 $returnType 的方法。") + return methods.map { "$className.${it.name}" } + } + + val allKeys = buildList { + addAll(findBooleanMethods(TARGET_CLASS_CUSTOMIZE, "boolean")) + addAll(findBooleanMethods(TARGET_CLASS_SYS, "boolean")) + addAll(findBooleanMethods(TARGET_CLASS_DEVICE_INFO, "boolean")) + addAll(findBooleanMethods(TARGET_CLASS_DEVICE_INFO, "java.lang.Boolean")) + }.distinct().sorted() + + YLog.info("扫描完成,共找到 ${allKeys.size} 个唯一功能 Key,发送回模块UI。") + dataChannel.put(KEY_RETURN_METHODS, ArrayList(allKeys)) + } + } catch (e: Throwable) { + YLog.error("扫描 Oplus 功能方法时出错!", e) + dataChannel.put(KEY_RETURN_METHODS, ArrayList()) + } + }.start() + } + + // Hook 已配置的功能项 + val prefs = prefs(OPLUS_SETTINGS_PREFS_NAME) + val modifiedKeys = prefs.getStringSet(KEY_CACHED_METHODS, emptySet()) + .filter { prefs.getInt(it, 0) != 0 } + + if (modifiedKeys.isNotEmpty()) { + YLog.info("找到 ${modifiedKeys.size} 个已配置功能 Key,开始 Hook") + modifiedKeys.forEach { uniqueKey -> + val className = uniqueKey.substringBeforeLast('.') + val methodName = uniqueKey.substringAfterLast('.') + if (className.isNotEmpty() && methodName.isNotEmpty()) + hookFeatureMethod(uniqueKey, methodName, className) + } + } + } + } + + private fun PackageParam.hookFeatureMethod(uniqueKey: String, methodName: String, className: String) { + val prefs = prefs(OPLUS_SETTINGS_PREFS_NAME) + val mode = prefs.getInt(uniqueKey, 0) + className.toClass().resolve().firstMethod { name = methodName }.hook { + before { + YLog.info("Hooked $className.$methodName -> 模式: $mode") + result = when (mode) { + 1 -> true + 2 -> false + else -> result + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/settings/settings.kt b/app/src/main/java/com/suqi8/oshin/hook/settings/settings.kt new file mode 100644 index 00000000..6b2687f5 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/settings/settings.kt @@ -0,0 +1,132 @@ +package com.suqi8.oshin.hook.settings + +import android.annotation.SuppressLint +import android.graphics.Bitmap +import android.graphics.ImageDecoder +import android.os.Environment +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.highcapable.kavaref.KavaRef.Companion.asResolver +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.java.BooleanType +import com.highcapable.yukihookapi.hook.type.java.StringClass +import java.io.File + +class settings : YukiBaseHooker() { + @SuppressLint("PrivateApi") + override fun onHook() { + //loadApp(hooker = feature()) + loadApp(hooker = Accessibility()) + loadHooker(SettingsFeature()) + loadApp(name = "com.android.settings") { + if (prefs("settings").getString("custom_display_model", "") != "") { + "com.oplus.settings.feature.deviceinfo.controller.OplusDeviceModelPreferenceController".toClass() + .apply { + method { + name = "getStatusText" + emptyParam() + returnType = StringClass + }.hook { + replaceTo(prefs("settings").getString("custom_display_model", "")) + } + } + } + if (prefs("settings").getBoolean("enable_ota_card_bg", false)) { + "com.oplus.settings.widget.preference.AboutDeviceOtaUpdatePreference".toClass() + .resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "onBindViewHolder" + parameters("androidx.preference.PreferenceViewHolder") + returnType = Void.TYPE + }.hook { + after { + val mAboutDeviceTopBg = instance.asResolver().firstField { + name = "mAboutDeviceTopBg" + }.get() as ImageView + File("${Environment.getExternalStorageDirectory()}/.OShin/settings/ota_card.png").takeIf { it.exists() } + ?.let { file -> + val bitmap = + ImageDecoder.decodeBitmap(ImageDecoder.createSource(file)) + val safeBitmap = try { + if (bitmap.config == Bitmap.Config.HARDWARE) { + bitmap.copy(Bitmap.Config.ARGB_8888, true) ?: bitmap + } else bitmap + } catch (_: Exception) { + bitmap + } + RoundedBitmapDrawableFactory.create( + mAboutDeviceTopBg.resources, + safeBitmap + ).apply { + cornerRadius = + prefs("settings").getFloat("ota_corner_radius", 0f) + }.also { + mAboutDeviceTopBg.setImageDrawable(it) + } + } + } + } + } + } + if (prefs("settings").getBoolean("force_show_nfc_security_chip", false)) { + "com.oplus.settings.feature.deviceinfo.DeviceInfoUtils".toClass().apply { + method { + name = "isSupportNfcEse" + emptyParam() + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + } + /*"com.oplus.settings.feature.fingerprint.NewFingerEnrollActivity".toClass().resolve().apply { + firstMethod { + name = "handleEnrollHelp" + }.hook { + before { + YLog.info(args[0]) + //args[0] = 1303 + if (args[0] == 1002) { + YLog.info("Duplicate fingerprint detected (1002). Forcing enrollment completion.") + + // 1. 阻止 handleEnrollHelp 方法的原始逻辑执行,避免任何多余的提示或操作 + resultNull() + + val uiHandler = + instance.asResolver().firstField { name = "mUIHandler" }.get() as Handler + + uiHandler.post { + YLog.info("Executing completion task now.") + // 在 Runnable 任务中,安全地调用 handleEnrollCompleted + instance.asResolver().firstMethod { + "handleEnrollCompleted" + emptyParameters() + }.invoke() + } + } + } + } + }*/ + /*"com.android.settings.applications.appinfo.AppInfoDashboardFragment".toClass().apply { + method { + name = "onCreateOptionsMenu" + param("android.view.Menu", "android.view.MenuInflater") + returnType = UnitType + }.hook { + after { + val menu = args[0] as Menu + menu.add(0, 3, 0, "aaa").setShowAsAction(0) + args[0] = menu + } + } + }*/ + } + } +} + diff --git a/app/src/main/java/com/suqi8/oshin/hook/speechassist/speechassist.kt b/app/src/main/java/com/suqi8/oshin/hook/speechassist/speechassist.kt new file mode 100644 index 00000000..9444afaf --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/speechassist/speechassist.kt @@ -0,0 +1,31 @@ +package com.suqi8.oshin.hook.speechassist + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method + +class speechassist: YukiBaseHooker() { + override fun onHook() { + if (prefs("speechassist").getBoolean("ai_call", false)) { + loadApp(name = "com.heytap.speechassist") { + "com.heytap.speechassist.aicall.setting.config.AiCallCommonBean".toClass().apply { + method { + name = "getSupportAiCall" + }.hook { + before { + result = true + } + } + } + "com.heytap.speechassist.aicall.setting.config.AiCallCommonBean".toClass().apply { + method { + name = "getSupportAiCallV2" + }.hook { + before { + result = true + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/systemui/DisableDataTransferAuth.kt b/app/src/main/java/com/suqi8/oshin/hook/systemui/DisableDataTransferAuth.kt new file mode 100644 index 00000000..4b6458f0 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/systemui/DisableDataTransferAuth.kt @@ -0,0 +1,30 @@ +package com.suqi8.oshin.hook.systemui + +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker + +class DisableDataTransferAuth: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.android.systemui") { + if (prefs("systemui").getBoolean("disable_data_transfer_auth", false)) { + "com.oplus.systemui.shutdown.ShutdownBiometricPrompt".toClass().resolve().firstMethod { + modifiers(Modifiers.PUBLIC, Modifiers.FINAL) + name = "authenticate" + parameters(Boolean::class, "android.os.CancellationSignal", "com.oplus.systemui.shutdown.ShutdownBiometricPrompt\$AuthenticationCallback") + returnType = Void.TYPE + }.hook { + before { + val isCalledFromUsbService = Throwable().stackTrace.any { stackTraceElement -> + stackTraceElement.className == "com.oplus.systemui.usb.UsbService" + } + if (!isCalledFromUsbService) return@before + args[2]?.javaClass?.resolve()?.firstMethod { + name = "onAuthenticationSucceeded" + }?.invoke() + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/systemui/RemoveUsbSelectionDialog.kt b/app/src/main/java/com/suqi8/oshin/hook/systemui/RemoveUsbSelectionDialog.kt new file mode 100644 index 00000000..4ebf847c --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/systemui/RemoveUsbSelectionDialog.kt @@ -0,0 +1,24 @@ +package com.suqi8.oshin.hook.systemui + +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker + +class RemoveUsbSelectionDialog: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.android.systemui") { + if (prefs("systemui").getBoolean("remove_usb_selection_dialog", false)) { + "com.oplus.systemui.usb.UsbService".toClass().resolve().firstMethod { + modifiers(Modifiers.PUBLIC, Modifiers.FINAL) + name = "performUsbDialogAction" + parameters(Int::class) + returnType = Void.TYPE + }.hook { + before { + if (args[0] == 1002 || args[0] == 1003) resultNull() + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/BatteryBar.kt b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/BatteryBar.kt new file mode 100644 index 00000000..bf97db5e --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/BatteryBar.kt @@ -0,0 +1,15 @@ +package com.suqi8.oshin.hook.systemui.StatusBar + +import android.annotation.SuppressLint +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker + +class BatteryBar: YukiBaseHooker() { + @SuppressLint("SetTextI18n") + override fun onHook() { + loadApp("com.android.systemui"){ + "com.android.systemui.statusbar.policy.Clock".toClass().apply { + + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/Clock.kt b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/Clock.kt new file mode 100644 index 00000000..bfa24f6a --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/Clock.kt @@ -0,0 +1,291 @@ +package com.suqi8.oshin.hook.systemui.StatusBar + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Handler +import android.provider.Settings +import android.util.TypedValue +import android.view.Gravity +import android.widget.TextView +import com.highcapable.kavaref.KavaRef +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import java.lang.ref.WeakReference +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date +import java.util.Timer +import java.util.TimerTask + +class Clock: YukiBaseHooker() { + + companion object { + private var highFrequencyTimer: Timer? = null + private var mainHandler: Handler? = null + private var clockViewRef: WeakReference? = null + } + + private val statusBarPrefs by lazy { prefs("systemui\\status_bar\\status_bar_clock") } + private val clockEnabled by lazy { statusBarPrefs.getBoolean("status_bar_clock", false) } + private val clockStyleSelectedOption by lazy { statusBarPrefs.getInt("ClockStyleSelectedOption", 0) } + private val showYears by lazy { statusBarPrefs.getBoolean("ShowYears", false) } + private val showMonth by lazy { statusBarPrefs.getBoolean("ShowMonth", false) } + private val showDay by lazy { statusBarPrefs.getBoolean("ShowDay", false) } + private val showWeek by lazy { statusBarPrefs.getBoolean("ShowWeek", false) } + private val showCNHour by lazy { statusBarPrefs.getBoolean("ShowCNHour", false) } + private val showtimePeriod by lazy { statusBarPrefs.getBoolean("Showtime_period", false) } + private val showSeconds by lazy { statusBarPrefs.getBoolean("ShowSeconds", true) } + private val showMillisecond by lazy { statusBarPrefs.getBoolean("ShowMillisecond", false) } + private val hideSpace by lazy { statusBarPrefs.getBoolean("HideSpace", false) } + private val dualRow by lazy { statusBarPrefs.getBoolean("DualRow", false) } + private val fontSize by lazy { statusBarPrefs.getFloat("ClockSize", 0f) } + private val updateSpeed by lazy { statusBarPrefs.getFloat("ClockUpdateSpeed", 0f) } + private val customClockStyle by lazy { statusBarPrefs.getString("CustomClockStyle", "HH:mm") } + private val customAlignment by lazy { statusBarPrefs.getInt("alignment", 0) } + private val clockLeftPadding by lazy { statusBarPrefs.getFloat("LeftPadding", 0f) } + private val clockRightPadding by lazy { statusBarPrefs.getFloat("RightPadding", 0f) } + private val clockTopPadding by lazy { statusBarPrefs.getFloat("TopPadding", 0f) } + private val clockBottomPadding by lazy { statusBarPrefs.getFloat("BottomPadding", 0f) } + + private lateinit var hookContext: Context + private val sdfCache = mutableMapOf() + private fun getFormatter(pattern: String): SimpleDateFormat = + sdfCache.getOrPut(pattern) { SimpleDateFormat(pattern) } + + @SuppressLint("SetTextI18n") + override fun onHook() { + if (!clockEnabled) return + + loadApp("com.android.systemui") { + val kavaRef = "com.android.systemui.statusbar.policy.Clock".toClass().resolve() + hookConstructor(kavaRef) + hookGetSmallTime(kavaRef) + } + } + + /** Hook 构造函数 */ + private fun hookConstructor(kavaRef: KavaRef.MemberScope) = kavaRef.apply { + firstConstructor { + modifiers(Modifiers.PUBLIC) + parameters("android.content.Context", "android.util.AttributeSet", Int::class) + }.hook { + after { + hookContext = args(0).cast()!! + val clockView = instance() + if (clockView.resources.getResourceEntryName(clockView.id) != "clock") return@after + setupClockView(clockView) + clockViewRef = WeakReference(clockView) + + if (updateSpeed > 0f) { + setupHighFrequencyUpdate() + } + } + } + } + + /** Hook getSmallTime 方法,进行自定义格式化 */ + private fun hookGetSmallTime(kavaRef: KavaRef.MemberScope) = kavaRef.apply { + firstMethod { + modifiers(Modifiers.PRIVATE, Modifiers.FINAL) + name = "getSmallTime" + emptyParameters() + returnType = "java.lang.CharSequence" + }.hook { + after { + instance().apply { + if (this.resources.getResourceEntryName(id) != "clock") return@after + } + val now = Calendar.getInstance().time + + result = if (clockStyleSelectedOption == 0) { + val dateStr = getDate(now) + val timeStr = getTime(now) + val newline = if (dualRow) "\n" else "" + dateStr + newline + timeStr + } else { + getCustomDate(now, customClockStyle) + } + val clockView = instance() + applyClockViewStyle(clockView) + } + } + } + + private fun applyClockViewStyle(clockView: TextView) { + clockView.apply { + if (dualRow) { + val defaultSize = if (fontSize != 0f) fontSize else 8F + setTextSize(TypedValue.COMPLEX_UNIT_DIP, defaultSize) + setLineSpacing(0F, 0.8F) + } else { + if (fontSize != 0f) { + setTextSize(TypedValue.COMPLEX_UNIT_DIP, fontSize) + } + } + } + } + + /** 设置 Clock View 的外观 */ + @SuppressLint("SetTextI18n") + private fun setupClockView(clockView: TextView) { + clockView.apply { + if (this.resources.getResourceEntryName(id) != "clock") return@apply + if (mainHandler == null) { + mainHandler = Handler(context.mainLooper) + } + + isSingleLine = false + gravity = when (customAlignment) { + 0 -> Gravity.CENTER + 1 -> Gravity.TOP + 2 -> Gravity.BOTTOM + 3 -> Gravity.START + 4 -> Gravity.END + 5 -> Gravity.CENTER_HORIZONTAL + 6 -> Gravity.CENTER_VERTICAL + 7 -> Gravity.FILL + 8 -> Gravity.FILL_HORIZONTAL + 9 -> Gravity.FILL_VERTICAL + else -> Gravity.CENTER + } + + fun dp(value: Float): Int = + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, resources.displayMetrics).toInt() + + setPadding( + if (clockLeftPadding != 0f) dp(clockLeftPadding) else paddingLeft, + if (clockTopPadding != 0f) dp(clockTopPadding) else paddingTop, + if (clockRightPadding != 0f) dp(clockRightPadding) else paddingRight, + if (clockBottomPadding != 0f) dp(clockBottomPadding) else paddingBottom + ) + + if (dualRow) { + val defaultSize = if (fontSize != 0f) fontSize else 8F + setTextSize(TypedValue.COMPLEX_UNIT_DIP, defaultSize) + setLineSpacing(0F, 0.8F) + } else { + if (fontSize != 0f) setTextSize(TypedValue.COMPLEX_UNIT_DIP, fontSize) + } + } + } + + /** 设置高频更新 */ + private fun setupHighFrequencyUpdate() { + highFrequencyTimer?.cancel() + highFrequencyTimer = null + + highFrequencyTimer = Timer("ClockUpdateTimer", true).apply { + schedule(object : TimerTask() { + override fun run() { + mainHandler?.post { + val clockView = clockViewRef?.get() ?: return@post + + val now = Calendar.getInstance().time + val newText = if (clockStyleSelectedOption == 0) { + val dateStr = getDate(now) + val timeStr = getTime(now) + val newline = if (dualRow) "\n" else "" + dateStr + newline + timeStr + } else { + getCustomDate(now, customClockStyle) + } + clockView.text = newText + } + } + }, 1000 - (System.currentTimeMillis() % 1000), updateSpeed.toLong()) + } + } + + @SuppressLint("SimpleDateFormat") + private fun getCustomDate(now: Date, format: String): String = getFormatter(format).format(now) + + @SuppressLint("SimpleDateFormat") + private fun getDate(now: Date): String { + var dateFormat = "" + if (isZh(hookContext)) { + if (showYears) dateFormat += "yy年" + if (showMonth) dateFormat += "M月" + if (showDay) dateFormat += "d日" + if (showWeek) dateFormat += "E" + if (!hideSpace && !dualRow) dateFormat += " " + } else { + if (showYears) { + dateFormat += "yy" + if (showMonth || showDay) dateFormat += "/" + } + if (showMonth) { + dateFormat += "M" + if (showDay) dateFormat += "/" + } + if (showDay) dateFormat += "d" + if (showWeek) dateFormat += " E" + if (!hideSpace && !dualRow) dateFormat += " " + } + return if (dateFormat.trim().isNotEmpty()) getFormatter(dateFormat).format(now) else "" + } + + @SuppressLint("SimpleDateFormat") + private fun getTime(now: Date): String { + var timeFormatPattern = if (is24(hookContext)) "HH:mm" else "hh:mm" + if (showSeconds) timeFormatPattern += ":ss" + if (showMillisecond) timeFormatPattern += ".SSS" + + var timeFormat = getFormatter(timeFormatPattern).format(now) + timeFormat = if (isZh(hookContext)) getPeriod(now) + timeFormat else timeFormat + getPeriod(now) + timeFormat = getDoubleHour(now) + timeFormat + return timeFormat + } + + @SuppressLint("SimpleDateFormat") + private fun getDoubleHour(now: Date): String { + var doubleHour = "" + if (showCNHour) { + when (getFormatter("HH").format(now)) { + "23", "00" -> doubleHour = "子时" + "01", "02" -> doubleHour = "丑时" + "03", "04" -> doubleHour = "寅时" + "05", "06" -> doubleHour = "卯时" + "07", "08" -> doubleHour = "辰时" + "09", "10" -> doubleHour = "巳时" + "11", "12" -> doubleHour = "午时" + "13", "14" -> doubleHour = "未时" + "15", "16" -> doubleHour = "申时" + "17", "18" -> doubleHour = "酉时" + "19", "20" -> doubleHour = "戌时" + "21", "22" -> doubleHour = "亥时" + } + if (!hideSpace) doubleHour += " " + } + return doubleHour + } + + @SuppressLint("SimpleDateFormat") + private fun getPeriod(now: Date): String { + var period = "" + if (showtimePeriod) { + if (isZh(hookContext)) { + when (getFormatter("HH").format(now)) { + in "00".."05" -> period = "凌晨" + in "06".."11" -> period = "上午" + "12" -> period = "中午" + in "13".."17" -> period = "下午" + "18" -> period = "傍晚" + in "19".."23" -> period = "晚上" + } + if (!hideSpace) period += " " + } else { + period = " " + getFormatter("a").format(now) + } + } + return period + } + + private fun isZh(context: Context): Boolean { + val locale = context.resources.configuration.locales[0] + return locale.language.endsWith("zh") + } + + private fun is24(context: Context): Boolean = + Settings.System.getString(context.contentResolver, Settings.System.TIME_12_24) == "24" +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/Fragment.kt b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/Fragment.kt new file mode 100644 index 00000000..4dccb9a7 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/Fragment.kt @@ -0,0 +1,27 @@ +package com.suqi8.oshin.hook.systemui.StatusBar + +import android.view.View +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.android.BundleClass +import com.highcapable.yukihookapi.hook.type.android.ViewClass + +class Fragment: YukiBaseHooker() { + override fun onHook() { + if (prefs("systemui\\status_bar").getBoolean("hide_status_bar", false)) { + loadApp(name = "com.android.systemui") { + "com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment".toClass().apply { + method { + name = "onViewCreated" + param(ViewClass, BundleClass) + }.hook { + after { + val view = args[0] as View + view.visibility = View.INVISIBLE + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/HardwareIndicator.kt b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/HardwareIndicator.kt new file mode 100644 index 00000000..27eec7dc --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/HardwareIndicator.kt @@ -0,0 +1,369 @@ +package com.suqi8.oshin.hook.systemui.StatusBar + +import android.annotation.SuppressLint +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.graphics.Typeface +import android.os.Handler +import android.os.Looper +import android.view.Gravity +import android.view.ViewGroup +import android.view.ViewTreeObserver +import android.widget.LinearLayout +import android.widget.TextView +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.log.YLog +import java.io.File +import java.io.FileInputStream +import java.util.Properties +import java.util.concurrent.Executors +import kotlin.math.abs + +class HardwareIndicator : YukiBaseHooker() { + + private val localPrefs by lazy { prefs("systemui\\status_bar\\hardware_indicator") } + private val backgroundExecutor = Executors.newSingleThreadExecutor() + private val uiHandler = Handler(Looper.getMainLooper()) + private val clockResources = mutableMapOf() + + private data class ClockHookResources( + var indicatorContainer: LinearLayout? = null, + var consumptionIndicator: TextView? = null, + var temperatureIndicator: TextView? = null, + var consumptionRunnable: Runnable? = null, + var temperatureRunnable: Runnable? = null, + var preDrawListener: ViewTreeObserver.OnPreDrawListener? = null, + var configChangeReceiver: BroadcastReceiver? = null, + var lastCpuTotalTime: Long = 0L, + var lastCpuIdleTime: Long = 0L + ) + + override fun onHook() { + loadApp("com.android.systemui") { + val clockClass = "com.android.systemui.statusbar.policy.Clock".toClass() + + clockClass.resolve().firstMethod { + name = "onAttachedToWindow" + }.hook { + after { + val clockTextView = instance as? TextView ?: return@after + if (clockTextView.javaClass.name != "com.oplus.systemui.statusbar.widget.StatClock") return@after + + // [核心修改] 将所有视图操作 post 到 UI 线程消息队列的末尾执行 + // 以确保系统自身的布局流程已经完成,避免竞争条件导致通知图标消失 + uiHandler.post { + // 在 post 的代码块内部,需要重新检查 clock 是否还附着在窗口上 + if (!clockTextView.isAttachedToWindow) return@post + + val clockWrapper = clockTextView.parent as? ViewGroup ?: return@post + val parent = clockWrapper.parent as? ViewGroup ?: return@post + + cleanupResourcesFor(clockTextView) + + val resources = ClockHookResources() + clockResources[clockTextView] = resources + + val powerEnabled = localPrefs.getBoolean("power_indicator_enabled", false) + val tempEnabled = localPrefs.getBoolean("temp_indicator_enabled", false) + + if (!powerEnabled && !tempEnabled) return@post + + // 1. 创建总容器 + val container = LinearLayout(clockTextView.context).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.MATCH_PARENT + ).apply { + //gravity = Gravity.CENTER_VERTICAL + marginStart = 2.dpToPx(context) + } + } + resources.indicatorContainer = container + + if (powerEnabled) createConsumptionIndicator(clockTextView, container, resources) + if (tempEnabled) createTemperatureIndicator(clockTextView, container, resources) + + val wrapperIndex = parent.indexOfChild(clockWrapper) + if (wrapperIndex != -1) { + parent.addView(container, wrapperIndex + 1) + } else { + parent.addView(container) // 作为备用方案 + } + + // 4. 启动监听 + startSyncListeners(clockTextView, resources) + } + } + } + + clockClass.resolve().firstMethod { + name = "onDetachedFromWindow" + }.hook { + after { + val clockTextView = instance as? TextView ?: return@after + cleanupResourcesFor(clockTextView) + } + } + } + } + + private fun cleanupResourcesFor(clock: TextView) { + clockResources.remove(clock)?.apply { + consumptionRunnable?.let { uiHandler.removeCallbacks(it) } + temperatureRunnable?.let { uiHandler.removeCallbacks(it) } + preDrawListener?.let { clock.viewTreeObserver.removeOnPreDrawListener(it) } + configChangeReceiver?.let { + runCatching { clock.context.unregisterReceiver(it) } + .onFailure { YLog.error("Failed to unregister BroadcastReceiver", it) } + } + indicatorContainer?.let { + (it.parent as? ViewGroup)?.removeView(it) + } + } + } + + private fun createConsumptionIndicator(clock: TextView, container: LinearLayout, res: ClockHookResources) { + val indicator = createIndicatorTextView(clock) + res.consumptionIndicator = indicator + container.addView(indicator) + + res.consumptionRunnable = createPeriodicUpdater { + updateIndicator( + indicator = indicator, + prefix = "power_indicator", + resources = res + ) + localPrefs.getFloat("power_indicator_update_interval", 1000f).toLong() + }.also { uiHandler.post(it) } + } + + private fun createTemperatureIndicator(clock: TextView, container: LinearLayout, res: ClockHookResources) { + val indicator = createIndicatorTextView(clock) + res.temperatureIndicator = indicator + container.addView(indicator) + + res.temperatureRunnable = createPeriodicUpdater { + updateIndicator( + indicator = indicator, + prefix = "temp_indicator", + resources = res + ) + localPrefs.getFloat("temp_indicator_update_interval", 1000f).toLong() + }.also { uiHandler.post(it) } + } + + private fun updateIndicator(indicator: TextView, prefix: String, resources: ClockHookResources) { + val fontSize = localPrefs.getFloat("${prefix}_font_size", 8f) + val boldText = localPrefs.getBoolean("${prefix}_bold", false) + val alignment = localPrefs.getInt("${prefix}_alignment", Gravity.CENTER) + indicator.gravity = alignment + indicator.textSize = fontSize + indicator.setTypeface(null, if (boldText) Typeface.BOLD else Typeface.NORMAL) + updateIndicatorText(indicator, prefix, resources) + } + + @SuppressLint("SetTextI18n", "DefaultLocale") + private fun updateIndicatorText(indicator: TextView, prefix: String, resources: ClockHookResources) { + backgroundExecutor.execute { + val props = runCatching { readBatteryProperties() }.getOrNull() + val currentNow = props?.getProperty("POWER_SUPPLY_CURRENT_NOW")?.toFloatOrNull() ?: 0f + val voltageNow = props?.getProperty("POWER_SUPPLY_VOLTAGE_NOW")?.toFloatOrNull() ?: 0f + val cpuTempSource = localPrefs.getFloat("data_temp_cpu_source", 0f) + val cpuTemp = readCpuTemp(cpuTempSource) + val cpuFreqSource = localPrefs.getFloat("data_freq_cpu_source", 0f) + val cpuFreq = readCpuFreq(cpuFreqSource) + val cpuUsage = calculateCpuUsage(resources) + val ramUsage = readRamUsage() + val batteryTemp = runCatching { + File("/sys/class/power_supply/battery/temp").readText().trim().toFloat() / 10f + }.getOrElse { 0f } + + val absolute = localPrefs.getBoolean("data_power_absolute_current", false) + var batteryCurrentMa = currentNow * -1 + if (absolute) { + batteryCurrentMa = abs(batteryCurrentMa) + } + val isDualCell = localPrefs.getBoolean("data_power_dual_cell", false) + if (isDualCell) { + batteryCurrentMa *= 2 + } + val batteryCurrent = batteryCurrentMa / 1000f + val batteryVoltage = voltageNow / 1_000_000f + val batteryWatt = batteryCurrent * batteryVoltage + + uiHandler.post { + val show1 = localPrefs.getInt("${prefix}_line1_content", 0) + val show2 = localPrefs.getInt("${prefix}_line2_content", 0) + val isDualRow = localPrefs.getBoolean("${prefix}_dual_row", false) + val dataStrings = formatAllDataToStrings( + batteryWatt, batteryCurrentMa, batteryCurrent, batteryVoltage, + batteryTemp, cpuTemp, cpuFreq, cpuUsage, ramUsage + ) + val line1 = dataStrings.getOrElse(show1) { "" } + val line2 = dataStrings.getOrElse(show2) { "" } + indicator.text = if (isDualRow) { + if (line1 == line2 || line2.isBlank()) line1 + else if (line1.isBlank()) line2 + else "$line1\n$line2" + } else { + line1 + } + } + } + } + + private fun formatAllDataToStrings( + batteryWatt: Float, batteryCurrentMa: Float, batteryCurrent: Float, batteryVoltage: Float, + batteryTemp: Float, cpuTemp: Float, cpuFreq: Int, cpuUsage: Int, ramUsage: Int + ): List { + val hidePowerUnit = localPrefs.getBoolean("unit_hide_power", false) + val hideCurrentUnit = localPrefs.getBoolean("unit_hide_current", false) + val hideVoltageUnit = localPrefs.getBoolean("unit_hide_voltage", false) + val hideBatteryUnit = localPrefs.getBoolean("unit_hide_temp_battery", false) + val hideCpuUnit = localPrefs.getBoolean("unit_hide_temp_cpu", false) + val hideCpuFreqUnit = localPrefs.getBoolean("unit_hide_cpu_frequency", false) + val hideCpuUsageUnit = localPrefs.getBoolean("unit_hide_cpu_usage", false) + val hideRamUsageUnit = localPrefs.getBoolean("unit_hide_ram_usage", false) + + val powerStr = String.format("%.2f", batteryWatt) + if (hidePowerUnit) "" else "W" + val currentStr = if (abs(batteryCurrentMa) >= 800) { + String.format("%.2f", batteryCurrent) + if (hideCurrentUnit) "" else "A" + } else { + String.format("%.0f", batteryCurrentMa) + if (hideCurrentUnit) "" else "mA" + } + val voltageStr = String.format("%.2f", batteryVoltage) + if (hideVoltageUnit) "" else "V" + val batteryTempStr = String.format("%.1f", batteryTemp) + if (hideBatteryUnit) "" else "°C" + val cpuTempStr = String.format("%.1f", cpuTemp) + if (hideCpuUnit) "" else "°C" + val cpuFreqStr = "$cpuFreq" + if (hideCpuFreqUnit) "" else "MHz" + val cpuUsageStr = "$cpuUsage" + if (hideCpuUsageUnit) "" else "%" + val ramUsageStr = "$ramUsage" + if (hideRamUsageUnit) "" else "%" + + return listOf(powerStr, currentStr, voltageStr, cpuTempStr, batteryTempStr, cpuFreqStr, cpuUsageStr, ramUsageStr) + } + + private fun readCpuTemp(source: Float) = runCatching { + File("/sys/class/thermal/thermal_zone${source.toInt()}/temp").readText().trim().toFloat() / 1000f + }.getOrElse { 0f } + + private fun readCpuFreq(source: Float) = runCatching { + File("/sys/devices/system/cpu/cpu${source.toInt()}/cpufreq/scaling_cur_freq").readText().trim().toInt() / 1000 + }.getOrElse { 0 } + + private fun readRamUsage(): Int { + return try { + val file = File("/proc/meminfo") + var memTotal: Long = -1 + var memAvailable: Long = -1 + file.forEachLine { line -> + when { + line.startsWith("MemTotal:") -> memTotal = line.split("\\s+".toRegex())[1].toLong() + line.startsWith("MemAvailable:") -> memAvailable = line.split("\\s+".toRegex())[1].toLong() + } + if (memTotal != -1L && memAvailable != -1L) return@forEachLine + } + if (memTotal > 0 && memAvailable > 0) { + (((memTotal - memAvailable) * 100) / memTotal).toInt() + } else 0 + } catch (e: Exception) { + 0 + } + } + + private fun calculateCpuUsage(res: ClockHookResources): Int { + return try { + val stats = File("/proc/stat").readText().lines().first() + val fields = stats.split("\\s+".toRegex()).drop(1) + val user = fields[0].toLong() + val nice = fields[1].toLong() + val system = fields[2].toLong() + val idle = fields[3].toLong() + val iowait = fields[4].toLong() + val irq = fields[5].toLong() + val softirq = fields[6].toLong() + + val totalTime = user + nice + system + idle + iowait + irq + softirq + val idleTime = idle + val totalDiff = totalTime - res.lastCpuTotalTime + val idleDiff = idleTime - res.lastCpuIdleTime + res.lastCpuTotalTime = totalTime + res.lastCpuIdleTime = idleTime + + if (totalDiff > 0) { + (100 * (totalDiff - idleDiff) / totalDiff).toInt() + } else 0 + } catch (e: Exception) { + 0 + } + } + + private fun createIndicatorTextView(clock: TextView): TextView = + TextView(clock.context).apply { + isSingleLine = false + setTextColor(clock.currentTextColor) + val params = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + params.marginStart = 2.dpToPx(context) + params.marginEnd = 2.dpToPx(context) + layoutParams = params + } + + private fun createPeriodicUpdater(updateAction: () -> Long): Runnable { + return object : Runnable { + override fun run() { + val delay = updateAction() + uiHandler.postDelayed(this, delay) + } + } + } + + private fun readBatteryProperties(): Properties = + Properties().apply { + runCatching { + FileInputStream("/sys/class/power_supply/battery/uevent").use { load(it) } + }.onFailure { YLog.error("Failed to read battery properties", it) } + } + + private fun startSyncListeners(clock: TextView, res: ClockHookResources) { + val container = res.indicatorContainer ?: return + val indicators = listOfNotNull(res.consumptionIndicator, res.temperatureIndicator) + if (indicators.isEmpty()) return + + res.preDrawListener = object : ViewTreeObserver.OnPreDrawListener { + override fun onPreDraw(): Boolean { + if (!clock.isAttachedToWindow) return true + val currentColor = clock.currentTextColor + val currentVisibility = clock.visibility + + if (container.visibility != currentVisibility) { + container.visibility = currentVisibility + } + + indicators.forEach { target -> + if (target.currentTextColor != currentColor) target.setTextColor(currentColor) + } + return true + } + }.also { clock.viewTreeObserver.addOnPreDrawListener(it) } + + res.configChangeReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + uiHandler.postDelayed({ + if (clock.isAttachedToWindow) { + indicators.forEach { it.setTextColor(clock.currentTextColor) } + } + }, 100) + } + }.also { clock.context.registerReceiver(it, IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)) } + } + + private fun Int.dpToPx(context: Context): Int { + return (this * context.resources.displayMetrics.density).toInt() + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/Notification.kt b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/Notification.kt new file mode 100644 index 00000000..da576b23 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/Notification.kt @@ -0,0 +1,63 @@ +package com.suqi8.oshin.hook.systemui.StatusBar + +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker + +class Notification: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.android.systemui") { + if (prefs("systemui\\notification").getBoolean("remove_developer_options_notification", false)) { + "com.oplus.systemui.statusbar.controller.SystemPromptController".toClass().resolve().apply { + firstMethod { + name = "updateDeveloperMode" + }.hook { + replaceUnit { } + } + } + + } + if (prefs("systemui\\notification").getBoolean("remove_and_do_not_disturb_notification", false)) { + "com.oplus.systemui.statusbar.controller.NoDisturbController".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC, Modifiers.FINAL) + name = "updateNoDisturbStatus" + emptyParameters() + returnType = Void.TYPE + }.hook { + replaceUnit { } + } + } + } + if (prefs("systemui\\notification").getBoolean("remove_charging_complete_notification", false)) { + "com.oplus.systemui.statusbar.notification.power.OplusPowerNotificationWarnings".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC, Modifiers.FINAL) + name = "showChargeErrorDialog" + parameters(Int::class) + returnType = Void.TYPE + }.hook { + before { + if (args[0] != 7) return@before + resultNull() + } + } + } + } + } + loadSystem { + if (prefs("systemui\\notification").getBoolean("remove_active_vpn_notification", false)) { + "com.android.server.connectivity.VpnExtImpl".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "showNotification" + parameters(String::class, Int::class, Int::class, String::class, "android.app.PendingIntent", "com.android.internal.net.VpnConfig") + returnType = Void.TYPE + }.hook { + replaceUnit { } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/ShowRealBattery.kt b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/ShowRealBattery.kt new file mode 100644 index 00000000..b3ca0f2f --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/ShowRealBattery.kt @@ -0,0 +1,39 @@ +package com.suqi8.oshin.hook.systemui.StatusBar + +import android.content.Intent +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import java.io.File + +class ShowRealBattery: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.android.systemui") { + if (prefs("systemui\\status_bar").getBoolean("show_real_battery", false)) { + "com.android.systemui.statusbar.policy.BatteryControllerImpl".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC, Modifiers.FINAL) + name = "onReceive" + parameters("android.content.Context", "android.content.Intent") + returnType = Void.TYPE + }.hook { + before { + val intent = args[1] as? Intent ?: return@before + if (intent.action == "android.intent.action.BATTERY_CHANGED") { + val oplusSocPath = "/sys/class/oplus_chg/battery/chip_soc" + val socFile = File(oplusSocPath) + if (socFile.exists() && socFile.canRead()) { + val socValue = socFile.readText().trim().toIntOrNull() + if (socValue != null) { + intent.putExtra("level", socValue) + intent.putExtra("scale", 100) + } + } + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/StatusBarLayout.kt b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/StatusBarLayout.kt new file mode 100644 index 00000000..0563e36b --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/StatusBarLayout.kt @@ -0,0 +1,262 @@ +package com.suqi8.oshin.hook.systemui.StatusBar + +import android.content.Context +import android.content.res.Resources +import android.graphics.Rect +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.core.graphics.toColorInt +import androidx.core.view.isNotEmpty +import com.github.kyuubiran.ezxhelper.params +import com.google.gson.Gson +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.log.YLog +import com.suqi8.oshin.ui.activity.func.StatusBarLayout.ViewConfig +import com.suqi8.oshin.ui.activity.func.StatusBarLayout.ViewNode +import java.util.concurrent.ConcurrentHashMap + +/** + * Xposed Hook 类,用于注入到 SystemUI 进程。 + * 负责解析和修改状态栏视图、与主 App 进行数据通信。 + */ +class StatusBarLayout : YukiBaseHooker() { + + private val gson = Gson() + private val statusBarViews = ConcurrentHashMap() + private val highlightOverlays = ConcurrentHashMap() + private val mainHandler by lazy { Handler(Looper.getMainLooper()) } + + companion object { + const val KEY_REQUEST_TREE = "request_view_tree" + const val KEY_RECEIVE_TREE = "receive_view_tree" + const val KEY_UPDATE_CONFIG = "update_view_config" + const val KEY_HIGHLIGHT_ANCHOR = "highlight_anchor_view" + + const val PREFS_NAME = "systemui\\status_bar\\StatusBarLayout" + const val PREFS_KEY = "statusbar_layout_configs" + } + + override fun onHook() { + if (packageName != "com.android.systemui") return + YLog.info("视图控制器Hooker已初始化, 目标包 $packageName") + + "com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment".toClass().resolve().firstMethod { + name = "onViewCreated" + params(View::class.java, Bundle::class.java) + }.hook { + after { + val statusBarView = args[0] as? ViewGroup ?: return@after + YLog.info("成功通过 Fragment Hook 找到 status_bar 视图: $statusBarView") + statusBarViews[statusBarView.hashCode()] = statusBarView + + // 初始化 DataChannel 监听器 + dataChannel.apply { + wait(KEY_REQUEST_TREE) { + YLog.info("🔔 [Hook] 收到请求树的指令") + val mainStatusBarView = statusBarViews.values.firstOrNull() + val jsonTree = if (mainStatusBarView != null) { + try { + gson.toJson(parseView(mainStatusBarView)) + } catch (e: Throwable) { + YLog.error("解析视图树时出错", e) + "{}" + } + } else { + YLog.warn("尚未找到状态条视图,返回空树。") + "{}" + } + put(KEY_RECEIVE_TREE, jsonTree) + } + + wait(KEY_UPDATE_CONFIG) { + YLog.info("🔔 [Hook] 收到更新配置的指令") + handleUpdateConfig() + } + + wait(KEY_HIGHLIGHT_ANCHOR) { hashCode -> + YLog.info("🔔 [Hook] 收到高亮指令, HashCode: $hashCode") + highlightView(hashCode) // 调用新的高亮方法 + } + } + YLog.info("✅ YukiHookDataChannel 监听器已设置。") + applyAllViewConfigs() + } + } + } + + /** + * 将单个视图配置应用到指定的根视图中。 + * 此方法会在主线程中执行。 + * @param rootView 根视图容器,通常是 PhoneStatusBarView。 + * @param config 要应用的视图配置。 + */ + private fun applyConfigToView(rootView: ViewGroup, config: ViewConfig) { + if (config.id.isBlank()) return + mainHandler.post { + try { + val resId = findResId(rootView.context, config.id) + if (resId != 0) { + val targetView = rootView.findViewById(resId) + if (targetView != null) { + val newVisibility = when (config.mode) { + ViewConfig.Companion.MODE_ALWAYS_VISIBLE -> View.VISIBLE + ViewConfig.Companion.MODE_ALWAYS_HIDDEN -> View.GONE // 不可见且不占位 + ViewConfig.Companion.MODE_ALWAYS_INVISIBLE -> View.INVISIBLE // 不可见但占位 + //ViewConfig.Companion.MODE_NORMAL -> View.VISIBLE // 默认行为设为 VISIBLE + else -> -1 // -1 表示无效模式,不作处理 + } + if (newVisibility != -1 && targetView.visibility != newVisibility) { + targetView.visibility = newVisibility + YLog.info("已应用配置到 ${config.id}: 模式=${config.mode}, 可见性设置为=${newVisibility}") + } + } else { YLog.warn("应用配置失败: 未在 $rootView 中找到视图 ID: ${config.id}") } + } else { YLog.warn("应用配置失败: 未找到资源 ID: ${config.id}") } + } catch (e: Exception) { YLog.error("应用配置时发生异常, ID: ${config.id}", e) } + } + } + + /** + * 一个递归函数,用于根据 hashCode 查找视图 + */ + private fun findView(root: ViewGroup, hashCode: Int): View? { + if (root.hashCode() == hashCode) return root + for (i in 0 until root.childCount) { + val child = root.getChildAt(i) + if (child.hashCode() == hashCode) { + return child + } + if (child is ViewGroup) { + val found = findView(child, hashCode) + if (found != null) return found + } + } + return null + } + + /** + * 在指定的状态栏视图上高亮某个子视图。 + * @param hashCode 要高亮的视图资源 hashCode。如果为空,则取消所有高亮。 + */ + private fun highlightView(hashCode: Int) { + mainHandler.post { + statusBarViews.values.forEach { statusBarView -> + val overlay = highlightOverlays.getOrPut(statusBarView) { + FrameLayout(statusBarView.context).apply { + setBackgroundColor("#55FF0000".toColorInt()) + visibility = View.GONE + isClickable = false + isFocusable = false + statusBarView.addView(this, FrameLayout.LayoutParams(0, 0)) + } + } + if (hashCode == 0) { // 使用 0 作为取消高亮的信号 + overlay.visibility = View.GONE + return@forEach + } + try { + val targetView = findView(statusBarView, hashCode) + if (targetView != null && targetView.isAttachedToWindow) { + val location = IntArray(2).also { targetView.getLocationInWindow(it) } + val statusBarLocation = IntArray(2).also { statusBarView.getLocationInWindow(it) } + val x = location[0] - statusBarLocation[0] + val y = location[1] - statusBarLocation[1] + val params = overlay.layoutParams as FrameLayout.LayoutParams + params.width = targetView.width + params.height = targetView.height + params.leftMargin = x + params.topMargin = y + overlay.layoutParams = params + overlay.visibility = View.VISIBLE + overlay.bringToFront() + } else { + overlay.visibility = View.GONE + } + } catch (_: Exception) { + overlay.visibility = View.GONE + } + } + } + } + + /** + * 根据字符串形式的资源ID(例如 "com.android.systemui:id/wifi_combo")查找其实际整型ID。 + * @param context 上下文。 + * @param resIdString 字符串资源ID。 + * @return 成功则返回整型ID,失败则返回 0。 + */ + private fun findResId(context: Context, resIdString: String): Int { + if (resIdString.isBlank()) return 0 + return try { + val resName = resIdString.substringAfterLast('/') + val resType = resIdString.substringAfter(':').substringBefore('/') + val resPackage = resIdString.substringBefore(':') + context.resources.getIdentifier(resName, resType, resPackage) + } catch (e: Exception) { 0 } + } + + /** + * 处理来自 App 的配置更新请求。 + */ + private fun handleUpdateConfig() { + YLog.info("正在处理更新配置的请求。") + applyAllViewConfigs() + } + + /** + * 从 SharedPreferences 加载所有配置,并应用到当前已 hook 到的所有状态栏视图上。 + */ + private fun applyAllViewConfigs() { + val jsonConfigs = prefs(PREFS_NAME).getString(PREFS_KEY, "[]") + val configs = runCatching { + gson.fromJson(jsonConfigs, Array::class.java).toList() + }.getOrNull() ?: emptyList() + + if (configs.isEmpty()) { + YLog.debug("配置为空,无需应用。") + return + } + statusBarViews.values.forEach { statusBarView -> + YLog.info("正在应用配置到 $statusBarView") + configs.forEach { config -> + applyConfigToView(statusBarView, config) + } + } + } + + /** + * 递归解析一个视图及其所有子视图,构建成 ViewNode 树形结构。 + * @param view 要解析的根视图。 + * @return 构建好的 ViewNode 对象。 + */ + private fun parseView(view: View): ViewNode { + val nodeId = try { + if (view.id != View.NO_ID) view.resources.getResourceName(view.id) else "" + } catch (e: Resources.NotFoundException) { "" } + val nodeType = view.javaClass.name + val children = if (view is ViewGroup && view.isNotEmpty()) { + (0 until view.childCount).mapNotNull { + view.getChildAt(it)?.let { child -> parseView(child) } + } + } else { emptyList() } + val visibilityString = when (view.visibility) { + View.VISIBLE -> "Visible" + View.INVISIBLE -> "Invisible" + View.GONE -> "Gone" + else -> "Unknown" + } + val bounds = Rect().apply { view.getHitRect(this) } + return ViewNode( + id = nodeId, + type = nodeType, + children = children, + visibility = visibilityString, + bounds = bounds, + hashCodeValue = view.hashCode() + ) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/Wifi.kt b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/Wifi.kt new file mode 100644 index 00000000..c7b2c016 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/systemui/StatusBar/Wifi.kt @@ -0,0 +1,582 @@ +package com.suqi8.oshin.hook.systemui.StatusBar + +import android.annotation.SuppressLint +import android.graphics.Typeface +import android.net.TrafficStats +import android.os.SystemClock +import android.util.TypedValue +import android.view.Gravity +import android.view.View +import android.widget.TextView +import com.highcapable.kavaref.KavaRef.Companion.asResolver +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import java.util.Locale +import android.view.ViewGroup.LayoutParams as ViewGroupParams +import android.widget.FrameLayout.LayoutParams as FrameLayoutParams + +/** + * Hook 系统状态栏网络速度指示器 + * + * 功能说明: + * - 自定义状态栏网络速度显示样式 + * - 支持上传/下载速度分别显示或合并显示 + * - 支持多种箭头指示器样式 + * - 支持慢速隐藏、字体自定义等功能 + */ +class Wifi : YukiBaseHooker() { + + // ========== 常量定义 ========== + private companion object { + /** 默认字体大小 (单位: dp) */ + private const val DEFAULT_FONT_SIZE = 8F + + /** SI 单位前缀数组: 字节 -> KB -> MB -> GB -> TB */ + private val SI_PREFIXES = arrayOf("", "K", "M", "G", "T") + + /** 网速计算时间间隔 (毫秒) */ + private const val CALCULATION_INTERVAL_MS = 20L + + /** Oplus 系统默认宽度 (单位: dp) */ + private const val OPLUS_DEFAULT_WIDTH_DP = 22 + } + + // ========== 状态变量 ========== + /** 上次计算时的时间戳 (纳秒) */ + private var lastTimestampNanos: Long = 0L + + /** 上次记录的接收字节数 */ + private var lastRxBytes: Long = 0L + + /** 上次记录的发送字节数 */ + private var lastTxBytes: Long = 0L + + /** 上次计算出的网速 (接收速度, 发送速度) */ + private var lastCalculatedSpeed: Pair = Pair(0L, 0L) + + /** 原始数字字体 (用于恢复) */ + private var originalNumTypeface: Typeface? = null + + /** 原始单位字体 (用于恢复) */ + private var originalUnitTypeface: Typeface? = null + + // ========== 配置数据类 ========== + /** + * 网速显示设置 + * + * @property styleOption 样式选项: 0=增强原生, 1=完全自定义 + * @property slowThreshold 慢速阈值 (KB/s) + * @property hideOnSlow 慢速时隐藏 + * @property hideWhenBothSlow 上传下载都慢时隐藏 + * @property iconIndicator 箭头指示器样式 (0-5) + * @property hideBs 隐藏 "B/s" 后缀 + * @property hideSpace 隐藏数字和单位之间的空格 + * @property swapUploadDownload 交换上传下载位置 + * @property uploadFontSize 上传字体大小 + * @property downloadFontSize 下载字体大小 + * @property positionIndicatorFront 箭头指示器放在前面 + * @property speedFontSize 速度数字字体大小 + * @property unitFontSize 单位字体大小 + * @property useSystemFont 使用系统字体 + * @property useUppercaseB 使用大写 B (Byte) + * @property hidePerSecond 隐藏 "/s" 后缀 + * @property alignment 对齐方式: 0=居中, 1=左对齐, 3=右对齐 + */ + data class Settings( + val styleOption: Int, + val slowThreshold: Int, + val hideOnSlow: Boolean, + val hideWhenBothSlow: Boolean, + val iconIndicator: Int, + val hideBs: Boolean, + val hideSpace: Boolean, + val swapUploadDownload: Boolean, + val uploadFontSize: Int, + val downloadFontSize: Int, + val positionIndicatorFront: Boolean, + val speedFontSize: Int, + val unitFontSize: Int, + val useSystemFont: Boolean, + val useUppercaseB: Boolean, + val hidePerSecond: Boolean, + val alignment: Int + ) + + /** + * 从配置文件读取设置 + * + * @return Settings 配置对象 + */ + private fun getSettings(): Settings { + val prefs = prefs("systemui\\status_bar\\status_bar_wifi") + return Settings( + styleOption = prefs.getInt("StyleSelectedOption", 0), + slowThreshold = prefs.getFloat("slow_speed_threshold", 20f).toInt(), + hideOnSlow = prefs.getBoolean("hide_on_slow", false), + hideWhenBothSlow = prefs.getBoolean("hide_when_both_slow", false), + iconIndicator = prefs.getInt("icon_indicator", 0), + hideBs = prefs.getBoolean("hide_bs", false), + hideSpace = prefs.getBoolean("hide_space", false), + swapUploadDownload = prefs.getBoolean("swap_upload_download", false), + uploadFontSize = prefs.getFloat("upload_font_size", -1f).toInt(), + downloadFontSize = prefs.getFloat("download_font_size", -1f).toInt(), + positionIndicatorFront = prefs.getBoolean("position_speed_indicator_front", false), + speedFontSize = prefs.getFloat("speed_font_size", -1f).toInt(), + unitFontSize = prefs.getFloat("unit_font_size", -1f).toInt(), + useSystemFont = prefs.getBoolean("use_system_font", false), + useUppercaseB = prefs.getBoolean("use_uppercase_b", false), + hidePerSecond = prefs.getBoolean("hide_per_second", false), + alignment = prefs.getInt("alignment", 0) + ) + } + + /** + * 应用布局宽度和对齐方式 + * + * 这个函数负责设置网速视图的布局参数,包括: + * - 父视图的宽度 (根据对齐方式决定是固定宽度还是匹配父布局) + * - 数字和单位文本的对齐方式 (左/中/右对齐) + * - 重置边距以确保紧凑布局 + * + * @param view 父视图 (NetworkSpeedView) + * @param numView 数字文本视图 + * @param unitView 单位文本视图 + * @param settings 用户设置 + */ + private fun applyLayoutAlignment( + view: View, + numView: TextView, + unitView: TextView, + settings: Settings + ) { + // 获取布局参数,如果为空则直接返回 + val viewLp = view.layoutParams ?: return + val numLp = numView.layoutParams as? FrameLayoutParams ?: return + val unitLp = unitView.layoutParams as? FrameLayoutParams ?: return + + // 获取屏幕密度,用于 dp 到 px 的转换 + val density = view.resources.displayMetrics.density + val defaultWidthPx = (OPLUS_DEFAULT_WIDTH_DP * density).toInt() + val align = settings.alignment + + // 1. 设置父视图宽度 + // - 如果是居中对齐 (align == 0),使用固定宽度 + // - 否则使用 MATCH_PARENT 以便文本可以左右对齐 + viewLp.width = if (align != 0) ViewGroupParams.MATCH_PARENT else defaultWidthPx + viewLp.height = ViewGroupParams.MATCH_PARENT + view.layoutParams = viewLp + + // 2. 根据用户设置确定水平对齐方式 + val horizontalGravity = when (align) { + 1 -> Gravity.START // 左对齐 + 3 -> Gravity.END // 右对齐 + else -> Gravity.CENTER_HORIZONTAL // 居中 + } + + // 3. 设置数字和单位文本的布局参数 + // 关键修改: 重置 margins 并设置紧凑间距,避免文本重叠 + numLp.width = ViewGroupParams.WRAP_CONTENT + numLp.gravity = horizontalGravity + + unitLp.width = ViewGroupParams.WRAP_CONTENT + unitLp.gravity = horizontalGravity + + // 应用布局参数 + numView.layoutParams = numLp + unitView.layoutParams = unitLp + } + + /** + * Hook 入口方法 + * + * 在这里进行三个关键的 Hook: + * 1. onFinishInflate - 初始化时保存原始字体 + * 2. updateSpeedNumberParams - 更新字体和布局参数 + * 3. applyNetworkState - 应用网速状态和显示 + */ + @SuppressLint("SetTextI18n") + override fun onHook() { + // 检查功能是否启用 + if (!prefs("systemui\\status_bar\\status_bar_wifi") + .getBoolean("status_bar_wifi", false)) return + + // Oplus 系统网速视图的完整类名 + val viewClassName = "com.oplus.systemui.statusbar.phone.netspeed.widget.NetworkSpeedView" + + loadApp("com.android.systemui") { + viewClassName.toClass().resolve().apply { + + // ========== HOOK 1: onFinishInflate ========== + // 在视图加载完成时执行,保存原始字体以便后续恢复 + firstMethod { name = "onFinishInflate" }.hook { + after { + // 获取数字和单位文本视图 + val numView = firstField { name = "mSpeedNumber" }.of(instance).get() as TextView + val unitView = firstField { name = "mSpeedUnit" }.of(instance).get() as TextView + + // 保存原始字体 + originalNumTypeface = numView.typeface + originalUnitTypeface = unitView.typeface + + // 如果用户选择使用系统字体,则立即应用 + val settings = getSettings() + if (settings.useSystemFont) { + numView.setTypeface(Typeface.DEFAULT) + unitView.setTypeface(Typeface.DEFAULT) + } + } + } + + // ========== HOOK 2: updateSpeedNumberParams ========== + // [关键] 在原方法执行后修改参数 + // 允许原始方法先设置边距,然后我们覆盖字体和布局 + firstMethod { name = "updateSpeedNumberParams" }.hook { + after { + val settings = getSettings() + + // 获取视图实例 + val view: View = instance() + val numView = firstField { name = "mSpeedNumber" } + .of(instance).get() as TextView? ?: return@after + val unitView = firstField { name = "mSpeedUnit" } + .of(instance).get() as TextView? ?: return@after + + // 1. 覆盖字体设置 (以防配置在运行时更改) + if (settings.useSystemFont) { + numView.setTypeface(Typeface.DEFAULT) + unitView.setTypeface(Typeface.DEFAULT) + } else { + // 恢复原始字体 + originalNumTypeface?.let { numView.setTypeface(it) } + originalUnitTypeface?.let { unitView.setTypeface(it) } + } + + // 2. 覆盖字体大小 + if (settings.styleOption == 1) { + // 样式1: 上传和下载使用不同字体大小 + val upFont = settings.uploadFontSize + .takeIf { it != -1 }?.toFloat() ?: DEFAULT_FONT_SIZE + val downFont = settings.downloadFontSize + .takeIf { it != -1 }?.toFloat() ?: DEFAULT_FONT_SIZE + + // 根据是否交换上传下载,应用相应字体大小 + numView.setTextSize( + TypedValue.COMPLEX_UNIT_DIP, + if (!settings.swapUploadDownload) upFont else downFont + ) + unitView.setTextSize( + TypedValue.COMPLEX_UNIT_DIP, + if (!settings.swapUploadDownload) downFont else upFont + ) + } else { + // 样式0: 统一字体大小 + settings.speedFontSize.takeIf { it != -1 }?.let { + numView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, it.toFloat()) + } + settings.unitFontSize.takeIf { it != -1 }?.let { + unitView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, it.toFloat()) + } + } + + // 3. 覆盖布局和对齐方式 + applyLayoutAlignment(view, numView, unitView, settings) + } + } + + // ========== HOOK 3: applyNetworkState ========== + // 在应用网络状态前执行,更新网速显示 + firstMethod { name = "applyNetworkState" }.hook { + before { + val settings = getSettings() + + // 获取当前网速 (接收速度, 发送速度) + val (rxSpeed, txSpeed) = getCurrentSpeed() + + // 获取视图实例 + val view: View = instance() + val numView = firstField { name = "mSpeedNumber" } + .of(instance).get() as TextView? ?: return@before + val unitView = firstField { name = "mSpeedUnit" } + .of(instance).get() as TextView? ?: return@before + + // 1. 每次更新时都确保布局正确 + // 这样可以保证对齐方式实时生效 + applyLayoutAlignment(view, numView, unitView, settings) + + if (settings.styleOption == 1) { + // ========== 样式1: 完全自定义模式 ========== + // 完全接管网速显示逻辑 + + // 获取网络图标状态 + val iconState = args[0] ?: return@before + firstField { name = "mState" }.of(instance).set(iconState) + + // 检查是否应该显示网速 + val shouldBeVisible = iconState.asResolver() + .firstMethod { name = "getVisible" } + .invoke() as Boolean + + if (!shouldBeVisible) { + // 网络不可用时隐藏 + view.visibility = View.GONE + resultNull() + return@before + } + + // 显示网速 + view.visibility = View.VISIBLE + + // 设置数字文本 (上传或下载,根据交换设置) + numView.visibility = View.VISIBLE + numView.text = if (!settings.swapUploadDownload) + txSpeed(settings, txSpeed, rxSpeed) + else + rxSpeed(settings, rxSpeed, txSpeed) + + // 设置单位文本 (下载或上传,根据交换设置) + unitView.visibility = View.VISIBLE + unitView.text = if (!settings.swapUploadDownload) + rxSpeed(settings, rxSpeed, txSpeed) + else + txSpeed(settings, txSpeed, rxSpeed) + + // 阻止原方法执行 + resultNull() + + } else { + // ========== 样式0: 增强系统原生模式 ========== + // 基于系统原生显示,添加慢速隐藏等增强功能 + + // 转换为 KB/s + val rxKB = rxSpeed / 1024 + val txKB = txSpeed / 1024 + + // 判断是否应该隐藏 + val hide = (settings.hideWhenBothSlow && + (rxKB <= settings.slowThreshold && + txKB <= settings.slowThreshold)) || + (settings.hideOnSlow && + (rxKB <= settings.slowThreshold)) + + // 根据隐藏标志显示/隐藏 + numView.visibility = if (hide) View.GONE else View.VISIBLE + unitView.visibility = if (hide) View.GONE else View.VISIBLE + + // 允许原始方法继续运行 (它会设置文本内容) + } + } + } + } + } + } + + /** + * 获取当前网速 + * + * 计算逻辑: + * 1. 获取当前时间戳和流量数据 + * 2. 计算与上次记录的时间差和流量差 + * 3. 通过 速度 = 流量差 / 时间差 计算网速 + * 4. 每隔 CALCULATION_INTERVAL_MS 更新一次 + * + * @return Pair (接收速度, 发送速度) 单位: 字节/秒 + */ + private fun getCurrentSpeed(): Pair { + // 获取当前时间戳 (纳秒) + val currentTimestamp = SystemClock.elapsedRealtimeNanos() + + // 获取总流量,排除本地回环接口 (lo) + val currentRxBytes = TrafficStats.getTotalRxBytes() - TrafficStats.getRxBytes("lo") + val currentTxBytes = TrafficStats.getTotalTxBytes() - TrafficStats.getTxBytes("lo") + + // 首次调用时初始化 + if (lastTimestampNanos == 0L) { + lastTimestampNanos = currentTimestamp + lastRxBytes = currentRxBytes + lastTxBytes = currentTxBytes + return Pair(0L, 0L) + } + + // 计算时间差 (纳秒) + val dtNanos = currentTimestamp - lastTimestampNanos + + // 如果时间间隔足够长,则重新计算网速 + if (dtNanos >= CALCULATION_INTERVAL_MS * 1_000_000) { + // 转换为秒 + val dtSeconds = dtNanos / 1e9 + + if (dtSeconds > 0.1) { + // 计算速度: (当前流量 - 上次流量) / 时间差 + val rxSpeed = ((currentRxBytes - lastRxBytes) / dtSeconds) + .toLong().coerceAtLeast(0) + val txSpeed = ((currentTxBytes - lastTxBytes) / dtSeconds) + .toLong().coerceAtLeast(0) + + // 保存计算结果 + lastCalculatedSpeed = Pair(rxSpeed, txSpeed) + } + + // 更新上次记录 + lastTimestampNanos = currentTimestamp + lastRxBytes = currentRxBytes + lastTxBytes = currentTxBytes + } + + // 返回最新的网速 + return lastCalculatedSpeed + } + + /** + * 格式化上传速度文本 + * + * @param settings 用户设置 + * @param txBytes 发送字节数/秒 + * @param rxBytes 接收字节数/秒 (用于慢速检测) + * @return 格式化的上传速度字符串 + */ + private fun txSpeed(settings: Settings, txBytes: Long, rxBytes: Long): String { + val txKB = txBytes / 1024 + val rxKB = rxBytes / 1024 + + // 根据慢速设置判断是否隐藏 + if (settings.hideWhenBothSlow && + (txKB <= settings.slowThreshold && rxKB <= settings.slowThreshold)) return "" + if (settings.hideOnSlow && (txKB <= settings.slowThreshold)) return "" + + // 获取上传箭头 + val arrow = txArrow(settings, txKB) + + // 根据设置决定箭头位置 (前/后) + return if (settings.positionIndicatorFront) + arrow + formatBytes(settings, txBytes) + else + formatBytes(settings, txBytes) + arrow + } + + /** + * 格式化下载速度文本 + * + * @param settings 用户设置 + * @param rxBytes 接收字节数/秒 + * @param txBytes 发送字节数/秒 (用于慢速检测) + * @return 格式化的下载速度字符串 + */ + private fun rxSpeed(settings: Settings, rxBytes: Long, txBytes: Long): String { + val rxKB = rxBytes / 1024 + val txKB = txBytes / 1024 + + // 根据慢速设置判断是否隐藏 + if (settings.hideWhenBothSlow && + (rxKB <= settings.slowThreshold && txKB <= settings.slowThreshold)) return "" + if (settings.hideOnSlow && (rxKB <= settings.slowThreshold)) return "" + + // 获取下载箭头 + val arrow = rxArrow(settings, rxKB) + + // 根据设置决定箭头位置 (前/后) + return if (settings.positionIndicatorFront) + arrow + formatBytes(settings, rxBytes) + else + formatBytes(settings, rxBytes) + arrow + } + + /** + * 生成上传箭头图标 + * + * 根据上传速度和用户设置的指示器样式,生成对应的箭头符号 + * 部分样式支持根据速度快慢显示不同的箭头 + * + * @param settings 用户设置 + * @param speedKB 上传速度 (KB/s) + * @return 箭头字符串 + */ + private fun txArrow(settings: Settings, speedKB: Long): String = when (settings.iconIndicator) { + 1 -> if (speedKB < settings.slowThreshold) "△" else "▲" // 空心/实心三角形 + 2 -> if (speedKB < settings.slowThreshold) "▵ " else "▴ " // 白色/黑色三角形 + 3 -> if (speedKB < settings.slowThreshold) "☖ " else "☗ " // 将棋符号 + 4 -> "↑" // 简单向上箭头 + 5 -> "⇧" // 双线向上箭头 + else -> "" // 无箭头 + } + + /** + * 生成下载箭头图标 + * + * 根据下载速度和用户设置的指示器样式,生成对应的箭头符号 + * 部分样式支持根据速度快慢显示不同的箭头 + * + * @param settings 用户设置 + * @param speedKB 下载速度 (KB/s) + * @return 箭头字符串 + */ + private fun rxArrow(settings: Settings, speedKB: Long): String = when (settings.iconIndicator) { + 1 -> if (speedKB < settings.slowThreshold) "▽" else "▼" // 空心/实心倒三角 + 2 -> if (speedKB < settings.slowThreshold) "▿ " else "▾ " // 白色/黑色倒三角 + 3 -> if (speedKB < settings.slowThreshold) "⛉ " else "⛊ " // 十字符号 + 4 -> "↓" // 简单向下箭头 + 5 -> "⇩" // 双线向下箭头 + else -> "" // 无箭头 + } + + /** + * 格式化字节数为人类可读的字符串 + * + * 转换规则: + * - 小于 1024 字节: 显示字节 + * - 1024 - 1024² 字节: 显示 KB + * - 1024² - 1024³ 字节: 显示 MB + * - 以此类推... + * + * 小数位数规则: + * - 0: 显示为 "0" + * - < 10: 保留 2 位小数 (如 9.87 MB) + * - < 100: 保留 1 位小数 (如 98.7 MB) + * - >= 100: 不保留小数 (如 987 MB) + * + * @param settings 用户设置 + * @param bytes 字节数 + * @return 格式化的字符串 (如 "1.23 MB/s") + */ + private fun formatBytes(settings: Settings, bytes: Long): String { + // 负数返回空字符串 + if (bytes < 0) return "" + + // 根据用户设置确定格式 + val space = if (settings.hideSpace) "" else " " // 空格 + val baseUnit = if (settings.useUppercaseB) "B" else "b" // B(字节) 或 b(比特) + val perSecond = if (settings.hidePerSecond) "" else "/s" // "/s" 后缀 + + var size = bytes.toDouble() + var index = 0 // SI 前缀索引 + + // 转换为合适的单位 + if (bytes > 0) { + while (size >= 1024 && index < SI_PREFIXES.size - 1) { + size /= 1024 + index++ + } + } + + // 获取 SI 前缀 (如 K, M, G) + val prefix = SI_PREFIXES[index] + + // 根据大小确定小数位数 + val formattedNumber = when { + size == 0.0 -> "0" + index == 0 -> String.format(Locale.US, "%.0f", size) // 字节不显示小数 + size < 10 -> String.format(Locale.US, "%.2f", size) // < 10: 2位小数 + size < 100 -> String.format(Locale.US, "%.1f", size) // < 100: 1位小数 + else -> String.format(Locale.US, "%.0f", size) // >= 100: 无小数 + } + + // 组装单位字符串 + val unit = if (settings.hideBs) { + prefix // 只显示前缀 (如 "K", "M") + } else { + prefix + baseUnit + perSecond // 完整单位 (如 "KB/s", "Mb/s") + } + + // 返回最终格式化的字符串 + return formattedNumber + space + unit + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/systemui/ToastForceShowAppIcon.kt b/app/src/main/java/com/suqi8/oshin/hook/systemui/ToastForceShowAppIcon.kt new file mode 100644 index 00000000..487c9d74 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/systemui/ToastForceShowAppIcon.kt @@ -0,0 +1,36 @@ +package com.suqi8.oshin.hook.systemui + +import android.content.Context +import android.view.View +import android.widget.ImageView +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker + +class ToastForceShowAppIcon: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.android.systemui") { + if (prefs("systemui").getBoolean("toast_force_show_app_icon", false)) { + "com.oplus.systemui.toast.OplusSystemUIToast".toClass().resolve().apply { + firstConstructor { + modifiers(Modifiers.PUBLIC) + parameters("android.content.Context", "java.lang.CharSequence", "com.android.systemui.plugins.ToastPlugin\$Toast", String::class, Int::class, Int::class, "com.oplus.systemui.common.helper.ResponsiveUIModelHelper") + }.hook { + after { + val mIconView =firstField { + modifiers(Modifiers.PUBLIC) + name = "mIconView" + type = "android.widget.ImageView" + }.of(instance).get() as ImageView + val context = args[0] as Context + val pkgName = args[3] as String + val appIcon = context.packageManager.getApplicationIcon(pkgName) + mIconView.setImageDrawable(appIcon) + mIconView.visibility = View.VISIBLE + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/systemui/UsbDefaultFileTransfer.kt b/app/src/main/java/com/suqi8/oshin/hook/systemui/UsbDefaultFileTransfer.kt new file mode 100644 index 00000000..89e05f3d --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/systemui/UsbDefaultFileTransfer.kt @@ -0,0 +1,22 @@ +package com.suqi8.oshin.hook.systemui + +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker + +class UsbDefaultFileTransfer: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.android.systemui") { + if (prefs("systemui").getBoolean("usb_default_file_transfer", false)) { + "com.oplus.systemui.usb.UsbService".toClass().resolve().firstMethod { + modifiers(Modifiers.PRIVATE) + name = "getDefaultSelectType" + emptyParameters() + returnType = Int::class + }.hook { + before { result = 1 } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/systemui/allday_screenoff.kt b/app/src/main/java/com/suqi8/oshin/hook/systemui/allday_screenoff.kt new file mode 100644 index 00000000..832876f2 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/systemui/allday_screenoff.kt @@ -0,0 +1,40 @@ +package com.suqi8.oshin.hook.systemui + +import android.provider.Settings +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.constructor +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.java.BooleanType + +class allday_screenoff: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.android.systemui") { + if (prefs("systemui").getBoolean("enable_all_day_screen_off", false)) { + "com.oplus.systemui.aod.display.SmoothTransitionController".toClass().apply { + method { + name = "shouldWindowBeTransparent" + emptyParam() + returnType = BooleanType + }.hook { + before { + result = true + } + } + } + } + if (prefs("systemui").getBoolean("force_trigger_ltpo", false)) { + "com.oplus.systemui.aod.display.BaseDisplayUtil".toClass().apply { + constructor().hook { + before { + Settings.Secure.putInt( + appContext!!.contentResolver, + "Setting_AodClockModeOriginalType_ONEHZ", + 1 + ) + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/systemui/controlCenter/BigMediaArt.kt b/app/src/main/java/com/suqi8/oshin/hook/systemui/controlCenter/BigMediaArt.kt new file mode 100644 index 00000000..39d90f04 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/systemui/controlCenter/BigMediaArt.kt @@ -0,0 +1,182 @@ +package com.suqi8.oshin.hook.systemui.controlCenter + +import android.content.res.ColorStateList +import android.graphics.Bitmap +import android.graphics.Color +import android.graphics.Outline +import android.graphics.drawable.Icon +import android.view.View +import android.view.ViewGroup +import android.view.ViewOutlineProvider +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.graphics.drawable.toBitmap +import androidx.core.graphics.drawable.toDrawable +import androidx.palette.graphics.Palette +import com.highcapable.kavaref.KavaRef.Companion.asResolver +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import java.util.WeakHashMap + +// 颜色反转扩展函数 +fun Int.invert(): Int { + val r = 255 - Color.red(this) + val g = 255 - Color.green(this) + val b = 255 - Color.blue(this) + return Color.rgb(r, g, b) +} + +// 使用一个数据类来安全地管理每个面板实例对应的视图引用 +private data class PanelViews( + val background: FrameLayout, + val title: TextView?, + val text: TextView?, + val preBtn: ImageView?, + val playOrPauseBtn: ImageView?, + val nextBtn: ImageView? +) + +class BigMediaArt: YukiBaseHooker() { + override fun onHook() { + if (prefs("systemui\\controlCenter").getBoolean("enlarge_media_cover", false)) { + loadApp(name = "com.android.systemui") { + // 使用 WeakHashMap 来安全地将视图引用与每个面板实例绑定 + val viewMap = WeakHashMap() + + "com.oplus.systemui.qs.media.OplusQsMediaPanelView".toClass().resolve().apply { + // Hook onFinishInflate: 创建背景视图、查找子视图并设置视图裁切 + firstMethod { + name = "onFinishInflate" + emptyParameters() + }.hook { + after { + val hookedView = instance as? ViewGroup ?: return@after + if (viewMap.containsKey(hookedView)) return@after + + // 隐藏原始封面 + firstField { name = "mCoverImg" }.of(instance).get()?.let { + (it as View).visibility = View.GONE + } + + // 使用您提供的变量名查找视图 + val mTitle = firstField { name = "mTitle" }.of(instance).get() as? TextView + val mText = firstField { name = "mText" }.of(instance).get() as? TextView + val mPreBtn = firstField { name = "mPreBtn" }.of(instance).get() as? ImageView + val mPlayOrPauseBtn = firstField { name = "mPlayOrPauseBtn" }.of(instance).get() as? ImageView + val mNextBtn = firstField { name = "mNextBtn" }.of(instance).get() as? ImageView + + // 创建自定义背景层 + val mediaBackground = FrameLayout(hookedView.context).apply { + id = View.generateViewId() + layoutParams = ConstraintLayout.LayoutParams( + ConstraintLayout.LayoutParams.MATCH_PARENT, + ConstraintLayout.LayoutParams.MATCH_PARENT + ).apply { + startToStart = ConstraintLayout.LayoutParams.PARENT_ID + topToTop = ConstraintLayout.LayoutParams.PARENT_ID + endToEnd = ConstraintLayout.LayoutParams.PARENT_ID + bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID + } + + // --- ✨ 新增:通过视图轮廓实现圆角裁切 --- + val cornerRadiusDp = 18f + val cornerRadiusPx = cornerRadiusDp * context.resources.displayMetrics.density + outlineProvider = object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect(0, 0, view.width, view.height, cornerRadiusPx) + } + } + clipToOutline = true + // --- 视图裁切设置结束 --- + } + hookedView.addView(mediaBackground, 0) + + // 将所有找到的视图存入Map + viewMap[hookedView] = PanelViews( + background = mediaBackground, + title = mTitle, + text = mText, + preBtn = mPreBtn, + playOrPauseBtn = mPlayOrPauseBtn, + nextBtn = mNextBtn + ) + } + } + + // Hook bindCoverImg: 更新背景和分离的颜色 + firstMethod { + name = "bindCoverImg" + parameters("com.android.systemui.media.controls.shared.model.MediaData") + }.hook { + after { + val hookedView = instance as? View ?: return@after + val context = hookedView.context + val mediaData = args[0] + val views = viewMap[hookedView] ?: return@after + + firstField { name = "mCoverImg" }.of(instance).get()?.let { + (it as View).visibility = View.GONE + } + + val isResumption = mediaData?.asResolver()?.firstMethod { name = "getResumption" }?.invoke() as? Boolean ?: true + if (mediaData == null || isResumption) { + views.background.visibility = View.GONE + return@after + } + + val artworkIcon = mediaData.asResolver().firstMethod { name = "getArtwork" }.invoke() as? Icon + if (artworkIcon == null) { + views.background.visibility = View.GONE + return@after + } + + artworkIcon.loadDrawable(context)?.let { originalDrawable -> + val fullBitmap = originalDrawable.toBitmap() + views.background.background = fullBitmap.toDrawable(context.resources) + views.background.visibility = View.VISIBLE + + if (prefs("systemui\\controlCenter").getBoolean("qs_media_auto_color_label", true)) { + // --- ✨ 颜色分离逻辑 --- + val width = fullBitmap.width + val height = fullBitmap.height + if (width <= 0 || height <= 1) return@let // 确保Bitmap有效 + + // 裁切出上半部分和下半部分 + val upperBitmap = Bitmap.createBitmap(fullBitmap, 0, 0, width, height / 2) + val lowerBitmap = Bitmap.createBitmap(fullBitmap, 0, height / 2, width, height / 2) + + // 分析上半部分,设置文本颜色 + Palette.from(upperBitmap).generate { palette -> + val dominantColor = palette?.dominantSwatch?.rgb ?: Color.GRAY + val textColor = dominantColor.invert() + hookedView.post { + views.title?.setTextColor(textColor) + views.text?.setTextColor(textColor) + } + } + + // 分析下半部分,设置图标颜色 + Palette.from(lowerBitmap).generate { palette -> + val dominantColor = palette?.dominantSwatch?.rgb ?: Color.GRAY + val iconColor = dominantColor.invert() + hookedView.post { + val buttonTint = ColorStateList.valueOf(iconColor) + views.preBtn?.imageTintList = buttonTint + views.playOrPauseBtn?.imageTintList = buttonTint + views.nextBtn?.imageTintList = buttonTint + } + } + // --- 颜色分离逻辑结束 --- + } + } ?: run { + views.background.visibility = View.GONE + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/systemui/systemui.kt b/app/src/main/java/com/suqi8/oshin/hook/systemui/systemui.kt new file mode 100644 index 00000000..4f41a457 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/systemui/systemui.kt @@ -0,0 +1,49 @@ +package com.suqi8.oshin.hook.systemui + +import android.annotation.SuppressLint +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.suqi8.oshin.hook.systemui.StatusBar.BatteryBar +import com.suqi8.oshin.hook.systemui.StatusBar.Clock +import com.suqi8.oshin.hook.systemui.StatusBar.Fragment +import com.suqi8.oshin.hook.systemui.StatusBar.HardwareIndicator +import com.suqi8.oshin.hook.systemui.StatusBar.Notification +import com.suqi8.oshin.hook.systemui.StatusBar.ShowRealBattery +import com.suqi8.oshin.hook.systemui.StatusBar.StatusBarLayout +import com.suqi8.oshin.hook.systemui.StatusBar.Wifi +import com.suqi8.oshin.hook.systemui.controlCenter.BigMediaArt + + +class systemui: YukiBaseHooker() { + + @SuppressLint("UseCompatLoadingForDrawables", "UseKtx") + override fun onHook() { + loadApp(hooker = Clock()) + loadApp(hooker = HardwareIndicator()) + loadApp(hooker = Notification()) + loadApp(hooker = Fragment()) + loadApp(hooker = allday_screenoff()) + loadApp(hooker = BatteryBar()) + loadApp(hooker = Wifi()) + loadApp(hooker = BigMediaArt()) + loadApp(hooker = DisableDataTransferAuth()) + loadApp(hooker = UsbDefaultFileTransfer()) + loadApp(hooker = RemoveUsbSelectionDialog()) + loadApp(hooker = ToastForceShowAppIcon()) + loadHooker(ShowRealBattery()) + loadHooker(StatusBarLayout()) + + /*loadApp(name = "com.android.systemui") { + "com.oplus.systemui.plugins.qs.OplusQSSpecialModeProvider".toClass().apply { + method { + name = "getActiveColor" + emptyParam() + returnType = IntType + }.hook { + before { + result = -3342490 + } + } + } + }*/ + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/themestore/themestore.kt b/app/src/main/java/com/suqi8/oshin/hook/themestore/themestore.kt new file mode 100644 index 00000000..ad73d86b --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/themestore/themestore.kt @@ -0,0 +1,263 @@ +package com.suqi8.oshin.hook.themestore + +import android.content.Intent +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.kavaref.extension.JDouble +import com.highcapable.kavaref.extension.JInteger +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import org.luckypray.dexkit.DexKitBridge +import java.lang.reflect.Modifier + +class themestore : YukiBaseHooker() { + override fun onHook() { + val prefs = prefs("themestore") + loadApp(name = "com.heytap.themestore") { + val bridge = DexKitBridge.create(this.appInfo.sourceDir) + + //VIP + if (prefs.getBoolean("unlock_themestore_vip_features", false)) { + + "com.oppo.cdo.card.theme.dto.page.WeatherPageResponseDto".toClass().resolve() + .apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "getVipStatus" + emptyParameters() + returnType = Int::class + }.hook { + before { + firstField { name = "vipStatus" }.of(instance).set(1) + } + } + } + + "com.oppo.cdo.card.theme.dto.vip.VipUserDto".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "getVipStatus" + emptyParameters() + returnType = Int::class + }.hook { + before { + firstField { name = "vipStatus" }.of(instance).set(1) + firstField { name = "vipDays" }.of(instance).set(99999) + firstField { name = "endTime" }.of(instance).set(999999999L) + } + } + } + + "com.oppo.cdo.card.theme.dto.vip.VipUserDto".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "getVipDays" + emptyParameters() + returnType = Int::class + }.hook { + before { + firstField { name = "vipStatus" }.of(instance).set(1) + firstField { name = "vipDays" }.of(instance).set(99999) + firstField { name = "endTime" }.of(instance).set(999999999L) + } + } + } + + "com.oppo.cdo.theme.domain.dto.response.ResourceItemDto".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "getIsVip" + emptyParameters() + returnType = JInteger::class + }.hook { + before { + firstField { name = "isVip" }.of(instance).set(1) + firstField { name = "isVipAvailable" }.of(instance).set(1) + } + } + } + + "com.oppo.cdo.theme.domain.dto.response.ResourceItemDto".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "getIsVipAvailable" + emptyParameters() + returnType = JInteger::class + }.hook { + before { + firstField { name = "isVip" }.of(instance).set(1) + firstField { name = "isVipAvailable" }.of(instance).set(1) + } + } + } + + "com.nearme.themespace.UserInfoManager".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "w" + emptyParameters() + returnType = Int::class + }.hook { + after { + result = 1 + } + } + } + + "com.nearme.themespace.UserInfoManager".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PRIVATE) + name = "D" + emptyParameters() + returnType = "com.nearme.themespace.account.VipUserStatus" + }.hook { + after { + val status = + "com.nearme.themespace.account.VipUserStatus".toClass().resolve() + .firstField { name = "VALID" }.of(result).get() + result = status + } + } + } + + "com.nearme.themespace.download.mvvm.DownloadRepository".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "c" + parameters("com.nearme.themespace.model.LocalProductInfo") + returnType = "com.oplus.aiunit.vision.jv2" + }.hook { + before { + args[0]?.apply { + firstField { name = "mPurchaseStatus" }.set(1) + firstField { name = "mResourceVipType" }.set(0) + firstField { name = "forceVip" }.set(0) + } + } + } + } + + "com.nearme.themespace.trial.ThemeTrialExpireReceiver".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PRIVATE) + name = "a" + parameters("android.content.Context", String::class) + returnType = Boolean::class + }.hook { + before { + args[0] = null + } + } + } + + "com.nearme.themespace.trial.ThemeTrialExpireReceiver".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "onReceive" + parameters("android.content.Context", "android.content.Intent") + returnType = Void.TYPE + }.hook { + before { + val intent = args[1] as Intent + intent.action = "" + } + } + } + + bridge.findMethod { + matcher { + modifiers = Modifier.PUBLIC + returnType = JDouble.TYPE.name + name = "getPrice" + paramTypes = emptyList() + } + }.forEach { methodData -> + methodData.declaredClass?.name?.toClass()?.let { + it.resolve().firstMethod { + modifiers(Modifiers.PUBLIC) + name = methodData.name + emptyParameters() + returnType = JDouble.TYPE + }.hook { + after { + result = 0.0 + } + } + } + } + } + + //去广告 + if (prefs.getBoolean("remove_themestore_splash_ads", false)) { + println("testMe themestore splash ad hook") + "com.oppo.cdo.card.theme.dto.SplashDto".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "getAdData" + emptyParameters() + returnType = "com.oppo.cdo.card.theme.dto.AdDataDto" + }.hook { + after { + result = null + } + } + } + + "com.oppo.cdo.card.theme.dto.SplashDto".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "getImage" + emptyParameters() + returnType = String::class + }.hook { + after { + result = null + } + } + } + + "com.oppo.cdo.card.theme.dto.SplashDto".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "getStartTime" + emptyParameters() + returnType = Long::class + }.hook { + after { + result = System.currentTimeMillis() + 86400000 + } + } + } + + "com.oppo.cdo.card.theme.dto.SplashDto".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "getEndTime" + emptyParameters() + returnType = Long::class + }.hook { + after { + result = System.currentTimeMillis() - 86400000 + } + } + } + } + + //去更新 + if (prefs.getBoolean("remove_themestore_upgrade", false)) { + println("testMe remove_themestore_upgrade") + "com.heytap.upgrade.UpgradeSDK".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC) + name = "checkUpgrade" + parameters("com.oplus.aiunit.vision.qv0") + returnType = Void.TYPE + }.hook{ + replaceUnit { } + } + } + } + + + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/utils/DexKitCacheManager.kt b/app/src/main/java/com/suqi8/oshin/hook/utils/DexKitCacheManager.kt new file mode 100644 index 00000000..1ca67a8a --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/utils/DexKitCacheManager.kt @@ -0,0 +1,197 @@ +package com.suqi8.oshin.hook.utils + + +/** + * [最终方案] DexKit 探索结果的持久化缓存管理器。 + * + * 实现了与您提供的参考项目 (HyperCeiler, XAutoDaily) 完全相同的缓存策略。 + * 遵循 Xposed 生命周期,在 Application 创建后再进行扫描和缓存,并使用 CountDownLatch 解决时序问题。 + *//* + +object DexKitCacheManager { + + // 确保 MMKV 的初始化是线程安全的,并且每个进程只执行一次 + @Volatile + private var isMmkvInitialized = false + private val mmkvInitializationLock = Any() + + // 内存缓存,防止在同一次运行中反复创建 DexKitBridge + private val bridgeCache = ConcurrentHashMap() + + // 关键:用于同步的倒数锁存器 + private val initializationLatch = CountDownLatch(1) + + @Volatile + private var isManagerInitialized = false + + */ +/** + * [在 Hook 入口调用一次] 负责 Hook Application.onCreate 并准备好所有资源。 + * @param param 来自 loadApp/loadSystem 的包参数。 + *//* + + fun init(param: PackageParam) { + if (isManagerInitialized) return + isManagerInitialized = true + + param.onAppLifecycle { + onCreate { + // "this" 是 Application 实例,它的 Context 是有效的 + val context = this + YLog.info("Application.onCreate 触发 (宿主: '${param.packageName}'), 正在初始化缓存管理器...") + try { + getMMKV(context) + getInMemoryBridge(param) + YLog.info("缓存管理器初始化成功。") + } catch (e: Throwable) { + YLog.error("缓存管理器初始化失败。", e) + } finally { + // 无论成功与否,都必须释放锁存器,以防其他线程永久等待 + initializationLatch.countDown() + } + } + } + } + + */ +/** + * 获取 MMKV 实例,并在此过程中自动完成初始化。 + * @param context 必须是一个有效的 Context。 + * @return [MMKV] 实例。 + *//* + + private fun getMMKV(context: Context): MMKV { + if (!isMmkvInitialized) { + synchronized(mmkvInitializationLock) { + if (!isMmkvInitialized) { + MMKV.initialize(context.applicationContext) + isMmkvInitialized = true + } + } + } + return MMKV.mmkvWithID("oshin_dex_cache", MMKV.MULTI_PROCESS_MODE) + } + + */ +/** + * [推荐] 查找并 Hook 方法,全自动处理缓存和时序问题。 + * + * @param param 当前 Hook 的包参数。 + * @param queryKey 此次查询的唯一标识符。 + * @param finder 实际执行 DexKit 探索的 lambda 表达式。 + * @param hooker 对查找到的每个方法进行 Hook 的 lambda 表达式。 + *//* + + fun findAndHookMethod( + param: PackageParam, + queryKey: String, + finder: (DexKitBridge) -> List, + hooker: (Method) -> Unit + ) { + // 在后台线程中执行,以避免阻塞主线程 + Thread { + try { + // 等待初始化完成,最多等待10秒 + val success = initializationLatch.await(10, java.util.concurrent.TimeUnit.SECONDS) + if (!success) { + YLog.error("等待缓存管理器初始化超时 (Key: '$queryKey')。") + return@Thread + } + + val context = param.appContext!! // 此时 appContext 必定不为 null + val overallStartTime = System.currentTimeMillis() + YLog.info("开始处理 Hook 任务 (Key: '$queryKey')") + + val mmkv = getMMKV(context) + val packageName = context.packageName + val packageInfo: PackageInfo? = try { + context.packageManager.getPackageInfo(packageName, 0) + } catch (e: PackageManager.NameNotFoundException) { + YLog.error("无法获取包信息 for \"$packageName\"", e) + null + } + + // 因为 minSdk >= 35, 我们可以安全地直接使用 longVersionCode + val currentVersionCode = packageInfo?.longVersionCode ?: -1L + val versionKey = "version#$packageName" + val cacheKey = "cache#$packageName#$queryKey" + val cachedVersionCode = mmkv.decodeLong(versionKey, -1L) + + var descriptors: Set? + + // 1. 检查版本并尝试从缓存读取 + if (cachedVersionCode == currentVersionCode) { + descriptors = mmkv.decodeStringSet(cacheKey, null) + if (descriptors != null) { + YLog.info("DexKit 缓存命中 (Key: '$queryKey'),从缓存加载 ${descriptors.size} 条数据。") + } + } else { + descriptors = null + YLog.info("DexKit 缓存版本不匹配 (Key: '$queryKey' | 缓存版本: $cachedVersionCode, 当前版本: $currentVersionCode),需要重新扫描。") + } + + // 2. 如果缓存不存在或版本已更新,则执行实时搜索 + if (descriptors == null) { + val searchStartTime = System.currentTimeMillis() + + if (cachedVersionCode != -1L) { + val keysToRemove = mmkv.allKeys()?.filter { it.startsWith("cache#$packageName#") } + if (!keysToRemove.isNullOrEmpty()) { + mmkv.removeValuesForKeys(keysToRemove.toTypedArray()) + } + } + + val bridge = getInMemoryBridge(param) + val newDescriptors = finder(bridge).map { it.toDexMethod().serialize() }.toSet() + + if (newDescriptors.isNotEmpty()) { + mmkv.encode(cacheKey, newDescriptors) + mmkv.encode(versionKey, currentVersionCode) + } + descriptors = newDescriptors + YLog.info("DexKit 搜索完成,耗时: ${System.currentTimeMillis() - searchStartTime}ms") + } + + // 3. 对找到的所有方法执行 Hook + val appClassLoader = param.appClassLoader ?: run { + YLog.error("ClassLoader 为空,无法执行 Hook (Key: '$queryKey')") + return@Thread + } + + descriptors.forEach { descriptor -> + try { + val method = org.luckypray.dexkit.wrap.DexMethod(descriptor).getMethodInstance(appClassLoader) + hooker(method) + } catch (e: Throwable) { + YLog.error("从描述符 \"$descriptor\" 恢复并 Hook 失败", e) + } + } + YLog.info("Hook 任务 (Key: '$queryKey') 处理完毕,总耗时: ${System.currentTimeMillis() - overallStartTime}ms") + + } catch (e: InterruptedException) { + YLog.error("等待缓存初始化时被中断", e) + Thread.currentThread().interrupt() + } catch (e: Throwable) { + YLog.error("处理 Hook 任务时发生未知错误 (Key: '$queryKey')", e) + } + }.start() + } + + */ +/** 获取 DexKitBridge 实例(带内存缓存) *//* + + private fun getInMemoryBridge(param: PackageParam): DexKitBridge { + val sourceDir = param.appInfo?.sourceDir ?: error("无法获取 App 的源路径") + return bridgeCache.getOrPut(param.packageName) { + DexKitBridge.create(sourceDir) ?: error("创建 DexKitBridge 失败") + } + } + + */ +/** 因为 minSdk >= 35, 我们可以安全地直接使用 longVersionCode *//* + + @get:SuppressLint("NewApi") + private val PackageInfo.longVersionCode: Long + get() = this.longVersionCode +} +*/ diff --git a/app/src/main/java/com/suqi8/oshin/hook/utils/toRoundedDrawable.kt b/app/src/main/java/com/suqi8/oshin/hook/utils/toRoundedDrawable.kt new file mode 100644 index 00000000..6b8a7913 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/utils/toRoundedDrawable.kt @@ -0,0 +1,27 @@ +package com.suqi8.oshin.hook.utils + +import androidx.core.graphics.createBitmap + +fun android.graphics.drawable.Drawable.toRoundedDrawable( + context: android.content.Context, + cornerRadiusDp: Float +): androidx.core.graphics.drawable.RoundedBitmapDrawable { + // 将Drawable转换为Bitmap + val bitmap = + createBitmap(this.intrinsicWidth.coerceAtLeast(1), this.intrinsicHeight.coerceAtLeast(1)) + val canvas = android.graphics.Canvas(bitmap) + this.setBounds(0, 0, canvas.width, canvas.height) + this.draw(canvas) + + // 将dp转换为px + val cornerRadiusPx = cornerRadiusDp * context.resources.displayMetrics.density + + // 创建并返回带圆角的RoundedBitmapDrawable + return androidx.core.graphics.drawable.RoundedBitmapDrawableFactory.create( + context.resources, + bitmap + ).apply { + cornerRadius = cornerRadiusPx + setAntiAlias(true) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/wallet/wallet.kt b/app/src/main/java/com/suqi8/oshin/hook/wallet/wallet.kt new file mode 100644 index 00000000..a73c74a9 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/wallet/wallet.kt @@ -0,0 +1,46 @@ +package com.suqi8.oshin.hook.wallet + +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.method + +class wallet: YukiBaseHooker() { + override fun onHook() { + loadApp(name = "com.finshell.wallet") { + if (prefs("wallet").getBoolean("remove_swipe_page_ads", false)) { + "com.finshell.quickcardpkg.travel.repository.QcpTaxiServiceAppRequest".toClass().apply { + method { + name = "getUrl" + emptyParam() + returnType = "java.lang.String" + }.hook { + before { + result = null + } + } + } + "com.finshell.setting.net.SimpleSwipeBizIdRequest".toClass().apply { + method { + name = "getUrl" + emptyParam() + returnType = "java.lang.String" + }.hook { + before { + result = null + } + } + } + "com.nearme.domain.OperatePageResourceRequest".toClass().apply { + method { + name = "getUrl" + emptyParam() + returnType = "java.lang.String" + }.hook { + before { + result = null + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/hook/weather/weather.kt b/app/src/main/java/com/suqi8/oshin/hook/weather/weather.kt new file mode 100644 index 00000000..3db1a593 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/hook/weather/weather.kt @@ -0,0 +1,79 @@ +package com.suqi8.oshin.hook.weather + +import android.content.Context +import android.content.Intent +import androidx.core.net.toUri +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.condition.type.Modifiers +import com.highcapable.kavaref.extension.JInteger +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker + +class weather: YukiBaseHooker() { + override fun onHook() { + val prefs = prefs("weather") + loadApp(name = "com.coloros.weather2") { + "com.oplus.weather.utils.SecondaryPageUtil".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC, Modifiers.STATIC, Modifiers.FINAL) + name = "startJumpToBrowser" + parameters("android.content.Context", String::class, Boolean::class, Boolean::class, Boolean::class, Boolean::class, Boolean::class, JInteger::class, "kotlin.jvm.functions.Function0") + returnType = Void.TYPE + }.hook { + before { + val context = args[0] as Context + var originalUrl = args[1] as String + + if (prefs.getBoolean("remove_second_page_ads")) { + originalUrl = if (originalUrl.contains("infoEnable=true")) { + originalUrl.replace("infoEnable=true", "infoEnable=false") + } else { + "$originalUrl&infoEnable=false" + } + args[1] = originalUrl + } + + if (prefs.getBoolean("prevent_system_browser_redirect")) { + val redirectIntent = Intent(Intent.ACTION_VIEW, originalUrl.toUri()).apply { + // 添加此标志位,以便在非Activity的上下文中启动Activity + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + context.startActivity(redirectIntent) + resultNull() + } + } + } + } + + "com.oplus.weather.utils.LocalUtils".toClass().resolve().apply { + firstMethod { + modifiers(Modifiers.PUBLIC, Modifiers.STATIC) + name = "launchHeyTapBrowserFirst" + parameters("android.content.Context", Int::class, String::class, String::class, Boolean::class) + returnType = Void.TYPE + }.hook { + before { + val context = args[0] as Context + var originalUrl = args[2] as String + + if (prefs.getBoolean("remove_second_page_ads")) { + originalUrl = if (originalUrl.contains("&isNotificationGranted=")) { + originalUrl.replace("&isNotificationGranted=", "&infoEnable=false&isNotificationGranted=") + } else { + "$originalUrl&infoEnable=false" + } + } + + if (prefs.getBoolean("prevent_system_browser_redirect")) { + val redirectIntent = Intent(Intent.ACTION_VIEW, originalUrl.toUri()).apply { + // 添加此标志位,以便在非Activity的上下文中启动Activity + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + context.startActivity(redirectIntent) + resultNull() + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/models/Condition.kt b/app/src/main/java/com/suqi8/oshin/models/Condition.kt new file mode 100644 index 00000000..2b36e022 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/models/Condition.kt @@ -0,0 +1,56 @@ +package com.suqi8.oshin.models + +import com.suqi8.oshin.models.Operator.EQUALS +import com.suqi8.oshin.models.Operator.NOT_EQUALS + + +/** + * 条件判断的基础接口。 + * + * 用于声明式定义 UI 元素的显示条件。支持简单条件和复合条件的组合。 + * + * @see SimpleCondition 单一条件判断 + * @see AndCondition 多条件与逻辑 + */ +sealed interface Condition + +/** + * 简单条件,基于单一依赖项和操作符的条件判断。 + * + * 用于比较特定键的值是否满足条件,支持相等和不相等两种操作符。 + * + * @property dependencyKey 依赖项的键,对应某个 ScreenItem 的 key + * @property operator 比较操作符,默认为 [Operator.EQUALS] + * @property requiredValue 期望的值,默认为 true + * + * @see Operator 可用的操作符枚举 + */ +data class SimpleCondition( + val dependencyKey: String, + val operator: Operator = EQUALS, + val requiredValue: Any = true +) : Condition + +/** + * 复合条件,表示多个条件的与逻辑(全部满足)。 + * + * 仅当所有包含的条件都满足时,该复合条件才为真。 + * + * @property conditions 条件列表,支持嵌套的复合条件 + * + * @see Condition 条件接口 + */ +data class AndCondition( + val conditions: List +) : Condition + +/** + * 条件比较操作符枚举。 + * + * @property EQUALS 相等判断 + * @property NOT_EQUALS 不相等判断 + */ +enum class Operator { + EQUALS, // 值相等 + NOT_EQUALS // 值不相等 +} diff --git a/app/src/main/java/com/suqi8/oshin/models/ModuleEntry.kt b/app/src/main/java/com/suqi8/oshin/models/ModuleEntry.kt new file mode 100644 index 00000000..df527168 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/models/ModuleEntry.kt @@ -0,0 +1,17 @@ +package com.suqi8.oshin.models + +/** + * 应用模块入口定义。 + * + * 定义 Main_Module 页面上的一个应用入口。每个入口代表一个可点击的应用模块卡片, + * 点击后导航到该模块对应的详情页面。 + * + * @property packageName 入口对应的应用包名 + * @property routeId 点击该入口后导航的目标页面 ID + * + * @see PageDefinition 页面定义 + */ +data class ModuleEntry( + val packageName: String, + val routeId: String +) diff --git a/app/src/main/java/com/suqi8/oshin/models/PageDefinition.kt b/app/src/main/java/com/suqi8/oshin/models/PageDefinition.kt new file mode 100644 index 00000000..5b26f026 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/models/PageDefinition.kt @@ -0,0 +1,22 @@ +package com.suqi8.oshin.models + +/** + * 完整页面定义模型。 + * + * 定义一个完整的页面,包含页面的所有元数据(标题、分类等)和 UI 结构(页面元素列表)。 + * 这是页面配置和渲染的顶层数据模型。 + * + * @property title 页面的标题,使用灵活的 [Title] 模型 + * @property category 页面的分类标签,用于组织和分类多个页面 + * @property appList 该页面关联的应用包名列表,用于标记该页面适用的应用范围。默认为空列表 + * @property items 页面的所有 UI 元素列表,包括卡片、链接组等 + * + * @see Title 标题模型 + * @see PageItem 页面级别 UI 元素基类 + */ +data class PageDefinition( + val title: Title, + val category: String, + val appList: List = emptyList(), + val items: List +) diff --git a/app/src/main/java/com/suqi8/oshin/models/PageItem.kt b/app/src/main/java/com/suqi8/oshin/models/PageItem.kt new file mode 100644 index 00000000..d3c5755a --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/models/PageItem.kt @@ -0,0 +1,81 @@ +package com.suqi8.oshin.models + +import androidx.annotation.StringRes + +/** + * 页面级别的 UI 元素基础接口。 + * + * 所有页面级别的组件(如卡片、链接组、提示等)都应实现此接口。 + * 该接口提供统一的条件显示机制,允许每个页面元素独立控制其可见性。 + * + * @property condition 该页面元素的显示条件。当条件不满足时,该元素不应被渲染。 + * + * @see CardDefinition 设置项卡片 + * @see RelatedLinks 相关链接卡片 + * @see NoEnable 未启用提示卡片 + */ +sealed interface PageItem { + val condition: Condition? +} + +/** + * 设置项卡片,用于在页面中展示一组相关的设置项。 + * + * 该类型的卡片通常包含一个标题和多个可交互的设置项。 + * 整个卡片的显示可以通过 [condition] 属性进行条件控制。 + * + * @property titleRes 卡片标题的字符串资源 ID。若为 null 则不显示标题。 + * @property items 该卡片包含的所有设置项列表。 + * @property condition 卡片的显示条件。若条件不满足,整个卡片将被隐藏。 + * + * @see ScreenItem 设置项的详细定义 + * @see PageItem 页面级别元素接口 + */ +data class CardDefinition( + @StringRes val titleRes: Int? = null, + val items: List, + override val condition: Condition? = null +) : PageItem + +/** + * 相关链接卡片,用于在页面中展示一组相关的导航链接。 + * + * 该类型的卡片提供快速导航功能,帮助用户访问相关的页面或功能。 + * 每条链接包含标题和目标路由。整个卡片的显示可通过 [condition] 进行条件控制。 + * + * @property links 该卡片包含的所有链接列表。 + * @property condition 卡片的显示条件。若条件不满足,整个卡片将被隐藏。 + * + * @see Link 单条链接的定义 + * @see PageItem 页面级别元素接口 + */ +data class RelatedLinks( + val links: List, + override val condition: Condition? = null +) : PageItem { + + /** + * 表示一条导航链接。 + * + * @property titleRes 链接显示文本的字符串资源 ID。 + * @property route 链接目标的路由地址。用于导航到指定页面或功能。 + */ + data class Link( + @StringRes val titleRes: Int, + val route: String + ) +} + +/** + * 未启用提示卡片,用于在页面中显示功能暂未启用的提示。 + * + * 该类型的卡片通常用于展示某些功能因条件不满足而暂时不可用的状态提示。 + * 它是一个独立的、页面级别的提示元素。 + * + * @property condition 卡片的显示条件。通常用于控制该提示何时出现。 + * + * @see PageItem 页面级别元素接口 + */ +data class NoEnable( + override val condition: Condition? = null +) : PageItem diff --git a/app/src/main/java/com/suqi8/oshin/models/ScreenItem.kt b/app/src/main/java/com/suqi8/oshin/models/ScreenItem.kt new file mode 100644 index 00000000..faf82c71 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/models/ScreenItem.kt @@ -0,0 +1,221 @@ +package com.suqi8.oshin.models + +import androidx.annotation.ArrayRes +import androidx.annotation.StringRes + +/** + * 声明式 UI 的数据模型根接口。 + * + * 列表中的每一项都代表一个可渲染的 UI 组件,例如开关、滑块或导航项等。 + * 所有 UI 项都支持条件显示机制。 + * + * @property condition 此项的显示条件。若为 null 则始终显示;若不为 null,则仅在条件满足时显示 + * + * @see TitledScreenItem 带有标题和摘要的 UI 项基类 + */ +sealed interface ScreenItem { + val condition: Condition? +} + +/** + * 带有标题和摘要的 UI 项基接口。 + * + * 为可被搜索和导航的 UI 组件提供统一的接口,这些组件都具有唯一标识符、标题和可选摘要。 + * + * @property title 组件的标题,使用灵活的 [Title] 模型 + * @property summary 组件的摘要文本资源 ID,可为 null + * @property key 组件的唯一标识符,用于在 ViewModel 中追踪状态或存储偏好设置 + * + * @see Title 标题模型 + * @see ScreenItem 父级接口 + */ +sealed interface TitledScreenItem : ScreenItem { + val title: Title + val summary: Int? + val key: String +} + +/** + * 开关项组件(对应 FunSwitch)。 + * + * 用于呈现一个布尔值的开关控件,状态持久化到 SharedPreferences。 + * 支持标题、摘要和条件显示。 + * + * @property key 用于 SharedPreferences 的键,用于持久化开关状态 + * @property title 开关的标题 + * @property summary 开关的摘要描述,可选 + * @property defaultValue 开关的默认值,默认为 false + * @property condition 开关的显示条件 + * + * @see TitledScreenItem 父级接口 + */ +data class Switch( + override val key: String, + override val title: Title, + @StringRes override val summary: Int? = null, + val defaultValue: Boolean = false, + override val condition: Condition? = null +) : TitledScreenItem + +/** + * 滑块项组件(对应 FunSlider)。 + * + * 用于呈现一个浮点数值的滑块控件,支持自定义范围、单位和精度。 + * 数值持久化到 SharedPreferences。 + * + * @property key 用于 SharedPreferences 的键 + * @property title 滑块的标题 + * @property summary 滑块的摘要描述,可选 + * @property defaultValue 默认值,默认为 0f + * @property valueRange 滑块的值范围,默认为 0f..100f + * @property unit 数值的单位,如 "dp"、"ms" 等,默认为空字符串 + * @property decimalPlaces 数值显示的小数位数,默认为 1 + * @property condition 滑块的显示条件 + * + * @see TitledScreenItem 父级接口 + */ +data class Slider( + override val key: String, + override val title: Title, + @StringRes override val summary: Int? = null, + val defaultValue: Float = 0f, + val valueRange: ClosedFloatingPointRange = 0f..100f, + val unit: String = "", + val decimalPlaces: Int = 1, + override val condition: Condition? = null +) : TitledScreenItem + +/** + * 下拉选择项组件(对应 FunDropdown)。 + * + * 用于呈现一个选项列表的下拉菜单控件。选中的索引持久化到 SharedPreferences。 + * + * @property key 用于 SharedPreferences 的键,存储选中项的索引 + * @property title 下拉菜单的标题 + * @property summary 下拉菜单的摘要描述,可选 + * @property optionsRes 选项列表的字符串数组资源 ID + * @property defaultValue 默认选中的选项索引,默认为 0 + * @property condition 下拉菜单的显示条件 + * + * @see TitledScreenItem 父级接口 + */ +data class Dropdown( + override val key: String, + override val title: Title, + @StringRes override val summary: Int? = null, + @ArrayRes val optionsRes: Int, + val defaultValue: Int = 0, + override val condition: Condition? = null +) : TitledScreenItem + +/** + * 字符串输入项组件(对应 funString)。 + * + * 用于呈现一个文本输入框,允许用户输入字符串。 + * 输入值持久化到 SharedPreferences。 + * + * @property key 用于 SharedPreferences 的键 + * @property title 输入框的标题 + * @property summary 输入框的摘要描述,可选 + * @property defaultValue 默认的字符串值,默认为空字符串 + * @property nullable 是否允许输入为空字符串,默认为 false + * @property condition 输入框的显示条件 + * + * @see TitledScreenItem 父级接口 + */ +data class StringInput( + override val key: String, + override val title: Title, + @StringRes override val summary: Int? = null, + val defaultValue: String = "", + val nullable: Boolean = false, + override val condition: Condition? = null +) : TitledScreenItem + +/** + * 导航动作项组件(对应 FunArrow)。 + * + * 用于呈现一个可点击的导航项,点击后导航到指定的页面或功能。 + * + * @property title 导航项的标题 + * @property summary 导航项的摘要描述,可选 + * @property route 点击后导航的目标路由 ID + * @property condition 导航项的显示条件 + * + * 注意:该类的 [key] 属性使用 [route] 作为唯一标识符。 + * + * @see TitledScreenItem 父级接口 + */ +data class Action( + override val title: Title, + @StringRes override val summary: Int? = null, + val route: String, + override val condition: Condition? = null +) : TitledScreenItem { + override val key: String get() = route +} + +/** + * URL 跳转动作项组件。 + * + * 用于呈现一个可点击的导航项,点击后跳转到指定的 URL。 + * 支持打开网页或调用其他应用。 + * + * @property title 导航项的标题 + * @property summary 导航项的摘要描述,可选 + * @property url 点击后跳转的目标 URL + * @property condition 导航项的显示条件 + * + * 注意:该类的 [key] 属性使用 [url] 作为唯一标识符。 + * + * @see TitledScreenItem 父级接口 + */ +data class UrlAction( + override val title: Title, + @StringRes override val summary: Int? = null, + val url: String, + override val condition: Condition? = null +) : TitledScreenItem { + override val key: String get() = url +} + +/** + * 图片选择项组件(对应 funPicSele)。 + * + * 用于呈现一个图片选择控件,允许用户选择图片并复制到指定路径。 + * + * @property key 用于在 ViewModel 中追踪状态的唯一键 + * @property title 图片选择项的标题 + * @property summary 图片选择项的摘要描述,可选 + * @property targetPath 用户选中的图片要复制到的最终文件路径 + * @property condition 图片选择项的显示条件 + * + * @see TitledScreenItem 父级接口 + */ +data class Picture( + override val key: String, + override val title: Title, + @StringRes override val summary: Int? = null, + val targetPath: String, + override val condition: Condition? = null +) : TitledScreenItem + +/** + * 应用选择项组件(对应 funAppSele)。 + * + * 用于呈现一个应用选择控件,允许用户从系统中选择一个或多个应用。 + * 选中的应用包名列表持久化到 SharedPreferences。 + * + * @property key 用于 SharedPreferences 的键,存储选中应用的包名列表 + * @property title 应用选择项的标题 + * @property summary 应用选择项的摘要描述,可选 + * @property condition 应用选择项的显示条件 + * + * @see TitledScreenItem 父级接口 + */ +data class AppSelection( + override val key: String, + override val title: Title, + @StringRes override val summary: Int? = null, + override val condition: Condition? = null +) : TitledScreenItem diff --git a/app/src/main/java/com/suqi8/oshin/models/Title.kt b/app/src/main/java/com/suqi8/oshin/models/Title.kt new file mode 100644 index 00000000..58bd2552 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/models/Title.kt @@ -0,0 +1,42 @@ +package com.suqi8.oshin.models + +import androidx.annotation.StringRes + +/** + * 灵活的标题模型,支持多种标题来源形式。 + * + * 该接口定义了应用中各类 UI 组件的标题表示方式,允许标题来自不同的来源, + * 使得 UI 组件的配置更加灵活。 + * + * @see StringResource 从资源文件加载的标题 + * @see AppName 从应用包名动态获取的标题 + * @see PlainText 纯文本标题 + */ +sealed interface Title + +/** + * 字符串资源标题。 + * + * 标题文本来自应用的字符串资源文件,支持多语言和资源管理。 + * + * @property id 字符串资源的 ID + */ +data class StringResource(@StringRes val id: Int) : Title + +/** + * 应用名称标题。 + * + * 标题文本从指定包名的应用动态获取其应用名称。 + * + * @property packageName 应用的包名 + */ +data class AppName(val packageName: String) : Title + +/** + * 纯文本标题。 + * + * 标题文本直接提供为字符串,无需资源查询。 + * + * @property text 标题的具体文本内容 + */ +data class PlainText(val text: String) : Title diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/BasicComponent.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/BasicComponent.kt new file mode 100644 index 00000000..b2b0aea9 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/BasicComponent.kt @@ -0,0 +1,244 @@ +package com.suqi8.oshin.ui.activity.components + +import androidx.compose.foundation.LocalIndication +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.SubcomposeLayout +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.em +import androidx.compose.ui.unit.sp +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.interfaces.HoldDownInteraction +import top.yukonga.miuix.kmp.theme.MiuixTheme + + +/** + * 列表项在卡片组中的位置。 + */ +enum class CouiListItemPosition { + Top, Middle, Bottom, Single +} + +private enum class SlotsEnum { Start, Center, End } + +/** + * A basic component with Miuix style. Widely used in other extension components. + * + * @param title The title of the [BasicComponent]. + * @param titleColor The color of the title. + * @param summary The summary of the [BasicComponent]. + * @param summaryColor The color of the summary. + * @param leftAction The [Composable] content that on the left side of the [BasicComponent]. + * @param rightActions The [Composable] content on the right side of the [BasicComponent]. + * @param modifier The modifier to be applied to the [BasicComponent]. + * @param insideMargin The margin inside the [BasicComponent]. + * @param onClick The callback when the [BasicComponent] is clicked. + * @param holdDownState Used to determine whether it is in the pressed state. + * @param enabled Whether the [BasicComponent] is enabled. + * @param interactionSource The [MutableInteractionSource] for the [BasicComponent]. + */ +@Composable +fun BasicComponent( + title: String? = null, + titleModifier: Modifier = Modifier, + titleColor: BasicComponentColors = BasicComponentDefaults.titleColor(), + summary: String? = null, + summaryColor: BasicComponentColors = BasicComponentDefaults.summaryColor(), + leftAction: @Composable (() -> Unit?)? = null, + rightActions: @Composable RowScope.() -> Unit = {}, + modifier: Modifier = Modifier, + insideMargin: PaddingValues = BasicComponentDefaults.InsideMargin, + onClick: (() -> Unit)? = null, + position: CouiListItemPosition = CouiListItemPosition.Middle, + holdDownState: Boolean = false, + enabled: Boolean = true, + interactionSource: MutableInteractionSource? = null, +) { + @Suppress("NAME_SHADOWING") + val interactionSource = interactionSource ?: remember { MutableInteractionSource() } + val indication = LocalIndication.current + val extraTopDp = if (position == CouiListItemPosition.Top || position == CouiListItemPosition.Single) 2.dp else 0.dp + val extraBottomDp = if (position == CouiListItemPosition.Bottom || position == CouiListItemPosition.Single) 2.dp else 0.dp + val minHeight = 48.dp + val density = LocalDensity.current + val horizontalGapPx = with(density) { 16.dp.roundToPx() } + + val holdDown = remember { mutableStateOf(null) } + LaunchedEffect(holdDownState) { + if (holdDownState) { + val interaction = HoldDownInteraction.HoldDown() + holdDown.value = interaction + interactionSource.emit(interaction) + } else { + holdDown.value?.let { oldValue -> + interactionSource.emit(HoldDownInteraction.Release(oldValue)) + holdDown.value = null + } + } + } + + val clickableModifier = remember(onClick, enabled, interactionSource) { + if (onClick != null && enabled) { + Modifier.clickable( + indication = indication, + interactionSource = interactionSource, + onClick = onClick + ) + } else Modifier + } + + val layoutDirection = androidx.compose.ui.platform.LocalLayoutDirection.current + + SubcomposeLayout( + modifier = modifier + .heightIn(min = minHeight + extraTopDp + extraBottomDp) + .fillMaxWidth() + .then(clickableModifier) + .padding( + start = insideMargin.calculateStartPadding(layoutDirection), + end = insideMargin.calculateEndPadding(layoutDirection), + top = insideMargin.calculateTopPadding() + extraTopDp, + bottom = insideMargin.calculateBottomPadding() + extraBottomDp + ) + ) { constraints -> + val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0) + // 1. leftAction + val leftPlaceables = leftAction?.let { + subcompose("leftAction") { it() }.map { it -> it.measure(looseConstraints) } + } ?: emptyList() + val leftWidth = leftPlaceables.maxOfOrNull { it.width } ?: 0 + val leftHeight = leftPlaceables.maxOfOrNull { it.height } ?: 0 + // 2. rightActions + val rightPlaceables = subcompose("rightActions") { + Row( + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + content = rightActions + ) + }.map { it.measure(looseConstraints) } + val rightWidth = rightPlaceables.maxOfOrNull { it.width } ?: 0 + val rightHeight = rightPlaceables.maxOfOrNull { it.height } ?: 0 + // 3. content + val leftGap = if (leftWidth > 0) horizontalGapPx else 0 + val rightGap = if (rightWidth > 0) horizontalGapPx else 0 + + val contentMaxWidth = maxOf(0, constraints.maxWidth - leftWidth - leftGap - rightWidth - rightGap) + val titlePlaceable = title?.let { + subcompose("title") { + Box( + modifier = Modifier.heightIn(min = 21.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = it, + fontSize = 16.sp, + lineHeight = 1.2.em, + fontWeight = FontWeight.Medium, + color = titleColor.color(enabled), + modifier = titleModifier + ) + } + }.first().measure(looseConstraints.copy(maxWidth = contentMaxWidth)) + } + val summaryPlaceable = summary?.let { + subcompose("summary") { + Text( + text = it, + fontSize = 14.sp, + lineHeight = 1.3.em, + fontWeight = FontWeight.Normal, + color = summaryColor.color(enabled) + ) + }.first().measure(looseConstraints.copy(maxWidth = contentMaxWidth)) + } + val gap = 2.dp.roundToPx() + val contentHeight = (titlePlaceable?.height ?: 0) + + (if (titlePlaceable != null && summaryPlaceable != null) gap else 0) + + (summaryPlaceable?.height ?: 0) + val layoutHeight = maxOf(leftHeight, rightHeight, contentHeight).coerceAtLeast(constraints.minHeight) + layout(constraints.maxWidth, layoutHeight) { + var x = 0 + leftPlaceables.forEach { + it.placeRelative(x, (layoutHeight - it.height) / 2) + } + x += leftWidth + leftGap + // content + var contentY = (layoutHeight - contentHeight) / 2 + titlePlaceable?.let { + it.placeRelative(x, contentY) + contentY += it.height + if (summaryPlaceable != null) contentY += gap + } + summaryPlaceable?.placeRelative(x, contentY) + // rightActions + val rightX = constraints.maxWidth - rightWidth + rightPlaceables.forEach { + it.placeRelative(rightX, (layoutHeight - it.height) / 2) + } + } + } +} + +object BasicComponentDefaults { + + /** + * The default margin inside the [BasicComponent]. + */ + val InsideMargin = PaddingValues(vertical = 8.dp, horizontal = 16.dp) + + /** + * The default color of the title. + */ + @Composable + fun titleColor( + enabledColor: Color = MiuixTheme.colorScheme.onSurface, + disabledColor: Color = MiuixTheme.colorScheme.disabledOnSecondaryVariant + ): BasicComponentColors { + return BasicComponentColors( + enabledColor = enabledColor, + disabledColor = disabledColor + ) + } + + /** + * The default color of the summary. + */ + @Composable + fun summaryColor( + enabledColor: Color = MiuixTheme.colorScheme.onSurfaceVariantSummary, + disabledColor: Color = MiuixTheme.colorScheme.disabledOnSecondaryVariant + ): BasicComponentColors = BasicComponentColors( + enabledColor = enabledColor, + disabledColor = disabledColor + ) +} + +@Immutable +class BasicComponentColors( + private val enabledColor: Color, + private val disabledColor: Color +) { + @Stable + internal fun color(enabled: Boolean): Color = if (enabled) enabledColor else disabledColor +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/BlurredTopBarBackground.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/BlurredTopBarBackground.kt new file mode 100644 index 00000000..18597315 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/BlurredTopBarBackground.kt @@ -0,0 +1,59 @@ +package com.suqi8.oshin.ui.activity.components + +import android.graphics.RenderEffect +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.unit.dp +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.drawPlainBackdrop +import com.kyant.backdrop.effects.blur +import com.kyant.backdrop.effects.effect +import top.yukonga.miuix.kmp.theme.MiuixTheme + +@Composable +fun BlurredTopBarBackground(backdrop: Backdrop) { + val background = MiuixTheme.colorScheme.background + + Box( + Modifier + .height(72.dp) + .fillMaxWidth() + .drawPlainBackdrop( + backdrop = backdrop, + shape = { RectangleShape }, + effects = { + blur(4f.dp.toPx()) + effect( + RenderEffect.createRuntimeShaderEffect( + obtainRuntimeShader( + "TopBarAlphaMask", + """ +uniform shader content; +uniform float2 size; +layout(color) uniform half4 tint; +uniform float tintIntensity; + +half4 main(float2 coord) { + float blurAlpha = smoothstep(size.y, size.y * 0.2, coord.y); + float tintAlpha = smoothstep(size.y, size.y * 0.2, coord.y); + return mix(content.eval(coord) * blurAlpha, tint * tintAlpha, tintIntensity); +} +""".trimIndent() + ).apply { + setFloatUniform("size", size.width, size.height) + setColorUniform("tint", background.value.toLong()) + setFloatUniform("tintIntensity", 0.8f) + }, + "content" + ) + ) + } + ), + contentAlignment = Alignment.Center + ) {} +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/BottomTabs.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/BottomTabs.kt new file mode 100644 index 00000000..0dbcf5c3 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/BottomTabs.kt @@ -0,0 +1,358 @@ +package com.suqi8.oshin.ui.activity.components + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.EaseOut +import androidx.compose.animation.core.spring +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.text.BasicText +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.layout +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.util.fastCoerceIn +import androidx.compose.ui.util.fastRoundToInt +import androidx.compose.ui.util.lerp +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberCombinedBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.effects.blur +import com.kyant.backdrop.effects.lens +import com.kyant.backdrop.effects.vibrancy +import com.kyant.backdrop.highlight.Highlight +import com.kyant.backdrop.shadow.InnerShadow +import com.kyant.backdrop.shadow.Shadow +import com.kyant.capsule.ContinuousCapsule +import com.suqi8.oshin.ui.mainscreen.LocalColorMode +import com.suqi8.oshin.utils.DampedDragAnimation +import com.suqi8.oshin.utils.InteractiveHighlight +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import top.yukonga.miuix.kmp.basic.NavigationItem +import kotlin.math.abs +import kotlin.math.sign + +/** + * 为了让图标和文字也能缩放,需要定义这个 CompositionLocal + * 这是原版示例 `LiquidBottomTabs` 的一部分 + * + * --- MIGRATION: 变为 () -> Float --- + */ +val LocalLiquidBottomTabScale = compositionLocalOf { { 1f } } + +@Composable +fun BottomTabs( + tabs: List, + pagerState: PagerState, + onTabSelected: (Int) -> Unit, + backdrop: Backdrop, + modifier: Modifier = Modifier +) { + val isLightTheme = !when (LocalColorMode.current.value) { + 1 -> false // 1 = 白天 + 2 -> true // 2 = 黑夜 + else -> isSystemInDarkTheme() // 0 = 跟随系统 + } + val contentColor = if (isLightTheme) Color.Black else Color.White + val accentColor = if (isLightTheme) Color(0xFF0088FF) else Color(0xFF0091FF) + val containerColor = if (isLightTheme) Color(0xFFFAFAFA).copy(0.4f) else Color(0xFF121212).copy(0.4f) + val iconColorFilter = ColorFilter.tint(contentColor) + val tabsBackdrop = rememberLayerBackdrop() + + BoxWithConstraints( + modifier = modifier + .padding(bottom = 16.dp) // 保留原有 padding + .padding(horizontal = 40.dp) // 保留原有 padding + .layout { measurable, constraints -> // --- MIGRATION: 使用 layout 约束高度 + val placeable = measurable.measure(constraints.copy(maxHeight = 64f.dp.roundToPx())) + layout(placeable.width, placeable.height) { + placeable.place(0, 0) + } + }, + contentAlignment = Alignment.CenterStart + ) { + val density = LocalDensity.current + val tabWidth = with(density) { + (constraints.maxWidth.toFloat() - 8f.dp.toPx()) / tabs.size + } + + val animationScope = rememberCoroutineScope() + + val offsetAnimation = remember { Animatable(0f) } + val panelOffset by remember(density) { + derivedStateOf { + val fraction = (offsetAnimation.value / constraints.maxWidth.toFloat()).fastCoerceIn(-1f, 1f) + with(density) { + 4f.dp.toPx() * fraction.sign * EaseOut.transform(abs(fraction)) + } + } + } + + // --- MIGRATION: 添加 isLtr 支持 --- + val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr + var didDrag by remember { mutableStateOf(false) } + val dampedDragAnimation = remember(animationScope) { + DampedDragAnimation( + animationScope = animationScope, + initialValue = pagerState.currentPage.toFloat(), + valueRange = 0f..(tabs.size - 1).toFloat(), + visibilityThreshold = 0.001f, + initialScale = 1f, + pressedScale = 78f / 56f, + onDragStarted = {}, + onDragStopped = { + if (didDrag) { + // --- MIGRATION: 使用 fastRoundToInt --- + val targetIndex = targetValue.fastRoundToInt().coerceIn(0, tabs.size - 1) + if (pagerState.currentPage != targetIndex) { + onTabSelected(targetIndex) + } + didDrag = false + } + animationScope.launch { + offsetAnimation.animateTo(0f, spring(1f, 300f, 0.5f)) + } + }, + onDrag = { _, dragAmount -> + if (!didDrag) { + didDrag = dragAmount.x != 0f + } + // --- MIGRATION: 添加 isLtr 支持 --- + updateValue( + (targetValue + dragAmount.x / tabWidth * if (isLtr) 1f else -1f).fastCoerceIn( + 0f, + (tabs.size - 1).toFloat() + ) + ) + animationScope.launch { + offsetAnimation.snapTo(offsetAnimation.value + dragAmount.x) + } + } + ) + } + + LaunchedEffect(dampedDragAnimation, pagerState) { + snapshotFlow { pagerState.currentPage + pagerState.currentPageOffsetFraction } + .collectLatest { position -> + if (dampedDragAnimation.targetValue != position) { + dampedDragAnimation.animateToValue(position) + } + } + } + + val interactiveHighlight = remember(animationScope) { + InteractiveHighlight( + animationScope = animationScope, + position = { size, _ -> + // --- MIGRATION: 添加 isLtr 支持 --- + Offset( + if (isLtr) (dampedDragAnimation.value + 0.5f) * tabWidth + panelOffset + else size.width - (dampedDragAnimation.value + 0.5f) * tabWidth + panelOffset, + size.height / 2f + ) + } + ) + } + + val content: @Composable RowScope.() -> Unit = { + tabs.forEachIndexed { index, screen -> + val scale = lerp(1f, 1.05f, dampedDragAnimation.pressProgress) + Column( + Modifier + .clip(ContinuousCapsule) + .clickable { onTabSelected(index) } + .fillMaxHeight() + .weight(1f) + .graphicsLayer { + scaleX = scale + scaleY = scale + }, + verticalArrangement = Arrangement.spacedBy(2.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + // --- MIGRATION: 调用 lambda 获取 scale --- + val contentScale = LocalLiquidBottomTabScale.current() + Image( + imageVector = screen.icon, + contentDescription = screen.label, + modifier = Modifier + .size(28.dp) + .graphicsLayer { + scaleX = contentScale + scaleY = contentScale + }, + colorFilter = iconColorFilter + ) + BasicText( + screen.label, + style = TextStyle(color = contentColor, fontSize = 12.sp), + modifier = Modifier.graphicsLayer { + scaleX = contentScale + scaleY = contentScale + } + ) + } + } + } + + Row( + Modifier + .graphicsLayer { translationX = panelOffset } + .drawBackdrop( + backdrop = backdrop, + shape = { ContinuousCapsule }, + effects = { + vibrancy() + blur(8f.dp.toPx()) + lens(24f.dp.toPx(), 24f.dp.toPx()) + }, + layerBlock = { + val progress = dampedDragAnimation.pressProgress + val scale = lerp(1f, 1f + 16f.dp.toPx() / this.size.width, progress) + scaleX = scale + scaleY = scale + }, + onDrawSurface = { drawRect(containerColor) } + ) + .then(interactiveHighlight.modifier) + .height(64f.dp) + .fillMaxWidth() + .padding(4f.dp), + verticalAlignment = Alignment.CenterVertically, + content = content + ) + + CompositionLocalProvider( + // --- MIGRATION: provides 变为一个 lambda --- + LocalLiquidBottomTabScale provides { lerp(1f, 1.2f, dampedDragAnimation.pressProgress) }, + content = { + Row( + Modifier + .clearAndSetSemantics {} + .alpha(0f) + .layerBackdrop(tabsBackdrop) + .graphicsLayer { translationX = panelOffset } + .drawBackdrop( + backdrop = backdrop, + shape = { ContinuousCapsule }, + highlight = { // (格式更新,逻辑不变) + val progress = dampedDragAnimation.pressProgress + Highlight.Default.copy(alpha = progress) + }, + effects = { + val progress = dampedDragAnimation.pressProgress + vibrancy() + blur(8f.dp.toPx()) + lens(24f.dp.toPx() * progress, 24f.dp.toPx() * progress) + }, + onDrawSurface = { drawRect(containerColor) } + ) + .then(interactiveHighlight.modifier) + .height(56f.dp) + .fillMaxWidth() + .padding(horizontal = 4f.dp) + .graphicsLayer(colorFilter = ColorFilter.tint(accentColor)), + verticalAlignment = Alignment.CenterVertically, + content = content + ) + } + ) + + Box( + Modifier + .padding(horizontal = 4f.dp) + .graphicsLayer { + // --- MIGRATION: 添加 isLtr 支持 --- + translationX = + if (isLtr) dampedDragAnimation.value * tabWidth + panelOffset + else size.width - (dampedDragAnimation.value + 1f) * tabWidth + panelOffset + } + .then(interactiveHighlight.gestureModifier) + .then(dampedDragAnimation.modifier) + .drawBackdrop( + backdrop = rememberCombinedBackdrop(backdrop, tabsBackdrop), + shape = { ContinuousCapsule }, + highlight = { // (格式更新,逻辑不变) + val progress = dampedDragAnimation.pressProgress + Highlight.Default.copy(alpha = progress) + }, + shadow = { // (格式更新,逻辑不变) + val progress = dampedDragAnimation.pressProgress + Shadow(alpha = progress) + }, + innerShadow = { // (格式更新,逻辑不变) + val progress = dampedDragAnimation.pressProgress + InnerShadow( + radius = 8f.dp * progress, + alpha = progress + ) + }, + effects = { + // --- MIGRATION: 更新 lens 参数和 chromaticAberration --- + val progress = dampedDragAnimation.pressProgress + lens( + 10f.dp.toPx() * progress, + 14f.dp.toPx() * progress, + chromaticAberration = true // 变为 Boolean + ) + }, + layerBlock = { + scaleX = dampedDragAnimation.scaleX + scaleY = dampedDragAnimation.scaleY + val velocity = dampedDragAnimation.velocity / 10f + scaleX /= 1f - (velocity * 0.75f).fastCoerceIn(-0.2f, 0.2f) + // --- MIGRATION: scaleY 逻辑更新 --- + scaleY *= 1f - (velocity * 0.25f).fastCoerceIn(-0.2f, 0.2f) + }, + onDrawSurface = { + val progress = dampedDragAnimation.pressProgress + drawRect( + if (isLightTheme) Color.Black.copy(0.1f) else Color.White.copy(0.1f), + alpha = 1f - progress + ) + drawRect(Color.Black.copy(alpha = 0.03f * progress)) + } + ) + .height(56f.dp) + .fillMaxWidth(1f / tabs.size) + ) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/Card.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/Card.kt new file mode 100644 index 00000000..75d826a0 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/Card.kt @@ -0,0 +1,205 @@ +package com.suqi8.oshin.ui.activity.components + +import androidx.compose.foundation.LocalIndication +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.takeOrElse +import androidx.compose.ui.semantics.isTraversalGroup +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.kyant.capsule.ContinuousRoundedRectangle +import top.yukonga.miuix.kmp.theme.LocalContentColor +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.utils.PressFeedbackType +import top.yukonga.miuix.kmp.utils.SinkFeedback +import top.yukonga.miuix.kmp.utils.TiltFeedback +import top.yukonga.miuix.kmp.utils.pressable + +/** + * A [Card] component with Miuix style. + * Card contain contain content and actions that relate information about a subject. + * + * This [Card] does not handle input events + * + * @param modifier The modifier to be applied to the [Card]. + * @param cornerRadius The corner radius of the [Card]. + * @param insideMargin The margin inside the [Card]. + * @param colors [CardColors] that will be used to resolve the color(s) used for the [Card]. + * @param content The [Composable] content of the [Card]. + */ +@Composable +fun Card( + modifier: Modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 6.dp), + colors: CardColors = CardDefaults.defaultColors(), + cornerRadius: Dp = 12.dp, + insideMargin: PaddingValues = PaddingValues(vertical = 0.dp), + content: @Composable ColumnScope.() -> Unit, +) { + BasicCard( + modifier = modifier, + cornerRadius = cornerRadius, + colors = colors + ) { + Column( + modifier = Modifier.padding(insideMargin), + content = content + ) + } +} + +/** + * A [Card] component with Miuix style. + * Card contain contain content and actions that relate information about a subject. + * + * This [Card] handles input events + * + * @param modifier The modifier to be applied to the [Card]. + * @param cornerRadius The corner radius of the [Card]. + * @param insideMargin The margin inside the [Card]. + * @param colors [CardColors] that will be used to resolve the color(s) used for the [Card]. + * @param pressFeedbackType The press feedback type of the [Card]. + * @param showIndication Whether to show indication of the [Card]. + * @param onClick The callback to be invoked when the [Card] is clicked. + * @param onLongPress The callback to be invoked when the [Card] is long pressed. + * @param content The [Composable] content of the [Card]. + */ +@Composable +fun Card( + modifier: Modifier = Modifier, + cornerRadius: Dp = CardDefaults.CornerRadius, + insideMargin: PaddingValues = CardDefaults.InsideMargin, + colors: CardColors = CardDefaults.defaultColors(), + pressFeedbackType: PressFeedbackType = PressFeedbackType.None, + showIndication: Boolean? = false, + onClick: (() -> Unit)? = null, + onLongPress: (() -> Unit)? = null, + content: @Composable ColumnScope.() -> Unit, +) { + val interactionSource = remember { MutableInteractionSource() } + + val pressFeedback = remember(pressFeedbackType) { + when (pressFeedbackType) { + PressFeedbackType.None -> null + PressFeedbackType.Sink -> SinkFeedback() + PressFeedbackType.Tilt -> TiltFeedback() + } + } + + BasicCard( + modifier = modifier.pressable( + interactionSource = if (pressFeedback != null) interactionSource else null, + indication = pressFeedback, + delay = null + ), + cornerRadius = cornerRadius, + colors = colors + ) { + Column( + modifier = Modifier + .combinedClickable( + interactionSource = interactionSource, + indication = if (showIndication == true) LocalIndication.current else null, + onClick = { onClick?.invoke() }, + onLongClick = onLongPress + ) + .padding(insideMargin), + content = content + ) + } +} + +/** + * A [BasicCard] component. + * + * @param modifier The modifier to be applied to the [BasicCard]. + * @param colors [CardColors] that will be used to resolve the color(s) used for the [BasicCard]. + * @param cornerRadius The corner radius of the [BasicCard]. + * @param content The [Composable] content of the [BasicCard]. + */ +@Composable +private fun BasicCard( + modifier: Modifier = Modifier, + colors: CardColors = CardDefaults.defaultColors(), + cornerRadius: Dp = CardDefaults.CornerRadius, + content: @Composable () -> Unit, +) { + val shape = remember(cornerRadius) { + ContinuousRoundedRectangle( + cornerRadius + ) + } + + CompositionLocalProvider( + LocalContentColor provides colors.contentColor, + ) { + Box( + modifier = modifier + .semantics(mergeDescendants = false) { + isTraversalGroup = true + } + .clip(shape) // For touch feedback, there is a problem when using G2RoundedCornerShape. + .background(color = colors.color, shape = shape), + propagateMinConstraints = true, + ) { + content() + } + } +} + +object CardDefaults { + + /** + * The default corner radius of the [Card]. + */ + val CornerRadius = 16.dp + + /** + * The default margin inside the [Card]. + */ + val InsideMargin = PaddingValues(0.dp) + + /** + * The default colors width of the [Card]. + */ + @Composable + fun defaultColors( + color: Color = MiuixTheme.colorScheme.surface, + contentColor: Color = MiuixTheme.colorScheme.onSurface + ): CardColors { + return CardColors( + color = color, + contentColor = contentColor + ) + } +} + +@Immutable +class CardColors( + val color: Color, + val contentColor: Color +) { + fun copy( + color: Color = this.color, + contentColor: Color = this.contentColor, + ) = CardColors( + color.takeOrElse { this.color }, + contentColor.takeOrElse { this.contentColor }, + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunArrow.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunArrow.kt new file mode 100644 index 00000000..5967fee9 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunArrow.kt @@ -0,0 +1,151 @@ +package com.suqi8.oshin.ui.activity.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.suqi8.oshin.R +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.theme.MiuixTheme + +/** + * 一个带有标题、摘要(可选)、右侧文本(可选)和右向箭头的列表项组件。 + * 通常用于导航到下一个页面。 + * + * @param title 列表项的主标题。 + * @param modifier 应用于组件的修饰符。 + * @param summary 可选的副标题或摘要信息。 + * @param rightText 显示在箭头左侧的可选文本。 + * @param leftAction 在标题左侧显示的可选组件(如图标)。 + * @param position 列表项在卡片组中的位置,用于自动调整高度和圆角边距。 + * @param onClick 点击时的回调。 + */ +@Composable +fun FunArrow( + title: String, + modifier: Modifier = Modifier, + titleModifier: Modifier = Modifier, + summary: String? = null, + rightText: String? = null, + leftAction: @Composable (() -> Unit)? = null, + // [新增] 引入位置参数,默认 Middle + position: CouiListItemPosition = CouiListItemPosition.Middle, + onClick: () -> Unit +) { + SuperArrow( + title = title, + summary = summary, + rightText = rightText, + leftAction = leftAction, + // [修改] 传递 position,移除了 externalPadding + position = position, + modifier = modifier, + titleModifier = titleModifier, + onClick = onClick + ) +} + +/** + * FunArrow 的高级版本,提供更多的自定义选项。 + */ +@Composable +fun SuperArrow( + title: String, + titleModifier: Modifier = Modifier, + titleColor: BasicComponentColors = BasicComponentDefaults.titleColor(), + summary: String? = null, + summaryColor: BasicComponentColors = BasicComponentDefaults.summaryColor(), + leftAction: @Composable (() -> Unit)? = null, + rightText: String? = null, + rightActionColor: RightActionColors = SuperArrowDefaults.rightActionColors(), + modifier: Modifier = Modifier, + insideMargin: PaddingValues = BasicComponentDefaults.InsideMargin, + onClick: (() -> Unit)? = null, + position: CouiListItemPosition = CouiListItemPosition.Middle, + holdDownState: Boolean = false, + enabled: Boolean = true +) { + BasicComponent( + modifier = modifier, + insideMargin = insideMargin, + title = title, + titleModifier = titleModifier, + titleColor = titleColor, + summary = summary, + summaryColor = summaryColor, + leftAction = leftAction, + // [修改] 传递 position 给 BasicComponent + position = position, + rightActions = { + SuperArrowRightActions( + rightText = rightText, + rightActionColor = rightActionColor, + enabled = enabled + ) + }, + onClick = onClick?.takeIf { enabled }, + holdDownState = holdDownState, + enabled = enabled + ) +} + +@Composable +private fun SuperArrowRightActions( + rightText: String?, + rightActionColor: RightActionColors, + enabled: Boolean +) { + val currentRightActionColor = rightActionColor.color(enabled) + + if (rightText != null) { + Text( + modifier = Modifier.widthIn(max = 130.dp), + text = rightText, + fontSize = MiuixTheme.textStyles.body2.fontSize, + color = currentRightActionColor, + textAlign = TextAlign.End, + overflow = TextOverflow.Ellipsis, + maxLines = 2 + ) + } + Image( + modifier = Modifier + .padding(start = 8.dp) + .size(width = 12.dp, height = 24.dp), + painter = painterResource(R.drawable.coui_btn_next_normal), + contentDescription = null, + colorFilter = ColorFilter.tint(currentRightActionColor), + ) +} + +object SuperArrowDefaults { + /** + * The default color of the arrow. + */ + @Composable + fun rightActionColors() = RightActionColors( + color = MiuixTheme.colorScheme.onSurfaceVariantActions, + disabledColor = MiuixTheme.colorScheme.disabledOnSecondaryVariant + ) +} + + +@Immutable +class RightActionColors( + private val color: Color, + private val disabledColor: Color +) { + @Stable + internal fun color(enabled: Boolean): Color = if (enabled) color else disabledColor +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunDropdown.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunDropdown.kt new file mode 100644 index 00000000..afa49d04 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunDropdown.kt @@ -0,0 +1,882 @@ +package com.suqi8.oshin.ui.activity.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.gestures.waitForUpOrCancellation +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.captionBar +import androidx.compose.foundation.layout.displayCutout +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.BlendModeColorFilter +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.hapticfeedback.HapticFeedback +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.SubcomposeLayout +import androidx.compose.ui.layout.layout +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntRect +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.icon.MiuixIcons +import top.yukonga.miuix.kmp.icon.icons.basic.ArrowUpDownIntegrated +import top.yukonga.miuix.kmp.icon.icons.basic.Check +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.utils.BackHandler +import top.yukonga.miuix.kmp.utils.G2RoundedCornerShape +import top.yukonga.miuix.kmp.utils.MiuixPopupUtils.Companion.PopupLayout +import top.yukonga.miuix.kmp.utils.getWindowSize +import kotlin.math.roundToInt + +@Composable +fun FunDropdown( + title: String, + summary: String? = null, + selectedIndex: Int, + options: List, + position: CouiListItemPosition = CouiListItemPosition.Middle, + onSelectedIndexChange: (Int) -> Unit +) { + SuperDropdown( + title = title, + summary = summary, + items = options, + selectedIndex = selectedIndex, + position = position, + onSelectedIndexChange = onSelectedIndexChange + ) +} + +/** + * A dropdown with a title and a summary. + * + * @param items The options of the [SuperDropdown]. + * @param selectedIndex The index of the selected option. + * @param title The title of the [SuperDropdown]. + * @param titleColor The color of the title. + * @param summary The summary of the [SuperDropdown]. + * @param summaryColor The color of the summary. + * @param dropdownColors The [DropdownColors] of the [SuperDropdown]. + * @param mode The dropdown show mode of the [SuperDropdown]. + * @param modifier The modifier to be applied to the [SuperDropdown]. + * @param insideMargin The margin inside the [SuperDropdown]. + * @param maxHeight The maximum height of the [ListPopup]. + * @param enabled Whether the [SuperDropdown] is enabled. + * @param showValue Whether to show the selected value of the [SuperDropdown]. + * @param onClick The callback when the [SuperDropdown] is clicked. + * @param onSelectedIndexChange The callback when the selected index of the [SuperDropdown] is changed. + */ +@Composable +fun SuperDropdown( + items: List, + selectedIndex: Int, + title: String, + titleColor: BasicComponentColors = BasicComponentDefaults.titleColor(), + summary: String? = null, + summaryColor: BasicComponentColors = BasicComponentDefaults.summaryColor(), + dropdownColors: DropdownColors = DropdownDefaults.dropdownColors(), + mode: DropDownMode = DropDownMode.Normal, + modifier: Modifier = Modifier, + insideMargin: PaddingValues = BasicComponentDefaults.InsideMargin, + maxHeight: Dp? = null, + enabled: Boolean = true, + showValue: Boolean = true, + position: CouiListItemPosition = CouiListItemPosition.Middle, + onClick: (() -> Unit)? = null, + onSelectedIndexChange: ((Int) -> Unit)?, +) { + val interactionSource = remember { MutableInteractionSource() } + val isDropdownExpanded = remember { mutableStateOf(false) } + val hapticFeedback = LocalHapticFeedback.current + + val itemsNotEmpty = items.isNotEmpty() + val actualEnabled = enabled && itemsNotEmpty + + val actionColor = if (actualEnabled) { + MiuixTheme.colorScheme.onSurfaceVariantActions + } else { + MiuixTheme.colorScheme.disabledOnSecondaryVariant + } + + var alignLeft by rememberSaveable { mutableStateOf(true) } + + val componentModifier = modifier.pointerInput(actualEnabled) { + if (!actualEnabled) return@pointerInput + awaitPointerEventScope { + while (true) { + val event = awaitPointerEvent() + if (event.type != PointerEventType.Move) { + val eventChange = event.changes.first() + if (eventChange.pressed) { + alignLeft = eventChange.position.x < (size.width / 2) + } + } + } + } + } + + val handleClick: () -> Unit = { + if (actualEnabled) { + onClick?.invoke() + isDropdownExpanded.value = !isDropdownExpanded.value + if (isDropdownExpanded.value) { + hapticFeedback.performHapticFeedback(HapticFeedbackType.ContextClick) + } + } + } + + BasicComponent( + modifier = componentModifier, + interactionSource = interactionSource, + insideMargin = insideMargin, + title = title, + titleColor = titleColor, + summary = summary, + summaryColor = summaryColor, + position = position, + leftAction = if (itemsNotEmpty) { + { + SuperDropdownPopup( + items = items, + selectedIndex = selectedIndex, + isDropdownExpanded = isDropdownExpanded, + mode = mode, + alignLeft = alignLeft, + maxHeight = maxHeight, + dropdownColors = dropdownColors, + hapticFeedback = hapticFeedback, + onSelectedIndexChange = onSelectedIndexChange + ) + } + } else null, + rightActions = { + SuperDropdownRightActions( + showValue = showValue, + itemsNotEmpty = itemsNotEmpty, + items = items, + selectedIndex = selectedIndex, + actionColor = actionColor + ) + }, + onClick = handleClick, + holdDownState = isDropdownExpanded.value, + enabled = actualEnabled + ) +} + +@Composable +private fun SuperDropdownPopup( + items: List, + selectedIndex: Int, + isDropdownExpanded: MutableState, + mode: DropDownMode, + alignLeft: Boolean, + maxHeight: Dp?, + dropdownColors: DropdownColors, + hapticFeedback: HapticFeedback, + onSelectedIndexChange: ((Int) -> Unit)? +) { + var hoveredIndex by remember { mutableStateOf(-1) } + var pressedIndex by remember { mutableStateOf(-1) } + ListPopup( + show = isDropdownExpanded, + alignment = if (mode == DropDownMode.AlwaysOnRight || !alignLeft) { + PopupPositionProvider.Align.Right + } else { + PopupPositionProvider.Align.Left + }, + onDismissRequest = { + isDropdownExpanded.value = false + hoveredIndex = -1 + pressedIndex = -1 + }, + maxHeight = maxHeight + ) { + ListPopupColumn( + onPressedIndexChange = { pressedIndex = it }, + onDragHover = { + hoveredIndex = it + }, + onTap = { index -> + hapticFeedback.performHapticFeedback(HapticFeedbackType.Confirm) + onSelectedIndexChange?.invoke(index) + isDropdownExpanded.value = false + pressedIndex = -1 + hoveredIndex = -1 + }, + onDragEnd = { + if (hoveredIndex != -1) { + hapticFeedback.performHapticFeedback(HapticFeedbackType.Confirm) + onSelectedIndexChange?.invoke(hoveredIndex) + isDropdownExpanded.value = false + } + pressedIndex = -1 + hoveredIndex = -1 + }, + onDragCancel = { + pressedIndex = -1 + hoveredIndex = -1 + } + ) { + items.forEachIndexed { index, string -> + val dividerColor = MiuixTheme.colorScheme.dividerLine.copy(alpha = 0.5f) + Box( + modifier = Modifier.drawWithContent { + drawContent() + + if (index < items.size - 1) { + val thicknessPx = 0.5.dp.toPx() + drawLine( + color = dividerColor, + start = Offset(x = 16.dp.toPx(), y = size.height - thicknessPx / 2), + end = Offset(x = size.width - 16.dp.toPx(), y = size.height - thicknessPx / 2), + strokeWidth = thicknessPx + ) + } + } + ) { + val showCheckmark = (selectedIndex == index) || (hoveredIndex == index) + + val showDarkBackground = (pressedIndex == index) || (hoveredIndex == index) + + DropdownImpl( + text = string, + optionSize = items.size, + isSelected = showCheckmark, //传递 "选中" 逻辑 + isPressed = showDarkBackground, //传递 "变暗" 逻辑 + dropdownColors = dropdownColors, + onSelectedIndexChange = { }, // 父级已处理 + index = index + ) + } + } + } + } +} + +@Composable +private fun RowScope.SuperDropdownRightActions( + showValue: Boolean, + itemsNotEmpty: Boolean, + items: List, + selectedIndex: Int, + actionColor: Color +) { + if (showValue && itemsNotEmpty) { + Text( + modifier = Modifier.widthIn(max = 130.dp), + text = items[selectedIndex], + fontSize = MiuixTheme.textStyles.body2.fontSize, + color = actionColor, + textAlign = TextAlign.End, + overflow = TextOverflow.Ellipsis, + maxLines = 2 + ) + } + + Image( + modifier = Modifier + .padding(start = 8.dp) + .size(10.dp, 16.dp) + .align(Alignment.CenterVertically), + imageVector = MiuixIcons.Basic.ArrowUpDownIntegrated, + colorFilter = ColorFilter.tint(actionColor), + contentDescription = null + ) +} + +/** + * The implementation of the dropdown. + * + * @param text The text of the current option. + * @param optionSize The size of the options. + * @param isSelected Whether the option is selected. + * @param index The index of the current option in the options. + * @param onSelectedIndexChange The callback when the index is selected. + */ +@Composable +fun DropdownImpl( + text: String, + optionSize: Int, + isSelected: Boolean, + isPressed: Boolean, // ✨ "isPressed" 现在代表 "显示深色背景" + index: Int, + dropdownColors: DropdownColors = DropdownDefaults.dropdownColors(), + onSelectedIndexChange: (Int) -> Unit +) { + val additionalTopPadding = if (index == 0) 16.dp else 12.dp + val additionalBottomPadding = if (index == optionSize - 1) 16.dp else 12.dp + + // 1. 根据 "选中" 状态决定文本和检查标记的颜色 + val (textColor, backgroundColor) = if (isSelected) { + dropdownColors.selectedContentColor to dropdownColors.containerColor + } else { + dropdownColors.contentColor to dropdownColors.containerColor + } + + val checkColor = if (isSelected) { + dropdownColors.selectedContentColor + } else { + Color.Transparent + } + + // 2. ✨ 决定最终的背景颜色 + val finalBackgroundColor = if (isPressed) { + // 如果处于 "按下" 或 "悬停" 状态,应用一个深色遮罩 + // 0.12f 是一个标准的按下不透明度 + val overlayColor = MiuixTheme.colorScheme.onSurface.copy(alpha = 0.12f) + // 使用 compositeOver 将遮罩正确叠加在原始背景色上 + overlayColor.compositeOver(backgroundColor) + } else { + // 否则,只使用原始背景色 + backgroundColor + } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .widthIn(min = 178.dp) + // .clickable { onSelectedIndexChange(index) } // 父级已处理 + .background(finalBackgroundColor) // ✨ 使用新的 finalBackgroundColor + .padding(horizontal = 16.dp) + .padding( + top = additionalTopPadding, + bottom = additionalBottomPadding + ) + ) { + Text( + modifier = Modifier.widthIn(max = 178.dp), + text = text, + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = textColor, // ✨ 文本颜色仍然跟随 "选中" + ) + + Image( + modifier = Modifier + .padding(start = 12.dp) + .size(20.dp), + imageVector = MiuixIcons.Basic.Check, + colorFilter = BlendModeColorFilter(checkColor, BlendMode.SrcIn), // ✨ 检查标记仍然跟随 "选中" + contentDescription = null, + ) + } +} + +@Immutable +class DropdownColors( + val contentColor: Color, + val containerColor: Color, + val selectedContentColor: Color, + val selectedContainerColor: Color +) + +object DropdownDefaults { + + @Composable + fun dropdownColors( + contentColor: Color = MiuixTheme.colorScheme.onSurface, + containerColor: Color = MiuixTheme.colorScheme.surface, + selectedContentColor: Color = MiuixTheme.colorScheme.onTertiaryContainer, + selectedContainerColor: Color = MiuixTheme.colorScheme.tertiaryContainer + ): DropdownColors { + return DropdownColors( + contentColor = contentColor, + containerColor = containerColor, + selectedContentColor = selectedContentColor, + selectedContainerColor = selectedContainerColor + ) + } +} + +/** + * The dropdown show mode. + */ +enum class DropDownMode { + Normal, + AlwaysOnRight +} + +/** + * A popup with a list of items. + * + * @param show The show state of the [ListPopup]. + * @param popupModifier The modifier to be applied to the [ListPopup]. + * @param popupPositionProvider The [PopupPositionProvider] of the [ListPopup]. + * @param alignment The alignment of the [ListPopup]. + * @param enableWindowDim Whether to enable window dimming when the [ListPopup] is shown. + * @param shadowElevation The elevation of the shadow of the [ListPopup]. + * @param onDismissRequest The callback when the [ListPopup] is dismissed. + * @param maxHeight The maximum height of the [ListPopup]. If null, the height will be calculated automatically. + * @param minWidth The minimum width of the [ListPopup]. + * @param content The [Composable] content of the [ListPopup]. You should use the [ListPopupColumn] in general. + */ +@Composable +fun ListPopup( + show: MutableState, + popupModifier: Modifier = Modifier, + popupPositionProvider: PopupPositionProvider = ListPopupDefaults.DropdownPositionProvider, + alignment: PopupPositionProvider.Align = PopupPositionProvider.Align.Right, + enableWindowDim: Boolean = false, + shadowElevation: Dp = 24.dp, + onDismissRequest: (() -> Unit)? = null, + maxHeight: Dp? = null, + minWidth: Dp = 178.dp, + content: @Composable () -> Unit +) { + if (!show.value) return + + val windowSize by rememberUpdatedState(getWindowSize()) + var parentBounds by remember { mutableStateOf(IntRect.Zero) } + + Layout( + modifier = Modifier + .onGloballyPositioned { childCoordinates -> + childCoordinates.parentLayoutCoordinates?.let { parentLayoutCoordinates -> + val positionInWindow = parentLayoutCoordinates.positionInWindow() + parentBounds = IntRect( + left = positionInWindow.x.toInt(), + top = positionInWindow.y.toInt(), + right = positionInWindow.x.toInt() + parentLayoutCoordinates.size.width, + bottom = positionInWindow.y.toInt() + parentLayoutCoordinates.size.height + ) + } + } + ) { _, _ -> layout(0, 0) {} } + if (parentBounds == IntRect.Zero) return + + val density = LocalDensity.current + val layoutDirection = LocalLayoutDirection.current + val displayCutout = WindowInsets.displayCutout.asPaddingValues() + val statusBars = WindowInsets.statusBars.asPaddingValues() + val navigationBars = WindowInsets.navigationBars.asPaddingValues() + val captionBar = WindowInsets.captionBar.asPaddingValues() + + val popupMargin = remember(windowSize, density) { + with(density) { + IntRect( + left = popupPositionProvider.getMargins().calculateLeftPadding(layoutDirection).roundToPx(), + top = popupPositionProvider.getMargins().calculateTopPadding().roundToPx(), + right = popupPositionProvider.getMargins().calculateRightPadding(layoutDirection).roundToPx(), + bottom = popupPositionProvider.getMargins().calculateBottomPadding().roundToPx() + ) + } + } + + val windowBounds = remember(windowSize, density) { + with(density) { + IntRect( + left = displayCutout.calculateLeftPadding(layoutDirection).roundToPx(), + top = statusBars.calculateTopPadding().roundToPx(), + right = windowSize.width - displayCutout.calculateRightPadding(layoutDirection).roundToPx(), + bottom = windowSize.height - navigationBars.calculateBottomPadding() + .roundToPx() - captionBar.calculateBottomPadding().roundToPx() + ) + } + } + + val transformOrigin = remember(windowSize, alignment, density) { + val xInWindow = when (alignment) { + PopupPositionProvider.Align.Right, + PopupPositionProvider.Align.TopRight, + PopupPositionProvider.Align.BottomRight -> parentBounds.right - popupMargin.right - with(density) { 64.dp.roundToPx() } + + else -> parentBounds.left + popupMargin.left + with(density) { 64.dp.roundToPx() } + } + val yInWindow = parentBounds.top + parentBounds.height / 2 - with(density) { 56.dp.roundToPx() } + safeTransformOrigin( + xInWindow / windowSize.width.toFloat(), + yInWindow / windowSize.height.toFloat() + ) + } + + PopupLayout( + visible = show, + enableWindowDim = enableWindowDim, + transformOrigin = { transformOrigin }, + ) { + val shape = remember { G2RoundedCornerShape(12.dp) } + val elevationPx = with(density) { shadowElevation.toPx() } + + Box( + modifier = popupModifier + .pointerInput(onDismissRequest) { + detectTapGestures( + onTap = { onDismissRequest?.invoke() } + ) + } + .layout { measurable, constraints -> + val placeable = measurable.measure( + constraints.copy( + minWidth = if (minWidth.roundToPx() <= windowSize.width) minWidth.roundToPx() else windowSize.width, + minHeight = if (50.dp.roundToPx() <= windowSize.height) 50.dp.roundToPx() else windowSize.height, + maxHeight = maxHeight?.roundToPx()?.coerceAtLeast(50.dp.roundToPx()) + ?: (windowBounds.height - popupMargin.top - popupMargin.bottom).coerceAtLeast( + 50.dp.roundToPx() + ), + maxWidth = if (minWidth.roundToPx() <= windowSize.width) windowSize.width else minWidth.roundToPx() + ) + ) + val measuredSize = IntSize(placeable.width, placeable.height) + + val calculatedOffset = popupPositionProvider.calculatePosition( + parentBounds, + windowBounds, + layoutDirection, + measuredSize, + popupMargin, + alignment + ) + + layout(constraints.maxWidth, constraints.maxHeight) { + placeable.place(calculatedOffset) + } + } + ) { + Box( + modifier = Modifier + .graphicsLayer( + clip = true, + shape = shape, + shadowElevation = elevationPx, + ambientShadowColor = MiuixTheme.colorScheme.windowDimming, + spotShadowColor = MiuixTheme.colorScheme.windowDimming + ) + .background(MiuixTheme.colorScheme.surface) + ) { + content() + } + } + } + + BackHandler(enabled = show.value) { + onDismissRequest?.invoke() + } +} + +/** + * A column that automatically aligns the width to the widest item + * @param content The items + */ +@Composable +fun ListPopupColumn( + onDragHover: (index: Int) -> Unit, + onDragEnd: () -> Unit, + onDragCancel: () -> Unit, + onPressedIndexChange: (index: Int) -> Unit, + onTap: (index: Int) -> Unit, + content: @Composable () -> Unit +) { + val scrollState = rememberScrollState() + val currentContent by rememberUpdatedState(content) + val itemBoundaries = remember { mutableListOf() } + val hapticFeedback = LocalHapticFeedback.current + + // ✨ 修复:1. "按下/点击" 检测器 + // 我们使用更底层的 API 来避免长按冲突 + val pressAndTapDetector = Modifier.pointerInput(Unit) { + awaitPointerEventScope { + while (true) { + // 1. 等待手指按下 + // requireUnconsumed = false 允许我们和滚动/拖动共享"按下"事件 + val down = awaitFirstDown(requireUnconsumed = false) + val index = findIndex(down.position.y, scrollState.value, itemBoundaries) + + if (index == -1) continue // 按在了空白处 + + // 2. 立即设置 "按下" 状态(变暗) + onPressedIndexChange(index) + + // 3. 等待手指抬起 或 手势被取消 + // (被滚动或长按拖动“抢走”手势) + val up = waitForUpOrCancellation() + + if (up != null) { + // 4a. 如果手指正常抬起 (up != null),这是一次 "点击" + onTap(index) + } + + // 4b. 手指抬起(Tap) 或 手势被取消(Scroll/Long-press) + // 都重置 "按下" 状态 + onPressedIndexChange(-1) + } + } + } + + // ✨ 2. "长按并拖动" 检测器 (这个保持不变) + val longPressDragDetector = Modifier.pointerInput(Unit) { + detectDragGesturesAfterLongPress( + onDragStart = { offset -> + val index = findIndex(offset.y, scrollState.value, itemBoundaries) + if (index != -1) { + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + + // ✨ 关键过渡: + // "悬停" 状态接管 "按下" 状态 + // 必须先设置 onPressedIndexChange(-1) + onPressedIndexChange(-1) + onDragHover(index) + } + }, + onDrag = { change, _ -> + val index = findIndex(change.position.y, scrollState.value, itemBoundaries) + onDragHover(index) + }, + onDragEnd = { onDragEnd() }, + onDragCancel = { onDragCancel() } + ) + } + + // ✨ 3. 应用修饰符 + SubcomposeLayout( + modifier = Modifier + // 顺序很重要: + .verticalScroll(scrollState) // 1. 滚动 (最外层) + .then(longPressDragDetector) // 2. 长按拖动 (中间) + .then(pressAndTapDetector) // 3. 按下/点击 (最内层) + // 事件会从外到内传递 (1 -> 2 -> 3) + // 当 1 或 2 "赢得" 手势时,它们会取消 3 + ) { constraints -> + // ... (SubcomposeLayout 的内部测量逻辑保持不变) + var listHeight = 0 + val tempConstraints = constraints.copy(minWidth = 178.dp.roundToPx(), maxWidth = 288.dp.roundToPx(), minHeight = 0) + + val listWidth = subcompose("miuixPopupListFake", currentContent).map { + it.measure(tempConstraints) + }.maxOfOrNull { it.width }?.coerceIn(178.dp.roundToPx(), 288.dp.roundToPx()) ?: 178.dp.roundToPx() + + val childConstraints = constraints.copy(minWidth = listWidth, maxWidth = listWidth, minHeight = 0) + + itemBoundaries.clear() + var currentY = 0 + + val placeables = subcompose("miuixPopupListReal", currentContent).map { + val placeable = it.measure(childConstraints) + + itemBoundaries.add(currentY until (currentY + placeable.height)) + currentY += placeable.height + + listHeight += placeable.height + placeable + } + + layout(listWidth, listHeight) { + var yPosition = 0 + placeables.forEach { + it.place(0, yPosition) + yPosition += it.height + } + } + } +} + +private fun findIndex(y: Float, scrollY: Int, boundaries: List): Int { + val yInList = y.roundToInt() + scrollY + return boundaries.indexOfFirst { yInList in it } +} + +interface PopupPositionProvider { + /** + * Calculate the position (offset) of Popup + * + * @param anchorBounds Bounds of the anchored (parent) component + * @param windowBounds Bounds of the safe area of window (excluding the [WindowInsets.Companion.statusBars], [WindowInsets.Companion.navigationBars] and [WindowInsets.Companion.captionBar]) + * @param layoutDirection [LayoutDirection] + * @param popupContentSize Actual size of the popup content + * @param popupMargin (Extra) Margins for the popup content. See [PopupPositionProvider.getMargins] + * @param alignment Alignment of the popup (relative to the window). See [PopupPositionProvider.Align] + */ + fun calculatePosition( + anchorBounds: IntRect, + windowBounds: IntRect, + layoutDirection: LayoutDirection, + popupContentSize: IntSize, + popupMargin: IntRect, + alignment: Align + ): IntOffset + + /** + * (Extra) Margins for the popup content. + */ + fun getMargins(): PaddingValues + + /** + * Position relative to the window, not relative to the anchor! + */ + enum class Align { + Left, + Right, + TopLeft, + TopRight, + BottomLeft, + BottomRight + } +} + +object ListPopupDefaults { + val DropdownPositionProvider = object : PopupPositionProvider { + override fun calculatePosition( + anchorBounds: IntRect, + windowBounds: IntRect, + layoutDirection: LayoutDirection, + popupContentSize: IntSize, + popupMargin: IntRect, + alignment: PopupPositionProvider.Align + ): IntOffset { + val offsetX = if (alignment == PopupPositionProvider.Align.Right) { + anchorBounds.right - popupContentSize.width - popupMargin.right + } else { + anchorBounds.left + popupMargin.left + } + val offsetY = if (windowBounds.bottom - anchorBounds.bottom > popupContentSize.height) { + // Show below + anchorBounds.bottom + popupMargin.bottom + } else if (anchorBounds.top - windowBounds.top > popupContentSize.height) { + // Show above + anchorBounds.top - popupContentSize.height - popupMargin.top + } else { + // Middle + anchorBounds.top + anchorBounds.height / 2 - popupContentSize.height / 2 + } + return IntOffset( + x = offsetX.coerceIn( + windowBounds.left, + (windowBounds.right - popupContentSize.width - popupMargin.right).coerceAtLeast(windowBounds.left) + ), + y = offsetY.coerceIn( + (windowBounds.top + popupMargin.top).coerceAtMost(windowBounds.bottom - popupContentSize.height - popupMargin.bottom), + windowBounds.bottom - popupContentSize.height - popupMargin.bottom + ) + ) + } + + override fun getMargins(): PaddingValues { + return PaddingValues(horizontal = 0.dp, vertical = 8.dp) + } + } + val ContextMenuPositionProvider = object : PopupPositionProvider { + override fun calculatePosition( + anchorBounds: IntRect, + windowBounds: IntRect, + layoutDirection: LayoutDirection, + popupContentSize: IntSize, + popupMargin: IntRect, + alignment: PopupPositionProvider.Align + ): IntOffset { + val offsetX: Int + val offsetY: Int + when (alignment) { + PopupPositionProvider.Align.TopLeft -> { + offsetX = anchorBounds.left + popupMargin.left + offsetY = anchorBounds.bottom + popupMargin.top + } + + PopupPositionProvider.Align.TopRight -> { + offsetX = anchorBounds.right - popupContentSize.width - popupMargin.right + offsetY = anchorBounds.bottom + popupMargin.top + } + + PopupPositionProvider.Align.BottomLeft -> { + offsetX = anchorBounds.left + popupMargin.left + offsetY = anchorBounds.top - popupContentSize.height - popupMargin.bottom + } + + PopupPositionProvider.Align.BottomRight -> { + offsetX = anchorBounds.right - popupContentSize.width - popupMargin.right + offsetY = anchorBounds.top - popupContentSize.height - popupMargin.bottom + } + + else -> { + // Fallback + offsetX = if (alignment == PopupPositionProvider.Align.Right) { + anchorBounds.right - popupContentSize.width - popupMargin.right + } else { + anchorBounds.left + popupMargin.left + } + offsetY = if (windowBounds.bottom - anchorBounds.bottom > popupContentSize.height) { + // Show below + anchorBounds.bottom + popupMargin.bottom + } else if (anchorBounds.top - windowBounds.top > popupContentSize.height) { + // Show above + anchorBounds.top - popupContentSize.height - popupMargin.top + } else { + // Middle + anchorBounds.top + anchorBounds.height / 2 - popupContentSize.height / 2 + } + } + } + return IntOffset( + x = offsetX.coerceIn( + windowBounds.left, + (windowBounds.right - popupContentSize.width - popupMargin.right).coerceAtLeast(windowBounds.left) + ), + y = offsetY.coerceIn( + (windowBounds.top + popupMargin.top).coerceAtMost(windowBounds.bottom - popupContentSize.height - popupMargin.bottom), + windowBounds.bottom - popupContentSize.height - popupMargin.bottom + ) + ) + } + + override fun getMargins(): PaddingValues { + return PaddingValues(horizontal = 20.dp, vertical = 0.dp) + } + } +} + +/** + * Ensure TransformOrigin is available. + */ +fun safeTransformOrigin(x: Float, y: Float): TransformOrigin { + val safeX = if (x.isNaN() || x < 0f) 0f else x + val safeY = if (y.isNaN() || y < 0f) 0f else y + return TransformOrigin(safeX, safeY) +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunNoEnable.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunNoEnable.kt new file mode 100644 index 00000000..da93afcc --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunNoEnable.kt @@ -0,0 +1,57 @@ +package com.suqi8.oshin.ui.activity.components + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.animateLottieCompositionAsState +import com.airbnb.lottie.compose.rememberLottieComposition +import com.suqi8.oshin.R +import com.suqi8.oshin.utils.drawColoredShadow +import top.yukonga.miuix.kmp.basic.Text + +@Composable +fun FunNoEnable() { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 6.dp) + .drawColoredShadow( + Color.Red, + 0.07f, + borderRadius = 0.dp, + shadowRadius = 15.dp, + offsetX = 0.dp, + offsetY = 0.dp, + roundedRect = false + ), + colors = CardDefaults.defaultColors(Color.Red.copy(alpha = 0.03f)) + ) { + val compositionResult = rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.error)) + val progress = animateLottieCompositionAsState( + composition = compositionResult.value + ) + Row(verticalAlignment = Alignment.CenterVertically) { + LottieAnimation( + composition = compositionResult.value, + progress = { progress.value }, + modifier = Modifier + .size(50.dp) + ) + Text( + text = stringResource(R.string.no_start_func), + color = Color.Red, + fontWeight = FontWeight.Medium + ) + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunPage.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunPage.kt new file mode 100644 index 00000000..97816165 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunPage.kt @@ -0,0 +1,269 @@ +package com.suqi8.oshin.ui.activity.components + +import android.graphics.RenderEffect +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.LayerBackdrop +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import com.kyant.backdrop.drawPlainBackdrop +import com.kyant.backdrop.effects.blur +import com.kyant.backdrop.effects.effect +import com.suqi8.oshin.ui.activity.components.apprestart.AppRestartScreen +import com.suqi8.oshin.utils.hasShortcut +import com.suqi8.oshin.utils.launchApp +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import top.yukonga.miuix.kmp.basic.Icon +import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior +import top.yukonga.miuix.kmp.basic.Scaffold +import top.yukonga.miuix.kmp.basic.ScrollBehavior +import top.yukonga.miuix.kmp.basic.TopAppBar +import top.yukonga.miuix.kmp.basic.rememberTopAppBarState +import top.yukonga.miuix.kmp.icon.MiuixIcons +import top.yukonga.miuix.kmp.icon.icons.useful.Back +import top.yukonga.miuix.kmp.icon.icons.useful.Play +import top.yukonga.miuix.kmp.icon.icons.useful.Refresh +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.utils.overScrollVertical +import top.yukonga.miuix.kmp.utils.scrollEndHaptic + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun FunPage( + title: String, + appList: List = emptyList(), + navController: NavController, + content: @Composable () -> Unit +) { + val topAppBarState = MiuixScrollBehavior(rememberTopAppBarState()) + FunPage( + title = title, + appList = appList, + navController = navController, + scrollBehavior = topAppBarState + ) { padding -> + LazyColumn( + modifier = Modifier + .fillMaxSize() + .overScrollVertical() + .scrollEndHaptic() + .nestedScroll(topAppBarState.nestedScrollConnection), + contentPadding = padding + ) { + item { content() } + } + } +} + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun FunPage( + title: String = "", + appList: List = emptyList(), + navController: NavController, + scrollBehavior: ScrollBehavior, + sharedTransitionScope: SharedTransitionScope? = null, + animatedVisibilityScope: AnimatedVisibilityScope? = null, + animationKey: String? = null, + action: @Composable (LayerBackdrop) -> Unit = { _ -> }, + content: @Composable (padding: PaddingValues) -> Unit +) { + val restartAPP = remember { mutableStateOf(false) } + val backdrop = rememberLayerBackdrop() + + Scaffold( + topBar = { + TopAppBar( + title = title, + color = Color.Transparent, + scrollBehavior = scrollBehavior, + modifier = Modifier.height(0.dp) + ) + }, + modifier = sharedTransitionScope?.let { scope -> + animationKey?.let { key -> + animatedVisibilityScope?.let { animScope -> + with(scope) { + Modifier.sharedBounds( + sharedContentState = rememberSharedContentState(key = key), + animatedVisibilityScope = animScope + ) + } + } + } + } ?: Modifier + ) { padding -> + val background = MiuixTheme.colorScheme.background + + Box(Modifier.fillMaxSize()) { + Box( + Modifier + .layerBackdrop(backdrop) + .background(background) + .fillMaxSize() + ) { + content(padding) + /*if (sharedTransitionScope?.isTransitionActive == true) { + Box(Modifier.fillMaxSize().pointerInput(Unit) {}) + }*/ + } + + // --- 顶部按钮栏 --- + Column(Modifier.align(Alignment.TopStart)) { + TopButtons(navController, appList, backdrop, restartAPP, action) + } + + // --- 顶部模糊栏 --- + if (title == "") { + Box( + Modifier + .height(72.dp) + .fillMaxWidth() + .drawPlainBackdrop( + backdrop = backdrop, + shape = { RectangleShape }, + effects = { + blur(4f.dp.toPx()) + effect( + RenderEffect.createRuntimeShaderEffect( + obtainRuntimeShader( + "AlphaMask", + """ +uniform shader content; +uniform float2 size; +layout(color) uniform half4 tint; +uniform float tintIntensity; +half4 main(float2 coord) { + float blurAlpha = smoothstep(size.y, size.y * 0.2, coord.y); + float tintAlpha = smoothstep(size.y, size.y * 0.2, coord.y); + return mix(content.eval(coord) * blurAlpha, tint * tintAlpha, tintIntensity); +}""" + ).apply { + setFloatUniform("size", size.width, size.height) + setColorUniform("tint", background.value.toLong()) + setFloatUniform("tintIntensity", 0.8f) + }, + "content" + ) + ) + } + ), + contentAlignment = Alignment.Center + ) {} + } + } + } + + if (appList.isNotEmpty() && restartAPP.value) { + AppRestartScreen(appList, restartAPP, backdrop) + } +} + +@Composable +private fun TopButtons( + navController: NavController, + appList: List, + backdrop: LayerBackdrop, + restartAPP: MutableState, + action: @Composable (LayerBackdrop) -> Unit +) { + val context = LocalContext.current + var showShortcut by remember { mutableStateOf(false) } + + LaunchedEffect(appList) { + withContext(Dispatchers.IO) { + if (appList.size == 1 && hasShortcut(context, appList.first())) { + showShortcut = true + } + } + } + + Row( + Modifier + .displayCutoutPadding() + .padding(horizontal = 16.dp, vertical = 4.dp) + .fillMaxWidth() + .wrapContentHeight(), + verticalAlignment = Alignment.CenterVertically + ) { + LiquidButton( + onClick = { navController.popBackStack() }, + modifier = Modifier.size(40.dp), + backdrop = backdrop + ) { + Icon( + imageVector = MiuixIcons.Useful.Back, + contentDescription = "Back", + modifier = Modifier.size(22.dp), + tint = MiuixTheme.colorScheme.onBackground + ) + } + + Spacer(modifier = Modifier.weight(1f)) + + action(backdrop) + + if (showShortcut) { + LiquidButton( + onClick = { launchApp(context, appList.first()) }, + modifier = Modifier.size(40.dp), + backdrop = backdrop + ) { + Icon( + imageVector = MiuixIcons.Useful.Play, + contentDescription = "Open Shortcut", + modifier = Modifier.size(22.dp), + tint = MiuixTheme.colorScheme.onBackground + ) + } + Spacer(modifier = Modifier.width(8.dp)) + } + + if (appList.isNotEmpty()) { + LiquidButton( + onClick = { restartAPP.value = true }, + modifier = Modifier.size(40.dp), + backdrop = backdrop + ) { + Icon( + imageVector = MiuixIcons.Useful.Refresh, + contentDescription = "Refresh", + modifier = Modifier.size(22.dp), + tint = MiuixTheme.colorScheme.onBackground + ) + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunPicSele.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunPicSele.kt new file mode 100644 index 00000000..37184736 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunPicSele.kt @@ -0,0 +1,65 @@ +package com.suqi8.oshin.ui.activity.components + +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.hjq.permissions.permission.PermissionLists +import com.kyant.capsule.ContinuousRoundedRectangle +import com.suqi8.oshin.utils.requestPermissions + +@Composable +fun FunPicSele( + title: String, + summary: String?, + imageBitmap: ImageBitmap?, + // [修改] 替换 externalPadding 为 position + position: CouiListItemPosition = CouiListItemPosition.Middle, + onImageSelected: (Uri?) -> Unit +) { + val imagePickerLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.PickVisualMedia(), + onResult = { uri -> + onImageSelected(uri) + } + ) + val context = LocalContext.current + // 权限请求逻辑保持不变 (注意:requestPermissions 需要确保存在) + LaunchedEffect(Unit) { + requestPermissions(context, PermissionLists.getManageExternalStoragePermission()) {} + } + + BasicComponent( + title = title, + summary = summary, + // [修改] 传递 position + position = position, + rightActions = { + imageBitmap?.let { bitmap -> + Image( + painter = BitmapPainter(bitmap), + contentDescription = title, + modifier = Modifier + .size(48.dp) + // 使用平滑圆角,更符合 ColorOS 风格 (如果 ContinuousRoundedRectangle 不可用,可用 RoundedCornerShape(8.dp) 代替) + .clip(ContinuousRoundedRectangle(8.dp)) + ) + } + }, + onClick = { + imagePickerLauncher.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + } + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunSlider.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunSlider.kt new file mode 100644 index 00000000..070fb039 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunSlider.kt @@ -0,0 +1,149 @@ +package com.suqi8.oshin.ui.activity.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.dp +import com.suqi8.oshin.R +import top.yukonga.miuix.kmp.basic.ButtonDefaults +import top.yukonga.miuix.kmp.basic.Slider +import top.yukonga.miuix.kmp.basic.TextButton +import top.yukonga.miuix.kmp.basic.TextField +import top.yukonga.miuix.kmp.extra.SuperDialog +import java.util.Locale + +@Composable +fun FunSlider( + title: String, + summary: String?, + value: Float, + valueRange: ClosedFloatingPointRange, + onValueChange: (Float) -> Unit, + unit: String, + decimalPlaces: Int, + // [修改] 替换 externalPadding 为 position,默认值为 Middle + position: CouiListItemPosition = CouiListItemPosition.Middle +) { + // 1. 弹窗的显示状态 + val showDialog = remember { mutableStateOf(false) } + + // 2. 用于在弹窗中临时编辑的值 + val cacheValue = remember { mutableStateOf(value.toString()) } + + // 3. 当弹窗打开时,确保它显示的是最新的外部传入值 + LaunchedEffect(showDialog.value) { + if (showDialog.value) { + val formattedValue = if (decimalPlaces <= 0) { + value.toInt().toString() + } else { + String.format(Locale.US, "%.${decimalPlaces}f", value) + } + cacheValue.value = formattedValue + } + } + + // 格式化当前值以在主页面显示 + val formattedDisplayValue = if (decimalPlaces <= 0) { + value.toInt().toString() + } else { + String.format(Locale.US, "%.${decimalPlaces}f", value) + } + + // 主页面上的显示项,点击后打开弹窗 + // [修改] 使用 FunArrow 并传递 position 参数 + FunArrow( + title = title, + summary = summary, + rightText = "$formattedDisplayValue$unit", + position = position, // 传递位置信息,由 FunArrow -> SuperArrow -> BasicComponent 处理 + onClick = { showDialog.value = true } + ) + + SuperDialog( + show = showDialog, + title = stringResource(R.string.settings) + " " + title, + summary = summary, + onDismissRequest = { showDialog.value = false } + ) { + SliderWithInput( + value = cacheValue.value, + onValueChange = { cacheValue.value = it }, + valueRange = valueRange, + decimalPlaces = decimalPlaces + ) + + Spacer(Modifier.height(12.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + TextButton( + modifier = Modifier.weight(1f), + text = stringResource(R.string.cancel), + onClick = { showDialog.value = false } + ) + Spacer(Modifier.width(12.dp)) + TextButton( + modifier = Modifier.weight(1f), + text = stringResource(R.string.ok), + colors = ButtonDefaults.textButtonColorsPrimary(), + enabled = cacheValue.value.toFloatOrNull() != null, + onClick = { + cacheValue.value.toFloatOrNull()?.let { + // 只有在点击确定时才提交值 + onValueChange(it) + } + showDialog.value = false + } + ) + } + } +} + +@Composable +private fun SliderWithInput( + value: String, + onValueChange: (String) -> Unit, + valueRange: ClosedFloatingPointRange, + decimalPlaces: Int +) { + val sliderPosition = value.toFloatOrNull() ?: valueRange.start + + Column { + Slider( + progress = sliderPosition, + onProgressChange = { + val newValue = if (decimalPlaces <= 0) { + it.toInt().toString() + } else { + String.format(Locale.US, "%.${decimalPlaces}f", it) + } + onValueChange(newValue) + }, + minValue = valueRange.start, + maxValue = valueRange.endInclusive, + effect = true + ) + + Spacer(Modifier.height(12.dp)) + + TextField( + value = value, + onValueChange = onValueChange, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done) + ) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunString.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunString.kt new file mode 100644 index 00000000..02583229 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunString.kt @@ -0,0 +1,88 @@ +package com.suqi8.oshin.ui.activity.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.dp +import com.suqi8.oshin.R +import top.yukonga.miuix.kmp.basic.ButtonDefaults +import top.yukonga.miuix.kmp.basic.TextButton +import top.yukonga.miuix.kmp.basic.TextField +import top.yukonga.miuix.kmp.extra.SuperDialog + +@Composable +fun FunString( + title: String, + summary: String?, + value: String, + onValueChange: (String) -> Unit, + nullable: Boolean = false, + // [修改] 替换 externalPadding 为 position,默认值为 Middle + position: CouiListItemPosition = CouiListItemPosition.Middle +) { + val showDialog = remember { mutableStateOf(false) } + val cacheValue = remember { mutableStateOf(value) } + + // 当弹窗打开时,确保它显示的是最新的外部传入值 + LaunchedEffect(showDialog.value) { + if (showDialog.value) { + cacheValue.value = value + } + } + + // [修改] 使用 FunArrow 并传递 position 参数 + FunArrow( + title = title, + summary = summary, + rightText = value, + position = position, // 传递位置信息 + onClick = { showDialog.value = true } + ) + + SuperDialog( + show = showDialog, + title = stringResource(R.string.settings) + " " + title, + summary = summary, + onDismissRequest = { showDialog.value = false } + ) { + TextField( + value = cacheValue.value, + onValueChange = { cacheValue.value = it }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done) + ) + Spacer(Modifier.height(12.dp)) + Row( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + TextButton( + modifier = Modifier.weight(1f), + text = stringResource(R.string.cancel), + onClick = { showDialog.value = false } + ) + Spacer(Modifier.width(12.dp)) + TextButton( + modifier = Modifier.weight(1f), + text = stringResource(R.string.ok), + colors = ButtonDefaults.textButtonColorsPrimary(), + enabled = (if (nullable) true else cacheValue.value.isNotEmpty()), + onClick = { + onValueChange(cacheValue.value) // 通过回调通知 ViewModel + showDialog.value = false + } + ) + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunSwich.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunSwich.kt new file mode 100644 index 00000000..57c82b97 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/FunSwich.kt @@ -0,0 +1,359 @@ +package com.suqi8.oshin.ui.activity.components + +import android.annotation.SuppressLint +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.CubicBezierEasing +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateDp +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.keyframes +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.foundation.background +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.selection.toggleable +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.unit.dp +import com.highcapable.yukihookapi.hook.factory.prefs +import com.suqi8.oshin.ui.mainscreen.LocalColorMode + +/** + * 一个带有数据持久化和触感反馈功能的 Switch 开关组件。 + * + * 此组件作为顶层调用者,封装了 [SuperSwitch],并使用 YukiHookAPI 的 `prefs` 来自动处理状态的读取和保存。 + * 当开关切换动画完成时,会触发一次手机震动。 + * + * **重要提示**: 请确保已在 AndroidManifest.xml 文件中添加震动权限: + * `` + */ +@Composable +fun FunSwitch( + title: String, + summary: String? = null, + category: String, + key: String, + defValue: Boolean = false, + // [新增] position 参数 + position: CouiListItemPosition = CouiListItemPosition.Middle, + onCheckedChange: ((Boolean) -> Unit)? = null +) { + val context = LocalContext.current + val haptic = LocalHapticFeedback.current + val isChecked = remember { mutableStateOf(context.prefs(category).getBoolean(key, defValue)) } + + SuperSwitch( + title = title, + summary = summary, + checked = isChecked.value, + // [修改] 传递 position + position = position, + onCheckedChange = { checked -> + context.prefs(category).edit { putBoolean(key, checked) } + isChecked.value = checked + onCheckedChange?.invoke(checked) + }, + onAnimationFinished = { + haptic.performHapticFeedback(HapticFeedbackType.ContextClick) + } + ) +} + +@Composable +fun FunSwitch( + title: String, + summary: String? = null, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + // [修改] 替换 externalPadding 为 position + position: CouiListItemPosition = CouiListItemPosition.Middle, + enabled: Boolean = true +) { + val haptic = LocalHapticFeedback.current + SuperSwitch( + title = title, + summary = summary, + checked = checked, + onCheckedChange = onCheckedChange, + enabled = enabled, + // [修改] 传递 position + position = position, + onAnimationFinished = { + haptic.performHapticFeedback(HapticFeedbackType.ContextClick) + }, + ) +} + +@Composable +fun SuperSwitch( + checked: Boolean, + onCheckedChange: ((Boolean) -> Unit)?, + title: String, + modifier: Modifier = Modifier, + titleColor: BasicComponentColors = BasicComponentDefaults.titleColor(), + summary: String? = null, + summaryColor: BasicComponentColors = BasicComponentDefaults.summaryColor(), + leftAction: @Composable (() -> Unit)? = null, + rightActions: @Composable RowScope.() -> Unit = {}, + switchColors: SwitchColors = SwitchDefaults.colors(), + insideMargin: PaddingValues = BasicComponentDefaults.InsideMargin, + onClick: (() -> Unit)? = null, + // [修改] 替换 externalPadding 为 position + position: CouiListItemPosition = CouiListItemPosition.Middle, + enabled: Boolean = true, + onAnimationFinished: () -> Unit = {} +) { + BasicComponent( + modifier = modifier, + insideMargin = insideMargin, + title = title, + titleColor = titleColor, + summary = summary, + summaryColor = summaryColor, + leftAction = leftAction, + // [修改] 传递 position 给 BasicComponent + position = position, + rightActions = { + rightActions() + Switch( + checked = checked, + onCheckedChange = null, // 点击由外层接管 + enabled = enabled, + colors = switchColors, + onAnimationFinished = onAnimationFinished + ) + }, + onClick = { + if (enabled) { + onClick?.invoke() + onCheckedChange?.invoke(!checked) + } + }, + enabled = enabled + ) +} + +/** + * ColorOS 风格 Switch 最终完美复刻版 (Standard Compact Edition)。 + * + * 数据来源 (全部来自反编译原厂文件): + * - [尺寸]: bar_width=38dp, bar_height=24dp, thumb_size=20dp (derived padding=2dp) + * - [颜色]: couiBlueTintControlNormal=#ff006aff, coui_color_controls=#29000000 + * - [动画]: 383ms/133ms, 贝塞尔(0.3, 0, 0.1, 1) + * - [物理]: Spring 阻尼按压反馈 + */ +@SuppressLint("UnusedTransitionTargetStateParameter") +/** + * ColorOS 风格 Switch 最终纯净复刻版 (Zero Guess Edition)。 + * + * 数据来源 (全部来自反编译原厂文件): + * - [尺寸]: 38x24dp 轨道, 18dp 滑块 (derived padding=3dp) + * - [颜色]: 基于 colors.xml 的精确值 + * - [动画]: 383ms/133ms, 贝塞尔(0.3, 0, 0.1, 1) + * - [物理]: Spring 阻尼按压反馈 + */ +@Composable +fun Switch( + checked: Boolean, + onCheckedChange: ((Boolean) -> Unit)?, + modifier: Modifier = Modifier, + enabled: Boolean = true, + colors: SwitchColors = SwitchDefaults.colors(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + onAnimationFinished: () -> Unit = {} +) { + val trackWidth = 38.dp + val trackHeight = 24.dp + val thumbSize = 18.dp + val thumbMargin = 3.dp + + // 仅在有回调时才监听按压状态,否则它应该是一个纯静止的视觉元素 + val isPressed by if (onCheckedChange != null) { + interactionSource.collectIsPressedAsState() + } else { + remember { mutableStateOf(false) } + } + + val toggleDuration = 383 + val scaleUpDuration = 133 + val toggleEasing = CubicBezierEasing(0.3f, 0.0f, 0.1f, 1.0f) + + val transition = updateTransition(checked, label = "Switch") + + val trackColor by animateColorAsState( + targetValue = colors.trackColor(enabled, checked), + animationSpec = tween(durationMillis = 450), + label = "TrackColor" + ) + + val thumbMoveRange = trackWidth - thumbSize - (thumbMargin * 2) + val thumbOffset by transition.animateDp( + transitionSpec = { + tween(durationMillis = toggleDuration, easing = toggleEasing) + }, + label = "ThumbOffset" + ) { isChecked -> + if (isChecked) thumbMoveRange else 0.dp + } + + LaunchedEffect(thumbOffset) { + if (thumbOffset == (if (checked) thumbMoveRange else 0.dp)) { + onAnimationFinished() + } + } + + val toggleScaleX by transition.animateFloat( + transitionSpec = { + keyframes { + durationMillis = toggleDuration + 1.0f at 0 using toggleEasing + 1.3f at scaleUpDuration using toggleEasing + 1.0f at toggleDuration + } + }, + label = "ThumbToggleScaleX" + ) { 1.0f } + + val pressScale = remember { Animatable(1f) } + LaunchedEffect(isPressed) { + pressScale.animateTo( + targetValue = if (isPressed) 0.9f else 1.0f, + animationSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessLow) + ) + } + + val finalScaleX = toggleScaleX * pressScale.value + val finalScaleY = pressScale.value + + // --- 关键修正开始 --- + // 只有当提供了 onCheckedChange 回调时,才应用 toggleable。 + // 否则,它只是一个单纯展示状态的 Box,点击事件会自然穿透给父级 (SuperSwitch)。 + val toggleableModifier = if (onCheckedChange != null) { + Modifier.toggleable( + value = checked, + onValueChange = onCheckedChange, + enabled = enabled, + role = Role.Switch, + interactionSource = interactionSource, + indication = null + ) + } else { + Modifier + } + // --- 关键修正结束 --- + + Box( + modifier = modifier + .then(toggleableModifier) // 应用条件修饰符 + .wrapContentSize(Alignment.Center) + .requiredSize(trackWidth, trackHeight) + .clip(RoundedCornerShape(percent = 50)) + .background(trackColor) + .padding(thumbMargin) + ) { + Box( + modifier = Modifier + .align(Alignment.CenterStart) + .offset(x = thumbOffset) + .requiredSize(thumbSize) + .graphicsLayer { + scaleX = finalScaleX + scaleY = finalScaleY + shadowElevation = if (isPressed) 1.dp.toPx() else 2.dp.toPx() + shape = CircleShape + clip = true + } + .background(colors.thumbColor(enabled)) + ) + } +} + +object SwitchDefaults { + private val ColorPrimary = Color(0xFFFF006AFF) + private val TrackUncheckedLight = Color(0xFFE5E5E5) + private val TrackUncheckedDark = Color(0xFF757575) + private val TrackDisabledLight = Color(0xFFF2F2F2) + private val TrackDisabledDark = Color(0x80757575) + private val TrackCheckedDisabledAlpha = 0.3f + private val ThumbNormal = Color(0xFFFFFFFF) + private val ThumbDisabled = Color(0x8AFFFFFF) + + @Composable + fun colors( + checkedTrackColor: Color = ColorPrimary, + uncheckedTrackColor: Color = Color.Unspecified, + thumbColor: Color = ThumbNormal, + disabledThumbColor: Color = ThumbDisabled + ): SwitchColors { + val colorModeState = LocalColorMode.current.value + val isDark = if (colorModeState == 2) true else isSystemInDarkTheme() + + val actualUncheckedTrackColor = if (uncheckedTrackColor != Color.Unspecified) { + uncheckedTrackColor + } else { + if (isDark) TrackUncheckedDark else TrackUncheckedLight + } + + val disabledTrackBase = if (isDark) TrackDisabledDark else TrackDisabledLight + + return SwitchColors( + checkedTrackColor = checkedTrackColor, + uncheckedTrackColor = actualUncheckedTrackColor, + thumbColor = thumbColor, + disabledThumbColor = disabledThumbColor, + disabledCheckedTrackColor = checkedTrackColor.copy(alpha = TrackCheckedDisabledAlpha), + disabledUncheckedTrackColor = disabledTrackBase + ) + } +} + +@Immutable +class SwitchColors( + private val checkedTrackColor: Color, + private val uncheckedTrackColor: Color, + private val disabledCheckedTrackColor: Color, + private val disabledUncheckedTrackColor: Color, + private val thumbColor: Color, + private val disabledThumbColor: Color +) { + @Stable + fun trackColor(enabled: Boolean, checked: Boolean): Color { + return if (!enabled) { + if (checked) disabledCheckedTrackColor else disabledUncheckedTrackColor + } else { + if (checked) checkedTrackColor else uncheckedTrackColor + } + } + + @Stable + fun thumbColor(enabled: Boolean): Color { + return if (enabled) thumbColor else disabledThumbColor + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/LiquidButton.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/LiquidButton.kt new file mode 100644 index 00000000..cb9b84eb --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/LiquidButton.kt @@ -0,0 +1,122 @@ +package com.suqi8.oshin.ui.activity.components + +import androidx.compose.foundation.LocalIndication +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.isSpecified +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastCoerceAtMost +import androidx.compose.ui.util.lerp +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.effects.blur +import com.kyant.backdrop.effects.lens +import com.kyant.backdrop.effects.vibrancy +import com.kyant.capsule.ContinuousCapsule +import com.suqi8.oshin.utils.InteractiveHighlight +import kotlin.math.abs +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.sin +import kotlin.math.tanh + +@Composable +fun LiquidButton( + onClick: () -> Unit, + backdrop: Backdrop, + modifier: Modifier = Modifier, + isInteractive: Boolean = true, + tint: Color = Color.Unspecified, + surfaceColor: Color = Color.Unspecified, + content: @Composable RowScope.() -> Unit +) { + val animationScope = rememberCoroutineScope() + + val interactiveHighlight = remember(animationScope) { + InteractiveHighlight( + animationScope = animationScope + ) + } + + Row( + modifier + .drawBackdrop( + backdrop = backdrop, + shape = { ContinuousCapsule }, + effects = { + vibrancy() + blur(2f.dp.toPx()) + lens(12f.dp.toPx(), 24f.dp.toPx()) + }, + layerBlock = if (isInteractive) { + { + val width = size.width + val height = size.height + + val progress = interactiveHighlight.pressProgress + val scale = lerp(1f, 1f + 4f.dp.toPx() / size.height, progress) + + val maxOffset = size.minDimension + val initialDerivative = 0.05f + val offset = interactiveHighlight.offset + translationX = maxOffset * tanh(initialDerivative * offset.x / maxOffset) + translationY = maxOffset * tanh(initialDerivative * offset.y / maxOffset) + + val maxDragScale = 4f.dp.toPx() / size.height + val offsetAngle = atan2(offset.y, offset.x) + scaleX = + scale + + maxDragScale * abs(cos(offsetAngle) * offset.x / size.maxDimension) * + (width / height).fastCoerceAtMost(1f) + scaleY = + scale + + maxDragScale * abs(sin(offsetAngle) * offset.y / size.maxDimension) * + (height / width).fastCoerceAtMost(1f) + } + } else { + null + }, + onDrawSurface = { + if (tint.isSpecified) { + drawRect(tint, blendMode = BlendMode.Hue) + drawRect(tint.copy(alpha = 0.75f)) + } + if (surfaceColor.isSpecified) { + drawRect(surfaceColor) + } + } + ) + .clickable( + interactionSource = null, + indication = if (isInteractive) null else LocalIndication.current, + role = Role.Button, + onClick = onClick + ) + .then( + if (isInteractive) { + Modifier + .then(interactiveHighlight.modifier) + .then(interactiveHighlight.gestureModifier) + } else { + Modifier + } + ) + .height(48f.dp) + .padding(horizontal = 4f.dp), + horizontalArrangement = Arrangement.spacedBy(8f.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically, + content = content + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/WantFind.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/WantFind.kt new file mode 100644 index 00000000..a3346816 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/WantFind.kt @@ -0,0 +1,47 @@ +package com.suqi8.oshin.ui.activity.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import com.suqi8.oshin.R +import com.suqi8.oshin.models.RelatedLinks +import top.yukonga.miuix.kmp.basic.Surface +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.theme.MiuixTheme + +data class WantFind( + val title: String, + val category: String +) +@Composable +fun wantFind( + links: List, + navController: NavController +) { + Card(colors = CardDefaults.defaultColors(MiuixTheme.colorScheme.surfaceContainerHigh.copy(alpha = 0.75f)), + modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp, horizontal = 12.dp)) { + Surface(color = Color.Transparent) { + Column(modifier = Modifier.padding(horizontal = 18.dp, vertical = 18.dp)) { + Text(stringResource(R.string.prompt_search_other_settings), fontSize = 16.sp, color = MiuixTheme.colorScheme.onSurfaceContainerHigh) + Spacer(modifier = Modifier.height(6.dp)) + links.forEach { link -> + Text(stringResource(link.titleRes), color = MiuixTheme.colorScheme.primary, modifier = Modifier.padding(top = 6.dp, bottom = 6.dp) + .clickable { + navController.navigate("feature/${link.route}") + },fontWeight = FontWeight.Medium) + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/addline.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/addline.kt new file mode 100644 index 00000000..acc5954a --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/addline.kt @@ -0,0 +1,21 @@ +package com.suqi8.oshin.ui.activity.components + +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.highcapable.yukihookapi.hook.factory.prefs +import top.yukonga.miuix.kmp.basic.HorizontalDivider +import top.yukonga.miuix.kmp.theme.MiuixTheme + +@Composable +fun addline() { + val context = LocalContext.current + if (context.prefs("settings").getBoolean("addline", true)) + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp), + thickness = 0.5.dp, + color = MiuixTheme.colorScheme.dividerLine + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/apprestart/AppRestart.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/apprestart/AppRestart.kt new file mode 100644 index 00000000..f172c065 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/apprestart/AppRestart.kt @@ -0,0 +1,337 @@ +package com.suqi8.oshin.ui.activity.components.apprestart + +import android.annotation.SuppressLint +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.text.BasicText +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.effects.blur +import com.kyant.backdrop.effects.colorControls +import com.kyant.backdrop.effects.lens +import com.kyant.backdrop.highlight.Highlight +import com.kyant.capsule.ContinuousCapsule +import com.kyant.capsule.ContinuousRoundedRectangle +import com.suqi8.oshin.R +import com.suqi8.oshin.ui.activity.components.Card +import com.suqi8.oshin.ui.activity.components.CardDefaults +import com.suqi8.oshin.ui.mainscreen.module.AppUiInfo +import com.suqi8.oshin.utils.drawColoredShadow +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.theme.MiuixTheme + +@Composable +fun AppRestartScreen( + appList: List, + showresetAppDialog: MutableState, + backdrop: Backdrop, + viewModel: AppRestartViewModel = hiltViewModel() +) { + val defaultColor = MiuixTheme.colorScheme.primary + + // 2. 在 LaunchedEffect 中触发 ViewModel 加载数据 + LaunchedEffect(appList, defaultColor) { + viewModel.loadAppsInfo(appList, defaultColor) + } + + ConfirmationDialog( + appPackageList = appList, // 传递原始列表 + appInfoMap = viewModel.appInfoCache, // 传递从 VM 拿到的缓存 + defaultColor = defaultColor, + onConfirm = { + // 3. 将确认事件委托给 ViewModel + viewModel.restartApps(appList) + showresetAppDialog.value = false + }, + show = showresetAppDialog, + onDismiss = { + showresetAppDialog.value = false + }, + backdrop = backdrop + ) +} + +@SuppressLint("UnrememberedMutableState") +@Composable +fun ConfirmationDialog( + appPackageList: List, + appInfoMap: Map, + defaultColor: Color, + show: MutableState, + onConfirm: () -> Unit, + onDismiss: () -> Unit, + backdrop: Backdrop +) { + val isLightTheme = !isSystemInDarkTheme() + val contentColor = if (isLightTheme) Color.Black else Color.White + val accentColor = + if (isLightTheme) Color(0xFF0088FF) + else Color(0xFF0091FF) + val containerColor = + if (isLightTheme) Color(0xFFFAFAFA).copy(0.6f) + else Color(0xFF121212).copy(0.4f) + val dimColor = + if (isLightTheme) Color(0xFF29293A).copy(0.23f) + else Color(0xFF121212).copy(0.56f) + + Box( + modifier = Modifier + .fillMaxSize() + .background(dimColor) // 背景变暗(不会影响弹窗) + .clickable( + onClick = { onDismiss() }, + indication = null, + interactionSource = remember { MutableInteractionSource() } + ), + contentAlignment = Alignment.Center + ) { + Column( + Modifier + .padding(40f.dp) + .drawBackdrop( + backdrop = backdrop, + shape = { ContinuousRoundedRectangle(48f.dp) }, + highlight = { Highlight.Plain }, + effects = { + colorControls( + brightness = if (isLightTheme) 0.2f else 0f, + saturation = 1.5f + ) + blur(if (isLightTheme) 16f.dp.toPx() else 8f.dp.toPx()) + lens(24f.dp.toPx(), 48f.dp.toPx(), true) + }, + onDrawSurface = { drawRect(containerColor) } + ) + .fillMaxWidth() + ) { + BasicText( + stringResource(R.string.Researt_app), + Modifier.padding(28f.dp, 24f.dp, 28f.dp, 12f.dp), + style = TextStyle(contentColor, 24f.sp, FontWeight.Medium) + ) + + BasicText( + stringResource(R.string.confirm_restart_applications), + Modifier + .then( + if (isLightTheme) { + // plus darker + Modifier + } else { + // plus lighter + Modifier.graphicsLayer(blendMode = BlendMode.Plus) + } + ) + .padding(24f.dp, 12f.dp, 24f.dp, 12f.dp), + style = TextStyle(contentColor.copy(0.68f), 15f.sp), + maxLines = 5 + ) + + LazyColumn( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(0.dp) + ) { + // 4. LazyColumn 现在从 appInfoMap 中读取数据 + items(appPackageList, key = { it }) { pkg -> + val appInfo = appInfoMap[pkg] // 从 VM 的 Map 中获取状态 + ResetAppList( + appInfo = appInfo, + packageName = pkg, + defaultColor = defaultColor + ) + if (appPackageList.indexOf(pkg) < appPackageList.lastIndex) { + // 假设 addline() 是一个 @Composable 函数 + // addline() + } + } + } + + Row( + Modifier + .padding(24f.dp, 12f.dp, 24f.dp, 24f.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16f.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Row( + Modifier + .clip(ContinuousCapsule) + .background(containerColor.copy(0.2f)) + .clickable { onDismiss() } + .height(48f.dp) + .weight(1f) + .padding(horizontal = 16f.dp), + horizontalArrangement = Arrangement.spacedBy(4f.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically + ) { + BasicText( + stringResource(R.string.cancel), + style = TextStyle(contentColor, 16f.sp) + ) + } + Row( + Modifier + .clip(ContinuousCapsule) + .background(accentColor) + .clickable { onConfirm() } + .height(48f.dp) + .weight(1f) + .padding(horizontal = 16f.dp), + horizontalArrangement = Arrangement.spacedBy(4f.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically + ) { + BasicText( + stringResource(R.string.ok), + style = TextStyle(Color.White, 16f.sp) + ) + } + } + } + } + + // --- 保留 SuperDialog 注释 --- + /*SuperDialog( + title = stringResource(R.string.Researt_app), + show = show, + onDismissRequest = { + onDismiss() + }, + enableWindowDim = false, + summary = stringResource(R.string.confirm_restart_applications), + backgroundColor = Color.Transparent, + modifier = Modifier.liquidGlass( + liquidGlassProviderState, + dialogGlassStyle // 应用提取的样式 + ) + ) { + LazyColumn { + item { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 12.dp) + .liquidGlass( + liquidGlassProviderState, + cardAndCancelButtonStyle // 应用提取的样式 + ), + colors = CardDefaults.defaultColors(Color.Transparent) + ) { + appPackage.forEachIndexed { index, it -> + ResetAppList(it) + if (index < appPackage.size - 1) { + addline() + } + } + } + } + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + TextButton( + modifier = Modifier + .weight(1f) + .liquidGlass( + liquidGlassProviderState, + cardAndCancelButtonStyle // 应用提取的样式 + ), + colors = ButtonDefaults.textButtonColors(color = Color.Transparent), + text = stringResource(R.string.cancel), + onClick = { + onDismiss() + } + ) + Spacer(Modifier.width(12.dp)) + TextButton( + modifier = Modifier + .weight(1f) + .liquidGlass( + liquidGlassProviderState, + confirmButtonStyle // 应用提取的样式 + ), + text = stringResource(R.string.ok), + colors = ButtonDefaults.textButtonColors(color = Color.Transparent), + onClick = { + onConfirm() + } + ) + } + }*/ + // --- 注释结束 --- +} + +@Composable +fun ResetAppList( + appInfo: AppUiInfo?, // 接收来自 ViewModel 的状态 + packageName: String, // 仍然需要包名作为兜底 + defaultColor: Color // 接收默认颜色用于比较 +) { + if (appInfo != null) { + val isLoadingColor = appInfo.dominantColor == defaultColor // 检查是否仍在等待主色 + + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically + ) { + Card( + colors = CardDefaults.defaultColors(appInfo.dominantColor), + modifier = Modifier + .padding(top = 16.dp, bottom = 16.dp) + .drawColoredShadow( + color = appInfo.dominantColor, + alpha = if (isLoadingColor) 0f else 1f, // 颜色加载完成前不显示阴影 + borderRadius = 13.dp, + shadowRadius = 7.dp, + roundedRect = false + ) + ) { + Image( + bitmap = appInfo.icon, + contentDescription = "App Icon", + modifier = Modifier.size(45.dp) + ) + } + Column(modifier = Modifier.padding(start = 16.dp)) { + Text(text = appInfo.name) + Text( + text = packageName, + fontSize = MiuixTheme.textStyles.subtitle.fontSize, + fontWeight = FontWeight.Medium, + color = MiuixTheme.colorScheme.onBackgroundVariant + ) + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/apprestart/AppRestartViewModel.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/apprestart/AppRestartViewModel.kt new file mode 100644 index 00000000..86673999 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/apprestart/AppRestartViewModel.kt @@ -0,0 +1,103 @@ +package com.suqi8.oshin.ui.activity.components.apprestart + +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.ui.graphics.Color +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.highcapable.yukihookapi.YukiHookAPI +import com.suqi8.oshin.data.repository.AppInfoProvider +import com.suqi8.oshin.ui.mainscreen.module.AppUiInfo +import com.suqi8.oshin.utils.getAutoColor +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.util.concurrent.ConcurrentHashMap +import javax.inject.Inject + +@HiltViewModel +class AppRestartViewModel @Inject constructor( + private val appInfoProvider: AppInfoProvider +) : ViewModel() { + + // 状态缓存:PkgName -> AppUiInfo + // 这个缓存是此 ViewModel 实例私有的 + private val _appInfoCache = mutableStateMapOf() + val appInfoCache: Map = _appInfoCache + + // 用于跟踪哪些应用未安装 + private val _notInstalledApps = ConcurrentHashMap.newKeySet() + + /** + * 加载此对话框所需的所有应用信息 + */ + fun loadAppsInfo( + packageNames: List, + defaultColor: Color + ) { + viewModelScope.launch { + packageNames.forEach { pkg -> + // 只有在没有被加载过,且未被标记为 "未安装" 时才加载 + if (!_appInfoCache.containsKey(pkg) && !_notInstalledApps.contains(pkg)) { + loadAppInfoWithColor(pkg, defaultColor) + } + } + } + } + + /** + * 重启应用的核心逻辑 + */ + fun restartApps(packageNames: List) { + viewModelScope.launch(Dispatchers.IO) { + packageNames.forEach { packageName -> + try { + if (packageName == "android") { + val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "reboot")) + process.waitFor() + } else { + val command = "pkill -f $packageName" + val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command)) + process.waitFor() + } + } catch (e: Exception) { + e.printStackTrace() + // 可以在这里向 UI 暴露一个错误状态 + } + } + } + } + + /** + * 内部函数:加载单个应用的信息(图标、名称、颜色) + */ + private suspend fun loadAppInfoWithColor(packageName: String, defaultColor: Color) { + // 1. 从 Repository 获取基础信息 + val appInfo = appInfoProvider.getInfo(packageName) + + if (appInfo == null) { + // 2a. 应用未找到 + _notInstalledApps.add(packageName) + // 缓存一个 null 来表示 "未安装" + _appInfoCache[packageName] = null + } else { + // 2b. 应用已找到,立即缓存(带默认颜色) + val partialInfo = AppUiInfo(appInfo.name, appInfo.icon, defaultColor) + _appInfoCache[packageName] = partialInfo + + // 3. 异步提取主色 + val newColor = withContext(Dispatchers.Default) { + try { + if (!YukiHookAPI.Status.isModuleActive) defaultColor else getAutoColor(appInfo.icon) + } catch (e: Exception) { + defaultColor + } + } + + // 4. 更新缓存,提供最终颜色 + if (newColor != defaultColor) { + _appInfoCache[packageName] = AppUiInfo(appInfo.name, appInfo.icon, newColor) + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/appselection/AppSelectionViewModel.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/appselection/AppSelectionViewModel.kt new file mode 100644 index 00000000..89270e48 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/appselection/AppSelectionViewModel.kt @@ -0,0 +1,118 @@ +package com.suqi8.oshin.ui.activity.components.appselection + +import android.content.Context +import android.content.pm.ApplicationInfo +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.palette.graphics.Palette +import com.highcapable.yukihookapi.YukiHookAPI +import com.suqi8.oshin.data.repository.AppInfoProvider +import com.suqi8.oshin.ui.mainscreen.module.AppUiInfo +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject + +/** + * 封装应用选择对话框的所有状态和逻辑 + */ +@HiltViewModel +class AppSelectionViewModel @Inject constructor( + @ApplicationContext private val context: Context, + private val appInfoProvider: AppInfoProvider // 注入我们的单一数据源 +) : ViewModel() { + + // 状态 1: 完整的、未过滤的应用列表 (名称 + 包名) + private val _allApps = MutableStateFlow>>(emptyList()) + val allApps = _allApps.asStateFlow() + + // 状态 2: 搜索框的文本 + val searchText = mutableStateOf("") + + // 状态 3: UI 缓存 (图标 + 颜色) + // 这是 ViewModel 私有的缓存,随 ViewModel 销毁 + private val _appUiCache = mutableStateMapOf() + + /** + * 触发加载所有非系统应用 + */ + fun loadAllApps() { + // 只有在列表为空时才加载 + if (_allApps.value.isNotEmpty()) return + + viewModelScope.launch(Dispatchers.IO) { + val pm = context.packageManager + val allAppsInfo = pm.getInstalledApplications(0) + + // 关键:一次性构建整个列表 + val appList = allAppsInfo + .filter { (it.flags and ApplicationInfo.FLAG_SYSTEM) == 0 } // 仅非系统应用 + .map { app -> + val appName = pm.getApplicationLabel(app).toString() + val pkgName = app.packageName + Pair(appName, pkgName) + } + .sortedBy { it.first.lowercase() } // 按名称排序 + + // 一次性更新状态 + _allApps.value = appList + } + } + + /** + * 获取单个应用的详细 UI 信息 (图标 + 颜色) + * 这是一个 Flow,它会首先发射缓存/默认值,然后在 I/O 完成后发射最终值。 + */ + fun getAppUiInfo(packageName: String, defaultColor: Color): Flow = flow { + // 1. 检查缓存 + if (_appUiCache.containsKey(packageName)) { + emit(_appUiCache[packageName]) + return@flow + } + + // 2. 从 AppInfoProvider 获取基础信息 + val appInfo = appInfoProvider.getInfo(packageName) + if (appInfo == null) { + _appUiCache[packageName] = null + emit(null) // 未安装 + return@flow + } + + // 3. 发射 "加载中" 状态 (有图标,但用默认色) + val partialInfo = AppUiInfo(appInfo.name, appInfo.icon, defaultColor) + _appUiCache[packageName] = partialInfo + emit(partialInfo) + + // 4. 异步提取主色 + val newColor = withContext(Dispatchers.Default) { + try { + // 仅在模块激活时提取颜色 + if (YukiHookAPI.Status.isModuleActive) { + val bitmap = appInfo.icon.asAndroidBitmap() + Palette.from(bitmap).generate().dominantSwatch?.rgb?.let { Color(it) } ?: defaultColor + } else { + defaultColor // 否则使用默认色 + } + } catch (e: Exception) { + defaultColor // 提取失败也用默认色 + } + } + + // 5. 发射 "已完成" 状态 (包含最终颜色) + if (newColor != defaultColor) { + val finalInfo = partialInfo.copy(dominantColor = newColor) + _appUiCache[packageName] = finalInfo + emit(finalInfo) + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/components/appselection/FunAppSele.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/components/appselection/FunAppSele.kt new file mode 100644 index 00000000..35ba745a --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/components/appselection/FunAppSele.kt @@ -0,0 +1,224 @@ +package com.suqi8.oshin.ui.activity.components.appselection + +import android.annotation.SuppressLint +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import com.hjq.permissions.permission.PermissionLists +import com.suqi8.oshin.R +import com.suqi8.oshin.ui.activity.components.Card +import com.suqi8.oshin.ui.activity.components.CardDefaults +import com.suqi8.oshin.ui.activity.components.FunArrow +import com.suqi8.oshin.utils.drawColoredShadow +import com.suqi8.oshin.utils.requestPermissions +import top.yukonga.miuix.kmp.basic.TextField +import top.yukonga.miuix.kmp.extra.CheckboxLocation +import top.yukonga.miuix.kmp.extra.SuperCheckbox +import top.yukonga.miuix.kmp.extra.SuperDialog +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.utils.overScrollVertical + + +@SuppressLint("ConfigurationScreenWidthHeight") +@Composable +fun FunAppSele( + title: String, + summary: String?, + selectedApps: Set, + onSelectionChanged: (Set) -> Unit, + viewModel: AppSelectionViewModel = hiltViewModel() +) { + val context = LocalContext.current + val showDialog = remember { mutableStateOf(false) } + + val loadAppsAndShow = { + // 2. 委托 ViewModel 加载 + viewModel.loadAllApps() + showDialog.value = true + } + + val summaryText = summary ?: "${stringResource(R.string.selected_app)}: ${selectedApps.size}" + + FunArrow( + title = title, + summary = summaryText, + onClick = { + requestPermissions( + context, + PermissionLists.getGetInstalledAppsPermission(), + onGranted = { + loadAppsAndShow() + } + ) + } + ) + + val configuration = LocalConfiguration.current + val density = LocalDensity.current + val maxHeightDp = with(density) { (configuration.screenHeightDp.dp.toPx() * 0.85f).toDp() } + + // 3. Dialog 现在依赖 ViewModel 的状态 + if (showDialog.value) { + SuperDialog( + show = showDialog, + onDismissRequest = { showDialog.value = false } + ) { + // 将 ViewModel 传递给 Dialog 内容 + AppSelectionDialogContent( + viewModel = viewModel, + selectedApps = selectedApps, + onSelectionChanged = onSelectionChanged, + maxHeightDp = maxHeightDp + ) + } + } +} + +@Composable +private fun AppSelectionDialogContent( + viewModel: AppSelectionViewModel, + selectedApps: Set, + onSelectionChanged: (Set) -> Unit, + maxHeightDp: Dp +) { + // 4. 从 ViewModel 收集状态 + val allApps by viewModel.allApps.collectAsState() + val searchText by viewModel.searchText + + Box( + modifier = Modifier + .fillMaxWidth() + .heightIn(max = maxHeightDp) + .animateContentSize() + ) { + Column { + // 5. 搜索框现在由 ViewModel 控制 + TextField( + value = searchText, + onValueChange = { viewModel.searchText.value = it }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp) + ) + + // 6. 过滤和排序逻辑 (这可以在 Composable 中,因为它现在是纯计算) + val filteredAndSortedList by remember(searchText, allApps, selectedApps) { + derivedStateOf { + allApps + .filter { (appName, pkgName) -> + appName.contains(searchText, ignoreCase = true) || + pkgName.contains(searchText, ignoreCase = true) + } + .sortedByDescending { (_, pkgName) -> + selectedApps.contains(pkgName) + } + } + } + + LazyColumn( + modifier = Modifier + .animateContentSize() + .overScrollVertical() + ) { + items(filteredAndSortedList, key = { it.second }) { (appName, pkgName) -> + val isChecked = selectedApps.contains(pkgName) + + // 7. 将 ViewModel 传递给列表项 + ResetAppList( + viewModel = viewModel, + packageName = pkgName, + appName = appName, + isChecked = isChecked, + onCheckedChange = { isSelected -> + val newSet = if (isSelected) { + selectedApps + pkgName + } else { + selectedApps - pkgName + } + onSelectionChanged(newSet) + } + ) + } + } + } + } +} + +@Composable +private fun ResetAppList( + viewModel: AppSelectionViewModel, // 接收 ViewModel + packageName: String, + appName: String, + isChecked: Boolean, + onCheckedChange: (Boolean) -> Unit +) { + val defaultColor = MiuixTheme.colorScheme.primary + + val appUiInfo by viewModel.getAppUiInfo(packageName, defaultColor).collectAsState(null) + + val info = appUiInfo // 可能是 null (加载中) 或 AppUiInfo + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Card( + colors = CardDefaults.defaultColors( + color = info?.dominantColor + ?: MiuixTheme.colorScheme.onBackground.copy(alpha = 0.1f) + ), + modifier = Modifier + .padding(start = 16.dp, top = 16.dp, bottom = 16.dp) + .drawColoredShadow( + color = info?.dominantColor ?: Color.Transparent, + alpha = 1f, + borderRadius = 13.dp, + shadowRadius = 7.dp, + ) + ) { + if (info != null) { + Image( + bitmap = info.icon, + contentDescription = appName, + modifier = Modifier.size(45.dp) + ) + } else { + // 加载占位符 + Box(modifier = Modifier.size(45.dp)) + } + } + + // 复选框 + SuperCheckbox( + title = appName, + checked = isChecked, + onCheckedChange = onCheckedChange, + summary = packageName, + checkboxLocation = CheckboxLocation.Right + ) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/feature/featureScreen.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/feature/featureScreen.kt new file mode 100644 index 00000000..ccff50a3 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/feature/featureScreen.kt @@ -0,0 +1,457 @@ +package com.suqi8.oshin.ui.activity.feature + +import android.content.Intent +import android.content.pm.PackageManager +import androidx.compose.animation.Animatable +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringArrayResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.net.toUri +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.navigation.NavController +import com.suqi8.oshin.models.Action +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.AppSelection +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.Dropdown +import com.suqi8.oshin.models.NoEnable +import com.suqi8.oshin.models.Picture +import com.suqi8.oshin.models.PlainText +import com.suqi8.oshin.models.RelatedLinks +import com.suqi8.oshin.models.ScreenItem +import com.suqi8.oshin.models.Slider +import com.suqi8.oshin.models.StringInput +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Switch +import com.suqi8.oshin.models.Title +import com.suqi8.oshin.models.TitledScreenItem +import com.suqi8.oshin.models.UrlAction +import com.suqi8.oshin.ui.activity.components.Card +import com.suqi8.oshin.ui.activity.components.CouiListItemPosition +import com.suqi8.oshin.ui.activity.components.appselection.FunAppSele +import com.suqi8.oshin.ui.activity.components.FunArrow +import com.suqi8.oshin.ui.activity.components.FunDropdown +import com.suqi8.oshin.ui.activity.components.FunNoEnable +import com.suqi8.oshin.ui.activity.components.FunPage +import com.suqi8.oshin.ui.activity.components.FunPicSele +import com.suqi8.oshin.ui.activity.components.FunSlider +import com.suqi8.oshin.ui.activity.components.FunString +import com.suqi8.oshin.ui.activity.components.FunSwitch +import com.suqi8.oshin.ui.activity.components.addline +import com.suqi8.oshin.ui.activity.components.wantFind +import kotlinx.coroutines.launch +import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior +import top.yukonga.miuix.kmp.basic.SmallTitle +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.basic.rememberTopAppBarState +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.utils.overScrollVertical +import top.yukonga.miuix.kmp.utils.scrollEndHaptic + +/** + * 通用的功能页面渲染器。 + * 它会根据 ViewModel 提供的 PageDefinition 动态构建整个页面。 + */ +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun featureScreen( + navController: NavController, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope, + viewModel: featureViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsState() + val pageDef = uiState.pageDefinition + val listState = rememberLazyListState() + val coroutineScope = rememberCoroutineScope() + val topAppBarState = MiuixScrollBehavior(rememberTopAppBarState()) + val animationKey = uiState.highlightKey ?: "item-${uiState.categoryId}" + LaunchedEffect(uiState.isLoading, uiState.highlightKey) { + if (!uiState.isLoading && uiState.highlightKey != null && pageDef != null) { + // 计算目标 item 所在的 PageItem (通常是 CardDefinition) 的索引 + val targetIndex = uiState.highlightKey?.let { key -> + pageDef.items.indexOfFirst { pageItem -> + if (pageItem is CardDefinition) { + pageItem.items.any { (it as? TitledScreenItem)?.key == key } + } else { + false // RelatedLinks 等其他类型暂不支持被高亮定位 + } + } + } ?: -1 + + if (targetIndex != -1) { + coroutineScope.launch { + // 滚动到目标卡片 + listState.animateScrollToItem(index = targetIndex) + } + } + } + } + + // 处理页面定义未找到的情况 + if (pageDef == null) { + FunPage(title = "Error", navController = navController, scrollBehavior = topAppBarState) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text(text = "Page definition not found.") + } + } + return + } + + // 渲染主页面 + FunPage( + appList = pageDef.appList, + navController = navController, + scrollBehavior = topAppBarState, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + animationKey = animationKey + ) { padding -> + val itemStates = uiState.itemStates + LazyColumn( + state = listState, + modifier = Modifier + .fillMaxSize() + .overScrollVertical() + .scrollEndHaptic() + .nestedScroll(topAppBarState.nestedScrollConnection), + contentPadding = padding + ) { + item { + with(sharedTransitionScope) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .displayCutoutPadding() + .padding(top = padding.calculateTopPadding() + 72.dp, bottom = 8.dp) + ) { + val isTransitionActive = sharedTransitionScope.isTransitionActive + + // 1. 检查是否从搜索页导航而来 + val isFromSearch = uiState.highlightKey != null + val initialFontSize = if (isFromSearch) 28f else 16f + var targetFontSize by remember { mutableStateOf(initialFontSize) } + + // 3. 仅在 *不是* 从搜索页来,并且动画 *结束* 时,才触发大小变化 + LaunchedEffect(isTransitionActive, isFromSearch) { + if (!isTransitionActive && !isFromSearch) { + // 动画结束,更新到最终样式 + targetFontSize = 28f + } + } + + val animatedFontSize by animateFloatAsState( + targetValue = targetFontSize, + label = "TitleFontSize" + ) + + Text( + text = resolveTitle(title = pageDef.title), + fontSize = animatedFontSize.sp, + fontWeight = FontWeight.Medium, + color = MiuixTheme.colorScheme.onBackground, + modifier = Modifier + .sharedElement( + sharedContentState = rememberSharedContentState(key = "title-${uiState.categoryId}"), + animatedVisibilityScope = animatedVisibilityScope + ) + ) + } + } + } + itemsIndexed(pageDef.items) { _, pageItem -> + when (pageItem) { + is CardDefinition -> { + val isVisible = viewModel.evaluateCondition(pageItem.condition, itemStates) + AnimatedVisibility(visible = isVisible) { + Column { + pageItem.titleRes?.let { + SmallTitle(text = stringResource(it)) + } + Card { + Column { + val itemCount = pageItem.items.size + pageItem.items.forEachIndexed { itemIndex, item -> + // 可见性判断在这里,针对卡片内部的每一个 item + val isVisible = viewModel.evaluateCondition( + item.condition, + itemStates + ) + AnimatedVisibility(visible = isVisible) itemAV@ { + Column { + val isHighlighted = + (item as? TitledScreenItem)?.key == uiState.highlightKey + + val position = when { + itemCount == 1 -> CouiListItemPosition.Single + itemIndex == 0 -> CouiListItemPosition.Top + itemIndex == itemCount - 1 -> CouiListItemPosition.Bottom + else -> CouiListItemPosition.Middle + } + + RenderScreenItem( + item = item, + viewModel = viewModel, + navController = navController, + isHighlighted = isHighlighted, + position = position, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + + if (itemIndex < pageItem.items.lastIndex) { + addline() + } + } + } + } + } + } + } + } + } + + is RelatedLinks -> { + // RelatedLinks 作为一个整体,有自己的显示条件 + val isVisible = + viewModel.evaluateCondition(pageItem.condition, itemStates) + AnimatedVisibility(visible = isVisible) { + wantFind( + links = pageItem.links, + navController = navController + ) + } + } + + is NoEnable -> { + val isVisible = + viewModel.evaluateCondition(pageItem.condition, itemStates) + AnimatedVisibility(visible = isVisible) { + FunNoEnable() + } + } + } + } + } + } +} + +/** + * 根据 ScreenItem 的具体类型,选择并渲染对应的 "fun" 组件。 + */ +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +private fun RenderScreenItem( + item: ScreenItem, + viewModel: featureViewModel, + navController: NavController, + isHighlighted: Boolean, + position: CouiListItemPosition, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + val itemStates by viewModel.uiState.collectAsState() + val highlightColor = remember { Animatable(Color.Transparent) } + val coroutineScope = rememberCoroutineScope() + val highlightColorPrimary = MiuixTheme.colorScheme.primary.copy(alpha = 0.25f) + + LaunchedEffect(isHighlighted) { + if (isHighlighted) { + coroutineScope.launch { + repeat(2) { // 闪两次 + // 动画亮起 + highlightColor.animateTo( + targetValue = highlightColorPrimary, + animationSpec = tween( + durationMillis = 1000, // 亮起速度 + delayMillis = 0 + ) + ) + // 动画熄灭 + highlightColor.animateTo( + targetValue = Color.Transparent, + animationSpec = tween( + durationMillis = 1000, // 渐灭速度 + delayMillis = 100 // 熄灭前停顿 + ) + ) + } + } + } + } + + Column(modifier = Modifier.background(highlightColor.value)) { + when (item) { + is Switch -> { + val checked = itemStates.itemStates[item.key] as? Boolean ?: item.defaultValue + FunSwitch( + title = resolveTitle(title = item.title), + summary = item.summary?.let { stringResource(it) }, + checked = checked, + position = position, + onCheckedChange = { newValue -> viewModel.updateState(item.key, newValue) } + ) + } + + is Slider -> { + val value = itemStates.itemStates[item.key] as? Float ?: item.defaultValue + FunSlider( + title = resolveTitle(title = item.title), + summary = item.summary?.let { stringResource(it) }, + value = value, + valueRange = item.valueRange, + onValueChange = { newValue -> viewModel.updateState(item.key, newValue) }, + unit = item.unit, + decimalPlaces = item.decimalPlaces, + position = position, + ) + } + + is Dropdown -> { + val selectedIndex = itemStates.itemStates[item.key] as? Int ?: item.defaultValue + FunDropdown( + title = resolveTitle(title = item.title), + summary = item.summary?.let { stringResource(it) }, + selectedIndex = selectedIndex, + options = stringArrayResource(id = item.optionsRes).toList(), + position = position, + onSelectedIndexChange = { newIndex -> + viewModel.updateState( + item.key, + newIndex + ) + } + ) + } + + is Action -> { + with(sharedTransitionScope) { + FunArrow( + title = resolveTitle(title = item.title), + modifier = Modifier.sharedBounds( + sharedContentState = rememberSharedContentState(key = "item-${item.route}"), + animatedVisibilityScope = animatedVisibilityScope + ), + titleModifier = Modifier.sharedElement( + sharedContentState = rememberSharedContentState(key = "title-${item.route}"), + animatedVisibilityScope = animatedVisibilityScope + ), + summary = item.summary?.let { stringResource(it) }, + position = position, + onClick = { navController.navigate("feature/${item.route}") } + ) + } + } + + is Picture -> { + // 从 itemStates 中获取当前显示的图片 + val imageBitmap = itemStates.itemStates[item.key] as? ImageBitmap + FunPicSele( + title = resolveTitle(title = item.title), + summary = item.summary?.let { stringResource(it) }, + imageBitmap = imageBitmap, + position = position, + onImageSelected = { uri -> + // 当用户选择了新图片,通知 ViewModel 处理 + viewModel.saveImageFromUri(item.key, item.targetPath, uri) + } + ) + } + + is StringInput -> { + val value = itemStates.itemStates[item.key] as? String ?: item.defaultValue + FunString( + title = resolveTitle(title = item.title), + summary = item.summary?.let { stringResource(it) }, + value = value, + position = position, + onValueChange = { newValue -> viewModel.updateState(item.key, newValue) }, + nullable = item.nullable + ) + } + + is UrlAction -> { + val context = LocalContext.current + FunArrow( + title = resolveTitle(title = item.title), + summary = item.summary?.let { stringResource(it) }, + position = position, // 确保 URL Action 也传递位置 + onClick = { + val intent = Intent(Intent.ACTION_VIEW, item.url.toUri()) + context.startActivity(intent) + } + ) + } + + is AppSelection -> { + val selectedApps = itemStates.itemStates[item.key] as? Set ?: emptySet() + FunAppSele( + title = resolveTitle(title = item.title), + summary = item.summary?.let { stringResource(it) }, + selectedApps = selectedApps, + onSelectionChanged = { newSet -> viewModel.updateState(item.key, newSet) } + ) + } + } + } +} + +/** + * 辅助 Composable,用于将灵活的 Title 模型解析为最终显示的字符串。 + */ +@Composable +private fun resolveTitle(title: Title): String { + return when (title) { + is StringResource -> stringResource(title.id) + is PlainText -> title.text + is AppName -> getAppName(title.packageName) + } +} + +/** + * 辅助 Composable,用于安全地获取应用名称。 + */ +@Composable +private fun getAppName(packageName: String): String { + val context = LocalContext.current + return try { + val pm = context.packageManager + val appInfo = pm.getApplicationInfo(packageName, 0) + pm.getApplicationLabel(appInfo).toString() + } catch (e: PackageManager.NameNotFoundException) { + packageName // 如果找不到应用,返回包名作为兜底 + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/feature/featureViewModel.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/feature/featureViewModel.kt new file mode 100644 index 00000000..4dcda4ad --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/feature/featureViewModel.kt @@ -0,0 +1,198 @@ +package com.suqi8.oshin.ui.activity.feature + +import android.content.Context +import android.graphics.BitmapFactory +import android.net.Uri +import androidx.compose.ui.graphics.asImageBitmap +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.highcapable.yukihookapi.hook.factory.prefs +import com.suqi8.oshin.features.FeatureRegistry +import com.suqi8.oshin.models.AndCondition +import com.suqi8.oshin.models.AppSelection +import com.suqi8.oshin.models.CardDefinition +import com.suqi8.oshin.models.Condition +import com.suqi8.oshin.models.Dropdown +import com.suqi8.oshin.models.Operator +import com.suqi8.oshin.models.PageDefinition +import com.suqi8.oshin.models.Picture +import com.suqi8.oshin.models.SimpleCondition +import com.suqi8.oshin.models.Slider +import com.suqi8.oshin.models.StringInput +import com.suqi8.oshin.models.Switch +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import java.io.FileOutputStream +import javax.inject.Inject + +/** + * FeatureScreen 的 UI 状态 + */ +data class featureUiState( + val isLoading: Boolean = true, + val pageDefinition: PageDefinition? = null, + val itemStates: Map = emptyMap(), + val highlightKey: String? = null, + val categoryId: String = "" +) + +@HiltViewModel +class featureViewModel @Inject constructor( + @ApplicationContext private val context: Context, + savedStateHandle: SavedStateHandle // 用于从导航参数中获取 categoryId +) : ViewModel() { + + private val _uiState = MutableStateFlow(featureUiState()) + val uiState = _uiState.asStateFlow() + + private val categoryId: String = savedStateHandle.get("categoryId")!! + private val highlightKey: String? = savedStateHandle.get("highlightKey") + private val pageDefinition: PageDefinition? = FeatureRegistry.screenMap[categoryId] + + init { + if (pageDefinition != null) { + _uiState.update { it.copy( + pageDefinition = pageDefinition, + highlightKey = highlightKey, + categoryId = this.categoryId + )} + loadInitialStates(pageDefinition) + } else { + _uiState.update { it.copy( + isLoading = false, + categoryId = this.categoryId + ) } // 页面定义未找到 + } + } + + /** + * 加载页面所有功能项的初始值 + */ + private fun loadInitialStates(pageDef: PageDefinition) { + viewModelScope.launch(Dispatchers.IO) { + val initialStates = mutableMapOf() + val prefs = context.prefs(pageDef.category) + + pageDef.items.filterIsInstance() + .flatMap { it.items } + .forEach { item -> + when (item) { + is Switch -> initialStates[item.key] = prefs.getBoolean(item.key, item.defaultValue) + is Slider -> { + // 兼容旧版 Int 类型的 Slider 值 + val value = try { + // 1. 优先尝试按新版 Float 类型读取 + prefs.getFloat(item.key, item.defaultValue) + } catch (e: ClassCastException) { + // 2. 如果失败 (类型转换异常),说明是旧版 Int 数据。 + // 则按 Int 类型读取,并手动转换为 Float。 + prefs.getInt(item.key, item.defaultValue.toInt()).toFloat() + } + initialStates[item.key] = value + } + is Dropdown -> initialStates[item.key] = prefs.getInt(item.key, item.defaultValue) + is StringInput -> initialStates[item.key] = prefs.getString(item.key, item.defaultValue) + is Picture -> { + val file = File(item.targetPath) + if (file.exists()) { + val bitmap = BitmapFactory.decodeFile(file.absolutePath) + if (bitmap != null) { + initialStates[item.key] = bitmap.asImageBitmap() + } + } + } + is AppSelection -> { + val stringSet = prefs.getString(item.key, "") + initialStates[item.key] = stringSet.split(',').filter { it.isNotEmpty() }.toSet() + } + else -> {} + } + } + + withContext(Dispatchers.Main) { + _uiState.update { it.copy(itemStates = initialStates, isLoading = false) } + } + } + } + + fun saveImageFromUri(key: String, targetPath: String, uri: Uri?) { + if (uri == null) return + + viewModelScope.launch(Dispatchers.IO) { // 在 IO 线程执行文件操作 + try { + val targetFile = File(targetPath) + targetFile.parentFile?.mkdirs() + + // 从 Uri 读取输入流,并写入到目标文件 + context.contentResolver.openInputStream(uri)?.use { input -> + FileOutputStream(targetFile).use { output -> + input.copyTo(output) + } + } + + // 复制成功后,重新加载 Bitmap 并更新 UI State + val bitmap = BitmapFactory.decodeFile(targetFile.absolutePath) + if (bitmap != null) { + _uiState.update { + it.copy(itemStates = it.itemStates + (key to bitmap.asImageBitmap())) + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + /** + * 当 UI 组件状态变化时调用此方法 + */ + fun updateState(key: String, value: Any) { + // 更新内存中的状态以立即响应 UI + _uiState.update { + it.copy(itemStates = it.itemStates + (key to value)) + } + // 在后台线程将新值写入 SharedPreferences + viewModelScope.launch(Dispatchers.IO) { + pageDefinition?.let { pageDef -> + context.prefs(pageDef.category).edit { + when (value) { + is Boolean -> putBoolean(key, value) + is Float -> putFloat(key, value) + is Int -> putInt(key, value) + is String -> putString(key, value) + is Set<*> -> putString(key, (value as Set).joinToString(",")) + } + } + } + } + } + + /** + * 计算一个项的显示条件是否满足 + */ + fun evaluateCondition(condition: Condition?, allCurrentStates: Map): Boolean { + return when (condition) { + null -> true // 没有条件,始终显示 + is SimpleCondition -> { + val dependencyValue = allCurrentStates[condition.dependencyKey] + when (condition.operator) { + Operator.EQUALS -> dependencyValue == condition.requiredValue + Operator.NOT_EQUALS -> dependencyValue != condition.requiredValue + } + } + is AndCondition -> { + // 对于 AND 条件,递归检查其包含的所有子条件是否都为 true + condition.conditions.all { evaluateCondition(it, allCurrentStates) } + } + // 未来可以添加 is OrCondition -> { ... } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/StatusBarLayout.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/StatusBarLayout.kt new file mode 100644 index 00000000..8d3245b2 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/StatusBarLayout.kt @@ -0,0 +1,380 @@ +package com.suqi8.oshin.ui.activity.func.StatusBarLayout + +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.times +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.navigation.NavController +import com.suqi8.oshin.R +import com.suqi8.oshin.ui.activity.components.FunPage +import com.suqi8.oshin.ui.activity.components.SuperArrow +import com.suqi8.oshin.ui.mainscreen.home.ModernSectionTitle +import top.yukonga.miuix.kmp.basic.Icon +import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.basic.rememberTopAppBarState +import top.yukonga.miuix.kmp.extra.SuperDialog +import top.yukonga.miuix.kmp.icon.MiuixIcons +import top.yukonga.miuix.kmp.icon.icons.basic.ArrowRight +import top.yukonga.miuix.kmp.icon.icons.useful.Play +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.utils.overScrollVertical +import top.yukonga.miuix.kmp.utils.scrollEndHaptic + +private val ViewNode.expandableId: String + get() = id.ifBlank { "group_${this.hashCode()}" } + +/** + * 视图控制器屏幕的主 Composable 函数。 + * @param navController 用于导航。 + * @param viewModel 关联的 ViewModel 实例。 + */ +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun StatusBarLayout( + navController: NavController, + viewModel: StatusBarLayoutViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsState() + val scrollBehavior = MiuixScrollBehavior(rememberTopAppBarState()) + + // 控制配置对话框显示的状态 + val showDialog = remember { mutableStateOf(false) } + // 存储当前被选中的节点,用于对话框 + var selectedNode by remember { mutableStateOf(null) } + + // 当对话框关闭时,发送指令取消视图高亮 + LaunchedEffect(showDialog.value) { + if (!showDialog.value) { + viewModel.clearHighlight() + } + } + + FunPage( + appList = listOf("com.android.systemui"), + navController = navController, + scrollBehavior = scrollBehavior + ) { paddingValues -> + when { + uiState.isLoading -> Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text(text = "loading") + } + uiState.viewTree == null -> Box( + modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp), + contentAlignment = Alignment.Center + ) { + Text(text = stringResource(R.string.view_controller_load_failed)) + } + else -> { + // 使用标准的 LazyListState,不再需要排序功能 + val listState = rememberLazyListState() + + LazyColumn( + state = listState, + modifier = Modifier + .fillMaxSize() + .overScrollVertical() + .scrollEndHaptic() + .nestedScroll(scrollBehavior.nestedScrollConnection), + contentPadding = paddingValues + ) { + item { + ModernSectionTitle( + title = stringResource(id = R.string.status_bar_layout), + modifier = Modifier + .displayCutoutPadding() + .padding(top = 72.dp, bottom = 8.dp) + ) + } + // 列表项直接绑定到 ViewModel 中的 visibleNodes + items(viewModel.visibleNodes, key = { it.uniqueKey }) { itemInfo -> + // 列表项的根布局,现在是一个简单的 Column + Column( + modifier = Modifier + .fillMaxWidth() + .background(MiuixTheme.colorScheme.background) + ) { + NodeItem( + node = itemInfo.node, + level = itemInfo.level, + isExpanded = uiState.expandedNodes.contains(itemInfo.node.expandableId), + onToggle = { viewModel.toggleNode(itemInfo.node.expandableId) }, + onItemClick = { clickedNode -> + if (clickedNode.id.isNotBlank()) { + selectedNode = clickedNode + showDialog.value = true + viewModel.highlightView(clickedNode.hashCodeValue) + } + } + ) + } + } + } + } + } + } + + // 当 selectedNode 不为空时,渲染配置对话框 + selectedNode?.let { node -> + ViewConfigDialog( + show = showDialog.value, + node = node, + currentConfigMode = uiState.configs[node.id] ?: ViewConfig.MODE_NORMAL, + onDismiss = { showDialog.value = false }, + onConfigChange = { mode -> + viewModel.updateConfig(node.id, mode) + showDialog.value = false + } + ) + } +} + +/** + * 递归地渲染视图树的子节点。 + * @param nodes 要渲染的子节点列表。 + * @param level 当前的层级深度。 + * @param uiState 全局UI状态,用于获取展开信息。 + * @param onNodeClick 节点点击事件的回调。 + * @param onToggle 节点展开/折叠事件的回调。 + */ +@Composable +private fun RenderSubTree( + nodes: List, + level: Int, + uiState: StatusBarLayoutUiState, + onNodeClick: (ViewNode) -> Unit, + onToggle: (String) -> Unit +) { + nodes.forEach { node -> + NodeItem( + node = node, + level = level, + isExpanded = uiState.expandedNodes.contains(node.expandableId), + onToggle = { onToggle(node.expandableId) }, + onItemClick = onNodeClick + ) + // 如果子节点也是展开状态,则继续递归渲染其子树 + if (uiState.expandedNodes.contains(node.expandableId)) { + RenderSubTree( + nodes = node.children, + level = level + 1, + uiState = uiState, + onNodeClick = onNodeClick, + onToggle = onToggle + ) + } + } +} + +/** + * 列表中的单个节点项 Composable。 + * @param node 要渲染的节点数据。 + * @param level 节点的层级,用于计算缩进。 + * @param isExpanded 节点当前是否为展开状态。 + * @param onToggle 点击展开/折叠箭头时的回调。 + * @param onItemClick 点击整个节点项时的回调。 + */ +@Composable +private fun NodeItem( + node: ViewNode, + level: Int, + isExpanded: Boolean, + onToggle: () -> Unit, + onItemClick: (ViewNode) -> Unit +) { + if (node.id.isBlank() && node.children.isEmpty() && node.type.isBlank()) return + + val simpleClassName = node.type.substringAfterLast('.').ifBlank { "ViewGroup" } + val simpleId = node.id.ifBlank { "No ID" } + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onItemClick(node) } + .padding(vertical = 8.dp, horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // 根据层级显示缩进 + HierarchyIndicator(level = level) + + // 展开/折叠箭头的旋转动画 + val rotationAngle by animateFloatAsState( + targetValue = if (isExpanded) 90f else 0f, + animationSpec = tween(durationMillis = 200), + label = "arrowRotation" + ) + + // 如果节点有子节点,则显示箭头 + if (node.children.isNotEmpty()) { + Icon( + imageVector = MiuixIcons.Basic.ArrowRight, + contentDescription = "Toggle", + modifier = Modifier + .size(24.dp) + .rotate(rotationAngle) + .clip(RoundedCornerShape(12.dp)) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { onToggle() } + ) + } else { + // 如果没有子节点,用空白占位以保持对齐 + Spacer(modifier = Modifier.width(24.dp)) + } + + Spacer(modifier = Modifier.width(8.dp)) + + // 显示节点信息(类型、ID、尺寸) + Column(modifier = Modifier.weight(1f)) { + Text( + text = simpleClassName, + fontWeight = FontWeight.Bold, + fontSize = 15.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Spacer(modifier = Modifier.height(2.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = simpleId, + fontSize = 12.sp, + color = MiuixTheme.colorScheme.onBackgroundVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f, fill = false) + ) + val dimensions = if (node.width > 0 && node.height > 0) { + " | ${node.width}x${node.height}dp" + } else "" + Text( + text = dimensions, + fontSize = 12.sp, + color = MiuixTheme.colorScheme.onBackgroundVariant, + maxLines = 1 + ) + } + } + Spacer(modifier = Modifier.width(8.dp)) + + // 显示可见性标记 (V, I, G) + VisibilityBadge(visibility = node.visibility) + } +} + +/** + * 一个简单的 Composable,用于根据层级生成空白间隔,实现缩进效果。 + */ +@Composable +private fun HierarchyIndicator(level: Int, spacePerLevel: Dp = 12.dp) { + Spacer(modifier = Modifier.width(level * spacePerLevel)) +} + +/** + * 显示视图可见性状态的徽章 (V/I/G)。 + */ +@Composable +fun VisibilityBadge(visibility: String) { + val (text, color) = when (visibility) { + "Visible" -> "V" to Color(0xFF4CAF50) + "Invisible" -> "I" to Color(0xFFFFA000) + "Gone" -> "G" to Color(0xFFF44336) + else -> "" to Color.Transparent + } + if (text.isNotEmpty()) { + Text( + text = text, + color = Color.White, + fontSize = 10.sp, + modifier = Modifier + .background(color, shape = RoundedCornerShape(4.dp)) + .padding(horizontal = 4.dp, vertical = 2.dp) + ) + } +} + +/** + * 用于配置单个视图显示模式的对话框。 + */ +@Composable +private fun ViewConfigDialog( + show: Boolean, + node: ViewNode, + currentConfigMode: Int, + onDismiss: () -> Unit, + onConfigChange: (Int) -> Unit +) { + val configOptions = listOf( + R.string.view_controller_mode_normal to ViewConfig.MODE_NORMAL, + R.string.view_controller_mode_visible to ViewConfig.MODE_ALWAYS_VISIBLE, + R.string.view_controller_mode_hidden to ViewConfig.MODE_ALWAYS_HIDDEN, + R.string.view_controller_mode_invisible to ViewConfig.MODE_ALWAYS_INVISIBLE + ) + + val showState = remember { mutableStateOf(true) } + LaunchedEffect(show) { showState.value = show } + + SuperDialog( + show = showState, + title = node.type.substringAfterLast('.').ifBlank { "ViewGroup" }, + summary = node.id, + onDismissRequest = onDismiss + ) { + Column { + configOptions.forEach { (textResId, mode) -> + val text = stringResource(textResId) + SuperArrow( + title = text, + leftAction = { + if (currentConfigMode == mode) { + Icon( + imageVector = MiuixIcons.Useful.Play, + contentDescription = "Selected", + tint = MiuixTheme.colorScheme.primary + ) + } + }, + onClick = { onConfigChange(mode) } + ) + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/StatusBarLayoutUiState.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/StatusBarLayoutUiState.kt new file mode 100644 index 00000000..545e1034 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/StatusBarLayoutUiState.kt @@ -0,0 +1,15 @@ +package com.suqi8.oshin.ui.activity.func.StatusBarLayout + +/** + * 定义 ViewControllerScreen 的整体UI状态。 + * @property isLoading 是否正在加载视图树。 + * @property viewTree 根 ViewNode,代表整个视图树结构。 + * @property configs 当前已保存的视图配置 Map (Key: viewId, Value: mode)。 + * @property expandedNodes 记录当前已展开的节点ID集合。 + */ +data class StatusBarLayoutUiState( + val isLoading: Boolean = true, + val viewTree: ViewNode? = null, + val configs: Map = emptyMap(), + val expandedNodes: Set = emptySet() +) diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/StatusBarLayoutViewModel.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/StatusBarLayoutViewModel.kt new file mode 100644 index 00000000..aa59c34a --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/StatusBarLayoutViewModel.kt @@ -0,0 +1,224 @@ +package com.suqi8.oshin.ui.activity.func.StatusBarLayout + +import android.content.Context +import android.util.Log +import androidx.compose.runtime.mutableStateListOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.gson.Gson +import com.highcapable.yukihookapi.hook.factory.dataChannel +import com.highcapable.yukihookapi.hook.factory.prefs +import com.suqi8.oshin.hook.systemui.StatusBar.StatusBarLayout +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +private val ViewNode.expandableId: String + get() = id.ifBlank { "group_${this.hashCode()}" } + +/** + * ViewController 功能的 ViewModel。 + * 负责: + * 1. 通过 DataChannel 与 Hook 端通信,请求和接收视图树。 + * 2. 管理视图树的UI状态,包括加载状态、展开/折叠状态。 + * 3. 构建用于UI渲染的扁平化列表 (visibleNodes)。 + * 4. 处理和持久化用户对视图的显示配置。 + */ +@HiltViewModel +class StatusBarLayoutViewModel @Inject constructor( + @ApplicationContext private val context: Context +) : ViewModel() { + + private val _uiState = MutableStateFlow(StatusBarLayoutUiState()) + val uiState = _uiState.asStateFlow() + + /** 持有转换后的、用于UI展示的扁平化节点列表。 */ + val visibleNodes = mutableStateListOf() + + private val gson = Gson() + private val dataChannel = context.dataChannel("com.android.systemui") + + companion object { + const val TAG = "视图控制器VM" + } + + init { + loadInitialConfigs() + setupDataChannelListener() + requestTree() + } + + /** + * 设置 DataChannel 监听器,用于接收来自 Hook 端的数据。 + */ + private fun setupDataChannelListener() { + dataChannel.wait(StatusBarLayout.KEY_RECEIVE_TREE) { jsonTree -> + Log.d(TAG, "DataChannel 已收到 JSON 树") + viewModelScope.launch { + val tree = runCatching { gson.fromJson(jsonTree, ViewNode::class.java) }.getOrNull() + if (tree != null) { + val allParentNodes = getAllParentNodeIds(tree) + _uiState.update { it.copy(isLoading = false, viewTree = tree, expandedNodes = allParentNodes) } + rebuildVisibleTree() + } else { + _uiState.update { it.copy(isLoading = false, viewTree = null) } + } + } + } + Log.d(TAG, "App 端 DataChannel 监听器已设置。") + } + + /** + * 向 Hook 端发送指令,请求获取最新的视图树。 + */ + fun requestTree() { + Log.d(TAG, "正在通过 DataChannel 请求视图树...") + _uiState.update { it.copy(isLoading = true) } + dataChannel.put(StatusBarLayout.KEY_REQUEST_TREE) + viewModelScope.launch { + delay(1000L) + + // 5秒后,如果仍然处于加载状态,说明超时了 + if (_uiState.value.isLoading) { + Log.w(TAG, "请求视图树超时!请检查 Hook 是否已激活。") + _uiState.update { it.copy(isLoading = false, viewTree = null) } + } + } + } + + /** + * 切换一个节点的展开/折叠状态。 + * @param nodeId 节点的唯一标识符 (expandableId)。 + */ + fun toggleNode(nodeId: String) { + _uiState.update { + val newExpanded = it.expandedNodes.toMutableSet() + if (newExpanded.contains(nodeId)) newExpanded.remove(nodeId) else newExpanded.add(nodeId) + it.copy(expandedNodes = newExpanded) + } + rebuildVisibleTree() // 状态更新后需要重建可见列表 + } + + /** + * 更新指定视图的显示配置。 + * @param viewId 视图的资源 ID。 + * @param mode 新的显示模式。 + */ + fun updateConfig(viewId: String, mode: Int) { + _uiState.update { + val newConfigs = it.configs.toMutableMap() + newConfigs[viewId] = mode + it.copy(configs = newConfigs) + } + saveConfigs() + } + + /** + * 将当前的所有配置保存到 SharedPreferences,并通知 Hook 端更新。 + */ + private fun saveConfigs() { + viewModelScope.launch(Dispatchers.IO) { + val configList = _uiState.value.configs.map { (id, mode) -> ViewConfig(id, mode) } + val json = gson.toJson(configList) + val editor = context.prefs(StatusBarLayout.PREFS_NAME).edit() + editor.putString(StatusBarLayout.PREFS_KEY, json) + editor.commit() + Log.d(TAG, "配置已同步保存, 正在发送更新广播...") + dataChannel.put(StatusBarLayout.KEY_UPDATE_CONFIG) + } + } + + /** + * 向 Hook 端发送指令,高亮指定的视图。 + * @param viewId 要高亮的视图资源 ID,如果为空字符串则取消高亮。 + */ + fun highlightView(hashCode: Int) { + dataChannel.put(StatusBarLayout.KEY_HIGHLIGHT_ANCHOR, hashCode) + Log.d(TAG, "发送高亮指令, HashCode: $hashCode") + } + + /** + * 取消高亮 + */ + fun clearHighlight() { + // 发送一个无效的 hashCode (例如 0) 来取消高亮 + dataChannel.put(StatusBarLayout.KEY_HIGHLIGHT_ANCHOR, 0) + Log.d(TAG, "发送取消高亮指令") + } + + /** + * 当UI状态(如展开节点)或原始树更新时,重建 [visibleNodes] 列表。 + */ + private fun rebuildVisibleTree() { + _uiState.value.viewTree?.let { root -> + val allVisible = buildVisibleTree(root, _uiState.value.expandedNodes) + visibleNodes.clear() + visibleNodes.addAll(allVisible) + } + } + + /** + * 递归遍历原始视图树,根据展开状态构建一个扁平化的、供UI使用的列表。 + * @param root 视图树的根节点。 + * @param expandedNodes 当前所有已展开节点的 ID 集合。 + * @return 一个包含层级信息的 [VisibleNodeInfo] 列表。 + */ + private fun buildVisibleTree(root: ViewNode, expandedNodes: Set): List { + val visibleList = mutableListOf() + + // 递归辅助函数 + fun traverse(node: ViewNode, level: Int, parentPath: String, index: Int) { + val currentPath = "$parentPath/$index" + val uniqueKey = "path_key_$currentPath" + if (node.id.isBlank() && node.type.isBlank() && node.children.isEmpty()) return + + visibleList.add(VisibleNodeInfo(node, level, uniqueKey)) + + // 如果当前节点是展开状态且有子节点,则继续递归其子节点 + if (expandedNodes.contains(node.expandableId) && node.children.isNotEmpty()) { + node.children.forEachIndexed { childIndex, child -> + traverse(child, level + 1, currentPath, childIndex) + } + } + } + + // 从根节点的直接子节点开始遍历,它们的层级为 0 + root.children.forEachIndexed { index, childNode -> + traverse(childNode, 0, "root", index) + } + return visibleList + } + + /** + * 遍历整个树,获取所有包含子节点的父节点的 ID。 + * 用于默认将所有父节点设置为展开状态。 + */ + private fun getAllParentNodeIds(node: ViewNode): Set { + val nodeIds = mutableSetOf() + fun traverse(currentNode: ViewNode) { + if (currentNode.children.isNotEmpty()) { + nodeIds.add(currentNode.expandableId) + currentNode.children.forEach { traverse(it) } + } + } + traverse(node) + return nodeIds + } + + /** + * 从 SharedPreferences 加载初始的视图配置。 + */ + private fun loadInitialConfigs() { + val jsonConfigs = context.prefs(StatusBarLayout.PREFS_NAME).getString(StatusBarLayout.PREFS_KEY, "[]") + val configs = runCatching { + gson.fromJson(jsonConfigs, Array::class.java) + }.getOrNull() ?: emptyArray() + _uiState.update { it.copy(configs = configs.associate { cfg -> cfg.id to cfg.mode }) } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/ViewConfig.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/ViewConfig.kt new file mode 100644 index 00000000..97236718 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/ViewConfig.kt @@ -0,0 +1,30 @@ +package com.suqi8.oshin.ui.activity.func.StatusBarLayout + +import com.google.gson.annotations.SerializedName + +/** + * 定义一个视图的显示配置数据模型。 + * + * @property id 视图的资源 ID 字符串,与 ViewNode.id 对应。 + * @property mode 视图的显示模式。 + */ +data class ViewConfig( + @SerializedName("id") + val id: String, + @SerializedName("mode") + val mode: Int +) { + companion object { + /** 默认模式,遵循原始视图状态。 */ + const val MODE_NORMAL = 0 + + /** 强制显示模式 (View.VISIBLE)。 */ + const val MODE_ALWAYS_VISIBLE = 1 + + /** 强制隐藏模式 (View.GONE),视图将不可见且不占据任何布局空间。 */ + const val MODE_ALWAYS_HIDDEN = 2 + + /** 强制隐藏(占位)模式 (View.INVISIBLE),视图将不可见但仍然占据其布局空间。 */ + const val MODE_ALWAYS_INVISIBLE = 3 + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/ViewNode.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/ViewNode.kt new file mode 100644 index 00000000..4df132f4 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/ViewNode.kt @@ -0,0 +1,17 @@ +package com.suqi8.oshin.ui.activity.func.StatusBarLayout + +import android.graphics.Rect + +// 在你的 com.suqi8.oshin.ui.activity.func.StatusBarLayout.ViewNode 文件中 +data class ViewNode( + val id: String, + val type: String, + val children: List, + val visibility: String, // "Visible", "Invisible", "Gone" + val bounds: Rect?, // Rect 是一个可以包含 left, top, right, bottom 的类 + val hashCodeValue: Int +) { + // 方便计算宽高的辅助属性 + val width: Int get() = bounds?.width() ?: 0 + val height: Int get() = bounds?.height() ?: 0 +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/VisibleNodeInfo.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/VisibleNodeInfo.kt new file mode 100644 index 00000000..f2bea90c --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/func/StatusBarLayout/VisibleNodeInfo.kt @@ -0,0 +1,13 @@ +package com.suqi8.oshin.ui.activity.func.StatusBarLayout + +/** + * 用于在UI上展示的、包含层级信息的单个节点数据类。 + * @property node 原始的 ViewNode 数据。 + * @property level 节点在树中的层级深度。 + * @property uniqueKey 为 Compose LazyColumn 提供的唯一且稳定的键。 + */ +data class VisibleNodeInfo( + val node: ViewNode, + val level: Int, + val uniqueKey: String +) diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/func/cpufreq.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/func/cpufreq.kt new file mode 100644 index 00000000..bf20ffea --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/func/cpufreq.kt @@ -0,0 +1,198 @@ +package com.suqi8.oshin.ui.activity.func + +import android.util.Log +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.suqi8.oshin.R +import com.suqi8.oshin.ui.activity.components.Card +import com.suqi8.oshin.ui.activity.components.FunPage +import com.suqi8.oshin.ui.activity.components.SuperDropdown +import com.suqi8.oshin.ui.activity.components.addline +import com.suqi8.oshin.ui.mainscreen.home.ModernSectionTitle +import com.suqi8.oshin.utils.executeCommand +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior +import top.yukonga.miuix.kmp.basic.PullToRefresh +import top.yukonga.miuix.kmp.basic.SmallTitle +import top.yukonga.miuix.kmp.basic.rememberPullToRefreshState +import top.yukonga.miuix.kmp.basic.rememberTopAppBarState +import top.yukonga.miuix.kmp.utils.overScrollVertical +import top.yukonga.miuix.kmp.utils.scrollEndHaptic + + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun cpu_freq( + navController: NavController, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + val topAppBarState = MiuixScrollBehavior(rememberTopAppBarState()) + + FunPage( + navController = navController, + scrollBehavior = topAppBarState, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + animationKey = "func\\cpu_freq" + ) { padding -> + val pullToRefreshState = rememberPullToRefreshState() + var isRefreshing by rememberSaveable { mutableStateOf(false) } + val scope = rememberCoroutineScope() + val cpuFrequencies = + remember { mutableStateOf, Int, Int>>>(emptyMap()) } + LaunchedEffect(isRefreshing) { + if (cpuFrequencies.value.isEmpty()) isRefreshing = true + if (isRefreshing) { + cpuFrequencies.value = getAllCoresFrequencies() + isRefreshing = false + } + } + + PullToRefresh( + modifier = Modifier, + pullToRefreshState = pullToRefreshState, + isRefreshing = isRefreshing, + onRefresh = { isRefreshing = true } + ) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .overScrollVertical() + .scrollEndHaptic() + .nestedScroll(topAppBarState.nestedScrollConnection), + contentPadding = padding + ) { + item { + ModernSectionTitle( + title = stringResource(id = R.string.cpu_freq_main), + modifier = Modifier + .displayCutoutPadding() + .padding(top = padding.calculateTopPadding() + 72.dp, bottom = 8.dp) + ) + } + item { + AnimatedVisibility(!cpuFrequencies.value.isEmpty()) { + Column { + fun updateCpuFrequencies(core: Int, maxIndex: Int, minIndex: Int) { + cpuFrequencies.value = cpuFrequencies.value.toMutableMap().apply { + this[core] = Triple(this[core]!!.first, maxIndex, minIndex) + } + } + cpuFrequencies.value.forEach { (core, data) -> + val (freqs, maxIndex, minIndex) = data + var selectedMaxIndex by remember { mutableStateOf(maxIndex) } + var selectedMinIndex by remember { mutableStateOf(minIndex) } + SmallTitle("CPU$core") + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(bottom = 6.dp) + ) { + SuperDropdown( + title = stringResource(R.string.cpu_group_max_freq), + items = freqs, + selectedIndex = selectedMaxIndex, + onSelectedIndexChange = { + selectedMaxIndex = it + scope.launch { + updateCpuFrequencies(core, selectedMaxIndex, selectedMinIndex) + setCpuFrequency(core, freqs[it], isMax = true) + } + } + ) + addline() + SuperDropdown( + title = stringResource(R.string.cpu_group_min_freq), + items = freqs, + selectedIndex = selectedMinIndex, + onSelectedIndexChange = { + selectedMinIndex = it + scope.launch { + updateCpuFrequencies(core, selectedMaxIndex, selectedMinIndex) + setCpuFrequency(core, freqs[it], isMax = false) + } + } + ) + } + } + } + } + } + } + } + } +} + +fun setCpuFrequency(core: Int, frequency: String, isMax: Boolean) { + val path = if (isMax) { + "/sys/devices/system/cpu/cpu$core/cpufreq/scaling_max_freq" + } else { + "/sys/devices/system/cpu/cpu$core/cpufreq/scaling_min_freq" + } + + val command = "echo $frequency > $path" + + val result = executeCommand(command) + if (result == "0") { + Log.e("CPU", "写入失败: $command") + } else { + Log.d("CPU", "成功写入: $command") + } +} + +suspend fun getAllCoresFrequencies(): Map, Int, Int>> = + coroutineScope { + val coreCount = withContext(Dispatchers.IO) { + executeCommand("ls /sys/devices/system/cpu/ | grep -E 'cpu[0-9]+' | wc -l") + .toIntOrNull() ?: 0 + } + + (0 until coreCount).map { i -> + async(Dispatchers.IO) { + val freqPath = "/sys/devices/system/cpu/cpu$i/cpufreq/scaling_available_frequencies" + val maxFreqPath = "/sys/devices/system/cpu/cpu$i/cpufreq/scaling_max_freq" + val minFreqPath = "/sys/devices/system/cpu/cpu$i/cpufreq/scaling_min_freq" + + val freqResult = executeCommand("cat $freqPath") + val maxFreqValue = executeCommand("cat $maxFreqPath").trim() + val minFreqValue = executeCommand("cat $minFreqPath").trim() + + if (freqResult.isNotEmpty() && freqResult != "0") { + val frequencies = freqResult.split("\\s+".toRegex()).map { it.trim() } + val maxIndex = frequencies.indexOf(maxFreqValue).takeIf { it >= 0 } ?: 0 + val minIndex = frequencies.indexOf(minFreqValue).takeIf { it >= 0 } ?: 0 + i to Triple(frequencies, maxIndex, minIndex) + } else null + } + }.awaitAll() + .filterNotNull() + .toMap() + } diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/func/feature/OplusSettingsScreen.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/func/feature/OplusSettingsScreen.kt new file mode 100644 index 00000000..e3696612 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/func/feature/OplusSettingsScreen.kt @@ -0,0 +1,134 @@ +package com.suqi8.oshin.ui.activity.func.feature + +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringArrayResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.navigation.NavController +import com.highcapable.yukihookapi.hook.factory.dataChannel +import com.highcapable.yukihookapi.hook.factory.prefs +import com.suqi8.oshin.R +import com.suqi8.oshin.hook.settings.SettingsFeature +import com.suqi8.oshin.ui.activity.components.Card +import com.suqi8.oshin.ui.activity.components.FunDropdown +import com.suqi8.oshin.ui.activity.components.FunPage +import com.suqi8.oshin.ui.mainscreen.home.ModernSectionTitle +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.basic.rememberTopAppBarState +import top.yukonga.miuix.kmp.utils.overScrollVertical +import top.yukonga.miuix.kmp.utils.scrollEndHaptic + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun OplusSettingsScreen( + navController: NavController, + viewModel: OplusSettingsViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsState() + val context = LocalContext.current + val scrollBehavior = MiuixScrollBehavior(rememberTopAppBarState()) + + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + val prefs = context.prefs(SettingsFeature.OPLUS_SETTINGS_PREFS_NAME) + val cachedMethods = prefs.getStringSet(SettingsFeature.KEY_CACHED_METHODS, emptySet()) + + if (cachedMethods.isNotEmpty()) { + viewModel.loadItemsState(cachedMethods.toList().sorted()) + } else { + viewModel.setLoading(true) + } + } + + val dataChannel = context.dataChannel(packageName = "com.android.settings") + dataChannel.wait>(SettingsFeature.KEY_RETURN_METHODS) { newMethods -> + val sortedMethods = newMethods.sorted() + viewModel.loadItemsState(sortedMethods) + if (newMethods.isNotEmpty()) { + context.prefs(SettingsFeature.OPLUS_SETTINGS_PREFS_NAME).edit { + putStringSet(SettingsFeature.KEY_CACHED_METHODS, newMethods.toSet()) + } + } + } + dataChannel.put(SettingsFeature.KEY_GET_METHODS) + } + + FunPage( + appList = listOf("com.android.settings"), + navController = navController, + scrollBehavior = scrollBehavior + ) { padding -> + LazyColumn( + modifier = Modifier + .fillMaxSize() + .overScrollVertical() + .scrollEndHaptic() + .nestedScroll(scrollBehavior.nestedScrollConnection), + contentPadding = padding + ) { + item { + ModernSectionTitle( + title = stringResource(id = R.string.oplus_settings_features), + modifier = Modifier + .displayCutoutPadding() + .padding(top = padding.calculateTopPadding() + 72.dp, bottom = 8.dp) + ) + } + if (uiState.isLoading && uiState.methodNames.isEmpty()) { + item { + Card(modifier = Modifier.padding(12.dp)) { + Text( + text = stringResource(id = R.string.loading_oplus_features), + modifier = Modifier.padding(16.dp) + ) + } + } + } else if (uiState.methodNames.isNotEmpty()) { + // --- 优化 1 & 2: 正确使用 LazyColumn 的 itemsIndexed 并提供 key --- + itemsIndexed( + items = uiState.methodNames, + key = { _, uniqueKey -> uniqueKey } + ) { index, uniqueKey -> + val title = uniqueKey.substringAfterLast('.') + val summary = uniqueKey.substringBeforeLast('.').substringAfterLast('.') + val currentValue = uiState.itemStates[uniqueKey] ?: 0 + + Card(modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp)) { + FunDropdown( + title = title, + summary = stringResource(id = R.string.feature_from, summary), + selectedIndex = currentValue, + options = stringArrayResource(id = R.array.oplus_feature_options).toList(), + onSelectedIndexChange = { newIndex -> viewModel.updateState(uniqueKey, newIndex) } + ) + } + } + } else { + item { + Card(modifier = Modifier.padding(12.dp)) { + Text( + text = stringResource(id = R.string.oplus_features_not_found), + modifier = Modifier.padding(16.dp) + ) + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/func/feature/OplusSettingsViewModel.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/func/feature/OplusSettingsViewModel.kt new file mode 100644 index 00000000..318d32e5 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/func/feature/OplusSettingsViewModel.kt @@ -0,0 +1,94 @@ +package com.suqi8.oshin.ui.activity.func.feature + +import android.content.Context +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.highcapable.yukihookapi.hook.factory.prefs +import com.suqi8.oshin.hook.settings.SettingsFeature +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +/** + * OplusSettingsScreen 的专属 UI 状态。 + * + * @param isLoading 是否正在加载功能列表。 + * @param methodNames 动态扫描到的方法名列表。 + * @param itemStates 存储每个方法名(key)对应的下拉菜单选项索引(value)。 + */ +data class OplusSettingsUiState( + val isLoading: Boolean = true, + val methodNames: List = emptyList(), + val itemStates: Map = emptyMap() +) + +/** + * 专为 OplusSettingsScreen 设计的 ViewModel。 + * 负责管理动态扫描的功能列表及其状态,独立于通用的 featureViewModel。 + */ +@HiltViewModel +class OplusSettingsViewModel @Inject constructor( + @ApplicationContext private val context: Context +) : ViewModel() { + + private val _uiState = MutableStateFlow(OplusSettingsUiState()) + val uiState = _uiState.asStateFlow() + + private val prefs = context.prefs(SettingsFeature.OPLUS_SETTINGS_PREFS_NAME) + + /** + * 当从 DataChannel 接收到新的方法列表时,加载或刷新所有设置项的初始状态。 + * + * @param keys 从 Hook 端获取到的最新方法名列表。 + */ + fun loadItemsState(keys: List) { + viewModelScope.launch(Dispatchers.IO) { + val initialStates = mutableMapOf() + keys.forEach { key -> + // 所有动态项都是下拉菜单,其值为 Int 类型。默认值为 0 ("默认")。 + initialStates[key] = prefs.getInt(key, 0) + } + + // 更新 UI 状态 + _uiState.update { + it.copy( + isLoading = false, + methodNames = keys, + itemStates = initialStates + ) + } + } + } + + /** + * 当用户在 UI 上更改了某个下拉菜单的选项时,调用此方法更新状态。 + * + * @param key 发生变化的方法名。 + * @param newIndex 新选中的选项索引。 + */ + fun updateState(key: String, newIndex: Int) { + // 1. 立即更新内存中的 UI 状态,确保 UI 快速响应 + _uiState.update { currentState -> + currentState.copy(itemStates = currentState.itemStates + (key to newIndex)) + } + + // 2. 在后台线程将新值写入 SharedPreferences 持久化 + viewModelScope.launch(Dispatchers.IO) { + prefs.edit { + putInt(key, newIndex) + } + } + } + + /** + * 设置加载状态。 + */ + fun setLoading(isLoading: Boolean) { + _uiState.update { it.copy(isLoading = isLoading) } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/activity/func/romworkshop/main.kt b/app/src/main/java/com/suqi8/oshin/ui/activity/func/romworkshop/main.kt new file mode 100644 index 00000000..09bc217d --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/activity/func/romworkshop/main.kt @@ -0,0 +1,97 @@ +package com.suqi8.oshin.ui.activity.func.romworkshop + +import android.annotation.SuppressLint +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.LottieConstants +import com.airbnb.lottie.compose.animateLottieCompositionAsState +import com.airbnb.lottie.compose.rememberLottieComposition +import com.suqi8.oshin.R +import com.suqi8.oshin.ui.activity.components.Card +import com.suqi8.oshin.ui.activity.components.FunPage +import com.suqi8.oshin.ui.mainscreen.about.openUrl +import com.suqi8.oshin.ui.mainscreen.home.ModernSectionTitle +import top.yukonga.miuix.kmp.basic.ButtonDefaults +import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior +import top.yukonga.miuix.kmp.basic.TextButton +import top.yukonga.miuix.kmp.basic.rememberTopAppBarState +import top.yukonga.miuix.kmp.utils.overScrollVertical +import top.yukonga.miuix.kmp.utils.scrollEndHaptic + +@OptIn(ExperimentalSharedTransitionApi::class) +@SuppressLint("SuspiciousIndentation") +@Composable +fun RomWorkshop( + navController: NavController, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + val scrollBehavior = MiuixScrollBehavior(rememberTopAppBarState()) + val context = LocalContext.current + FunPage( + navController = navController, + scrollBehavior = scrollBehavior, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + animationKey = "func\\romworkshop" + ) { padding -> + LazyColumn( + modifier = Modifier + .fillMaxSize() + .overScrollVertical() + .scrollEndHaptic() + .nestedScroll(scrollBehavior.nestedScrollConnection), + contentPadding = padding + ) { + item { + ModernSectionTitle( + title = stringResource(id = R.string.rom_workshop), + modifier = Modifier + .displayCutoutPadding() + .padding(top = padding.calculateTopPadding() + 72.dp, bottom = 8.dp) + ) + } + item { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(top = 16.dp, bottom = 6.dp) + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + val compositionResult = + rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.coming_soon)) + val progress = + animateLottieCompositionAsState( + composition = compositionResult.value, + iterations = LottieConstants.IterateForever + ) + LottieAnimation( + composition = compositionResult.value, + progress = { progress.progress }, + modifier = Modifier.padding(1.dp) + ) + TextButton("立即体验", onClick = {openUrl(context, "https://github.com/suqi8/ImageStudio/releases/latest")}, modifier = Modifier.padding(6.dp).fillMaxWidth().padding(32.dp), colors = ButtonDefaults.textButtonColorsPrimary()) + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/AppNavHost.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/AppNavHost.kt new file mode 100644 index 00000000..8be9c65b --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/AppNavHost.kt @@ -0,0 +1,179 @@ +package com.suqi8.oshin.ui.mainscreen + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionLayout +import androidx.compose.animation.SizeTransform +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import com.suqi8.oshin.ui.mainscreen.softupdate.SoftwareUpdatePage +import com.suqi8.oshin.ui.mainscreen.about.about_contributors +import com.suqi8.oshin.ui.mainscreen.about.about_group +import com.suqi8.oshin.ui.mainscreen.about.about_references +import com.suqi8.oshin.ui.mainscreen.about.about_setting +import com.suqi8.oshin.ui.mainscreen.module.HideAppsNotice +import com.suqi8.oshin.ui.activity.feature.featureScreen +import com.suqi8.oshin.ui.activity.func.StatusBarLayout.StatusBarLayout +import com.suqi8.oshin.ui.activity.func.cpu_freq +import com.suqi8.oshin.ui.activity.func.feature.OplusSettingsScreen +import com.suqi8.oshin.ui.activity.func.romworkshop.RomWorkshop +import com.suqi8.oshin.utils.SpringEasing +import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior +import top.yukonga.miuix.kmp.basic.rememberTopAppBarState +import top.yukonga.miuix.kmp.utils.getWindowSize + +@OptIn(ExperimentalAnimationApi::class, ExperimentalSharedTransitionApi::class) +@Composable +fun AppNavHost() { + val navController = rememberNavController() + val windowWidth = getWindowSize().width + + val easing = SpringEasing.stiff() + val duration = easing.durationMillis.toInt() + VerifyDialog() + PrivacyDialog() + + + Column { + SharedTransitionLayout { + NavHost( + navController = navController, + startDestination = "Main", + /*enterTransition = { slideInHorizontally(initialOffsetX = { windowWidth }, animationSpec = tween(duration, 0, easing = easing)) }, + exitTransition = { slideOutHorizontally(targetOffsetX = { -windowWidth / 5 }, animationSpec = tween(duration, 0, easing = easing)) }, + popEnterTransition = { slideInHorizontally(initialOffsetX = { -windowWidth / 5 }, animationSpec = tween(duration, 0, easing = easing)) }, + popExitTransition = { slideOutHorizontally(targetOffsetX = { windowWidth }, animationSpec = tween(duration, 0, easing = easing)) },*/ + sizeTransform = { + SizeTransform(clip = false) + } + ) { + composable("Main") { + val sharedTransitionScope = this@SharedTransitionLayout + val animatedVisibilityScope = this + MainScreen( + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + composable("about_setting") { + val sharedTransitionScope = this@SharedTransitionLayout + val animatedVisibilityScope = this + about_setting( + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + composable("about_group") { + val sharedTransitionScope = this@SharedTransitionLayout + val animatedVisibilityScope = this + about_group( + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + composable("about_references") { + val sharedTransitionScope = this@SharedTransitionLayout + val animatedVisibilityScope = this + about_references( + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + composable("about_contributors") { + val sharedTransitionScope = this@SharedTransitionLayout + val animatedVisibilityScope = this + about_contributors( + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + composable("func\\cpu_freq") { + val sharedTransitionScope = this@SharedTransitionLayout + val animatedVisibilityScope = this + cpu_freq( + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + composable("func\\romworkshop") { + val sharedTransitionScope = this@SharedTransitionLayout + val animatedVisibilityScope = this + RomWorkshop( + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + composable("software_update") { + val sharedTransitionScope = this@SharedTransitionLayout + val animatedVisibilityScope = this + SoftwareUpdatePage( + navController = navController, + topAppBarScrollBehavior = MiuixScrollBehavior(rememberTopAppBarState()), + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + composable("feature/systemui\\status_bar\\StatusBarLayout") { + StatusBarLayout(navController) + } + composable("feature/settings\\oplus_settings") { + OplusSettingsScreen(navController) + } + composable( + route = "hide_apps_notice/{packages}", + arguments = listOf(navArgument("packages") { + type = NavType.StringType + nullable = true + }) + ) { backStackEntry -> + val sharedTransitionScope = this@SharedTransitionLayout + val animatedVisibilityScope = this + HideAppsNotice( + navController = navController, + packages = backStackEntry.arguments?.getString("packages"), + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + composable( + route = "feature/{categoryId}?highlightKey={highlightKey}", + // 定义 categoryId 参数为字符串类型 + arguments = listOf( + navArgument("categoryId") { type = NavType.StringType }, + navArgument("highlightKey") { + type = NavType.StringType + nullable = true + defaultValue = null + } + ) + ) { backStackEntry -> + val sharedTransitionScope = this@SharedTransitionLayout + val animatedVisibilityScope = this + + // 从导航参数中安全地获取 categoryId + val categoryId = backStackEntry.arguments?.getString("categoryId") + + if (categoryId != null) { + featureScreen( + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/MainScreen.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/MainScreen.kt new file mode 100644 index 00000000..a0f893a8 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/MainScreen.kt @@ -0,0 +1,335 @@ +package com.suqi8.oshin.ui.mainscreen + +import android.annotation.SuppressLint +import androidx.activity.ComponentActivity +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.BlurEffect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.navigation.NavController +import com.highcapable.yukihookapi.hook.factory.prefs +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import com.suqi8.oshin.Main_Function +import com.suqi8.oshin.R +import com.suqi8.oshin.ui.mainscreen.about.Main_About +import com.suqi8.oshin.ui.activity.components.BlurredTopBarBackground +import com.suqi8.oshin.ui.activity.components.BottomTabs +import com.suqi8.oshin.ui.mainscreen.home.MainHome +import com.suqi8.oshin.ui.mainscreen.module.Main_Module +import com.suqi8.oshin.ui.mainscreen.softupdate.UpdateViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.launch +import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior +import top.yukonga.miuix.kmp.basic.NavigationBar +import top.yukonga.miuix.kmp.basic.NavigationItem +import top.yukonga.miuix.kmp.basic.Scaffold +import top.yukonga.miuix.kmp.basic.ScrollBehavior +import top.yukonga.miuix.kmp.basic.TopAppBar +import top.yukonga.miuix.kmp.basic.rememberTopAppBarState +import top.yukonga.miuix.kmp.theme.MiuixTheme +import kotlin.math.abs + +// 将 LocalColorMode 定义在与使用它的地方更近的文件中 +val LocalColorMode = compositionLocalOf> { error("No ColorMode provided") } + +@OptIn(FlowPreview::class, ExperimentalSharedTransitionApi::class) +@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") +@Composable +fun MainScreen( + navController: NavController, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + val scope = rememberCoroutineScope() + val topAppBarScrollBehavior = MiuixScrollBehavior(rememberTopAppBarState()) + val pagerState = rememberPagerState(pageCount = { 4 }, initialPage = 0) + val context = LocalContext.current + val backdrop = rememberLayerBackdrop() + + // 底部导航栏项目 + val bottomNavItems = listOf( + NavigationItem(stringResource(R.string.home), ImageVector.vectorResource(R.drawable.home)), + NavigationItem(stringResource(R.string.module), ImageVector.vectorResource(R.drawable.module)), + NavigationItem(stringResource(R.string.func), ImageVector.vectorResource(R.drawable.func)), + NavigationItem(stringResource(R.string.about), ImageVector.vectorResource(R.drawable.about)) + ) + + // 底部栏可见性状态 + var isBottomBarVisible by rememberSaveable { mutableStateOf(true) } + + // 监听滚动行为控制底部栏显示/隐藏 + BottomBarVisibilityEffect( + scrollBehavior = topAppBarScrollBehavior, + onVisibilityChange = { isBottomBarVisible = it } + ) + + // 监听页面切换重置底部栏显示 + LaunchedEffect(pagerState.currentPage) { + isBottomBarVisible = true + } + + // 版本更新检查 + val activity = context as ComponentActivity + val updateViewModel: UpdateViewModel = hiltViewModel(activity) + val currentVersion = remember { + runCatching { + context.packageManager.getPackageInfo(context.packageName, 0).versionName ?: "0.0.0" + }.getOrDefault("0.0.0") + } + + LaunchedEffect(Unit) { + updateViewModel.autoCheckForUpdate(currentVersion) + } + + val updateResult by updateViewModel.updateCheckResult.collectAsState() + + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { + TopAppBar( + scrollBehavior = topAppBarScrollBehavior, + color = Color.Transparent, + modifier = Modifier.height(0.dp), + title = "" + ) + } + ) { padding -> + Box { + AppHorizontalPager( + modifier = Modifier + .layerBackdrop(backdrop) + .imePadding(), + pagerState = pagerState, + topAppBarScrollBehavior = topAppBarScrollBehavior, + padding = padding, + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + + BlurredTopBarBackground(backdrop) + + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.BottomCenter // 直接把所有子元素默认对齐到底部 + ) { + AnimatedBottomBar( + isVisible = isBottomBarVisible, + items = bottomNavItems, + pagerState = pagerState, + scope = scope, + backdrop = backdrop + ) + } + + UpdateAvailableDialog( + release = updateResult, + navController = navController, + onDismiss = { updateViewModel.clearUpdateCheckResult() }, + updateViewModel = updateViewModel + ) + } + } +} + +/** + * 底部栏可见性控制效果 + */ +@Composable +private fun BottomBarVisibilityEffect( + scrollBehavior: ScrollBehavior, + onVisibilityChange: (Boolean) -> Unit +) { + LaunchedEffect(scrollBehavior) { + var previousOffset = 0 + val threshold = 50 + + snapshotFlow { scrollBehavior.state.contentOffset.toInt() } + .collect { currentOffset -> + // 接近顶部时始终显示 + if (currentOffset >= -5) { + onVisibilityChange(true) + previousOffset = currentOffset + return@collect + } + + val delta = currentOffset - previousOffset + + // 滚动超过阈值时切换显示状态 + if (abs(delta) > threshold) { + onVisibilityChange(delta >= 0) + previousOffset = currentOffset + } + } + } +} + +/** + * 带动画的底部导航栏 + */ +@Composable +private fun AnimatedBottomBar( + isVisible: Boolean, + items: List, + pagerState: PagerState, + scope: CoroutineScope, + backdrop: Backdrop +) { + val context = LocalContext.current + val bottomTabMode = remember { context.prefs("settings").getBoolean("bottomtab") } + + Box( + modifier = Modifier + .fillMaxWidth() + ) { + AnimatedVisibility( + visible = isVisible, + enter = slideInVertically(initialOffsetY = { it }), + exit = slideOutVertically(targetOffsetY = { it }) + ) { + if (!bottomTabMode) { + BottomTabs( + modifier = Modifier, + tabs = items, + pagerState = pagerState, + onTabSelected = { screen -> + scope.launch { + pagerState.animateScrollToPage(screen) + } + }, + backdrop = backdrop + ) + } else { + NavigationBar( + items = items, + selected = pagerState.currentPage, + onClick = { screen -> + scope.launch { + pagerState.animateScrollToPage(screen) + } + }, + modifier = Modifier.fillMaxWidth() + ) + } + } + } +} + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun AppHorizontalPager( + modifier: Modifier = Modifier, + pagerState: PagerState, + topAppBarScrollBehavior: ScrollBehavior, + padding: PaddingValues, + navController: NavController, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + HorizontalPager( + modifier = modifier.background(MiuixTheme.colorScheme.background), + state = pagerState, + beyondViewportPageCount = 3, + userScrollEnabled = true, + pageContent = { page -> + Box(modifier = Modifier + .rememberPageBlurModifier(pagerState, page) + .fillMaxSize()) { + when (page) { + 0 -> MainHome( + topAppBarScrollBehavior = topAppBarScrollBehavior, + padding = padding, + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + + 1 -> Main_Module( + topAppBarScrollBehavior = topAppBarScrollBehavior, + padding = padding, + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + + 2 -> Main_Function( + topAppBarScrollBehavior = topAppBarScrollBehavior, + padding = padding, + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + + else -> Main_About( + topAppBarScrollBehavior = topAppBarScrollBehavior, + padding = padding, + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + } + } + ) +} + +/** + * 记住页面模糊效果的 Modifier + */ +@Composable +private fun Modifier.rememberPageBlurModifier(pagerState: PagerState, page: Int): Modifier { + val maxBlurRadius = 16.dp + val maxBlurRadiusPx = with(LocalDensity.current) { maxBlurRadius.toPx() } + + return remember(pagerState.currentPage, pagerState.currentPageOffsetFraction) { + // 页面偏移量(正负表示左右偏移) + val offset = runCatching { pagerState.getOffsetDistanceInPages(page) }.getOrDefault(0f) + val blurPx = abs(offset) * maxBlurRadiusPx + + Modifier.graphicsLayer { + if (blurPx > 0.1f) { + renderEffect = BlurEffect(blurPx, blurPx, TileMode.Decal) + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/PrivacyDialog.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/PrivacyDialog.kt new file mode 100644 index 00000000..544b2bac --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/PrivacyDialog.kt @@ -0,0 +1,86 @@ +package com.suqi8.oshin.ui.mainscreen + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.highcapable.yukihookapi.hook.factory.prefs +import com.suqi8.oshin.R +import com.suqi8.oshin.utils.executeCommand +import com.umeng.analytics.MobclickAgent +import com.umeng.commonsdk.UMConfigure +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import top.yukonga.miuix.kmp.basic.ButtonDefaults +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.basic.TextButton +import top.yukonga.miuix.kmp.extra.SuperDialog +import kotlin.system.exitProcess + + +val lspVersion = mutableStateOf("") + +@Composable +fun PrivacyDialog() { + val context = LocalContext.current + val isPrivacyEnabled = remember { mutableStateOf(context.prefs("settings").getBoolean("privacy", true)) } + + LaunchedEffect(isPrivacyEnabled.value) { + if (!isPrivacyEnabled.value) { + UMConfigure.init(context, "67c7dea68f232a05f127781e", "android", UMConfigure.DEVICE_TYPE_PHONE, "") + withContext(Dispatchers.IO) { + val lsposedVersionName = executeCommand("awk -F= '/version=/ {print $2}' /data/adb/modules/zygisk_lsposed/module.prop") + lspVersion.value = lsposedVersionName + val savedLspVersion = context.prefs("settings").getString("privacy_lspvername", "") + if (lsposedVersionName.isNotEmpty() && lsposedVersionName != savedLspVersion) { + val eventData = mapOf("version_name" to lsposedVersionName) + MobclickAgent.onEvent(context, "lsposed_usage", eventData) + context.prefs("settings").edit { + putString("privacy_lspvername", lsposedVersionName) + } + } + } + } + } + + SuperDialog( + show = isPrivacyEnabled, + title = stringResource(R.string.privacy_title), + onDismissRequest = {} + ) { + Text(stringResource(R.string.privacy_content)) + Spacer(Modifier.height(12.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + TextButton( + modifier = Modifier.weight(1f), + text = stringResource(R.string.exit), + onClick = { + exitProcess(0) + } + ) + Spacer(Modifier.width(12.dp)) + TextButton( + modifier = Modifier.weight(1f), + text = stringResource(R.string.ok), + colors = ButtonDefaults.textButtonColorsPrimary(), + onClick = { + isPrivacyEnabled.value = false + context.prefs("settings").edit { putBoolean("privacy", false) } + } + ) + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/UpdateAvailableDialog.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/UpdateAvailableDialog.kt new file mode 100644 index 00000000..3cbeab03 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/UpdateAvailableDialog.kt @@ -0,0 +1,63 @@ +package com.suqi8.oshin.ui.mainscreen + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.suqi8.oshin.R +import com.suqi8.oshin.ui.mainscreen.softupdate.GitHubRelease +import com.suqi8.oshin.ui.mainscreen.softupdate.UpdateViewModel +import top.yukonga.miuix.kmp.basic.ButtonDefaults +import top.yukonga.miuix.kmp.basic.TextButton +import top.yukonga.miuix.kmp.extra.SuperDialog + +@Composable +fun UpdateAvailableDialog( + release: GitHubRelease?, + navController: NavController, + onDismiss: () -> Unit, + updateViewModel: UpdateViewModel +) { + val show = remember(release) { mutableStateOf(release != null) } + + SuperDialog( + show = show, + title = stringResource(R.string.update_page_status_new_version), + summary = stringResource(R.string.update_available_dialog_summary, release?.name ?: ""), + onDismissRequest = onDismiss + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + TextButton( + modifier = Modifier.weight(1f), + text = stringResource(R.string.cancel), + onClick = { + onDismiss() + show.value = false + } + ) + Spacer(Modifier.width(12.dp)) + TextButton( + modifier = Modifier.weight(1f), + text = stringResource(R.string.update_available_dialog_now), + colors = ButtonDefaults.textButtonColorsPrimary(), + onClick = { + onDismiss() + show.value = false + updateViewModel.setAutoDownloadFlag() + navController.navigate("software_update") + } + ) + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/VerifyDialog.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/VerifyDialog.kt new file mode 100644 index 00000000..b64abfe4 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/VerifyDialog.kt @@ -0,0 +1,92 @@ +package com.suqi8.oshin.ui.mainscreen + +import android.annotation.SuppressLint +import android.widget.Toast +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.highcapable.yukihookapi.hook.factory.prefs +import com.suqi8.oshin.BuildConfig +import top.yukonga.miuix.kmp.basic.ButtonDefaults +import top.yukonga.miuix.kmp.basic.TextButton +import top.yukonga.miuix.kmp.basic.TextField +import top.yukonga.miuix.kmp.extra.SuperDialog + +@OptIn(ExperimentalAnimationApi::class) +@SuppressLint("LocalContextConfigurationRead") +@Composable +fun VerifyDialog() { + val context = LocalContext.current + + val fullVersion = BuildConfig.VERSION_NAME + val currentVersionPrefix = fullVersion.split(".")[0] + "." + fullVersion.split(".")[1] + + val show = remember { + mutableStateOf((context.prefs("settings").getString("verifyVersion", "0") != currentVersionPrefix) && context.resources.configuration.locales[0].language.endsWith("zh")) + } + var inputText by remember { mutableStateOf("") } + + val onVerificationSuccess = { + Toast.makeText(context, "验证成功!", Toast.LENGTH_SHORT).show() + context.prefs("settings").edit() + .putString("verifyVersion", currentVersionPrefix).apply() + show.value = false + } + + if (show.value && !BuildConfig.DEBUG) { + SuperDialog( + show = show, + summary = "为了尊重开发者的劳动成果,请输入模块下载地址进行验证。模块仅在 GitHub 发布,如在第三方赚钱网盘(如 123 网盘、迅雷等)下载,请举报发布者,谢谢。" + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + TextVerificationContent( + text = inputText, + onTextChange = { inputText = it }, + onVerify = { + when { + inputText.contains("github.com/suqi8/OShin", ignoreCase = true) -> onVerificationSuccess() + inputText.contains("github.com/Xposed-Modules-Repo/com.suqi8.oshin", ignoreCase = true) -> onVerificationSuccess() + else -> Toast.makeText(context, "输入内容不正确,请重试!", Toast.LENGTH_SHORT).show() + } + } + ) + } + } + } +} + +@Composable +private fun TextVerificationContent( + text: String, + onTextChange: (String) -> Unit, + onVerify: () -> Unit +) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + TextField( + value = text, + onValueChange = onTextChange, + label = "GitHub 地址", + modifier = Modifier.fillMaxWidth() + ) + Spacer(Modifier.height(8.dp)) + TextButton( + modifier = Modifier.fillMaxWidth(), + onClick = onVerify, + colors = ButtonDefaults.textButtonColorsPrimary(), + text = "验证" + ) + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/Main_About.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/Main_About.kt new file mode 100644 index 00000000..77392dee --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/Main_About.kt @@ -0,0 +1,739 @@ +package com.suqi8.oshin.ui.mainscreen.about + +import android.annotation.SuppressLint +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.os.Environment +import android.os.StatFs +import android.os.storage.StorageManager +import android.provider.Settings +import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.spring +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.scale +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.integerArrayResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.fontscaling.MathUtils.lerp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.graphics.toColorInt +import androidx.core.net.toUri +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.navigation.NavController +import com.highcapable.yukihookapi.YukiHookAPI +import com.highcapable.yukihookapi.YukiHookAPI_Impl +import com.suqi8.oshin.BuildConfig +import com.suqi8.oshin.R +import com.suqi8.oshin.ui.activity.components.Card +import com.suqi8.oshin.ui.activity.components.CouiListItemPosition +import com.suqi8.oshin.ui.activity.components.FunArrow +import com.suqi8.oshin.ui.activity.components.SuperArrow +import com.suqi8.oshin.ui.activity.components.addline +import com.suqi8.oshin.ui.mainscreen.LocalColorMode +import com.suqi8.oshin.ui.theme.BgEffectView +import com.suqi8.oshin.utils.executeCommand +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import top.yukonga.miuix.kmp.basic.ButtonDefaults +import top.yukonga.miuix.kmp.basic.ScrollBehavior +import top.yukonga.miuix.kmp.basic.SmallTitle +import top.yukonga.miuix.kmp.basic.Surface +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.basic.TextButton +import top.yukonga.miuix.kmp.basic.TextField +import top.yukonga.miuix.kmp.extra.SuperDialog +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme +import top.yukonga.miuix.kmp.utils.G2RoundedCornerShape +import top.yukonga.miuix.kmp.utils.overScrollVertical +import java.time.Instant +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import kotlin.math.log10 +import kotlin.math.pow + +@OptIn(ExperimentalSharedTransitionApi::class) +@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter", "UseKtx", "RestrictedApi", + "UnrememberedMutableState" +) +@Composable +fun Main_About( + topAppBarScrollBehavior: ScrollBehavior, + padding: PaddingValues, + navController: NavController, + viewModel: SettingsViewModel = hiltViewModel(), + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + val context = LocalContext.current + val showDeviceNameDialog = remember { mutableStateOf(false) } + val deviceName: MutableState = remember { + mutableStateOf( + Settings.Global.getString( + context.contentResolver, + "revise_device_name" + ) ?: "" + ) + } + val deviceNameCache: MutableState = remember { mutableStateOf(deviceName.value) } + val physicalTotalStorage = formatSize(getPhysicalTotalStorage(context)) + val usedStorage = formatSize(getUsedStorage()) + + val exportLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.CreateDocument("application/json") + ) { uri -> + viewModel.exportSettings(uri) + } + + val importLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.GetContent() + ) { uri -> + viewModel.importSettings(uri) + } + + val bgAlpha = remember { mutableFloatStateOf(1f) } + val mainAlpha = remember { mutableFloatStateOf(1f) } + val mainScale = remember { mutableFloatStateOf(1f) } + val secAlpha = remember { mutableFloatStateOf(1f) } + val secScale = remember { mutableFloatStateOf(1f) } + val scroll = rememberLazyListState() + + val density = LocalDensity.current + + LaunchedEffect(scroll) { + val min = with(density) { 0.dp.toPx() } + val sec = with(density) { 100.dp.toPx() } + val main = with(density) { 160.dp.toPx() } + val mainHeight = main - sec + val bgHeight = with(density) { 332.dp.toPx() } + + snapshotFlow { Pair(scroll.firstVisibleItemIndex, scroll.firstVisibleItemScrollOffset) } + .onEach { (index, offset) -> + if (index == 0) { + val floatOffset = offset.toFloat() + val alpha = ((bgHeight - floatOffset / 1.6f).coerceIn(min, bgHeight) / bgHeight).coerceIn(0f, 1f) + bgAlpha.floatValue = alpha + + val secValue = ((sec - floatOffset / 1.8f).coerceIn(min, sec) / sec).coerceIn(0f, 1f) + secAlpha.floatValue = secValue + secScale.floatValue = lerp(0.9f, 1f, secValue) + val mainValue = ((main - (floatOffset / 1.3f).coerceIn(sec, main)) / mainHeight).coerceIn(0f, 1f) + mainAlpha.floatValue = (mainValue * 1.5f) + mainScale.floatValue = lerp(0.9f, 1f, mainValue) + } else { + bgAlpha.floatValue = 0f + secAlpha.floatValue = 0f + mainAlpha.floatValue = 0f + } + }.collect() + } + + // --- 主题颜色修正 --- + val colorModeState = LocalColorMode.current + val systemIsDark = isSystemInDarkTheme() + val isFinalDarkMode = when (colorModeState.value) { + 1 -> false // 1 = 白天 + 2 -> true // 2 = 黑夜 + else -> systemIsDark // 0 = 跟随系统 + } + val bgEffectMode = if (isFinalDarkMode) 2 else 1 // BgEffectView 需要 1 或 2 + + Box(Modifier.fillMaxSize()) { + AndroidView( + modifier = Modifier + .fillMaxWidth() + .height(520.dp) + .offset(y = 50.dp), + factory = { ctx -> BgEffectView(ctx, bgEffectMode) }, + update = { + it.updateMode(bgEffectMode) + it.alpha = bgAlpha.floatValue + } + ) + + Column( + modifier = Modifier + .padding(top = 55.dp) + .fillMaxWidth() + .height(520.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + val gradientColors = if (isFinalDarkMode) { + listOf(Color("#D0A279ED".toColorInt()), Color("#D0E3BCB1".toColorInt())) + } else { + listOf(Color("#D03A18AD".toColorInt()), Color("#D0A56138".toColorInt())) + } + Text( + text = "OShin ${BuildConfig.BUILD_TYPE_TAG}", + fontWeight = FontWeight.Bold, + fontSize = 32.sp, + style = TextStyle(brush = Brush.linearGradient(colors = gradientColors), alpha = mainAlpha.floatValue), + modifier = Modifier.scale(mainScale.floatValue) + ) + Text( + text = context.packageManager.getPackageInfo(context.packageName, 0).versionName.toString(), + fontSize = 14.sp, + modifier = Modifier + .fillMaxWidth() + .scale(secScale.floatValue) + .alpha(secAlpha.floatValue) + .padding(top = 20.dp), + fontWeight = FontWeight.Medium, + color = colorScheme.onSurfaceVariantSummary, + textAlign = TextAlign.Center + ) + } + + LazyColumn( + modifier = Modifier + .fillMaxSize() + .overScrollVertical() + .nestedScroll(topAppBarScrollBehavior.nestedScrollConnection), + state = scroll, + contentPadding = PaddingValues(bottom = padding.calculateBottomPadding()), + ) { + item { + Spacer(modifier = Modifier.size(520.dp)) + } + item { + val cardAlpha by derivedStateOf { + if (scroll.firstVisibleItemIndex > 0) 1f else (scroll.firstVisibleItemScrollOffset.toFloat() / 1000f).coerceIn(0f, 1f) + } + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(bottom = 6.dp) + .alpha(cardAlpha) + ) { + val coroutineScope = rememberCoroutineScope() + FunArrow( + title = stringResource(R.string.Device_Name), + position = CouiListItemPosition.Top, + rightText = deviceName.value, + onClick = { + showDeviceNameDialog.value = true + } + ) + if (showDeviceNameDialog.value) { + SuperDialog( + title = stringResource(R.string.Device_Name), + onDismissRequest = { showDeviceNameDialog.value = false }, + show = showDeviceNameDialog + ) { + TextField( + value = deviceNameCache.value, + onValueChange = { deviceNameCache.value = it }, + backgroundColor = colorScheme.secondaryContainer, + label = "", + modifier = Modifier.padding(), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done) + ) + Spacer(Modifier.height(12.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + TextButton( + modifier = Modifier.weight(1f), + text = stringResource(R.string.cancel), + onClick = { showDeviceNameDialog.value = false } + ) + Spacer(Modifier.width(12.dp)) + TextButton( + modifier = Modifier.weight(1f), + text = stringResource(R.string.ok), + colors = ButtonDefaults.textButtonColorsPrimary(), + onClick = { + deviceName.value = deviceNameCache.value + showDeviceNameDialog.value = false + coroutineScope.launch(Dispatchers.IO) { + executeCommand("settings put global revise_device_name '${deviceName.value}'") + } + } + ) + } + } + } + addline() + FunArrow(title = stringResource(R.string.Device_Memory), + rightText = "$usedStorage / $physicalTotalStorage", + position = CouiListItemPosition.Bottom, + onClick = { openStorageSettings(context) } + ) + } + } + item { Spacer(modifier = Modifier.size(12.dp)) } + item { + val cardAlpha by derivedStateOf { + if (scroll.firstVisibleItemIndex > 0) 1f else (scroll.firstVisibleItemScrollOffset.toFloat() / 1000f).coerceIn(0f, 1f) + } + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(bottom = 6.dp) + .alpha(cardAlpha) + ) { + Text(deviceName.value + "", fontSize = 25.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(20.dp)) + Column(Modifier.padding(start = 20.dp, end = 20.dp, bottom = 10.dp)) { + InfoItem(label = "Commit ID", value = context.packageManager.getPackageInfo(context.packageName, 0).versionName.toString().substringAfterLast(".")) + InfoItem(label = stringResource(R.string.yuki_hook_api_version), value = YukiHookAPI.VERSION) + InfoItem(label = stringResource(R.string.compiled_timestamp), value = YukiHookAPI_Impl.compiledTimestamp.toString()) + InfoItem(label = stringResource(R.string.compiled_time), value = timestampToDateTime(YukiHookAPI_Impl.compiledTimestamp)) + } + } + } + item { SmallTitle(text = stringResource(R.string.by_the_way)) } + item { CommunityCard(context) } + item { SmallTitle(text = stringResource(R.string.thank)) } + item { + ThanksCard( + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + item { + SmallTitle(text = stringResource(R.string.config_management)) + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(bottom = 6.dp) + ) { + Column { + SuperArrow( + title = stringResource(R.string.export_config), + summary = stringResource(R.string.export_config_summary), + position = CouiListItemPosition.Top, + onClick = { + exportLauncher.launch("OShin_Config.json") + } + ) + addline() + SuperArrow( + title = stringResource(R.string.import_config), + summary = stringResource(R.string.import_config_summary), + onClick = { + importLauncher.launch("application/json") + } + ) + addline() + SuperArrow( + title = stringResource(R.string.clear_config), + summary = stringResource(R.string.clear_config_summary), + position = CouiListItemPosition.Bottom, + onClick = { + viewModel.clearAllSettings() + } + ) + } + } + } + item { SmallTitle(text = stringResource(R.string.other)) } + item { + AboutActionsCard( + navController = navController, + context = context, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + item { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 20.dp), + text = "Powered By SYCTeam & 酸奶", + fontSize = MiuixTheme.textStyles.subtitle.fontSize, + fontWeight = FontWeight.Medium, + color = colorScheme.onBackgroundVariant, + textAlign = TextAlign.Center + ) + } + } + + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + val scale by animateFloatAsState( + targetValue = if (isPressed) 0.95f else 1f, + animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow), + label = "" + ) + val (shadowColor, backgroundColor, borderColor) = if (isFinalDarkMode) { + Triple(Color(0x4D000000), Color(0x1FFFFFFF), integerArrayResource(R.array.my_card_stroke_gradient_colors_dark)) + } else { + Triple(Color(0x40000000), Color(0x99FFFFFF), integerArrayResource(R.array.my_card_stroke_gradient_colors_light)) + } + val buttonAlpha by derivedStateOf { + if (scroll.firstVisibleItemIndex > 0) 0f else (1f - (scroll.firstVisibleItemScrollOffset.toFloat() / 300)).coerceIn(0f, 1f) + } + // button of check update + with(sharedTransitionScope) { + Button( + modifier = Modifier + .sharedBounds( + sharedContentState = rememberSharedContentState(key = "update_card_transition"), + animatedVisibilityScope = animatedVisibilityScope + ) + .fillMaxWidth(0.8f) + .wrapContentHeight() + .padding(top = 430.dp) + .offset(y = -(scroll.firstVisibleItemScrollOffset.toFloat() / 3).dp) + .alpha(buttonAlpha) + .align(Alignment.TopCenter) + .scale(scale) + .drawBehind { + val strokeWidth = 1.5.dp.toPx() + val inset = strokeWidth / 2 + drawRoundRect( + brush = Brush.linearGradient( + colors = listOf( + Color(borderColor[1]), + Color(borderColor[0]) + ), + start = Offset(size.width / 2, 0f), + end = Offset(size.width / 2, size.height) + ), + topLeft = Offset(inset, inset), + size = Size(size.width - strokeWidth, size.height - strokeWidth), + cornerRadius = CornerRadius(16.dp.toPx()), + style = Stroke(width = strokeWidth) + ) + } + .shadow( + elevation = 1.5.dp, + shape = G2RoundedCornerShape(16.dp), + clip = true, + ambientColor = shadowColor, + spotColor = shadowColor + ), + onClick = { navController.navigate("software_update") }, + interactionSource = interactionSource, + colors = backgroundColor + ) { + Text(text = stringResource(R.string.check_update), fontSize = 17.sp, fontWeight = FontWeight.SemiBold, color = colorScheme.onSurface) + } + } + } +} + +// --- 以下为未改动的辅助组件和函数 --- + +@Composable +private fun InfoItem(label: String, value: String) { + Column(Modifier.padding(bottom = 10.dp)) { + Text(value, fontSize = 14.sp) + Text(label, fontSize = 12.sp, color = Color.Gray) + } +} + +@Composable +private fun CommunityCard(context: Context) { + Card(modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(bottom = 6.dp)) { + Card(Modifier.padding(10.dp)) { + Image(painter = painterResource(R.drawable.qq_pic_merged_1727926207595), contentDescription = null, modifier = Modifier.fillMaxWidth()) + } + val toastMessage = stringResource(R.string.please_install_cool_apk) + FunArrow(title = stringResource(R.string.go_to_his_homepage), + position = CouiListItemPosition.Bottom, + onClick = { + val coolApkUri = "coolmarket://u/894238".toUri() + val intent = Intent(Intent.ACTION_VIEW, coolApkUri) + try { + context.startActivity(intent) + } catch (e: ActivityNotFoundException) { + Toast.makeText(context, toastMessage, Toast.LENGTH_SHORT).show() + } + }) + } +} + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +private fun ThanksCard( + navController: NavController, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + Card(modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(bottom = 6.dp)) { + item( + name = "酸奶", + coolapk = "Stracha酸奶菌", + coolapkid = 15225420, + github = "suqi8", + qq = 3383787570 + ) + addline() + with(sharedTransitionScope) { + Box( + modifier = Modifier + .sharedBounds( + sharedContentState = rememberSharedContentState(key = "about_contributors"), + animatedVisibilityScope = animatedVisibilityScope + ) + .fillMaxWidth() + .wrapContentHeight() + ) { + FunArrow(title = stringResource(R.string.contributors), onClick = { navController.navigate("about_contributors") }) + } + } + addline() + with(sharedTransitionScope) { + Box( + modifier = Modifier + .sharedBounds( + sharedContentState = rememberSharedContentState(key = "about_references"), + animatedVisibilityScope = animatedVisibilityScope + ) + .fillMaxWidth() + .wrapContentHeight() + ) { + FunArrow( + title = stringResource(R.string.references), + position = CouiListItemPosition.Bottom, + onClick = { navController.navigate("about_references") } + ) + } + } + } +} + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +private fun AboutActionsCard( + navController: NavController, + context: Context, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(bottom = 6.dp) + ) { + with(sharedTransitionScope) { + Box( + modifier = Modifier + .sharedBounds( + sharedContentState = rememberSharedContentState(key = "about_setting"), + animatedVisibilityScope = animatedVisibilityScope + ) + .fillMaxWidth() + .wrapContentHeight() + ) { + IconFunArrow( + title = stringResource(R.string.settings), + iconRes = R.drawable.settings, + position = CouiListItemPosition.Top, + onClick = { navController.navigate("about_setting") }) + } + } + addline() + IconFunArrow( + title = stringResource(R.string.donors), + iconRes = R.drawable.donors, + onClick = { openUrl(context, "https://oshin.mikusignal.top/docs/donate.html") }) + addline() + with(sharedTransitionScope) { + Box( + modifier = Modifier + .sharedBounds( + sharedContentState = rememberSharedContentState(key = "about_group"), // <-- Key + animatedVisibilityScope = animatedVisibilityScope + ) + .fillMaxWidth() + .wrapContentHeight() + ) { + IconFunArrow( + title = stringResource(R.string.official_channel), + iconRes = R.drawable.group, + onClick = { navController.navigate("about_group") }) + } + } + addline() + IconFunArrow( + title = stringResource(R.string.official_website), + iconRes = R.drawable.website, + onClick = { openUrl(context, "https://oshin.mikusignal.top/") }) + addline() + IconFunArrow( + title = "GitHub", + summary = stringResource(R.string.github_summary), + iconRes = R.drawable.github, + onClick = { openUrl(context, "https://github.com/suqi8/OShin") }) + addline() + IconFunArrow( + title = stringResource(R.string.contribute_translation), + summary = stringResource(R.string.crowdin_contribute_summary), + iconRes = R.drawable.translators, + position = CouiListItemPosition.Bottom, + onClick = { + openUrl( + context, + "https://github.com/suqi8/OShin/tree/master/app/src/main/res" + ) + }) + } +} + +@Composable +fun IconFunArrow( + title: String, + summary: String? = null, + iconRes: Int, + position: CouiListItemPosition = CouiListItemPosition.Single, + onClick: () -> Unit +) { + FunArrow( + title = title, + summary = summary, + position = position, + leftAction = { + Image( + painter = painterResource(iconRes), + contentDescription = null, + modifier = Modifier.size(24.dp), + colorFilter = ColorFilter.tint(colorScheme.onSurface) + ) + }, + onClick = onClick + ) +} + +@Composable +fun Button(onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, cornerRadius: Dp = ButtonDefaults.CornerRadius, minWidth: Dp = ButtonDefaults.MinWidth, minHeight: Dp = ButtonDefaults.MinHeight, colors: Color = colorScheme.secondaryVariant, insideMargin: PaddingValues = ButtonDefaults.InsideMargin, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, content: @Composable RowScope.() -> Unit) { + Surface(modifier = modifier + .semantics { role = Role.Button } + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + onClick = onClick + ), shape = G2RoundedCornerShape(cornerRadius), color = colors) { + Row(Modifier + .defaultMinSize(minWidth = minWidth, minHeight = minHeight) + .padding(insideMargin), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, content = content) + } +} + +@SuppressLint("UseKtx") +fun openUrl(context: Context, url: String) { + val intent = Intent(Intent.ACTION_VIEW, url.toUri()) + context.startActivity(intent) +} + +private fun openStorageSettings(context: Context) { + try { + val intent = Intent().setClassName("com.android.settings", + $$"com.android.settings.Settings$StorageDashboardActivity" + ) + context.startActivity(intent) + } catch (e: Exception) { + e.printStackTrace() + Toast.makeText(context, "无法打开存储管理页面", Toast.LENGTH_SHORT).show() + } +} + +fun timestampToDateTime(timestamp: Long): String { + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault()) + return formatter.format(Instant.ofEpochMilli(timestamp)) +} + +fun getPhysicalTotalStorage(context: Context): Long { + val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager + + val primaryVolume = storageManager.storageVolumes.firstOrNull { it.isPrimary } + + val path = primaryVolume?.directory?.path + + if (path != null) { + try { + val statFs = StatFs(path) + return statFs.blockCountLong * statFs.blockSizeLong + } catch (e: IllegalArgumentException) { + // StatFs 可能会因为路径无效而抛出异常 + e.printStackTrace() + } + } + + // 如果以上方法失败,则使用备用方法 + return getTotalStorage() +} + +@SuppressLint("DefaultLocale") +fun formatSize(size: Long): String { + if (size <= 0) return "0 B" + val units = arrayOf("B", "KB", "MB", "GB", "TB") + val digitGroups = (log10(size.toDouble()) / log10(1024.0)).toInt() + return String.format("%.2f %s", size / 1024.0.pow(digitGroups.toDouble()), units[digitGroups]) +} + +fun getTotalStorage(): Long { + return try { StatFs(Environment.getDataDirectory().path).totalBytes } + catch (e: Exception) { 0L } +} + +fun getAvailableStorage(): Long { + return try { StatFs(Environment.getDataDirectory().path).availableBytes } + catch (e: Exception) { 0L } +} + +fun getUsedStorage(): Long { + val totalStorage = getTotalStorage() + val availableStorage = getAvailableStorage() + return totalStorage - availableStorage +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/SettingsViewModel.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/SettingsViewModel.kt new file mode 100644 index 00000000..2dd13c27 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/SettingsViewModel.kt @@ -0,0 +1,112 @@ +package com.suqi8.oshin.ui.mainscreen.about + +import android.content.Context +import android.net.Uri +import android.widget.Toast +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.gson.GsonBuilder +import com.google.gson.reflect.TypeToken +import com.highcapable.yukihookapi.hook.factory.prefs +import com.suqi8.oshin.R +import com.suqi8.oshin.features.FeatureRegistry +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.BufferedReader +import java.io.InputStreamReader +import javax.inject.Inject + +@HiltViewModel +class SettingsViewModel @Inject constructor( + @ApplicationContext private val context: Context +) : ViewModel() { + + private val gson = GsonBuilder().setPrettyPrinting().create() + + private val allPrefsFiles = FeatureRegistry.screenMap.values + .map { it.category.substringBefore('\\') } + .distinct() + "settings" + + fun exportSettings(uri: Uri?) { + if (uri == null) return + viewModelScope.launch(Dispatchers.IO) { + try { + val allSettings = mutableMapOf>() + allPrefsFiles.forEach { prefName -> + val prefs = context.prefs(prefName) + if (prefs.all().isNotEmpty()) { + allSettings[prefName] = prefs.all() + } + } + val jsonString = gson.toJson(allSettings) + context.contentResolver.openOutputStream(uri)?.use { outputStream -> + outputStream.write(jsonString.toByteArray()) + } + withContext(Dispatchers.Main) { + Toast.makeText(context, context.getString(R.string.export_success), Toast.LENGTH_SHORT).show() + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + Toast.makeText(context, context.getString(R.string.export_fail, e.message), Toast.LENGTH_SHORT).show() + } + } + } + } + + fun importSettings(uri: Uri?) { + if (uri == null) return + viewModelScope.launch(Dispatchers.IO) { + try { + val jsonString = context.contentResolver.openInputStream(uri)?.use { inputStream -> + BufferedReader(InputStreamReader(inputStream)).readText() + } ?: throw IllegalStateException("无法读取文件") + + val type = TypeToken.getParameterized( + Map::class.java, + String::class.java, + TypeToken.get(Map::class.java).type + ).type + val allSettings: Map> = gson.fromJson(jsonString, type) + + allSettings.forEach { (prefName, settings) -> + val editor = context.prefs(prefName).edit() + settings.forEach { (key, value) -> + when (value) { + is String -> editor.putString(key, value) + is Boolean -> editor.putBoolean(key, value) + is Number -> { + val doubleValue = value.toDouble() + if (doubleValue % 1 == 0.0 && doubleValue <= Int.MAX_VALUE && doubleValue >= Int.MIN_VALUE) + editor.putInt(key, doubleValue.toInt()) + else + editor.putFloat(key, doubleValue.toFloat()) + } + } + } + editor.apply() + } + withContext(Dispatchers.Main) { + Toast.makeText(context, context.getString(R.string.import_success), Toast.LENGTH_LONG).show() + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + Toast.makeText(context, context.getString(R.string.import_fail, e.message), Toast.LENGTH_SHORT).show() + } + } + } + } + + fun clearAllSettings() { + viewModelScope.launch(Dispatchers.IO) { + allPrefsFiles.forEach { prefName -> + context.prefs(prefName).edit().clear().apply() + } + withContext(Dispatchers.Main) { + Toast.makeText(context, context.getString(R.string.clear_success), Toast.LENGTH_LONG).show() + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/about_contributors.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/about_contributors.kt new file mode 100644 index 00000000..109eb1fa --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/about_contributors.kt @@ -0,0 +1,309 @@ +package com.suqi8.oshin.ui.mainscreen.about + +import android.content.ActivityNotFoundException +import android.content.Intent +import android.net.Uri +import android.widget.Toast +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.core.net.toUri +import androidx.navigation.NavController +import coil3.compose.AsyncImage +import coil3.request.CachePolicy +import coil3.request.ImageRequest +import com.kyant.capsule.ContinuousRoundedRectangle +import com.suqi8.oshin.R +import com.suqi8.oshin.ui.activity.components.BasicComponent +import com.suqi8.oshin.ui.activity.components.BasicComponentColors +import com.suqi8.oshin.ui.activity.components.Card +import com.suqi8.oshin.ui.activity.components.CardDefaults +import com.suqi8.oshin.ui.activity.components.FunArrow +import com.suqi8.oshin.ui.activity.components.FunPage +import com.suqi8.oshin.ui.activity.components.addline +import com.suqi8.oshin.ui.mainscreen.home.ModernSectionTitle +import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior +import top.yukonga.miuix.kmp.basic.rememberTopAppBarState +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.utils.overScrollVertical +import top.yukonga.miuix.kmp.utils.scrollEndHaptic + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun about_contributors( + navController: NavController, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + val scrollBehavior = MiuixScrollBehavior(rememberTopAppBarState()) + + FunPage( + navController = navController, + scrollBehavior = scrollBehavior, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + animationKey = "about_contributors" + ) { padding -> + LazyColumn( + modifier = Modifier + .fillMaxSize() + .overScrollVertical() + .scrollEndHaptic() + .nestedScroll(scrollBehavior.nestedScrollConnection), + contentPadding = padding + ) { + item { + ModernSectionTitle( + title = stringResource(id = R.string.contributors), + modifier = Modifier + .displayCutoutPadding() + .padding(top = padding.calculateTopPadding() + 72.dp, bottom = 8.dp) + ) + } + item { + Card(colors = CardDefaults.defaultColors(color = MiuixTheme.colorScheme.primaryVariant.copy(alpha = 0.1f))) { + BasicComponent( + summary = stringResource(R.string.thanks_contributors), + summaryColor = BasicComponentColors( + enabledColor = MiuixTheme.colorScheme.primaryVariant, + disabledColor = MiuixTheme.colorScheme.primaryVariant + ) + ) + } + } + item { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(bottom = 6.dp) + ) { + item( + name = "YuKong_A", + github = "YuKongA" + ) + addline() + item( + name = "天伞桜", + coolapk = "天伞桜", + coolapkid = 540690 + ) + addline() + item( + name = "shadow3", + github = "shadow3aaa" + ) + addline() + item( + name = "凌逸", + coolapk = "网恋秀牛被骗", + coolapkid = 34081897 + ) + addline() + item( + name = "psychosispy", + github = "psychosispy" + ) + addline() + item( + name = "咚踏取", + coolapk = "咚踏取", + coolapkid = 2035174 + ) + addline() + item( + name = "Mikusignal", + coolapk = "Mikusignal", + coolapkid = 12130388 + ) + addline() + item( + name = "hamjin", + github = "hamjin" + ) + addline() + item( + name = "kmiit", + github = "kmiit" + ) + addline() + item( + name = "Андрей", + github = "fatal1101" + ) + addline() + item( + name = "Jeong Seongpil", + github = "jjhitel" + ) + addline() + item( + name = "Pencil", + coolapk = "已跑路_勿扰", + coolapkid = 35139340 + ) + addline() + item( + name = "Liberation", + github = "Liberations" + ) + } + } + } + } +} + +@Composable +internal fun item( + name: String, + coolapk: String? = null, + coolapkid: Int? = null, + github: String? = null, + qq: Long? = null +) { + val context = LocalContext.current + var showExtra by remember { mutableStateOf(false) } + val toastMessage = stringResource(R.string.please_install_cool_apk) + + // 公共启动函数 + fun launchUri(uri: Uri) { + try { + context.startActivity(Intent(Intent.ACTION_VIEW, uri)) + } catch (e: ActivityNotFoundException) { + Toast.makeText(context, toastMessage, Toast.LENGTH_SHORT).show() + } + } + + // 拼接summary字符串 + val summaryText = buildString { + coolapk?.let { append("${stringResource(R.string.coolapk)}@$it ") } + github?.let { append("Github@$it ") } + qq?.let { append("QQ@$it ") } + } + FunArrow( + title = name, + leftAction = { + qq?.let { + Column(modifier = Modifier + .padding(end = 10.dp)) { + AsyncImage( + model = ImageRequest.Builder(context) + .data("https://q.qlogo.cn/headimg_dl?dst_uin=$it&spec=640&img_type=jpg") + .diskCachePolicy(CachePolicy.DISABLED) // 禁用磁盘缓存 + .build(), + contentDescription = null, + modifier = Modifier + .size(50.dp) + .clip(ContinuousRoundedRectangle(15.dp)) + ) + } + } + }, + summary = summaryText, + onClick = { + // 如果两个及以上信息存在,则弹出卡片,否则直接跳转 + val infoCount = listOfNotNull(coolapk, github, qq).size + if (infoCount >= 2) { + showExtra = !showExtra + } else { + when { + coolapk != null -> coolapkid?.let { launchUri("coolmarket://u/$it".toUri()) } + github != null -> launchUri("https://github.com/$github".toUri()) + qq != null -> launchUri("mqqapi://card/show_pslcard?src_type=internal&version=1&uin=$qq".toUri()) + } + } + } + ) + + AnimatedVisibility(visible = showExtra) { + Card( + colors = CardDefaults.defaultColors(color = MiuixTheme.colorScheme.secondaryContainer), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 12.dp) + ) { + Column { + coolapk?.let { + FunArrow( + title = stringResource(R.string.coolapk), + leftAction = { + Image( + painter = painterResource(R.drawable.coolapk), + contentDescription = null, + modifier = Modifier + .size(32.dp) + .padding(end = 8.dp), + colorFilter = ColorFilter.tint(MiuixTheme.colorScheme.onSurface) + ) + }, + onClick = { + coolapkid?.let { id -> + launchUri("coolmarket://u/$id".toUri()) + } + } + ) + addline() + } + github?.let { + FunArrow( + title = "Github", + leftAction = { + Image( + painter = painterResource(R.drawable.github), + contentDescription = null, + modifier = Modifier + .size(32.dp) + .padding(end = 8.dp), + colorFilter = ColorFilter.tint(MiuixTheme.colorScheme.onSurface) + ) + }, + onClick = { + launchUri("https://github.com/$it".toUri()) + } + ) + addline() + } + qq?.let { + FunArrow( + title = "QQ", + leftAction = { + Image( + painter = painterResource(R.drawable.qq), + contentDescription = null, + modifier = Modifier + .size(32.dp) + .padding(end = 8.dp), + colorFilter = ColorFilter.tint(MiuixTheme.colorScheme.onSurface) + ) + }, + onClick = { + launchUri("mqqapi://card/show_pslcard?src_type=internal&version=1&uin=$it".toUri()) + } + ) + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/about_group.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/about_group.kt new file mode 100644 index 00000000..6ab3b3aa --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/about_group.kt @@ -0,0 +1,150 @@ +package com.suqi8.oshin.ui.mainscreen.about + +import android.content.Intent +import android.net.Uri +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.core.net.toUri +import androidx.navigation.NavController +import com.suqi8.oshin.R +import com.suqi8.oshin.ui.activity.components.Card +import com.suqi8.oshin.ui.activity.components.FunArrow +import com.suqi8.oshin.ui.activity.components.FunPage +import com.suqi8.oshin.ui.activity.components.addline +import com.suqi8.oshin.ui.mainscreen.home.ModernSectionTitle +import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior +import top.yukonga.miuix.kmp.basic.SmallTitle +import top.yukonga.miuix.kmp.basic.rememberTopAppBarState +import top.yukonga.miuix.kmp.utils.overScrollVertical +import top.yukonga.miuix.kmp.utils.scrollEndHaptic + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun about_group( + navController: NavController, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + val context = LocalContext.current + + val scrollBehavior = MiuixScrollBehavior(rememberTopAppBarState()) + + FunPage( + navController = navController, + scrollBehavior = scrollBehavior, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + animationKey = "about_group" + ) { padding -> + LazyColumn( + modifier = Modifier + .fillMaxSize() + .overScrollVertical() + .scrollEndHaptic() + .nestedScroll(scrollBehavior.nestedScrollConnection), + contentPadding = padding + ) { + item { + ModernSectionTitle( + title = stringResource(id = R.string.discussion_group), + modifier = Modifier + .displayCutoutPadding() + .padding(top = padding.calculateTopPadding() + 72.dp, bottom = 8.dp) + ) + } + item { SmallTitle(text = "Telegram") } + item { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(bottom = 6.dp) + ) { + FunArrow(title = stringResource(id = R.string.official_channel), + onClick = { + val telegramIntent = Intent(Intent.ACTION_VIEW) + telegramIntent.data = "tg://resolve?domain=OPatchA".toUri() + // 检查是否安装了 Telegram 应用 + if (telegramIntent.resolveActivity(context.packageManager) != null) { + context.startActivity(telegramIntent) + } else { + // 如果未安装 Telegram,可以显示一个提示或打开 Telegram 网页版 + val webIntent = + Intent(Intent.ACTION_VIEW, "https://t.me/OPatchA".toUri()) + context.startActivity(webIntent) + } + }) + addline() + FunArrow(title = stringResource(id = R.string.discussion_group), + onClick = { + val telegramIntent = Intent(Intent.ACTION_VIEW) + telegramIntent.data = "tg://resolve?domain=OPatchB".toUri() + // 检查是否安装了 Telegram 应用 + if (telegramIntent.resolveActivity(context.packageManager) != null) { + context.startActivity(telegramIntent) + } else { + // 如果未安装 Telegram,可以显示一个提示或打开 Telegram 网页版 + val webIntent = + Intent(Intent.ACTION_VIEW, "https://t.me/OPatchB".toUri()) + context.startActivity(webIntent) + } + }) + addline() + FunArrow(title = stringResource(id = R.string.auto_build_release), + onClick = { + val telegramIntent = Intent(Intent.ACTION_VIEW) + telegramIntent.data = "tg://resolve?domain=OPatchC".toUri() + // 检查是否安装了 Telegram 应用 + if (telegramIntent.resolveActivity(context.packageManager) != null) { + context.startActivity(telegramIntent) + } else { + // 如果未安装 Telegram,可以显示一个提示或打开 Telegram 网页版 + val webIntent = + Intent(Intent.ACTION_VIEW, "https://t.me/OPatchC".toUri()) + context.startActivity(webIntent) + } + }) + } + } + item { SmallTitle(text = "QQ") } + item { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(bottom = 6.dp) + ) { + FunArrow(title = stringResource(id = R.string.discussion_group), + onClick = { + val qqIntent = Intent(Intent.ACTION_VIEW) + // 使用 mqqwpa 协议来打开 QQ 群 + qqIntent.data = + Uri.parse("mqqapi://card/show_pslcard?src_type=internal&version=1&uin=740266099&card_type=group&source=qrcode") + // 检查是否安装了 QQ 应用 + if (qqIntent.resolveActivity(context.packageManager) != null) { + context.startActivity(qqIntent) + } else { + val webIntent = Intent( + Intent.ACTION_VIEW, + Uri.parse("http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=dbP78P2qCYuR2RxGtwmwCrlMCsh2MB2N&authKey=uTkJAGf0gg7%2Fx%2B3OBPrf%2F%2FnyZY2ntPNvnz6%2BTUo%2BHa0Pe%2F%2FqtXvK%2BSJ3%2B4PS0zbO&noverify=0&group_code=740266099") + ) + context.startActivity(webIntent) + } + }) + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/about_references.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/about_references.kt new file mode 100644 index 00000000..e0211f6e --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/about_references.kt @@ -0,0 +1,262 @@ +package com.suqi8.oshin.ui.mainscreen.about + +import android.content.Intent +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.core.net.toUri +import androidx.navigation.NavController +import com.suqi8.oshin.R +import com.suqi8.oshin.ui.activity.components.BasicComponent +import com.suqi8.oshin.ui.activity.components.BasicComponentColors +import com.suqi8.oshin.ui.activity.components.Card +import com.suqi8.oshin.ui.activity.components.CardDefaults +import com.suqi8.oshin.ui.activity.components.FunArrow +import com.suqi8.oshin.ui.activity.components.FunPage +import com.suqi8.oshin.ui.activity.components.addline +import com.suqi8.oshin.ui.mainscreen.home.ModernSectionTitle +import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior +import top.yukonga.miuix.kmp.basic.SmallTitle +import top.yukonga.miuix.kmp.basic.rememberTopAppBarState +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.utils.overScrollVertical +import top.yukonga.miuix.kmp.utils.scrollEndHaptic + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun about_references( + navController: NavController, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + val scrollBehavior = MiuixScrollBehavior(rememberTopAppBarState()) + + // (5. 调用 FunPage v2) + FunPage( + navController = navController, + scrollBehavior = scrollBehavior, // <-- 传递 + sharedTransitionScope = sharedTransitionScope, // <-- 传递 + animatedVisibilityScope = animatedVisibilityScope, // <-- 传递 + animationKey = "about_references" // <-- (6. 设置 Key) + ) { padding -> + // (7. 添加 LazyColumn) + LazyColumn( + modifier = Modifier + .fillMaxSize() + .overScrollVertical() + .scrollEndHaptic() + .nestedScroll(scrollBehavior.nestedScrollConnection), + contentPadding = padding + ) { + item { + ModernSectionTitle( + title = stringResource(id = R.string.references), + modifier = Modifier + .displayCutoutPadding() + .padding(top = padding.calculateTopPadding() + 72.dp, bottom = 8.dp) + ) + } + item { + Card(colors = CardDefaults.defaultColors(color = MiuixTheme.colorScheme.primaryVariant.copy(alpha = 0.1f))) { + BasicComponent( + summary = stringResource(R.string.thanks_open_source_projects), + summaryColor = BasicComponentColors( + enabledColor = MiuixTheme.colorScheme.primaryVariant, + disabledColor = MiuixTheme.colorScheme.primaryVariant + ) + ) + } + } + item { + SmallTitle(text = stringResource(R.string.open_source_project)) + } + item { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(bottom = 6.dp) + ) { + about_references_item( + name = "Android", + username = "Android Open Source Project, Google Inc.", + url = "https://source.android.google.cn/license", + license = "Apache-2.0" + ) + addline() + about_references_item( + name = "AndroidX", + username = "Android Open Source Project, Google Inc.", + url = "https://github.com/androidx/androidx", + license = "Apache-2.0" + ) + addline() + about_references_item( + name = "CorePatch", + username = "LSPosed", + url = "https://github.com/LSPosed/CorePatch", + license = "GPL-2.0" + ) + addline() + about_references_item( + name = "Gson", + username = "Android Open Source Project, Google Inc.", + url = "https://github.com/google/gson", + license = "Apache-2.0" + ) + addline() + about_references_item( + name = "Kotlin", + username = "JetBrains", + url = "https://github.com/JetBrains/kotlin" + ) + addline() + about_references_item( + name = "Xposed", + username = "rovo89, Tungstwenty", + url = "https://github.com/rovo89/XposedBridge" + ) + addline() + about_references_item( + name = "YukiHookAPI", + username = "HighCapable", + url = "https://github.com/HighCapable/YukiHookAPI", + license = "Apache-2.0" + ) + addline() + about_references_item( + name = "Compose", + username = "JetBrains", + url = "https://github.com/JetBrains/compose", + license = "Apache-2.0" + ) + addline() + about_references_item( + name = "Miuix", + username = "YuKongA", + url = "https://github.com/miuix-kotlin-multiplatform/miuix", + license = "Apache-2.0" + ) + addline() + about_references_item( + name = "Magisk", + username = "topjohnwu", + url = "https://github.com/topjohnwu/Magisk", + license = "GPL-3.0" + ) + addline() + about_references_item( + name = "LSPosed", + username = "LSPosed", + url = "https://github.com/LSPosed/LSPosed", + license = "GPL-3.0" + ) + addline() + about_references_item( + name = "coloros-aod", + username = "Flyfish233", + url = "https://github.com/Flyfish233/coloros-aod" + ) + addline() + about_references_item( + name = "QAuxiliary", + username = "cinit", + url = "https://github.com/cinit/QAuxiliary", + license = "通用许可证") + addline() + about_references_item( + name = "HyperCeiler", + username = "Re.chronoRain & Sevtinge", + url = "https://github.com/ReChronoRain/HyperCeiler", + license = "AGPL-3.0") + addline() + about_references_item( + name = "Free Notifications", + username = "binarynoise", + url = "https://github.com/binarynoise/XposedModulets/tree/main/FreeNotifications", + license = "EUPL-1.2") + addline() + about_references_item( + name = "Liquid Glass", + username = "Kyant0", + url = "https://github.com/Kyant0/AndroidLiquidGlass", + license = "Apache-2.0") + addline() + about_references_item( + name = "HyperStar", + username = "YunZiA", + url = "https://github.com/YunZiA/HyperStar", + license = "GPL-3.0") + addline() + about_references_item( + name = "Oxygen-Customizer", + username = "DHD2280", + url = "https://github.com/DHD2280/Oxygen-Customizer", + license = "GPL-3.0") + addline() + about_references_item( + name = "Capsule", + username = "Kyant0", + url = "https://github.com/Kyant0/Capsule", + license = "Apache-2.0") + addline() + about_references_item( + name = "XposedSmsCode", + username = "tianma8023", + url = "https://github.com/tianma8023/XposedSmsCode", + license = "GPL-3.0") + } + } + item { + SmallTitle(text = stringResource(R.string.closed_source_project)) + } + item { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(bottom = 6.dp) + ) { + about_references_item( + name = "LuckyTool", + username = "luckyzyx", + url = "https://github.com/Xposed-Modules-Repo/com.luckyzyx.luckytool" + ) + } + } + } + } +} + +@Composable +fun about_references_item( + name: String, + username: String, + url: String? = null, + license: String? = null +) { + val context = LocalContext.current + FunArrow(title = name, + summary = username + if (license != null) " | $license" else " | " + stringResource(R.string.no_license), + onClick = { + if (url != null) { + val intent = Intent( + Intent.ACTION_VIEW, + url.toUri() + ) + context.startActivity(intent) + } + } + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/about_setting.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/about_setting.kt new file mode 100644 index 00000000..f1798304 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/about/about_setting.kt @@ -0,0 +1,228 @@ +package com.suqi8.oshin.ui.mainscreen.about + +import android.annotation.SuppressLint +import android.content.ComponentName +import android.content.pm.PackageManager +import android.content.res.Configuration +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.runtime.currentRecomposeScope +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringArrayResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.LottieConstants +import com.airbnb.lottie.compose.animateLottieCompositionAsState +import com.airbnb.lottie.compose.rememberLottieComposition +import com.highcapable.yukihookapi.hook.factory.prefs +import com.suqi8.oshin.R +import com.suqi8.oshin.ui.activity.components.Card +import com.suqi8.oshin.ui.activity.components.FunPage +import com.suqi8.oshin.ui.activity.components.FunSwitch +import com.suqi8.oshin.ui.activity.components.SuperDropdown +import com.suqi8.oshin.ui.activity.components.SuperSwitch +import com.suqi8.oshin.ui.activity.components.addline +import com.suqi8.oshin.ui.mainscreen.home.ModernSectionTitle +import com.suqi8.oshin.ui.mainscreen.LocalColorMode +import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior +import top.yukonga.miuix.kmp.basic.rememberTopAppBarState +import top.yukonga.miuix.kmp.utils.overScrollVertical +import top.yukonga.miuix.kmp.utils.scrollEndHaptic +import java.util.Locale + +@OptIn(ExperimentalSharedTransitionApi::class) +@SuppressLint("LocalContextConfigurationRead") +@Composable +fun about_setting( + navController: NavController, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + val context = LocalContext.current + val colorModeState = LocalColorMode.current + val colorMode = colorModeState.value + + val scrollBehavior = MiuixScrollBehavior(rememberTopAppBarState()) + + FunPage( + navController = navController, + scrollBehavior = scrollBehavior, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + animationKey = "about_setting" + ) { padding -> + // (7. 添加 LazyColumn) + LazyColumn( + modifier = Modifier + .fillMaxSize() + .overScrollVertical() + .scrollEndHaptic() + .nestedScroll(scrollBehavior.nestedScrollConnection), + contentPadding = padding + ) { + item { + ModernSectionTitle( + title = stringResource(id = R.string.settings), + modifier = Modifier + .displayCutoutPadding() + .padding(top = padding.calculateTopPadding() + 72.dp, bottom = 8.dp) + ) + } + item { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(bottom = 6.dp) + .padding(top = 15.dp) + ) { + val compositionResult = + rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.setting)) + val progress = + animateLottieCompositionAsState( + composition = compositionResult.value, + iterations = LottieConstants.IterateForever + ) + LottieAnimation( + composition = compositionResult.value, + progress = { progress.progress }, + modifier = Modifier.padding(1.dp) + ) + } + } + item { + Card { + SuperDropdown( + title = stringResource(R.string.Color_Mode), + items = listOf( + stringResource(R.string.Auto_Mode), + stringResource(R.string.Light_Mode), + stringResource( + R.string.Night_Mode + ) + ), + selectedIndex = colorMode, + onSelectedIndexChange = { + colorModeState.value = it + context.prefs("settings").edit { putInt("color_mode", it) } + } + ) + addline() + val context = LocalContext.current + // 1. 确保你的 R.array.language 数组里已经加上了“梗体中文” + // 假设它在第6个位置,也就是索引为 5 + val languageArray = stringArrayResource(id = R.array.language).toList() + val selectedLanguageIndex = remember { mutableStateOf(context.prefs("settings").getInt("app_language", 0)) } + val recompose = currentRecomposeScope + + // 2. 切换语言逻辑 + fun changeLanguage(index: Int) { + // ✨ 在这里添加我们的自定义语言 + val newLocale = when (index) { + 1 -> Locale.SIMPLIFIED_CHINESE + 2 -> Locale.ENGLISH + 3 -> Locale.JAPANESE + 4 -> Locale.Builder().setLanguage("ru").build() + 5 -> Locale.Builder().setLanguage("qaa").setExtension('x', "meme").build() + 6 -> Locale.KOREAN + else -> Locale.getDefault() // 跟随系统 + } + + val resources = context.resources + val config = Configuration(resources.configuration) + config.setLocale(newLocale) + + // 更新应用配置 + resources.updateConfiguration(config, resources.displayMetrics) + + // 强制重组以刷新UI + recompose.invalidate() + } + + SuperDropdown( + title = stringResource(R.string.app_language), + items = languageArray, + selectedIndex = selectedLanguageIndex.value, + onSelectedIndexChange = { index -> + selectedLanguageIndex.value = index + context.prefs("settings").edit { putInt("app_language", index) } + changeLanguage(index) + } + ) + addline() + val updateChannelIndex = remember { mutableStateOf(context.prefs("settings").getInt("app_update_channel", 0)) } + SuperDropdown( + title = stringResource(R.string.update_channel), + items = listOf( + stringResource(R.string.update_page_tab_release), // "Release" + stringResource(R.string.update_page_tab_ci) // "CI Build" + ), + selectedIndex = updateChannelIndex.value, + onSelectedIndexChange = { index -> + updateChannelIndex.value = index + context.prefs("settings").edit { putInt("app_update_channel", index) } + } + ) + addline() + FunSwitch( + title = "Debug", + category = "settings", + key = "Debug" + ) + addline() + FunSwitch( + title = stringResource(R.string.addline), + category = "settings", + key = "addline", + defValue = true + ) + addline() + val componentName = ComponentName(context, "com.suqi8.oshin.Home") + val pm = context.packageManager + val isIconVisible = remember { + mutableStateOf( + try { + pm.getComponentEnabledSetting(componentName) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED + } catch (e: Exception) { + true + } + ) + } + SuperSwitch(title = stringResource(R.string.hide_launcher_icon), + checked = !isIconVisible.value, + onCheckedChange = { hide -> + isIconVisible.value = !hide + pm.setComponentEnabledSetting( + componentName, + if (hide) + PackageManager.COMPONENT_ENABLED_STATE_DISABLED + else + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP + ) + }) + addline() + FunSwitch( + title = stringResource(R.string.disable_bottom_bar_glass), + category = "settings", + key = "bottomtab" + ) + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/home/HomeViewModel.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/home/HomeViewModel.kt new file mode 100644 index 00000000..c937d0b2 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/home/HomeViewModel.kt @@ -0,0 +1,437 @@ +package com.suqi8.oshin.ui.mainscreen + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Build +import android.util.Log +import android.view.View +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.highcapable.yukihookapi.YukiHookAPI +import com.suqi8.oshin.R +import com.suqi8.oshin.data.repository.FeatureRepository +import com.suqi8.oshin.ui.mainscreen.module.SearchableItem +import com.suqi8.oshin.utils.RouteFormatter +import com.umeng.union.UMNativeAD +import com.umeng.union.UMUnionSdk +import com.umeng.union.api.UMAdConfig +import com.umeng.union.api.UMUnionApi +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import org.json.JSONArray +import org.json.JSONObject +import java.io.BufferedReader +import java.io.InputStreamReader +import javax.inject.Inject + +// --- 数据模型 --- + +enum class Status { LOADING, SUCCESS, ERROR, WARNING } + +data class ModuleStatus(val status: Status, val message: String = "") +data class RootStatus(val status: Status, val version: String = "") +data class FridaStatus(val status: Status, val version: String = "") + +data class DeviceInfo( + val country: String, + val androidVersion: String, + val sdkVersion: String, + val systemVersion: String, + val designCapacity: Int, + val currentCapacity: Int, + val fullCapacity: Int, + val batteryHealthDisplay: String, + val batteryHealthRaw: String, + val batteryHealthPercent: Float, + val calculatedHealth: Float, + val cycleCount: Int, + val chipSoc: Int +) + +data class CarouselItem( + val title: String?, + val description: String?, + val actionUrl: String?, + val imageUrl: String? +) + +data class HomeUiState( + val carouselItems: List? = null, + val moduleStatus: ModuleStatus = ModuleStatus(Status.LOADING), + val rootStatus: RootStatus = RootStatus(Status.LOADING), + val fridaStatus: FridaStatus = FridaStatus(Status.ERROR, "未连接"), + val deviceInfo: DeviceInfo? = null, + val randomFeatures: List = emptyList(), + val combinedCarouselItems: List = emptyList(), + val isCarouselLoading: Boolean = true +) + +data class HighlightFeature( + val searchableItem: SearchableItem, // 原始数据 + val formattedRoute: String // 预先格式化好的路由 +) + +sealed interface CarouselContent { + // 为每个卡片提供一个唯一的 key,这对于 Pager 的性能和稳定性很重要 + val key: Any + + data class Promo(val item: CarouselItem) : CarouselContent { + override val key: Any = item.actionUrl ?: item.imageUrl ?: item.title ?: "promo_${item.hashCode()}" + } + + data class Ad(val ad: UMNativeAD) : CarouselContent { + override val key: Any = ad // 广告对象本身就可以作为 key + } +} + +// --- ViewModel --- + +@HiltViewModel +class HomeViewModel @Inject constructor( + @ApplicationContext private val context: Context, + private val featureRepository: FeatureRepository, + private val routeFormatter: RouteFormatter +) : ViewModel() { + + private val _uiState = MutableStateFlow(HomeUiState()) + val uiState = _uiState.asStateFlow() + + private var tempPromoItems: List? = null + private var tempAd: UMNativeAD? = null + + init { + loadAllData() + loadRandomFeatures() + loadNativeBannerAd() + } + + fun loadNativeBannerAd() { + Log.d("UmengAd", "loadNativeBannerAd: 开始请求广告...") + viewModelScope.launch { + _uiState.update { it.copy(isCarouselLoading = true) } + + // 1. 创建广告请求配置 + val config = UMAdConfig.Builder() + .setSlotId("100004273") // 请确保这是你的视频广告位ID + .build() + + // 2. 创建广告加载监听器 (每次调用时都创建新的实例以避免潜在的内存问题) + val listener = object : UMUnionApi.AdLoadListener { + + override fun onSuccess(type: UMUnionApi.AdType, display: UMNativeAD) { + Log.d("UmengAd", "onSuccess: 广告加载成功. Title: ${display.title}, isVideo: ${display.isVideo}") + + // 将获取到的广告对象暂存起来 + tempAd = display + + // 调用数据合并逻辑 + combineAdAndPromoItems() + + // 为广告对象设置曝光和点击监听 + display.setAdEventListener(object : UMUnionApi.AdEventListener { + override fun onExposed() { + Log.d("UmengAd", "Ad Event: 广告曝光成功") + } + override fun onClicked(view: View) { + Log.d("UmengAd", "Ad Event: 广告点击成功") + } + override fun onError(code: Int, message: String) { + Log.e("UmengAd", "Ad Event Error: code=$code, msg=$message") + } + }) + + // 如果是视频广告,可以设置视频相关的监听 + if (display.isVideo) { + display.setVideoListener(object : UMUnionApi.VideoListener { + override fun onReady() { Log.d("UmengAd", "Video Event: onReady") } + override fun onStart() { Log.d("UmengAd", "Video Event: onStart") } + override fun onPause() { Log.d("UmengAd", "Video Event: onPause") } + override fun onCompleted() { Log.d("UmengAd", "Video Event: onCompleted") } + override fun onError(message: String) { Log.e("UmengAd", "Video Event Error: $message") } + }) + } + } + + override fun onFailure(type: UMUnionApi.AdType, message: String) { + Log.e("UmengAd", "onFailure: 广告请求出错: $message") + + // 确保失败时广告对象为null + tempAd = null + + // 即使广告失败,也要调用一次合并,以便显示轮播图 + combineAdAndPromoItems() + } + } + + // 3. 发起广告加载请求 + UMUnionSdk.loadNativeBannerAd(config, listener) + } + } + + /** + * 合并推广内容和广告内容,并更新UI状态。 + * 这个函数是线程安全的,可以在任何时候被调用。 + */ + private fun combineAdAndPromoItems() { + // 确保轮播图数据已经加载完成,否则等待下一次调用 + val promos = tempPromoItems ?: return + + // 1. 创建一个 mutableListOf 用于存放所有内容 + val combinedList = mutableListOf() + + // 2. 将普通推广项转换为 CarouselContent.Promo 并添加到列表中 + val promoContent = promos.map { CarouselContent.Promo(it) } + combinedList.addAll(promoContent) + + // 3. 检查是否有有效广告,如果有,也添加到列表中 + val ad = tempAd + if (ad != null && ad.isValid) { + combinedList.add(CarouselContent.Ad(ad)) + Log.d("UmengAd", "combine: 成功添加一个广告到待打乱列表") + } else { + Log.d("UmengAd", "combine: 没有有效广告可添加") + } + + combinedList.shuffle() + Log.d("UmengAd", "combine: 列表已随机打乱,总数: ${combinedList.size}") + + // 5. 更新UI State + _uiState.update { it.copy(combinedCarouselItems = combinedList, isCarouselLoading = false) } + } + + private fun loadAllData() { + viewModelScope.launch { + coroutineScope { + // 并发加载轮播数据 + launch { + val items = fetchCarouselData() + _uiState.update { it.copy(carouselItems = items) } + } + + // 加载模块状态 + launch { + val status = getModuleStatus() + _uiState.update { it.copy(moduleStatus = status) } + } + + // 加载 Root 状态 + launch { + val status = getRootStatus() + _uiState.update { it.copy(rootStatus = status) } + } + + // 加载设备信息 + launch { + val info = getDeviceInfo() + _uiState.update { it.copy(deviceInfo = info) } + } + } + } + } + + private fun loadRandomFeatures() { + viewModelScope.launch { + val allItems = featureRepository.getAllSearchableItems() + + val highlightFeatures = allItems.shuffled().map { item -> + val routeId = item.route.substringAfter("feature/") + val formattedRoute = routeFormatter.formatRouteAsBreadcrumb(routeId) + + HighlightFeature( + searchableItem = item, + formattedRoute = formattedRoute + ) + } + + _uiState.update { + it.copy(randomFeatures = highlightFeatures) + } + } + } + + /** + * 安全地从 JSONObject 获取字符串,如果键不存在或值为 null,则返回 null。 + */ + private fun JSONObject.optStringOrNull(key: String): String? { + if (has(key) && !isNull(key)) { + return getString(key) + } + return null + } + + private suspend fun fetchCarouselData(): List = withContext(Dispatchers.IO) { + try { + val client = OkHttpClient.Builder().cache(null).build() + val request = Request.Builder() + .url("https://gitee.com/yo-gurt/OShin/raw/master/lunbo.json") + .header("Cache-Control", "no-cache") + .build() + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) return@use emptyList() + val body = response.body.string() + val jsonArray = JSONArray(body) + List(jsonArray.length()) { i -> + val obj = jsonArray.getJSONObject(i) + CarouselItem( + title = obj.optStringOrNull("title"), + description = obj.optStringOrNull("description"), + actionUrl = obj.optStringOrNull("action_url"), + imageUrl = obj.optStringOrNull("image_url") + ) + } + } + } catch (e: Exception) { + // 返回默认数据以供展示 + listOf( + CarouselItem( + title = "Freemium", + description = "A handpicked collection of stunning free wallpapers that fit your vibe perfectly.", + actionUrl = null, + imageUrl = null + ) + ) + } + }.also { fetchedItems -> + // 保存原始轮播数据,并调用合并函数 + tempPromoItems = fetchedItems + combineAdAndPromoItems() + } + + private suspend fun getModuleStatus(): ModuleStatus = withContext(Dispatchers.Default) { + if (YukiHookAPI.Status.isModuleActive) { + ModuleStatus(Status.SUCCESS, "SUCCESS") + } else { + ModuleStatus(Status.ERROR, context.getString(R.string.status_module_error_inactive)) + } + } + + private suspend fun getRootStatus(): RootStatus = withContext(Dispatchers.IO) { + try { + val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "echo Connected")) + if (process.waitFor() == 0) { + val ksuVersion = executeCommand("/data/adb/ksud -V") + val version = if (ksuVersion.isNotEmpty()) { + context.getString(R.string.status_root_version_kernelsu, ksuVersion) + } else { + context.getString(R.string.status_root_version_magisk, executeCommand("magisk -v")) + } + RootStatus(Status.SUCCESS, version.trim()) + } else { + RootStatus(Status.ERROR, context.getString(R.string.status_root_error_denied)) + } + } catch (e: Exception) { + RootStatus(Status.ERROR, context.getString(R.string.status_root_error_unavailable)) + } + } + + private suspend fun getDeviceInfo(): DeviceInfo = withContext(Dispatchers.IO) { + val command = """ + echo "charge_full=$(cat /sys/class/oplus_chg/battery/charge_full 2>/dev/null)" + echo "charge_counter=$(cat /sys/class/power_supply/battery/charge_counter 2>/dev/null)" + echo "fcc=$(cat /sys/class/oplus_chg/battery/battery_fcc 2>/dev/null)" + echo "soh=$(cat /sys/class/oplus_chg/battery/battery_soh 2>/dev/null)" + echo "cc=$(cat /sys/class/oplus_chg/battery/battery_cc 2>/dev/null)" + echo "charge_full_design=$(cat /sys/class/power_supply/battery/charge_full_design 2>/dev/null)" + echo "health=$(cat /sys/class/power_supply/battery/health 2>/dev/null)" + echo "chip_soc=$(cat /sys/class/oplus_chg/battery/chip_soc 2>/dev/null)" + """.trimIndent() + + val rawData = executeCommand(command) + + val dataMap = rawData.lines() + .filter { it.contains("=") } + .associate { + val parts = it.split("=", limit = 2) + parts[0] to parts[1] + } + + val chargeFull0 = dataMap["charge_full"]?.toIntOrNull() ?: 0 + val chargeFull1 = dataMap["charge_counter"]?.toIntOrNull() ?: 0 + val currentCapacity = (if (chargeFull0 != 0) chargeFull0 else chargeFull1) / 1000 + + val fullCapacity = dataMap["fcc"]?.toIntOrNull() ?: 0 + val soh = dataMap["soh"]?.toFloatOrNull() ?: 0f + val cycleCount = dataMap["cc"]?.toIntOrNull() ?: 0 + val designCapacity = (dataMap["charge_full_design"]?.toIntOrNull() ?: 0) / 1000 + val healthRaw = dataMap["health"]?.trim() ?: "Unknown" + val chipSoc = dataMap["chip_soc"]?.toIntOrNull() ?: 0 + + val calculatedHealth = if (designCapacity > 0 && fullCapacity > 0) { + (fullCapacity.toFloat() / designCapacity.toFloat()) * 100f + } else 0f + + DeviceInfo( + country = mapNvidToCountry(getSystemProperty("ro.build.oplus_nv_id")), + batteryHealthDisplay = mapHealthToString(healthRaw), + batteryHealthRaw = healthRaw, + batteryHealthPercent = soh, + calculatedHealth = calculatedHealth, + cycleCount = cycleCount, + androidVersion = Build.VERSION.RELEASE, + sdkVersion = Build.VERSION.SDK_INT.toString(), + systemVersion = Build.DISPLAY, + designCapacity = designCapacity, + currentCapacity = currentCapacity, + fullCapacity = fullCapacity, + chipSoc = chipSoc + ) + } + + // --- 辅助函数 --- + private fun mapNvidToCountry(nvid: String): String = when (nvid) { + "10010111" -> context.getString(R.string.nvid_CN) + "00011010" -> context.getString(R.string.nvid_TW) + "00110111" -> context.getString(R.string.nvid_RU) + "01000100" -> context.getString(R.string.nvid_GDPR_EU) + "10001101" -> context.getString(R.string.nvid_GDPR_Europe) + "00011011" -> context.getString(R.string.nvid_IN) + "00110011" -> context.getString(R.string.nvid_ID) + "00111000" -> context.getString(R.string.nvid_MY) + "00111001" -> context.getString(R.string.nvid_TH) + "00111110" -> context.getString(R.string.nvid_PH) + "10000011" -> context.getString(R.string.nvid_SA) + "10011010" -> context.getString(R.string.nvid_LATAM) + "10011110" -> context.getString(R.string.nvid_BR) + "10100110" -> context.getString(R.string.nvid_ME) + else -> context.getString(R.string.nvid_unknown, nvid) + } + + private fun mapHealthToString(health: String): String = when (health) { + "Good" -> context.getString(R.string.battery_health_good) + "Overheat" -> context.getString(R.string.battery_health_overheat) + "Dead" -> context.getString(R.string.battery_health_dead) + "Over Voltage" -> context.getString(R.string.battery_health_over_voltage) + "Cold" -> context.getString(R.string.battery_health_cold) + "Unknown" -> context.getString(R.string.battery_health_unknown) + else -> context.getString(R.string.battery_health_not_found) + } + + private suspend fun executeCommand(command: String): String = withContext(Dispatchers.IO) { + try { + val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command)) + BufferedReader(InputStreamReader(process.inputStream)).use { it.readText() } + .also { process.waitFor() }.trim() + } catch (e: Exception) { + "" + } + } + + @SuppressLint("PrivateApi") + private fun getSystemProperty(name: String): String { + return try { + Class.forName("android.os.SystemProperties").getMethod("get", String::class.java) + .invoke(null, name) as String + } catch (e: Exception) { + "null" + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/home/MainHome.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/home/MainHome.kt new file mode 100644 index 00000000..49ad4e78 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/home/MainHome.kt @@ -0,0 +1,1639 @@ +package com.suqi8.oshin.ui.mainscreen.home + +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ChevronRight +import androidx.compose.material.icons.filled.PhoneAndroid +import androidx.compose.material.icons.filled.Security +import androidx.compose.material.icons.filled.VerifiedUser +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.navigation.NavController +import coil3.compose.AsyncImage +import com.highcapable.yukihookapi.YukiHookAPI +import com.suqi8.oshin.R +import com.suqi8.oshin.ui.mainscreen.CarouselContent +import com.suqi8.oshin.ui.mainscreen.DeviceInfo +import com.suqi8.oshin.ui.mainscreen.FridaStatus +import com.suqi8.oshin.ui.mainscreen.HighlightFeature +import com.suqi8.oshin.ui.mainscreen.HomeViewModel +import com.suqi8.oshin.ui.mainscreen.ModuleStatus +import com.suqi8.oshin.ui.mainscreen.RootStatus +import com.suqi8.oshin.ui.mainscreen.Status +import com.suqi8.oshin.ui.mainscreen.lspVersion +import com.umeng.union.UMNativeAD +import com.umeng.union.widget.UMNativeLayout +import com.umeng.union.widget.UMVideoView +import kotlinx.coroutines.delay +import top.yukonga.miuix.kmp.basic.Icon +import top.yukonga.miuix.kmp.basic.ScrollBehavior +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.utils.overScrollVertical +import top.yukonga.miuix.kmp.utils.scrollEndHaptic +import kotlin.math.abs + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun MainHome( + padding: PaddingValues, + topAppBarScrollBehavior: ScrollBehavior, + navController: NavController, + viewModel: HomeViewModel = hiltViewModel(), + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + val uiState by viewModel.uiState.collectAsState() + + Box(modifier = Modifier + .fillMaxSize() + .background(MiuixTheme.colorScheme.background)) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .overScrollVertical() + .scrollEndHaptic() + .nestedScroll(topAppBarScrollBehavior.nestedScrollConnection), + contentPadding = PaddingValues( + top = padding.calculateTopPadding() + 16.dp, + bottom = padding.calculateBottomPadding() + 16.dp + ), + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + item { + Spacer(modifier = Modifier.height(16.dp)) + } + // 轮播图 + item { + uiState.combinedCarouselItems.let { + FeaturedCollectionsSection(items = it) + } + } + + // 状态面板 + item { + ModernDashboardSection( + moduleStatus = uiState.moduleStatus, + rootStatus = uiState.rootStatus, + fridaStatus = uiState.fridaStatus + ) + } + + // 设备信息 + item { + uiState.deviceInfo?.let { DeviceInfoSection(info = it) } + } + + // 官方频道 + item { + OfficialChannelCard( + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + + // 今日亮点(推荐功能) + if (uiState.randomFeatures.isNotEmpty()) { + item { + TodayHighlightsSection( + features = uiState.randomFeatures, + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + } + } + } +} + +@Composable +fun ModernSectionTitle( + title: String, + subtitle: String? = null, + modifier: Modifier = Modifier +) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + ) { + Text( + text = title, + fontSize = 28.sp, + fontWeight = FontWeight.Bold, + color = MiuixTheme.colorScheme.onBackground + ) + subtitle?.let { + Text( + text = it, + fontSize = 14.sp, + color = MiuixTheme.colorScheme.onBackground.copy(alpha = 0.6f), + modifier = Modifier.padding(top = 4.dp) + ) + } + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun FeaturedCollectionsSection(items: List) { + // 如果列表为空,直接不显示这个区域,避免不必要的计算 + if (items.isEmpty()) return + + val pagerState = rememberPagerState { items.size } + val uriHandler = LocalUriHandler.current + + // 自动轮播效果 + LaunchedEffect(pagerState.pageCount) { + while (true) { + delay(5000) // 每5秒切换一次 + // 确保页面数量大于1才轮播 + if (pagerState.pageCount > 1) { + val nextPage = (pagerState.currentPage + 1) % pagerState.pageCount + pagerState.animateScrollToPage(nextPage, animationSpec = tween(durationMillis = 800)) + } + } + } + + Column( + modifier = Modifier + .fillMaxWidth() + .animateContentSize() + ) { + ModernSectionTitle( + title = stringResource(id = R.string.section_title_featured), + subtitle = stringResource(id = R.string.section_subtitle_featured) + ) + + Spacer(Modifier.height(16.dp)) + + HorizontalPager( + state = pagerState, + modifier = Modifier.fillMaxWidth(), + contentPadding = PaddingValues(horizontal = 20.dp), + pageSpacing = 16.dp + ) { page -> + // 计算页面偏移量以实现缩放和透明度动画 + val pageOffset = (pagerState.currentPage - page) + pagerState.currentPageOffsetFraction + val scale = lerp(1f, 0.88f, abs(pageOffset).coerceAtMost(1f)) + val alpha = lerp(1f, 0.6f, abs(pageOffset).coerceAtMost(1f)) + + // 根据内容类型动态渲染UI + when (val content = items[page]) { + is CarouselContent.Ad -> { + // 渲染广告卡片 + UmengNativeBannerAd( + modifier = Modifier + .graphicsLayer { + scaleX = scale + scaleY = scale + this.alpha = alpha + } + .fillMaxWidth() + .aspectRatio(16f / 9f) + .clip(RoundedCornerShape(24.dp)), + nativeAd = content.ad + ) + } + is CarouselContent.Promo -> { + // 渲染原始的推广卡片 + val item = content.item + Box( + modifier = Modifier + .graphicsLayer { + scaleX = scale + scaleY = scale + this.alpha = alpha + } + .fillMaxWidth() + .aspectRatio(16f / 9f) + .clip(RoundedCornerShape(24.dp)) + .background( + Brush.linearGradient( + colors = listOf( + Color(0xFF6366F1), + Color(0xFF8B5CF6), + Color(0xFFEC4899) + ) + ) + ) + .clickable(enabled = item.actionUrl != null) { + item.actionUrl?.let { url -> + try { + uriHandler.openUri(url) + } catch (e: Exception) { + // 处理打开链接失败的情况 + } + } + } + ) { + if (item.imageUrl != null) { + AsyncImage( + model = item.imageUrl, + contentDescription = item.title, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + } + + if (item.title != null || item.description != null) { + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.7f)) + ) + ) + ) + Column( + modifier = Modifier + .align(Alignment.BottomStart) + .padding(20.dp) + ) { + item.title?.let { + Text( + text = it, + color = Color.White, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + item.description?.let { + Text( + text = it, + color = Color.White.copy(alpha = 0.9f), + fontSize = 14.sp, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(top = 4.dp) + ) + } + } + } + + if (item.actionUrl != null) { + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(16.dp) + .size(32.dp) + .clip(CircleShape) + .background(Brush.radialGradient(colors = listOf(Color.White.copy(alpha = 0.9f), Color.White.copy(alpha = 0.7f)))) + .border(width = 1.5.dp, color = Color.White.copy(alpha = 0.5f), shape = CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = "可点击", + tint = MiuixTheme.colorScheme.primary, + modifier = Modifier.size(16.dp) + ) + } + } + } + } + } + } + + Spacer(Modifier.height(16.dp)) + + // 页面指示器 + Row( + Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + repeat(pagerState.pageCount) { iteration -> + val isSelected = pagerState.currentPage == iteration + val width by animateFloatAsState(targetValue = if (isSelected) 24f else 8f, animationSpec = tween(300), label = "IndicatorWidth") + val color = if (isSelected) MiuixTheme.colorScheme.primary else MiuixTheme.colorScheme.onBackground.copy(alpha = 0.3f) + + Box( + modifier = Modifier + .padding(horizontal = 4.dp) + .size(width.dp, 8.dp) + .clip(RoundedCornerShape(4.dp)) + .background(color) + ) + } + } + } +} + +/** + * 用于展示友盟原生Banner广告(包括视频)的 Composable。 + * 包含了生命周期管理,以正确处理视频播放。 + * + * @param modifier Modifier for this composable. + * @param nativeAd 从友盟SDK获取的广告对象。 + */ +@Composable +fun UmengNativeBannerAd( + modifier: Modifier = Modifier, + nativeAd: UMNativeAD +) { + val lifecycleOwner = LocalLifecycleOwner.current + DisposableEffect(lifecycleOwner, nativeAd) { + val observer = LifecycleEventObserver { _, event -> + if (!nativeAd.isVideo) return@LifecycleEventObserver + when (event) { + Lifecycle.Event.ON_RESUME -> nativeAd.videoPlayer?.start() + Lifecycle.Event.ON_PAUSE -> nativeAd.videoPlayer?.pause() + else -> {} + } + } + lifecycleOwner.lifecycle.addObserver(observer) + onDispose { + lifecycleOwner.lifecycle.removeObserver(observer) + nativeAd.destroy() + } + } + + val context = LocalContext.current + val eventBinderLayout = remember { UMNativeLayout(context) } + + Box(modifier = modifier) { + + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.linearGradient( + colors = listOf( + Color(0xFF6366F1), + Color(0xFF8B5CF6), + Color(0xFFEC4899) + ) + ) + ) + ) + + if (nativeAd.isVideo) { + AndroidView( + factory = { ctx -> UMVideoView(ctx) }, + modifier = Modifier.fillMaxSize(), + update = { videoView -> + nativeAd.bindVideoView(videoView) + nativeAd.setVideoAutoplay(true) + } + ) + } else { + AsyncImage( + model = nativeAd.imageUrl, + contentDescription = nativeAd.title, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + } + + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.7f)) + ) + ) + ) + + Column( + modifier = Modifier + .align(Alignment.BottomStart) + .padding(20.dp) + ) { + Text( + text = nativeAd.title ?: "", + color = Color.White, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = nativeAd.content ?: "", + color = Color.White.copy(alpha = 0.9f), + fontSize = 14.sp, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(top = 4.dp) + ) + } + + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(16.dp) + .size(32.dp) + .clip(CircleShape) + .background(Brush.radialGradient(colors = listOf(Color.White.copy(alpha = 0.9f), Color.White.copy(alpha = 0.7f)))) + .border(width = 1.5.dp, color = Color.White.copy(alpha = 0.5f), shape = CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = "广告详情", + tint = MiuixTheme.colorScheme.primary, + modifier = Modifier.size(16.dp) + ) + } + + AndroidView( + factory = { eventBinderLayout }, + modifier = Modifier.fillMaxSize(), + update = { umNativeLayout -> + nativeAd.bindView(umNativeLayout.context, umNativeLayout, listOf(umNativeLayout)) + } + ) + } +} + +@Composable +fun ModernDashboardSection( + moduleStatus: ModuleStatus, + rootStatus: RootStatus, + fridaStatus: FridaStatus +) { + Column( + modifier = Modifier + .fillMaxWidth() + ) { + ModernSectionTitle( + title = stringResource(id = R.string.section_title_status) + ) + + Spacer(Modifier.height(16.dp)) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + ModernStatusCard( + modifier = Modifier.weight(1f), + status = moduleStatus.status, + icon = Icons.Default.VerifiedUser, + title = stringResource(id = R.string.module_status), + message = if (moduleStatus.status == Status.ERROR) moduleStatus.message else stringResource(R.string.status_module_success_details, lspVersion.value, YukiHookAPI.Status.Executor.apiLevel), + gradientColors = listOf(Color(0xFF6366F1), Color(0xFF8B5CF6)) + ) + + ModernStatusCard( + modifier = Modifier.weight(1f), + status = rootStatus.status, + icon = Icons.Default.Security, + title = stringResource(id = R.string.root_status), + message = rootStatus.version, + gradientColors = listOf(Color(0xFFEC4899), Color(0xFFF43F5E)) + ) + } + } +} + +@Composable +fun ModernStatusCard( + modifier: Modifier = Modifier, + status: Status, + icon: ImageVector, + title: String, + message: String, + gradientColors: List +) { + val statusColor = when (status) { + Status.SUCCESS -> Color(0xFF22C55E) + Status.ERROR -> Color(0xFFEF4444) + Status.WARNING -> Color(0xFFF59E0B) + Status.LOADING -> MiuixTheme.colorScheme.primary + } + + Box( + modifier = modifier + .aspectRatio(1f) + .clip(RoundedCornerShape(20.dp)) + .background(Brush.linearGradient(gradientColors)) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.SpaceBetween + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Top + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = Color.White, + modifier = Modifier.size(28.dp) + ) + + ModernStatusIndicator(status = status, color = statusColor) + } + + Column { + Text( + text = title, + color = Color.White, + fontWeight = FontWeight.Bold, + fontSize = 14.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = message, + color = Color.White.copy(alpha = 0.9f), + fontSize = 12.sp, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(top = 4.dp) + ) + } + } + } +} + +@Composable +fun ModernStatusIndicator(status: Status, color: Color) { + val infiniteTransition = rememberInfiniteTransition() + + val alpha by if (status == Status.LOADING) { + infiniteTransition.animateFloat( + 0.3f, 1f, + infiniteRepeatable(tween(800), RepeatMode.Reverse) + ) + } else { + remember { mutableStateOf(1f) } + } + + Box( + modifier = Modifier + .size(12.dp) + .clip(CircleShape) + .background(Color.White.copy(alpha = alpha)) + ) +} + + +// 今日亮点 - 展示推荐功能 (苹果风格) +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun TodayHighlightsSection( + features: List, + navController: NavController, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + Column(modifier = Modifier.fillMaxWidth()) { + ModernSectionTitle( + title = stringResource(id = R.string.section_title_highlights), + subtitle = stringResource(id = R.string.section_subtitle_highlights) + ) + + Spacer(Modifier.height(16.dp)) + + LazyRow( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + contentPadding = PaddingValues(vertical = 4.dp) + ) { + items(features) { feature -> + with(sharedTransitionScope) { + AppleStyleFeatureCard( + feature = feature, + onClick = { + navController.navigate("${feature.searchableItem.route}?highlightKey=${feature.searchableItem.key}") + }, + sharedTransitionScope = this, + animatedVisibilityScope = animatedVisibilityScope + ) + } + } + } + } +} + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun ModernFeatureCard( + feature: HighlightFeature, + onClick: () -> Unit, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + + val route = feature.formattedRoute + val searchableItem = feature.searchableItem + + val themeColor = remember(searchableItem.key) { + val colors = listOf( + Color(0xFF6366F1), // 靛蓝 + Color(0xFFEC4899), // 粉红 + Color(0xFF10B981), // 绿色 + Color(0xFFF59E0B), // 橙色 + Color(0xFF8B5CF6), // 紫色 + Color(0xFF14B8A6), // 青色 + ) + colors[abs(searchableItem.key.hashCode()) % colors.size] + } + + with(sharedTransitionScope) { + Box( + modifier = Modifier + .width(180.dp) + .height(160.dp) + .sharedBounds( + sharedContentState = rememberSharedContentState(key = searchableItem.key), + animatedVisibilityScope = animatedVisibilityScope + ) + .clip(RoundedCornerShape(16.dp)) + .background(MiuixTheme.colorScheme.surface) + .border( + width = 1.dp, + color = themeColor.copy(alpha = 0.3f), + shape = RoundedCornerShape(16.dp) + ) + .clickable(onClick = onClick) + .padding(16.dp) + ) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.SpaceBetween + ) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + // 标题和图标 + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Box( + modifier = Modifier + .size(36.dp) + .clip(RoundedCornerShape(10.dp)) + .background(themeColor.copy(alpha = 0.1f)), + contentAlignment = Alignment.Center + ) { + Text( + text = searchableItem.title.first().toString(), + color = themeColor, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + + Text( + text = searchableItem.title, + color = MiuixTheme.colorScheme.onBackground, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f) + ) + } + + // 简介信息 - 固定行数,统一高度 + Text( + text = searchableItem.summary.ifEmpty { stringResource(R.string.common_no_description) }, + color = MiuixTheme.colorScheme.onBackground.copy(alpha = 0.6f), + fontSize = 11.sp, + lineHeight = 14.sp, + maxLines = 2, + minLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + + // 底部路由信息 + if (route.isNotEmpty()) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + Box( + modifier = Modifier + .size(4.dp) + .clip(CircleShape) + .background(themeColor) + ) + Text( + text = route, + color = themeColor, + fontSize = 9.sp, + fontFamily = FontFamily.Monospace, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f) + ) + } + } else { + // 即使没有路由信息也保持占位高度 + Spacer(modifier = Modifier.height(16.dp)) + } + } + } + } +} + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun AppleStyleFeatureCard( + feature: HighlightFeature, + onClick: () -> Unit, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + ModernFeatureCard(feature, onClick, sharedTransitionScope, animatedVisibilityScope) +} + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun CompactFeatureCard( + feature: HighlightFeature, + onClick: () -> Unit, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + val searchableItem = feature.searchableItem + val route = feature.formattedRoute + + // 为不同的功能分配不同的主题色 + val themeColor = remember(searchableItem.key) { + val colors = listOf( + Color(0xFF6366F1), // 靛蓝 + Color(0xFFEC4899), // 粉红 + Color(0xFF10B981), // 绿色 + Color(0xFFF59E0B), // 橙色 + Color(0xFF8B5CF6), // 紫色 + Color(0xFF14B8A6), // 青色 + ) + colors[abs(searchableItem.key.hashCode()) % colors.size] + } + + with(sharedTransitionScope) { + Box( + modifier = Modifier + .width(140.dp) + .height(100.dp) + .sharedBounds( + sharedContentState = rememberSharedContentState(key = searchableItem.key), + animatedVisibilityScope = animatedVisibilityScope + ) + .clip(RoundedCornerShape(12.dp)) + .background(MiuixTheme.colorScheme.surface) + .border( + width = 1.dp, + color = themeColor.copy(alpha = 0.3f), + shape = RoundedCornerShape(12.dp) + ) + .clickable(onClick = onClick) + .padding(12.dp) + ) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Box( + modifier = Modifier + .size(24.dp) + .clip(RoundedCornerShape(6.dp)) + .background(themeColor.copy(alpha = 0.1f)), + contentAlignment = Alignment.Center + ) { + Text( + text = searchableItem.title.first().toString(), + color = themeColor, + fontSize = 12.sp, + fontWeight = FontWeight.Bold + ) + } + + Text( + text = searchableItem.title, + color = MiuixTheme.colorScheme.onBackground, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f) + ) + } + + if (searchableItem.summary.isNotEmpty()) { + Text( + text = searchableItem.summary, + color = MiuixTheme.colorScheme.onBackground.copy(alpha = 0.6f), + fontSize = 10.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.fillMaxWidth() + ) { + Box( + modifier = Modifier + .size(4.dp) + .clip(CircleShape) + .background(themeColor) + ) + Text( + text = route, + color = themeColor, + fontSize = 9.sp, + fontFamily = FontFamily.Monospace, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f) + ) + + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = null, + tint = themeColor.copy(alpha = 0.7f), + modifier = Modifier.size(12.dp) + ) + } + } + } + } +} + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun HighlightFeatureCard( + feature: HighlightFeature, + onClick: () -> Unit, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + val searchableItem = feature.searchableItem + val route = feature.formattedRoute + val summaryWithRoute = remember(searchableItem.summary, route) { + (searchableItem.summary) + if (route.isNotEmpty() && searchableItem.summary.isNotEmpty()) "\n$route" else route + } + + with(sharedTransitionScope) { + Box( + modifier = Modifier + .fillMaxWidth() + .sharedBounds( + sharedContentState = rememberSharedContentState(key = searchableItem.key), + animatedVisibilityScope = animatedVisibilityScope + ) + .clip(RoundedCornerShape(16.dp)) + .background(MiuixTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)) + .clickable(onClick = onClick) + .padding(16.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = searchableItem.title, + color = MiuixTheme.colorScheme.onBackground, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + if (summaryWithRoute.isNotEmpty()) { + Text( + text = summaryWithRoute, + color = MiuixTheme.colorScheme.onBackground.copy(alpha = 0.6f), + fontSize = 12.sp, + lineHeight = 14.sp, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(top = 4.dp) + ) + } + } + + Spacer(Modifier.width(12.dp)) + + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = null, + tint = MiuixTheme.colorScheme.primary, + modifier = Modifier.size(24.dp) + ) + } + } + } +} + +// 设备信息 - 混合风格设计 +@Composable +fun DeviceInfoSection(info: DeviceInfo) { + Column( + modifier = Modifier + .fillMaxWidth() + ) { + ModernSectionTitle( + title = stringResource(id = R.string.section_title_device_info) + ) + + Spacer(Modifier.height(16.dp)) + + // 电池状态卡片 - 渐变背景 + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .clip(RoundedCornerShape(20.dp)) + .background( + Brush.linearGradient( + colors = listOf( + when { + info.batteryHealthPercent >= 80 -> Color(0xFF10B981) + info.batteryHealthPercent >= 60 -> Color(0xFFF59E0B) + else -> Color(0xFFEF4444) + }, + when { + info.batteryHealthPercent >= 80 -> Color(0xFF059669) + info.batteryHealthPercent >= 60 -> Color(0xFFDC2626) + else -> Color(0xFFB91C1C) + } + ) + ) + ) + .padding(20.dp) + ) { + Column { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column { + Text( + text = stringResource(R.string.battery_status), + color = Color.White, + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + Text( + text = if (info.chipSoc > 0) "${stringResource(R.string.real_battery_level)} ${info.chipSoc}%" else "${stringResource(R.string.gauge_title_health)} ${"%.1f".format(info.batteryHealthPercent)}%", + color = Color.White.copy(alpha = 0.9f), + fontSize = 14.sp + ) + } + + Box( + modifier = Modifier + .size(60.dp) + .clip(CircleShape) + .background(Color.White.copy(alpha = 0.2f)), + contentAlignment = Alignment.Center + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = "${info.currentCapacity}", + color = Color.White, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + Text( + text = "mAh", + color = Color.White.copy(alpha = 0.8f), + fontSize = 10.sp + ) + } + } + } + + Spacer(Modifier.height(16.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + BatteryMetricItem(stringResource(R.string.battery_metric_cycle_count), stringResource(R.string.battery_metric_cycle_count_value, info.cycleCount)) + BatteryMetricItem(stringResource(R.string.battery_metric_design_capacity), stringResource(R.string.battery_metric_design_capacity_value, info.designCapacity)) + BatteryMetricItem(stringResource(R.string.battery_metric_system_health), info.batteryHealthDisplay) + } + + Spacer(Modifier.height(12.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.text_info_calculated_health), + color = Color.White.copy(alpha = 0.8f), + fontSize = 12.sp + ) + Spacer(Modifier.width(8.dp)) + Text( + text = "${"%.1f".format(info.calculatedHealth)}%", + color = Color.White, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + Spacer(Modifier.weight(1f)) + Text( + text = stringResource(R.string.battery_label_system_health_percent), + color = Color.White.copy(alpha = 0.8f), + fontSize = 12.sp + ) + Spacer(Modifier.width(8.dp)) + Text( + text = "${"%.1f".format(info.batteryHealthPercent)}%", + color = Color.White, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + } + } + + Spacer(Modifier.height(16.dp)) + + // 系统信息卡片 - 简洁风格 + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .clip(RoundedCornerShape(16.dp)) + .background(MiuixTheme.colorScheme.surface) + .border( + width = 1.dp, + color = MiuixTheme.colorScheme.onBackground.copy(alpha = 0.08f), + shape = RoundedCornerShape(16.dp) + ) + .clickable { /* 系统信息卡片点击事件 */ } + .padding(16.dp) + ) { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + // 标题行 + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Box( + modifier = Modifier + .size(36.dp) + .clip(RoundedCornerShape(12.dp)) + .background(MiuixTheme.colorScheme.primary.copy(alpha = 0.1f)), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.PhoneAndroid, + contentDescription = null, + tint = MiuixTheme.colorScheme.primary, + modifier = Modifier.size(18.dp) + ) + } + + Text( + text = stringResource(R.string.section_title_system_info), + color = MiuixTheme.colorScheme.onBackground, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold + ) + } + + // 信息列表 + Column( + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + SystemInfoRow(stringResource(R.string.info_region), info.country) + SystemInfoRow(stringResource(R.string.info_android), "${info.androidVersion} (API ${info.sdkVersion})") + SystemInfoRow(stringResource(R.string.info_system), info.systemVersion) + SystemInfoRow(stringResource(R.string.battery_status), "${info.batteryHealthDisplay} (${info.batteryHealthRaw})") + } + } + } + } +} + +// 电池指标项 +@Composable +fun BatteryMetricItem( + label: String, + value: String +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = value, + color = Color.White, + fontSize = 14.sp, + fontWeight = FontWeight.Bold + ) + Text( + text = label, + color = Color.White.copy(alpha = 0.7f), + fontSize = 12.sp + ) + } +} + +// 系统信息行 - 简洁的左右布局 +@Composable +fun SystemInfoRow( + label: String, + value: String +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = label, + color = MiuixTheme.colorScheme.onBackground.copy(alpha = 0.7f), + fontSize = 14.sp + ) + Text( + text = value, + color = MiuixTheme.colorScheme.onBackground, + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.End + ) + } +} + +// 系统信息卡片 - 用于渐变背景卡片内 +@Composable +fun SystemInfoCard( + modifier: Modifier = Modifier, + icon: ImageVector, + label: String, + value: String +) { + Box( + modifier = modifier + .aspectRatio(1f) + .clip(RoundedCornerShape(16.dp)) + .background(Color.White.copy(alpha = 0.15f)) + .padding(12.dp) + ) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.SpaceBetween + ) { + Column { + Box( + modifier = Modifier + .size(32.dp) + .clip(RoundedCornerShape(8.dp)) + .background(Color.White.copy(alpha = 0.2f)), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = Color.White, + modifier = Modifier.size(16.dp) + ) + } + + Text( + text = label, + color = Color.White.copy(alpha = 0.8f), + fontSize = 10.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + + Text( + text = value, + color = Color.White, + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center + ) + } + } +} + +// 系统信息项 - 保留备用 +@Composable +fun SystemInfoItem( + label: String, + value: String +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = label, + color = MiuixTheme.colorScheme.onBackground.copy(alpha = 0.6f), + fontSize = 15.sp + ) + Text( + text = value, + color = MiuixTheme.colorScheme.onBackground, + fontSize = 15.sp, + fontWeight = FontWeight.Medium + ) + } +} + +// 渐变信息卡片 - 类似系统状态卡片 +@Composable +fun GradientInfoCard( + modifier: Modifier = Modifier, + title: String, + value: String, + subtitle: String, + icon: ImageVector, + gradientColors: List +) { + Box( + modifier = modifier + .aspectRatio(1f) + .clip(RoundedCornerShape(20.dp)) + .background(Brush.linearGradient(gradientColors)) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.SpaceBetween + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Top + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = Color.White, + modifier = Modifier.size(24.dp) + ) + + // 状态指示器 + Box( + modifier = Modifier + .size(8.dp) + .clip(CircleShape) + .background(Color.White.copy(alpha = 0.8f)) + ) + } + + Column { + Text( + text = title, + color = Color.White, + fontWeight = FontWeight.Bold, + fontSize = 14.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = value, + color = Color.White, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(top = 2.dp) + ) + Text( + text = subtitle, + color = Color.White.copy(alpha = 0.9f), + fontSize = 12.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(top = 2.dp) + ) + } + } + } +} + +// 大型渐变信息卡片 - 用于系统信息 +@Composable +fun LargeGradientInfoCard( + title: String, + gradientColors: List, + content: @Composable ColumnScope.() -> Unit +) { + Box( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(20.dp)) + .background(Brush.linearGradient(gradientColors)) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = title, + color = Color.White, + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = null, + tint = Color.White.copy(alpha = 0.7f), + modifier = Modifier.size(20.dp) + ) + } + + Spacer(Modifier.height(16.dp)) + + content() + } + } +} + +// 渐变信息行 - 用于大卡片内的信息显示 +@Composable +fun GradientInfoRow( + label: String, + value: String +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = label, + color = Color.White.copy(alpha = 0.8f), + fontSize = 14.sp, + fontWeight = FontWeight.Medium + ) + Text( + text = value, + color = Color.White, + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + textAlign = TextAlign.End + ) + } +} + +@Composable +fun SimpleInfoCard( + modifier: Modifier = Modifier, + title: String, + value: String, + color: Color +) { + Column( + modifier = modifier + .clip(RoundedCornerShape(12.dp)) + .background(color.copy(alpha = 0.1f)) + .padding(12.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = value, + color = color, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = title, + color = MiuixTheme.colorScheme.onBackground.copy(alpha = 0.7f), + fontSize = 11.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(top = 2.dp) + ) + } +} + +@Composable +fun TextInfoRow(label: String, value: String) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = label, + color = MiuixTheme.colorScheme.onBackground.copy(alpha = 0.7f), + fontFamily = FontFamily.Monospace, + fontSize = 13.sp + ) + Text( + text = value, + color = MiuixTheme.colorScheme.onBackground, + fontFamily = FontFamily.Monospace, + fontSize = 13.sp, + fontWeight = FontWeight.Bold + ) + } +} + +@Composable +fun InfoChip(icon: ImageVector, label: String, value: String) { + Row( + modifier = Modifier + .clip(RoundedCornerShape(12.dp)) + .background(MiuixTheme.colorScheme.onBackground.copy(alpha = 0.08f)) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + imageVector = icon, + contentDescription = label, + tint = MiuixTheme.colorScheme.primary, + modifier = Modifier.size(18.dp) + ) + Column { + Text( + text = label, + fontSize = 10.sp, + fontFamily = FontFamily.Monospace, + color = MiuixTheme.colorScheme.onBackground.copy(alpha = 0.6f) + ) + Text( + text = value, + fontSize = 12.sp, + fontFamily = FontFamily.Monospace, + color = MiuixTheme.colorScheme.onBackground, + fontWeight = FontWeight.Bold + ) + } + } +} + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun OfficialChannelCard( + navController: NavController, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + with(sharedTransitionScope) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .sharedBounds( + sharedContentState = rememberSharedContentState(key = "about_group"), + animatedVisibilityScope = animatedVisibilityScope + ) + .clip(RoundedCornerShape(20.dp)) + .background( + Brush.linearGradient( + colors = listOf( + Color(0xFF667EEA), + Color(0xFF764BA2) + ) + ) + ) + .clickable { navController.navigate("about_group") } + .padding(20.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Image( + painter = painterResource(R.drawable.group), + contentDescription = null, + colorFilter = ColorFilter.tint(Color.White), + modifier = Modifier.size(32.dp) + ) + + Column { + Text( + text = stringResource(id = R.string.official_channel), + color = Color.White, + fontWeight = FontWeight.Bold, + fontSize = 16.sp + ) + Text( + text = stringResource(id = R.string.official_channel_subtitle), + color = Color.White.copy(alpha = 0.8f), + fontSize = 12.sp + ) + } + } + + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = null, + tint = Color.White, + modifier = Modifier.size(24.dp) + ) + } + } + } +} + +private fun lerp(start: Float, stop: Float, fraction: Float): Float { + return (1 - fraction) * start + fraction * stop +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/module/HideAppsNotice.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/module/HideAppsNotice.kt new file mode 100644 index 00000000..aa4ff393 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/module/HideAppsNotice.kt @@ -0,0 +1,78 @@ +package com.suqi8.oshin.ui.mainscreen.module + +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.suqi8.oshin.R +import com.suqi8.oshin.ui.activity.components.Card +import com.suqi8.oshin.ui.activity.components.FunPage +import com.suqi8.oshin.ui.mainscreen.home.ModernSectionTitle +import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.basic.rememberTopAppBarState +import top.yukonga.miuix.kmp.utils.overScrollVertical +import top.yukonga.miuix.kmp.utils.scrollEndHaptic + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun HideAppsNotice( + navController: NavController, + packages: String?, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + // 解析传递过来的包名列表 + val notInstalledApps = remember(packages) { + packages?.split(',')?.filter { it.isNotBlank() } ?: emptyList() + } + + val scrollBehavior = MiuixScrollBehavior(rememberTopAppBarState()) + + FunPage( + navController = navController, + scrollBehavior = scrollBehavior, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + animationKey = "hide_apps_notice" + ) { padding -> + LazyColumn( + modifier = Modifier + .fillMaxSize() + .overScrollVertical() + .scrollEndHaptic() + .nestedScroll(scrollBehavior.nestedScrollConnection), + contentPadding = padding + ) { + item { + ModernSectionTitle( + title = stringResource(id = R.string.help), + modifier = Modifier + .displayCutoutPadding() + .padding(top = padding.calculateTopPadding() + 72.dp, bottom = 8.dp) + ) + } + item { + Card { + Column(modifier = Modifier.padding(12.dp)) { + Text(stringResource(R.string.hide_apps_notice, notInstalledApps.size)) + if (notInstalledApps.isNotEmpty()) { + Text(notInstalledApps.joinToString(separator = "\n")) + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/module/Main_Module.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/module/Main_Module.kt new file mode 100644 index 00000000..b04a8f0d --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/module/Main_Module.kt @@ -0,0 +1,691 @@ +package com.suqi8.oshin.ui.mainscreen.module + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.navigation.NavController +import com.suqi8.oshin.R +import com.suqi8.oshin.models.ModuleEntry +import com.suqi8.oshin.ui.activity.components.BasicComponentDefaults +import com.suqi8.oshin.ui.activity.components.SuperArrow +import com.suqi8.oshin.ui.activity.components.addline +import com.suqi8.oshin.ui.mainscreen.home.ModernSectionTitle +import com.suqi8.oshin.utils.drawColoredShadow +import top.yukonga.miuix.kmp.basic.Card +import top.yukonga.miuix.kmp.basic.CardDefaults +import top.yukonga.miuix.kmp.basic.InputField +import top.yukonga.miuix.kmp.basic.ScrollBehavior +import top.yukonga.miuix.kmp.basic.SearchBar +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.utils.overScrollVertical +import top.yukonga.miuix.kmp.utils.scrollEndHaptic + +// ======================================== +// 主屏幕 +// ======================================== + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun Main_Module( + topAppBarScrollBehavior: ScrollBehavior, + navController: NavController, + padding: PaddingValues, + viewModel: ModuleViewModel = hiltViewModel(), + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + val uiState by viewModel.uiState.collectAsState() + var searchExpanded by remember { mutableStateOf(false) } + + // 使用 ViewModel 保存的滚动位置 + val listState = rememberLazyListState( + initialFirstVisibleItemIndex = viewModel.scrollIndex, + initialFirstVisibleItemScrollOffset = viewModel.scrollOffset + ) + + // 监听并保存滚动位置 + LaunchedEffect(listState) { + snapshotFlow { + listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset + }.collect { (index, offset) -> + viewModel.saveScrollPosition(index, offset) + } + } + + // 同步搜索状态 + LaunchedEffect(uiState.isSearching) { + searchExpanded = uiState.isSearching + } + + LazyColumn( + modifier = Modifier + .fillMaxSize() + .background(MiuixTheme.colorScheme.background) + .overScrollVertical() + .scrollEndHaptic() + .nestedScroll(topAppBarScrollBehavior.nestedScrollConnection), + contentPadding = padding, + state = listState, + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + item { + ModernSectionTitle( + title = stringResource(id = R.string.module), + modifier = Modifier + .displayCutoutPadding() + .padding(top = padding.calculateTopPadding() + 80.dp) + ) + } + + item(key = "searchbar") { + ModuleSearchBar( + query = uiState.searchQuery, + expanded = searchExpanded, + searchResults = uiState.searchResults, + onQueryChange = viewModel::onSearchQueryChanged, + onExpandedChange = { + searchExpanded = it + if (!it) { + viewModel.onSearchQueryChanged("") + } + }, + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + + if (!searchExpanded) { + item { + AppList( + appStyle = uiState.appStyle, + onStyleChange = viewModel::onAppStyleChanged, + moduleEntries = uiState.moduleEntries, + navController = navController, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + viewModel = viewModel + ) + } + + item { + AnimatedVisibility(visible = uiState.notInstalledApps.isNotEmpty()) { + with(sharedTransitionScope) { + Card( + modifier = Modifier + .padding(horizontal = 16.dp) + .sharedBounds( + sharedContentState = rememberSharedContentState(key = "hide_apps_notice"), + animatedVisibilityScope = animatedVisibilityScope + ) + ) { + SuperArrow( + title = stringResource(R.string.app_not_found_in_list), + titleColor = BasicComponentDefaults.titleColor( + enabledColor = MiuixTheme.colorScheme.primary + ), + onClick = { + val packages = uiState.notInstalledApps.joinToString(",") + navController.navigate("hide_apps_notice/$packages") + } + ) + } + } + } + } + } + } +} + +// ======================================== +// Miuix 风格搜索栏 +// ======================================== + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +private fun ModuleSearchBar( + query: String, + expanded: Boolean, + searchResults: List, + onQueryChange: (String) -> Unit, + onExpandedChange: (Boolean) -> Unit, + navController: NavController, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope, + modifier: Modifier = Modifier +) { + SearchBar( + modifier = modifier, + expanded = expanded, + onExpandedChange = onExpandedChange, + inputField = { + InputField( + query = query, + onQueryChange = onQueryChange, + onSearch = { onExpandedChange(false) }, + expanded = expanded, + onExpandedChange = onExpandedChange + ) + }, + outsideRightAction = { + Text( + modifier = Modifier + .padding(end = 12.dp) + .clickable( + interactionSource = null, + indication = null + ) { + onExpandedChange(false) + onQueryChange("") + }, + text = stringResource(R.string.cancel), + style = TextStyle(fontSize = 17.sp, fontWeight = FontWeight.Bold), + color = MiuixTheme.colorScheme.primary + ) + }, + content = { + SearchResultsList( + results = searchResults, + query = query, + navController = navController, + onItemClick = { onExpandedChange(false) }, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + ) +} + +// ======================================== +// 应用列表 +// ======================================== + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +private fun AppList( + appStyle: Int, + onStyleChange: () -> Unit, + moduleEntries: List, + navController: NavController, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope, + viewModel: ModuleViewModel +) { + Column(Modifier.padding(horizontal = 16.dp)) { + Row( + modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), + horizontalArrangement = Arrangement.End + ) { + Text( + text = stringResource(R.string.switch_style), + color = MiuixTheme.colorScheme.primary, + fontSize = 12.sp, + fontFamily = FontFamily.Monospace, + modifier = Modifier.clickable(onClick = onStyleChange) + ) + } + + ModuleCard { + if (appStyle == 0) { + // 网格布局 + FlowRow( + modifier = Modifier.fillMaxWidth().padding(8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + moduleEntries.forEach { entry -> + AppItemFlow( + entry = entry, + onClick = { navController.navigate("feature/${entry.routeId}") }, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + viewModel = viewModel + ) + } + } + } else { + // 列表布局 + Column { + moduleEntries.forEachIndexed { index, entry -> + AppItemList( + entry = entry, + onClick = { navController.navigate("feature/${entry.routeId}") }, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + viewModel = viewModel + ) + if (index < moduleEntries.size - 1) { + addline() + } + } + } + } + } + } +} + +// ======================================== +// 搜索结果列表 +// ======================================== + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +private fun SearchResultsList( + results: List, + query: String, + navController: NavController, + onItemClick: () -> Unit, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + com.suqi8.oshin.ui.activity.components.Card { + if (results.isEmpty()) { + Box( + Modifier + .fillMaxWidth() + .height(200.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "空空如也~", + fontFamily = FontFamily.Monospace, + color = MiuixTheme.colorScheme.onSurfaceVariantActions.copy(alpha = 0.6f) + ) + } + } else { + Column { + results.forEachIndexed { index, item -> + SearchResultItem( + item = item, + query = query, + onClick = { + navController.navigate("${item.item.route}?highlightKey=${item.item.key}") + onItemClick() + }, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + if (index < results.size - 1) { + addline() + } + } + } + } + } +} + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +private fun SearchResultItem( + item: SearchResultUiItem, + query: String, + onClick: () -> Unit, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + val highlightColor = MiuixTheme.colorScheme.primary + + val searchableItem = item.item + val featurePath = item.formattedRoute // <-- 直接使用 + + val titleAnnotated = highlightText(searchableItem.title, query, highlightColor) + val summaryAnnotated = highlightText(searchableItem.summary, query, highlightColor) + + with(sharedTransitionScope) { + Row( + modifier = Modifier + .fillMaxWidth() + .sharedBounds( + sharedContentState = rememberSharedContentState(key = searchableItem.key), + animatedVisibilityScope = animatedVisibilityScope + ) + .wrapContentHeight() + .clickable(onClick = onClick) + .padding(horizontal = 12.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column { + Text( + text = titleAnnotated, + fontSize = 16.sp, + color = MiuixTheme.colorScheme.onBackground + ) + if (searchableItem.summary.isNotBlank()) { + Text( + text = summaryAnnotated, + fontSize = 12.sp, + color = MiuixTheme.colorScheme.onBackground.copy(alpha = 0.7f) + ) + } + Text( + text = featurePath, + fontSize = 11.sp, + color = MiuixTheme.colorScheme.onSurfaceVariantActions.copy(alpha = 0.6f), + modifier = Modifier.padding(top = 4.dp) + ) + } + } + } +} + +// ======================================== +// 应用项组件 - 列表样式 +// ======================================== + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +private fun AppItemList( + entry: ModuleEntry, + onClick: () -> Unit, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope, + viewModel: ModuleViewModel +) { + val defaultColor = MiuixTheme.colorScheme.primary + + val appUiInfo by remember(entry.packageName) { + derivedStateOf { viewModel.getAppInfo(entry.packageName) } + } + + LaunchedEffect(entry.packageName, defaultColor) { + if (appUiInfo == null) { + viewModel.loadAppInfoWithColor(entry.packageName, defaultColor) + } + } + + // 3. 观察 "not found" 状态 + val isNotInstalled by viewModel.uiState.collectAsState().let { state -> + remember(entry.packageName) { + derivedStateOf { state.value.notInstalledApps.contains(entry.packageName) } + } + } + + if (!isNotInstalled) { + if (appUiInfo != null) { + // 状态 1: 已加载 + AppItemListContent( + appName = appUiInfo!!.name, + icon = appUiInfo!!.icon, + dominantColor = appUiInfo!!.dominantColor, + packageName = entry.packageName, + entry = entry, + onClick = onClick, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + } +} + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +private fun AppItemListContent( + appName: String, + icon: ImageBitmap, + dominantColor: Color, + packageName: String, + entry: ModuleEntry, + onClick: () -> Unit, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + with(sharedTransitionScope) { + Row( + modifier = Modifier + .clickable(onClick = onClick) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Card( + colors = CardDefaults.defaultColors(color = dominantColor), + modifier = Modifier + .sharedBounds( + sharedContentState = rememberSharedContentState(key = "item-${entry.routeId}"), + animatedVisibilityScope = animatedVisibilityScope + ) + .padding(start = 16.dp, top = 16.dp, bottom = 16.dp) + .drawColoredShadow( + dominantColor, + alpha = 1f, + borderRadius = 13.dp, + shadowRadius = 7.dp, + roundedRect = false + ) + ) { + Image( + bitmap = icon, + contentDescription = appName, + modifier = Modifier.size(45.dp) + ) + } + Column(modifier = Modifier.padding(start = 16.dp)) { + Text( + text = appName, + modifier = Modifier.sharedElement( + sharedContentState = rememberSharedContentState(key = "title-${entry.routeId}"), + animatedVisibilityScope = animatedVisibilityScope + ) + ) + Text( + text = packageName, + fontSize = MiuixTheme.textStyles.subtitle.fontSize, + fontWeight = FontWeight.Medium, + color = MiuixTheme.colorScheme.onBackgroundVariant + ) + } + } + } +} + +// ======================================== +// 应用项组件 - 网格样式 +// ======================================== + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +private fun AppItemFlow( + entry: ModuleEntry, + onClick: () -> Unit, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope, + viewModel: ModuleViewModel +) { + val defaultColor = MiuixTheme.colorScheme.primary + + // 1. 从 ViewModel 的缓存中观察状态 + val appUiInfo by remember(entry.packageName) { + derivedStateOf { viewModel.getAppInfo(entry.packageName) } + } + + // 2. 触发 ViewModel 加载数据 + LaunchedEffect(entry.packageName, defaultColor) { + if (appUiInfo == null) { + viewModel.loadAppInfoWithColor(entry.packageName, defaultColor) + } + } + + // 3. 观察 "not found" 状态 + val isNotInstalled by viewModel.uiState.collectAsState().let { state -> + remember(entry.packageName) { + derivedStateOf { state.value.notInstalledApps.contains(entry.packageName) } + } + } + + // 4. 渲染 UI + if (!isNotInstalled) { + if (appUiInfo != null) { + // 状态 1: 已加载 + AppItemFlowContent( + appName = appUiInfo!!.name, + icon = appUiInfo!!.icon, + dominantColor = appUiInfo!!.dominantColor, + entry = entry, + onClick = onClick, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope + ) + } + } +} + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +private fun AppItemFlowContent( + appName: String, + icon: ImageBitmap, + dominantColor: Color, + entry: ModuleEntry, + onClick: () -> Unit, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + with(sharedTransitionScope) { + Column( + modifier = Modifier + .width(65.dp) + .clickable(onClick = onClick), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Card( + colors = CardDefaults.defaultColors(color = dominantColor), + modifier = Modifier + .padding(top = 10.dp) + .sharedBounds( + sharedContentState = rememberSharedContentState(key = "item-${entry.routeId}"), + animatedVisibilityScope = animatedVisibilityScope + ) + .drawColoredShadow( + dominantColor, + alpha = 1f, + borderRadius = 13.dp, + shadowRadius = 7.dp, + roundedRect = false + ) + ) { + Image( + bitmap = icon, + contentDescription = appName, + modifier = Modifier.size(50.dp) + ) + } + Text( + text = appName, + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + softWrap = false, + modifier = Modifier + .padding(top = 10.dp, bottom = 6.dp) + .sharedElement( + sharedContentState = rememberSharedContentState(key = "title-${entry.routeId}"), + animatedVisibilityScope = animatedVisibilityScope + ) + ) + } + } +} + +// ======================================== +// UI 容器组件 +// ======================================== + +@Composable +private fun ModuleCard( + modifier: Modifier = Modifier, + content: @Composable ColumnScope.() -> Unit +) { + Card( + modifier = modifier, + content = content + ) +} + +// ======================================== +// 工具函数 +// ======================================== + +/** + * 高亮搜索关键词 + */ +@Composable +private fun highlightText( + text: String, + query: String, + highlightColor: Color +): AnnotatedString { + return buildAnnotatedString { + if (query.isBlank() || !text.contains(query, ignoreCase = true)) { + append(text) + return@buildAnnotatedString + } + + val regex = Regex(query, RegexOption.IGNORE_CASE) + var lastIndex = 0 + + regex.findAll(text).forEach { matchResult -> + append(text.substring(lastIndex, matchResult.range.first)) + withStyle( + style = SpanStyle( + color = highlightColor, + fontWeight = FontWeight.Bold + ) + ) { + append(matchResult.value) + } + lastIndex = matchResult.range.last + 1 + } + + if (lastIndex < text.length) { + append(text.substring(lastIndex)) + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/module/ModuleUiState.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/module/ModuleUiState.kt new file mode 100644 index 00000000..c1bcbe0a --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/module/ModuleUiState.kt @@ -0,0 +1,14 @@ +package com.suqi8.oshin.ui.mainscreen.module + +import com.suqi8.oshin.models.ModuleEntry + +// 定义UI状态 +data class ModuleUiState( + val searchQuery: String = "", + val isSearching: Boolean = false, + val isLoading: Boolean = true, // 用于初次加载索引时的加载状态 + val appStyle: Int = 0, + val moduleEntries: List = emptyList(), + val searchResults: List = emptyList(), + val notInstalledApps: Set = emptySet() +) diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/module/ModuleViewModel.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/module/ModuleViewModel.kt new file mode 100644 index 00000000..b92419bc --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/module/ModuleViewModel.kt @@ -0,0 +1,237 @@ +package com.suqi8.oshin.ui.mainscreen.module + +import android.content.Context +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.highcapable.yukihookapi.YukiHookAPI +import com.highcapable.yukihookapi.hook.factory.prefs +import com.suqi8.oshin.data.repository.AppInfoProvider +import com.suqi8.oshin.data.repository.FeatureRepository +import com.suqi8.oshin.features.FeatureRegistry +import com.suqi8.oshin.models.ModuleEntry +import com.suqi8.oshin.utils.RouteFormatter +import com.suqi8.oshin.utils.getAutoColor +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.text.Collator +import java.util.Locale +import javax.inject.Inject + +/** + * 应用信息缓存数据类 + */ +data class AppUiInfo( + val name: String, + val icon: ImageBitmap, + val dominantColor: Color +) + +data class SearchResultUiItem( + val item: SearchableItem, + val formattedRoute: String +) + +@HiltViewModel +class ModuleViewModel @Inject constructor( + @ApplicationContext private val context: Context, + private val featureRepository: FeatureRepository, + private val appInfoProvider: AppInfoProvider, + private val routeFormatter: RouteFormatter +) : ViewModel() { + + private val _uiState = MutableStateFlow(ModuleUiState()) + val uiState = _uiState.asStateFlow() + + // 缓存所有可搜索项的列表,避免重复构建 + private var allSearchableItems: List = emptyList() + + // 滚动位置状态 + var scrollIndex by mutableStateOf(0) + private set + var scrollOffset by mutableStateOf(0) + private set + + private val _appInfoCache = mutableStateMapOf() + val appInfoCache: Map = _appInfoCache + + /** + * 保存滚动位置 + */ + fun saveScrollPosition(index: Int, offset: Int) { + scrollIndex = index + scrollOffset = offset + } + + /** + * 缓存应用信息 + */ + fun cacheAppInfo(packageName: String, appInfo: AppUiInfo) { + _appInfoCache[packageName] = appInfo + } + + /** + * 获取缓存的应用信息 + */ + fun getAppInfo(packageName: String): AppUiInfo? { + return _appInfoCache[packageName] + } + + init { + viewModelScope.launch { + val savedStyle = context.prefs("settings").getInt("appstyle", 0) + _uiState.update { it.copy(appStyle = savedStyle) } + } + buildSearchIndex() + } + + fun onAppStyleChanged() { + val currentStyle = _uiState.value.appStyle + val newStyle = if (currentStyle == 0) 1 else 0 + _uiState.update { it.copy(appStyle = newStyle) } + + // 将新样式保存到 SharedPreferences + viewModelScope.launch(Dispatchers.IO) { + context.prefs("settings").edit { putInt("appstyle", newStyle) } + } + } + + private fun buildSearchIndex() { + viewModelScope.launch { + _uiState.update { it.copy(isLoading = true) } + + val sortedEntriesJob = async(Dispatchers.IO) { getSortedModuleEntries() } + val searchableItemsJob = async { featureRepository.getAllSearchableItems() } + + _uiState.update { + it.copy( + moduleEntries = sortedEntriesJob.await(), + isLoading = false + ) + } + + // 等待搜索索引任务完成并存入缓存 + allSearchableItems = searchableItemsJob.await() + } + } + + private suspend fun getSortedModuleEntries(): List { + val originalEntries = FeatureRegistry.moduleEntries + + // 1. 为每个 Entry 异步获取其应用名 + val entriesWithAppName = coroutineScope { + originalEntries.map { entry -> + async(Dispatchers.IO) { + appInfoProvider.getAppName(entry.packageName) to entry + } + }.awaitAll() + } + + // 2. 根据应用名进行排序 (使用 Collator 以支持中文等复杂排序) + val collator = Collator.getInstance(Locale.getDefault()) + val sortedEntries = entriesWithAppName.sortedWith( + compareBy(collator) { it.first } + ).map { + // 3. 排序后,只返回原始的 ModuleEntry 列表 + it.second + } + + return sortedEntries + } + + fun onAppNotFound(packageName: String) { + _uiState.update { + it.copy(notInstalledApps = it.notInstalledApps + packageName) + } + } + + /** + * 当搜索框内容变化时由 UI 调用。 + */ + fun onSearchQueryChanged(query: String) { + val isSearching = query.isNotBlank() + _uiState.update { it.copy(searchQuery = query, isSearching = isSearching) } + + if (isSearching) { + viewModelScope.launch { + val results = allSearchableItems.filter { + it.title.contains(query, ignoreCase = true) || + it.summary.contains(query, ignoreCase = true) + } + val uiResults = results.map { item -> + val routeId = item.route.substringAfter("feature/") + val formattedRoute = routeFormatter.formatRouteAsBreadcrumb(routeId) + SearchResultUiItem(item, formattedRoute) + } + + _uiState.update { it.copy(searchResults = uiResults) } + } + } + } + + /** + * 统一加载应用信息和主色 + * 1. 调用 AppInfoProvider 获取名称和图标 + * 2. 异步提取主色 + * 3. 将最终的 AppUiInfo 存入 _appInfoCache + * + * @param packageName 要加载的应用 + * @param defaultColor UI层传入的默认颜色,用于加载时显示 + */ + fun loadAppInfoWithColor( + packageName: String, + defaultColor: Color + ) { + if (getAppInfo(packageName) != null || uiState.value.notInstalledApps.contains(packageName)) { + return + } + + viewModelScope.launch { + val appInfo = appInfoProvider.getInfo(packageName) + + if (appInfo == null) { + // 2a. 应用未找到,更新 UiState + onAppNotFound(packageName) + } else { + // 2b. 应用已找到,立即缓存部分信息(带默认颜色) + // 这会让UI立即从 "null" 变为 "加载中" + val partialInfo = AppUiInfo(appInfo.name, appInfo.icon, defaultColor) + cacheAppInfo(packageName, partialInfo) // 使用 ViewModel 已有的 cacheAppInfo + + // 3. 异步提取主色 + val newColor = withContext(Dispatchers.Default) { + try { + if (!YukiHookAPI.Status.isModuleActive) { + defaultColor + } else { + // 调用我们放在 ColorUtils.kt 中的全局工具函数 + getAutoColor(appInfo.icon, defaultColor) + } + } catch (e: Exception) { + // 颜色提取失败,使用默认值 + defaultColor + } + } + + // 4. 更新缓存,提供包含最终颜色的完整信息 + if (newColor != defaultColor) { + cacheAppInfo(packageName, AppUiInfo(appInfo.name, appInfo.icon, newColor)) + } + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/module/SearchableItem.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/module/SearchableItem.kt new file mode 100644 index 00000000..a43951a0 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/module/SearchableItem.kt @@ -0,0 +1,11 @@ +package com.suqi8.oshin.ui.mainscreen.module + +/** + * 搜索结果列表项的数据模型。 + */ +data class SearchableItem( + val title: String, + val summary: String, + val route: String, + val key: String +) diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/GitHubAsset.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/GitHubAsset.kt new file mode 100644 index 00000000..1c1fde64 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/GitHubAsset.kt @@ -0,0 +1,16 @@ +package com.suqi8.oshin.ui.mainscreen.softupdate + +import com.google.gson.annotations.SerializedName + +data class GitHubAsset( + @SerializedName("id") + val id: Long, + @SerializedName("name") + val name: String?, + @SerializedName("size") + val size: Long, + @SerializedName("download_count") + val downloadCount: Int, + @SerializedName("browser_download_url") + val downloadUrl: String +) diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/GitHubRelease.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/GitHubRelease.kt new file mode 100644 index 00000000..31a41d33 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/GitHubRelease.kt @@ -0,0 +1,20 @@ +package com.suqi8.oshin.ui.mainscreen.softupdate + +import com.google.gson.annotations.SerializedName + +data class GitHubRelease( + @SerializedName("id") + val id: Long, + @SerializedName("name") + val name: String, + @SerializedName("tag_name") + val tagName: String, + @SerializedName("body") + val body: String, + @SerializedName("created_at") + val createdAt: String, + @SerializedName("published_at") + val publishedAt: String?, + @SerializedName("assets") + val assets: List +) diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/SoftwareUpdatePage.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/SoftwareUpdatePage.kt new file mode 100644 index 00000000..4d9224b0 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/SoftwareUpdatePage.kt @@ -0,0 +1,779 @@ +package com.suqi8.oshin.ui.mainscreen.softupdate + +import android.annotation.SuppressLint +import android.content.Context +import android.util.Log +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.Crossfade +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.navigation.NavController +import com.highcapable.yukihookapi.hook.factory.prefs +import com.suqi8.oshin.BuildConfig +import com.suqi8.oshin.R +import com.suqi8.oshin.ui.activity.components.Card +import com.suqi8.oshin.ui.activity.components.FunPage +import com.suqi8.oshin.ui.activity.components.addline +import com.suqi8.oshin.ui.activity.components.LiquidButton +import com.suqi8.oshin.utils.BaseMarkdown +import com.suqi8.oshin.utils.formatDate +import com.suqi8.oshin.utils.formatTimeAgo +import com.suqi8.oshin.utils.getPhoneName +import com.suqi8.oshin.utils.markdownTypography +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import top.yukonga.miuix.kmp.basic.Button +import top.yukonga.miuix.kmp.basic.ButtonDefaults +import top.yukonga.miuix.kmp.basic.CircularProgressIndicator +import top.yukonga.miuix.kmp.basic.Icon +import top.yukonga.miuix.kmp.basic.LinearProgressIndicator +import top.yukonga.miuix.kmp.basic.ProgressIndicatorDefaults +import top.yukonga.miuix.kmp.basic.ScrollBehavior +import top.yukonga.miuix.kmp.basic.TabRow +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.basic.TextButton +import top.yukonga.miuix.kmp.basic.TextField +import top.yukonga.miuix.kmp.extra.SuperDialog +import top.yukonga.miuix.kmp.icon.MiuixIcons +import top.yukonga.miuix.kmp.icon.icons.other.GitHub +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.utils.overScrollVertical +import top.yukonga.miuix.kmp.utils.scrollEndHaptic +import kotlin.math.roundToInt + +// ============ 常量和配置 ============ + +private object UpdatePageDimens { + val TabRowPadding = 100.dp + val TabRowMinWidth = 0.dp + val TabRowMaxWidth = 50.dp + val TabRowHeight = 40.dp + val TabRowCornerRadius = 20.dp + + val ContentStartPadding = 40.dp + val ContentEndPadding = 40.dp + val ContentTopPadding = 12.dp + val ContentBottomPadding = 8.dp + val ContentVerticalPadding = PaddingValues( + start = ContentStartPadding, + end = ContentEndPadding, + top = ContentTopPadding, + bottom = ContentBottomPadding + ) + + val CardCornerRadius = 20.dp + val DownloadButtonHeight = 52.dp + val DownloadButtonCornerRadius = 26.dp + val DownloadButtonHorizontalPadding = 16.dp + val DownloadButtonVerticalPadding = 12.dp + + val LazyColumnBottomPadding = 96.dp +} + +// ============ 主页面 ============ + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun SoftwareUpdatePage( + navController: NavController, + topAppBarScrollBehavior: ScrollBehavior, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + val TAG = "SoftwareUpdatePage" + val context = LocalContext.current + val activity = context as ComponentActivity + val viewModel: UpdateViewModel = hiltViewModel(activity) + var releaseType by remember { mutableIntStateOf(viewModel.getSavedUpdateChannel()) } + val isDebugEnabled = remember { context.prefs("settings").getBoolean("Debug", false) } + val currentVersion = rememberCurrentVersion(context) + val showTokenDialog = remember { mutableStateOf(false) } + + val installLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { result -> + Log.d(TAG, "安装 Activity 结果: ${result.resultCode}") + } + + val scope = rememberCoroutineScope() + + LaunchedEffect(releaseType) { + viewModel.fetchReleases(releaseType) + } + + LaunchedEffect(Unit) { + viewModel.checkTokenStatus() + } + + // 计算实际状态 + val latestRelease = remember(viewModel.releases) { viewModel.releases.firstOrNull() } + val latestApkAsset = remember(latestRelease) { + latestRelease?.assets?.firstOrNull { it.name?.endsWith(".apk") == true } + } + + val hasNewVersion = remember(latestRelease, currentVersion) { + if (isDebugEnabled && viewModel.releases.isNotEmpty()) { + true + } else { + viewModel.hasNewVersion(latestRelease, currentVersion, releaseType) + } + } + + val isDownloadActionEnabled = hasNewVersion && latestApkAsset?.downloadUrl != null + + LaunchedEffect(latestApkAsset, viewModel.isDownloading) { + Log.d(TAG, "triggerAutoDownload: ${viewModel.triggerAutoDownload} latestApkAsset: $latestApkAsset, isDownloading: ${viewModel.isDownloading}") + if (viewModel.triggerAutoDownload && latestApkAsset != null && !viewModel.isDownloading) { + + viewModel.consumeAutoDownloadFlag() + + Toast.makeText(context, context.getString(R.string.update_page_downloading), Toast.LENGTH_SHORT).show() + + scope.launch(Dispatchers.IO) { + viewModel.error = null + val file = viewModel.downloadApk(latestApkAsset.downloadUrl, context) + withContext(Dispatchers.Main) { + if (file != null) { + Toast.makeText(context, context.getString(R.string.update_page_download_complete), Toast.LENGTH_SHORT).show() + installApk(context, file, installLauncher, scope) + } else { + val errorMsg = viewModel.error ?: "" + Toast.makeText(context, context.getString(R.string.update_page_download_failed, errorMsg), Toast.LENGTH_LONG).show() + } + } + } + } + } + + FunPage( + title = stringResource(R.string.check_update), + navController = navController, + scrollBehavior = topAppBarScrollBehavior, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + animationKey = "update_card_transition", + action = { backdrop -> + LiquidButton( + onClick = { showTokenDialog.value = true }, + modifier = Modifier.size(40.dp), + backdrop = backdrop + ) { + Icon( + imageVector = MiuixIcons.Other.GitHub, + contentDescription = stringResource(R.string.update_page_github_token_desc), + tint = MiuixTheme.colorScheme.onBackground + ) + } + } + ) { padding -> + var showHistory by remember { mutableStateOf(false) } + + Box( + modifier = Modifier + .fillMaxSize() + .padding(padding), + contentAlignment = Alignment.BottomCenter + ) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .overScrollVertical() + .scrollEndHaptic() + .nestedScroll(topAppBarScrollBehavior.nestedScrollConnection), + contentPadding = PaddingValues(bottom = UpdatePageDimens.LazyColumnBottomPadding) + ) { + item { + Spacer(modifier = Modifier + .displayCutoutPadding() + .height(0.dp)) + } + + // TabRow + item { + Box( + modifier = Modifier.padding( + horizontal = UpdatePageDimens.TabRowPadding, + vertical = 8.dp + ) + ) { + TabRow( + tabs = listOf( + stringResource(R.string.update_page_tab_release), + stringResource(R.string.update_page_tab_ci) + ), + selectedTabIndex = releaseType, + minWidth = UpdatePageDimens.TabRowMinWidth, + maxWidth = UpdatePageDimens.TabRowMaxWidth, + cornerRadius = UpdatePageDimens.TabRowCornerRadius, + height = UpdatePageDimens.TabRowHeight, + onTabSelected = { releaseType = it } + ) + } + } + + // 统一的信息卡片 + item { + UnifiedUpdateInfoCard( + release = latestRelease, + releaseType = releaseType, + error = viewModel.error, + isLoading = viewModel.isLoading, + hasNewVersion = hasNewVersion + ) + } + + // 历史记录 + item { + UpdateInfoSection( + release = latestRelease, + hasNewVersion = hasNewVersion, + showHistory = showHistory, + onToggleHistory = { showHistory = !showHistory }, + context = context // 传入 context + ) + } + + // 历史版本列表 + if (!hasNewVersion && showHistory) { + items(viewModel.releases) { release -> + ReleaseHistoryItem(release = release) + } + } + + // 新版本详情 + item { + if (hasNewVersion && latestRelease != null) { + Column(modifier = Modifier.padding(UpdatePageDimens.ContentVerticalPadding)) { + addline() + Spacer(Modifier.height(20.dp)) + BaseMarkdown( + content = latestRelease.body, + typography = markdownTypography() + ) + } + } + } + } + + // 下载按钮 + if (hasNewVersion && latestApkAsset != null) { + DownloadButtonSection( + isDownloading = viewModel.isDownloading, + downloadProgress = viewModel.downloadProgress.collectAsState().value, + isEnabled = isDownloadActionEnabled, + onDownloadClick = { + scope.launch(Dispatchers.IO) { + viewModel.error = null + val file = viewModel.downloadApk(latestApkAsset.downloadUrl, context) + withContext(Dispatchers.Main) { + if (file != null) { + Toast.makeText(context, context.getString(R.string.update_page_download_complete), Toast.LENGTH_SHORT).show() + installApk(context, file, installLauncher, scope) + } else { + val errorMsg = viewModel.error ?: "" + Toast.makeText(context, context.getString(R.string.update_page_download_failed, errorMsg), Toast.LENGTH_LONG).show() + } + } + } + } + ) + } + } + + // Token 输入对话框 + TokenEntryDialog( + show = showTokenDialog, + currentToken = viewModel.currentToken, + onDismiss = { showTokenDialog.value = false }, + onSave = { token -> + viewModel.saveToken(token) + showTokenDialog.value = false + scope.launch { viewModel.fetchReleases(releaseType) } + }, + onClear = { + viewModel.clearToken() + showTokenDialog.value = false + scope.launch { viewModel.fetchReleases(releaseType) } + } + ) + } +} + +// ============ 子组件 ============ + +@Composable +private fun rememberCurrentVersion(context: Context): String { + return remember { + try { + context.packageManager.getPackageInfo(context.packageName, 0).versionName ?: "0.0.0" + } catch (e: Exception) { + "0.0.0" + } + } +} + +@Composable +private fun UpdateInfoSection( + release: GitHubRelease?, + hasNewVersion: Boolean, + showHistory: Boolean, + onToggleHistory: () -> Unit, + context: Context // 新增 Context 参数 +) { + Column(modifier = Modifier.padding(UpdatePageDimens.ContentVerticalPadding)) { + Text( + text = stringResource(R.string.update_page_software_version), + fontSize = 16.sp, + modifier = Modifier.padding(bottom = 8.dp), + color = MiuixTheme.colorScheme.onSurface + ) + + if (!hasNewVersion) { + Text( + text = stringResource(R.string.update_page_current_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE), + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + modifier = Modifier.padding(bottom = 8.dp), + color = MiuixTheme.colorScheme.onBackgroundVariant + ) + Text( + text = if (showHistory) stringResource(R.string.update_page_hide_history) else stringResource(R.string.update_page_show_history), + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + color = MiuixTheme.colorScheme.onBackgroundVariant, + modifier = Modifier.clickable { onToggleHistory() } + ) + } else { + release?.let { + val totalSizeBytes = it.assets.sumOf { asset -> asset.size } + val totalSizeMB = totalSizeBytes / 1024f / 1024f + Text( + text = stringResource(R.string.update_page_release_size, it.name, totalSizeMB), + fontWeight = FontWeight.Medium, + fontSize = 12.sp, + modifier = Modifier.padding(bottom = 8.dp), + color = MiuixTheme.colorScheme.onBackgroundVariant + ) + } + } + + val dateToShow = release?.publishedAt ?: release?.createdAt + + dateToShow?.let { date -> + Row( + modifier = Modifier.padding(top = 4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.update_page_published_at, formatDate(date)), + fontSize = 11.sp, + color = MiuixTheme.colorScheme.onSurfaceVariantActions + ) + Spacer(Modifier.width(8.dp)) + Text( + // 传入 context + text = stringResource(R.string.update_page_time_ago_wrapper, formatTimeAgo(date, context)), + fontSize = 11.sp, + color = MiuixTheme.colorScheme.onSurfaceVariantActions + ) + } + } + } +} + +@Composable +private fun ReleaseHistoryItem(release: GitHubRelease) { + Column( + modifier = Modifier.padding( + start = 40.dp, + end = 40.dp, + top = 12.dp, + bottom = 8.dp + ) + ) { + addline() + Spacer(Modifier.height(16.dp)) + Text( + text = release.name, + fontWeight = FontWeight.Bold, + fontSize = 14.sp, + color = MiuixTheme.colorScheme.onSurface, + modifier = Modifier.padding(bottom = 8.dp) + ) + BaseMarkdown( + content = release.body, + typography = markdownTypography() + ) + } +} + +@Composable +private fun DownloadButtonSection( + isDownloading: Boolean, + downloadProgress: Float, + isEnabled: Boolean, + onDownloadClick: () -> Unit +) { + Box( + modifier = Modifier + .fillMaxWidth() + .background(MiuixTheme.colorScheme.background) + .padding( + horizontal = UpdatePageDimens.DownloadButtonHorizontalPadding, + vertical = UpdatePageDimens.DownloadButtonVerticalPadding + ) + ) { + DownloadButton( + isDownloading = isDownloading, + downloadProgress = downloadProgress, + isEnabled = isEnabled || isDownloading, + onClick = onDownloadClick + ) + } +} + +@Composable +private fun DownloadButton( + isDownloading: Boolean, + downloadProgress: Float, + isEnabled: Boolean, + onClick: () -> Unit +) { + Button( + onClick = onClick, + insideMargin = PaddingValues(0.dp), + colors = ButtonDefaults.buttonColorsPrimary(), + modifier = Modifier + .fillMaxWidth() + .height(UpdatePageDimens.DownloadButtonHeight) + .clip(RoundedCornerShape(UpdatePageDimens.DownloadButtonCornerRadius)), + enabled = isEnabled + ) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + // 背景进度条 + if (isDownloading) { + LinearProgressIndicator( + progress = if (downloadProgress >= 0f) downloadProgress else null, + height = UpdatePageDimens.DownloadButtonHeight + ) + } + + // 前景文本 + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + val buttonText = if (isDownloading) { + val progressPercent = (downloadProgress * 100).roundToInt() + if (downloadProgress >= 0f) + stringResource(R.string.update_page_downloading_progress, progressPercent) + else + stringResource(R.string.update_page_downloading) + } else { + stringResource(R.string.update_page_download_and_install) + } + + Text( + text = buttonText, + color = MiuixTheme.colorScheme.onPrimary, + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp + ) + } + } + } +} + +// ============ 统一信息卡片 ============ + +@SuppressLint("PrivateApi") +@Composable +private fun UnifiedUpdateInfoCard( + release: GitHubRelease?, + releaseType: Int, + error: String? = null, + isLoading: Boolean, + hasNewVersion: Boolean +) { + val cardDimensions = rememberUpdateCardDimensions(hasNewVersion) + + Box(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 12.dp) + ) { + Card( + cornerRadius = UpdatePageDimens.CardCornerRadius, + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .height(cardDimensions.cardHeight) + ) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.TopCenter + ) { + Image( + painter = painterResource(id = R.drawable.updatebg), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Image( + painter = painterResource(id = R.drawable.colorlogo), + contentDescription = null, + modifier = Modifier + .padding(top = cardDimensions.logoTopPadding) + .height(cardDimensions.logoHeight) + .width(cardDimensions.logoWidth) + ) + + Text( + if (releaseType == 0) + stringResource(R.string.update_page_card_title_release) + else + stringResource(R.string.update_page_card_title_ci), + fontSize = cardDimensions.titleFontSize, + color = Color.White, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(top = cardDimensions.titleTopPadding) + ) + + val phoneName = getPhoneName() + Text( + phoneName, + fontSize = cardDimensions.phoneNameFontSize, + color = Color.White, + modifier = Modifier.padding(top = cardDimensions.phoneNameTopPadding) + ) + } + + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.BottomCenter + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + CardStatusRow( + error = error, + isLoading = isLoading, + release = release, + hasNewVersion = hasNewVersion, + statusFontSize = cardDimensions.statusFontSize + ) + Spacer(modifier = Modifier.height(20.dp)) + Spacer(modifier = Modifier.height(32.dp)) + } + } + } + } + } + } + } +} + +@Composable +private fun rememberUpdateCardDimensions(hasNewVersion: Boolean): UpdateCardDimensions { + val animSpec = tween(durationMillis = 400) + + val shrinkFactor70 by animateFloatAsState( + targetValue = if (!hasNewVersion) 1.0f else 0.8f, + label = "ShrinkFactor70", + animationSpec = animSpec + ) + + val shrinkFactor50 by animateFloatAsState( + targetValue = if (!hasNewVersion) 1.0f else 0.6f, + label = "ShrinkFactor50", + animationSpec = animSpec + ) + + val baseCardHeight = 500.dp + val baseLogoTopPadding = 100.dp + val baseLogoHeight = 100.dp + val baseLogoWidth = 128.dp + val baseTitleFontSize = 24.sp + val baseTitleTopPadding = 8.dp + val basePhoneNameFontSize = 13.sp + val basePhoneNameTopPadding = 4.dp + val baseStatusFontSize = 18.sp + + return UpdateCardDimensions( + cardHeight = baseCardHeight * shrinkFactor50, + logoTopPadding = baseLogoTopPadding * shrinkFactor70, + logoHeight = baseLogoHeight * shrinkFactor70, + logoWidth = baseLogoWidth * shrinkFactor70, + titleFontSize = baseTitleFontSize * shrinkFactor70, + titleTopPadding = baseTitleTopPadding * shrinkFactor70, + phoneNameFontSize = basePhoneNameFontSize * shrinkFactor70, + phoneNameTopPadding = basePhoneNameTopPadding * shrinkFactor70, + statusFontSize = baseStatusFontSize * shrinkFactor70 + ) +} + +@Composable +private fun CardStatusRow( + error: String?, + isLoading: Boolean, + release: GitHubRelease?, + hasNewVersion: Boolean, + statusFontSize: TextUnit +) { + Row(verticalAlignment = Alignment.Bottom) { + val text = when { + error != null -> error // 来自 ViewModel,已是多语言 + isLoading && release == null -> stringResource(R.string.update_page_status_checking) + hasNewVersion -> stringResource(R.string.update_page_status_new_version) + else -> stringResource(R.string.update_page_status_latest) + } + + Crossfade(targetState = text, animationSpec = tween(durationMillis = 250)) { current -> + Text(text = current, fontSize = statusFontSize, color = Color.White) + } + + AnimatedVisibility( + visible = isLoading && release == null, + enter = fadeIn(), + exit = fadeOut() + ) { + CircularProgressIndicator( + colors = ProgressIndicatorDefaults.progressIndicatorColors( + foregroundColor = Color.White, + backgroundColor = Color.Transparent + ), + size = 14.dp, + strokeWidth = 1.dp, + modifier = Modifier.padding(start = 5.dp, bottom = 3.dp) + ) + } + } +} + + +// ============ Token 对话框 ============ + +@Composable +private fun TokenEntryDialog( + show: MutableState, + currentToken: String?, + onDismiss: () -> Unit, + onSave: (String) -> Unit, + onClear: () -> Unit +) { + var tokenInput by remember { mutableStateOf(currentToken ?: "") } + + SuperDialog( + show = show, + title = stringResource(R.string.token_dialog_title), + onDismissRequest = onDismiss, + summary = stringResource(R.string.token_dialog_summary) + ) { + TextField( + value = tokenInput, + onValueChange = { tokenInput = it }, + label = stringResource(R.string.token_dialog_label), + singleLine = true, + visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done + ), + backgroundColor = MiuixTheme.colorScheme.secondaryContainer, + modifier = Modifier.fillMaxWidth() + ) + Spacer(Modifier.height(16.dp)) + Column(modifier = Modifier.fillMaxWidth()) { + TextButton( + text = stringResource(R.string.ok), + onClick = { onSave(tokenInput.trim()) }, + colors = ButtonDefaults.textButtonColorsPrimary(), + enabled = tokenInput.isNotBlank(), + modifier = Modifier.fillMaxWidth() + ) + Spacer(Modifier.height(12.dp)) + + if (!currentToken.isNullOrBlank()) { + TextButton( + text = stringResource(R.string.token_dialog_clear), + onClick = onClear, + modifier = Modifier.fillMaxWidth() + ) + Spacer(Modifier.height(12.dp)) + } + + TextButton( + text = stringResource(R.string.cancel), + onClick = onDismiss, + modifier = Modifier.fillMaxWidth() + ) + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/UpdateCardDimensions.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/UpdateCardDimensions.kt new file mode 100644 index 00000000..9cbcf441 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/UpdateCardDimensions.kt @@ -0,0 +1,16 @@ +package com.suqi8.oshin.ui.mainscreen.softupdate + +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit + +data class UpdateCardDimensions( + val cardHeight: Dp, + val logoTopPadding: Dp, + val logoHeight: Dp, + val logoWidth: Dp, + val titleFontSize: TextUnit, + val titleTopPadding: Dp, + val phoneNameFontSize: TextUnit, + val phoneNameTopPadding: Dp, + val statusFontSize: TextUnit +) diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/UpdateViewModel.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/UpdateViewModel.kt new file mode 100644 index 00000000..6149aa11 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/UpdateViewModel.kt @@ -0,0 +1,510 @@ +package com.suqi8.oshin.ui.mainscreen.softupdate + +import android.content.Context +import android.util.Log +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.gson.Gson +import com.highcapable.yukihookapi.hook.factory.prefs +import com.suqi8.oshin.BuildConfig +import com.suqi8.oshin.R +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +private object GitHubConfig { + const val BASE_URL = "https://api.github.com" + const val MAIN_REPO = "suqi8/OShin" + const val CI_REPO = "suqi8/OShin-Builds" + const val MAIN_RELEASES_PER_PAGE = 100 + const val CI_RELEASES_PER_PAGE = 30 + const val CONNECT_TIMEOUT_SECONDS = 30L + const val READ_TIMEOUT_SECONDS = 30L + const val WRITE_TIMEOUT_SECONDS = 30L +} + +private object DownloadConfig { + const val BUFFER_SIZE = 8192 // 8KB + const val DOWNLOAD_DIR = "downloads" + const val CI_DOWNLOAD_FILENAME = "OShin_CI_Latest.apk" + const val RELEASE_DOWNLOAD_FILENAME = "OShin_Release_Latest.apk" +} + +// ============ ViewModel ============ + +@HiltViewModel +class UpdateViewModel @Inject constructor( + @ApplicationContext private val context: Context // 已注入 Context +) : ViewModel() { + + private val TAG = "UpdateViewModel" + private val gson = Gson() + private val GITHUB_TOKEN_KEY = "github_pat" + private val NON_NUMERIC_REGEX = Regex("[^0-9.]") + + // UI 状态 + var releases by mutableStateOf>(emptyList()) + var isLoading by mutableStateOf(false) + var error by mutableStateOf(null) + var isDownloading by mutableStateOf(false) + var currentToken by mutableStateOf(null) + private var hasAutoChecked = false + + // 下载进度状态 + private val _downloadProgress = MutableStateFlow(-1f) + val downloadProgress = _downloadProgress.asStateFlow() + + private val _updateCheckResult = MutableStateFlow(null) + val updateCheckResult = _updateCheckResult.asStateFlow() + var triggerAutoDownload by mutableStateOf(false) + private set + + private val _downloadedBytes = MutableStateFlow(0L) + val downloadedBytes = _downloadedBytes.asStateFlow() + + private val _totalBytes = MutableStateFlow(0L) + val totalBytes = _totalBytes.asStateFlow() + + // Release 缓存 + private val releaseCache = mutableMapOf>() + + // 懒加载 HTTP 客户端 + private val httpClient: OkHttpClient by lazy { + createHttpClientWithAuth() + } + + init { + loadTokenFromPrefs() + } + + // ============ Token 管理 ============ + + private fun loadTokenFromPrefs() { + val token = context.prefs("settings").getString(GITHUB_TOKEN_KEY, "") + currentToken = token.ifBlank { null } + } + + fun checkTokenStatus() { + loadTokenFromPrefs() + } + + fun setAutoDownloadFlag() { + triggerAutoDownload = true + } + + fun consumeAutoDownloadFlag() { + triggerAutoDownload = false + } + + fun getSavedUpdateChannel(): Int { + return context.prefs("settings").getInt("app_update_channel", 0) + } + + fun clearUpdateCheckResult() { + _updateCheckResult.value = null + } + + fun autoCheckForUpdate(currentVersion: String) { + if (hasAutoChecked) return + viewModelScope.launch { + val savedChannel = getSavedUpdateChannel() + + fetchReleasesInternal(savedChannel) + + val latestRelease = releases.firstOrNull() + val isDebugEnabled = context.prefs("settings").getBoolean("Debug", false) + val shouldShowUpdate: Boolean = if (isDebugEnabled && releases.isNotEmpty()) true else { + hasNewVersion(latestRelease, currentVersion, savedChannel) + } + if (shouldShowUpdate) { + _updateCheckResult.value = latestRelease + } + hasAutoChecked = true + } + } + + fun saveToken(token: String) { + viewModelScope.launch(Dispatchers.IO) { + context.prefs("settings").edit { putString(GITHUB_TOKEN_KEY, token) } + withContext(Dispatchers.Main) { + currentToken = token.ifBlank { null } + } + } + } + + fun clearToken() { + viewModelScope.launch(Dispatchers.IO) { + context.prefs("settings").edit { remove(GITHUB_TOKEN_KEY) } + withContext(Dispatchers.Main) { + currentToken = null + } + } + } + + // ============ HTTP 客户端 ============ + + private fun createHttpClientWithAuth(): OkHttpClient { + val token = getTokenFromPrefs() + return OkHttpClient.Builder() + .followRedirects(true) + .connectTimeout(GitHubConfig.CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .readTimeout(GitHubConfig.READ_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .writeTimeout(GitHubConfig.WRITE_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .apply { + if (!token.isNullOrBlank()) { + addInterceptor { chain -> + chain.proceed( + chain.request().newBuilder() + .header("Authorization", "Bearer $token") + .build() + ) + } + } + } + .build() + } + + private fun getTokenFromPrefs(): String? { + val token = context.prefs("settings").getString(GITHUB_TOKEN_KEY, "") + return token.ifBlank { null } + } + + // ============ 版本比较 ============ + + private fun extractVersion(tag: String): List { + val clean = tag + .removePrefix("v") + .substringBefore("(") + .substringBefore("-") + .substringBefore(".g") + .replace(NON_NUMERIC_REGEX, "") + return clean.split(".").mapNotNull { it.toIntOrNull() } + } + + private fun isNewerVersion(latestTag: String, currentTag: String): Boolean { + val latestParts = extractVersion(latestTag) + val currentParts = extractVersion(currentTag) + + for (i in 0 until maxOf(latestParts.size, currentParts.size)) { + val latest = latestParts.getOrElse(i) { 0 } + val current = currentParts.getOrElse(i) { 0 } + when { + latest > current -> return true + latest < current -> return false + } + } + return false + } + + fun hasNewVersion( + latestRelease: GitHubRelease?, + currentVersion: String, + releaseType: Int + ): Boolean { + if (latestRelease == null) return false + + val latestTag = latestRelease.tagName + return if (releaseType == 0) { + isNewerVersion(latestTag, currentVersion) + } else { + val cleanLatest = latestTag.replace("CI", "", ignoreCase = true) + val cleanCurrent = currentVersion.replace("CI", "", ignoreCase = true) + isNewerVersion(cleanLatest, cleanCurrent) + } + } + + // ============ Release 获取 ============ + + private fun cleanMarkdownText(text: String?): String { + if (text.isNullOrBlank()) return "" + return text + .replace("\r\n", "\n") + .replace(Regex("(?i)"), "\n") + .replace(Regex("\n{3,}"), "\n\n") + .replace(Regex("(? + releases = if (releaseType == 0) { + fetchedReleases + .filter { !it.tagName.contains("CI", ignoreCase = true) } + .map { it.copy(body = cleanMarkdownText(it.body)) } + .take(30) + } else { + fetchedReleases.take(20) + } + releaseCache[releaseType] = releases + logDebug("解析到 ${releases.size} 个 releases") + } ?: run { + error = context.getString(R.string.view_model_error_fetch_failed) // 修改 + logError("未能从GitHub获取数据") + } + } catch (e: Exception) { + error = handleException(e) // 修改 + logError("获取异常: ${error}", e) + } finally { + isLoading = false + } + } + } + + private suspend fun fetchReleasesFromGitHub(releaseType: Int): List? { + return withContext(Dispatchers.IO) { + val repoUrlPart = if (releaseType == 0) GitHubConfig.MAIN_REPO else GitHubConfig.CI_REPO + val perPage = if (releaseType == 0) GitHubConfig.MAIN_RELEASES_PER_PAGE else GitHubConfig.CI_RELEASES_PER_PAGE + val url = "${GitHubConfig.BASE_URL}/repos/$repoUrlPart/releases?per_page=$perPage" + + logDebug("开始获取 Releases, URL: $url") + + val request = Request.Builder().url(url).build() + val response = httpClient.newCall(request).execute() + + try { + if (response.isSuccessful) { + logDebug("获取成功 (代码: ${response.code})") + val responseBody = response.body.string() + gson.fromJson(responseBody, Array::class.java)?.toList() + } else { + error = when (response.code) { + 403 -> context.getString(R.string.view_model_error_api_limit) // 修改 + 404 -> context.getString(R.string.view_model_error_not_found) // 修改 + else -> context.getString(R.string.view_model_error_fetch_failed_code, response.code) // 修改 + } + logError("HTTP错误: ${response.code}, 消息: ${response.message}") + null + } + } finally { + response.close() + } + } + } + + // ============ 下载功能 ============ + + suspend fun downloadApk(url: String, context: Context): File? { + return withContext(Dispatchers.IO) { + logDebug("开始下载APK: $url") + resetDownloadProgress() + isDownloading = true + error = null + + var outputFile: File? = null + + try { + val result = retryWithBackoff(maxRetries = 2, delayMillis = 1000) { + downloadApkInternal(url, context) + } + + outputFile = result + if (result != null) { + _downloadProgress.value = 1f + logDebug("文件下载完成: ${result.absolutePath}") + } + result + } catch (e: Exception) { + val errorMsg = e.message ?: context.getString(R.string.view_model_error_unknown) // 修改 + error = context.getString(R.string.view_model_error_download_exception, errorMsg) // 修改 + logError("下载异常", e) + outputFile?.delete() + null + } finally { + isDownloading = false + if (error != null) { + _downloadProgress.value = -1f + } + } + } + } + + private suspend fun downloadApkInternal(url: String, context: Context): File? { + return withContext(Dispatchers.IO) { + val request = Request.Builder().url(url).build() + val response = httpClient.newCall(request).execute() + + try { + if (!response.isSuccessful) { + error = context.getString(R.string.view_model_error_download_failed_code, response.code) // 修改 + logError("下载HTTP错误: ${response.code}") + return@withContext null + } + + val body = response.body + val total = body.contentLength() + _totalBytes.value = total + logDebug("文件总大小: $total bytes") + + if (total > 0) _downloadProgress.value = 0f + + val fileName = extractFileName(response, url) + val file = createDownloadFile(context, fileName) + + downloadToFile(body, file, total) + file + } finally { + response.close() + } + } + } + + private fun extractFileName(response: Response, url: String): String { + val contentDisposition = response.header("Content-Disposition") + val fileNameFromHeader = contentDisposition + ?.substringAfter("filename=", "") + ?.removeSurrounding("\"") + + val fileName = fileNameFromHeader?.takeIf { it.isNotBlank() } + ?: url.substringAfterLast("/") + + return if (fileName.isBlank() || !fileName.endsWith(".apk")) { + if (url.contains("OShin-Builds")) DownloadConfig.CI_DOWNLOAD_FILENAME + else DownloadConfig.RELEASE_DOWNLOAD_FILENAME + } else { + fileName + } + } + + private fun createDownloadFile(context: Context, fileName: String): File { + val downloadDir = File(context.getExternalFilesDir(null), DownloadConfig.DOWNLOAD_DIR) + downloadDir.mkdirs() + return File(downloadDir, fileName) + } + + private suspend fun downloadToFile(body: ResponseBody, file: File, total: Long) { + withContext(Dispatchers.IO) { + body.byteStream().use { inputStream -> + FileOutputStream(file).use { output -> + val buffer = ByteArray(DownloadConfig.BUFFER_SIZE) + var bytesCopied: Long = 0 + var bytes: Int + + while (inputStream.read(buffer).also { bytes = it } >= 0) { + output.write(buffer, 0, bytes) + bytesCopied += bytes + _downloadedBytes.value = bytesCopied + + if (total > 0) { + _downloadProgress.value = bytesCopied.toFloat() / total.toFloat() + } + } + } + } + } + } + + // ============ 工具函数 ============ + + private fun resetDownloadProgress() { + _downloadProgress.value = -1f + _downloadedBytes.value = 0L + _totalBytes.value = 0L + } + + private suspend fun retryWithBackoff( + maxRetries: Int = 3, + delayMillis: Long = 1000, + block: suspend () -> T? + ): T? { + repeat(maxRetries) { attempt -> + try { + val result = block() + if (result != null) return result + } catch (e: Exception) { + logDebug("重试 ${attempt + 1}/$maxRetries 失败: ${e.message}") + if (attempt < maxRetries - 1) { + delay(delayMillis * (attempt + 1)) + } + } + } + return null + } + + private fun handleException(e: Exception): String { + val unknownError = context.getString(R.string.view_model_error_unknown) // 修改 + return when (e) { + is SocketTimeoutException -> context.getString(R.string.view_model_error_timeout) // 修改 + is UnknownHostException -> context.getString(R.string.view_model_error_unknown_host) // 修改 + is IOException -> context.getString(R.string.view_model_error_network, e.message ?: unknownError) // 修改 + else -> context.getString(R.string.view_model_error_general, e.message ?: unknownError) // 修改 + } + } + + private fun logDebug(message: String) { + if (BuildConfig.DEBUG) { + Log.d(TAG, message) + } + } + + private fun logError(message: String, throwable: Throwable? = null) { + Log.e(TAG, message, throwable) + } + + // ============ 清理功能 ============ + + fun clearOldDownloads() { + viewModelScope.launch(Dispatchers.IO) { + try { + val downloadDir = File(context.getExternalFilesDir(null), DownloadConfig.DOWNLOAD_DIR) + val sevenDaysInMillis = 7 * 24 * 60 * 60 * 1000L + val currentTime = System.currentTimeMillis() + + downloadDir.listFiles()?.forEach { file -> + if (currentTime - file.lastModified() > sevenDaysInMillis) { + if (file.delete()) { + logDebug("已清理旧文件: ${file.name}") + } + } + } + } catch (e: Exception) { + logError("清理旧文件失败", e) + } + } + } + + override fun onCleared() { + releaseCache.clear() + super.onCleared() + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/installApk.kt b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/installApk.kt new file mode 100644 index 00000000..07778498 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/mainscreen/softupdate/installApk.kt @@ -0,0 +1,134 @@ +package com.suqi8.oshin.ui.mainscreen.softupdate + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.util.Log +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.core.content.FileProvider +import com.suqi8.oshin.R +import com.suqi8.oshin.utils.checkIfRooted +import com.suqi8.oshin.utils.executeCommand +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File + +@SuppressLint("QueryPermissionsNeeded") +fun installApk( + context: Context, + apkFile: File, + launcher: ActivityResultLauncher, + scope: CoroutineScope +) { + val TAG = "InstallApk" + Log.d(TAG, "开始安装流程: ${apkFile.absolutePath}") + + if (checkIfRooted()) { + scope.launch(Dispatchers.IO) { + handleRootInstall(context, apkFile, launcher, TAG) + } + } else { + scope.launch(Dispatchers.Main) { + fallbackInstall(context, apkFile, launcher) + } + } +} + +private suspend fun handleRootInstall( + context: Context, + apkFile: File, + launcher: ActivityResultLauncher, + TAG: String +) { + val originalPath = apkFile.absolutePath + val tempDir = "/data/local/tmp" + val tempPath = "$tempDir/${apkFile.name}" + val mainActivityName = "com.suqi8.oshin.MainActivity" + val packageName = context.packageName + + var operationFailed = false + var errorMessage = context.getString(R.string.install_error_root_unknown) // 修改 + + try { + Log.d(TAG, "尝试复制 APK 到临时目录") + val copyCommand = "cp \"$originalPath\" \"$tempPath\"" + val copyOutput = executeCommand(copyCommand) + Log.d(TAG, "复制命令输出: $copyOutput") + + Log.d(TAG, "尝试安装并启动应用") + val installAndStartCommand = "pm install -r \"$tempPath\" && am start -n $packageName/$mainActivityName" + val installOutput = executeCommand(installAndStartCommand) + Log.d(TAG, "安装并启动命令输出: $installOutput") + + if (installOutput.contains("Failure", ignoreCase = true)) { + Log.e(TAG, "安装命令报告失败") + errorMessage = if (installOutput.isNotBlank()) { + context.getString(R.string.install_error_root_output, installOutput) // 修改 + } else { + context.getString(R.string.install_error_root_no_output) // 修改 + } + operationFailed = true + } else { + Log.d(TAG, "安装并启动命令已发送 (假设成功)") + } + + } catch (e: Exception) { + Log.e(TAG, "Root 操作序列异常", e) + errorMessage = context.getString(R.string.install_error_root_exception, e.message ?: "") // 修改 + operationFailed = true + } finally { + // 清理临时文件 + Log.d(TAG, "尝试清理临时文件") + try { + if (checkIfRooted()) { + val removeCommand = "rm -f \"$tempPath\"" + executeCommand(removeCommand) + Log.d(TAG, "清理命令已发送") + } + } catch (e: Exception) { + Log.e(TAG, "清理临时文件失败", e) + } + + // 如果 Root 安装失败,回退到系统安装器 + if (operationFailed) { + Log.w(TAG, "Root 操作失败,回退到系统安装器") + withContext(Dispatchers.Main) { + Toast.makeText(context, errorMessage, Toast.LENGTH_LONG).show() + fallbackInstall(context, apkFile, launcher) + } + } + } +} + + +fun fallbackInstall( + context: Context, + apkFile: File, + launcher: ActivityResultLauncher +) { + val TAG = "FallbackInstall" + Log.d(TAG, "开始 Fallback 安装: ${apkFile.absolutePath}") + try { + val uri = FileProvider.getUriForFile( + context, + "${context.packageName}.provider", + apkFile + ) + Log.d(TAG, "生成 Content URI: $uri") + + val intent = Intent(Intent.ACTION_VIEW).apply { + setDataAndType(uri, "application/vnd.android.package-archive") + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + Log.d(TAG, "启动安装 Intent...") + launcher.launch(intent) + Log.d(TAG, "安装 Intent 已启动") + } catch (e: Exception) { + Log.e(TAG, "启动安装 Intent 失败", e) + Toast.makeText(context, context.getString(R.string.install_error_fallback, e.message ?: ""), Toast.LENGTH_SHORT).show() // 修改 + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/theme/BgEffectPainter.kt b/app/src/main/java/com/suqi8/oshin/ui/theme/BgEffectPainter.kt new file mode 100644 index 00000000..9a47ec9f --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/theme/BgEffectPainter.kt @@ -0,0 +1,231 @@ +package com.suqi8.oshin.ui.theme + +import android.content.Context +import android.content.res.Resources +import android.graphics.RenderEffect +import android.graphics.RuntimeShader +import android.os.Handler +import android.os.Looper +import android.util.TypedValue +import android.view.View +import android.view.ViewGroup +import com.suqi8.oshin.R +import java.util.Scanner + +class BgEffectPainter(context: Context) { + private lateinit var bound: FloatArray + var mBgRuntimeShader: RuntimeShader + var mResources: Resources = context.resources + private lateinit var uResolution: FloatArray + private var uAnimTime = (System.nanoTime().toFloat()) / 1.0E9f + private var uBgBound = floatArrayOf(0.0f, 0.4489f, 1.0f, 0.5511f) + private val uTranslateY = 0.0f + private var uPoints = floatArrayOf(0.67f, 0.42f, 1.0f, 0.69f, 0.75f, 1.0f, 0.14f, 0.71f, 0.95f, 0.14f, 0.27f, 0.8f) + private var uColors = generateRandomColors() + private val uAlphaMulti = 1.0f + private val uNoiseScale = 1.5f + private val uPointOffset = 0.1f + private val uPointRadiusMulti = 1.0f + private var uSaturateOffset = 0.2f + private var uLightOffset = 0.1f + private val uAlphaOffset = 0.5f + private val uShadowColorMulti = 0.3f + private val uShadowColorOffset = 0.3f + private val uShadowNoiseScale = 5.0f + private val uShadowOffset = 0.01f + private var startColors: FloatArray = uColors.copyOf() + private var targetColors: FloatArray = uColors.copyOf() + private var transitionStartTime: Long = 0L + private var isTransitioning = false + private var colorChangeHandler: Handler? = null + private var colorRunnable: Runnable? = null + + init { + val loadShader = loadShader(mResources, R.raw.bg_frag) + mBgRuntimeShader = RuntimeShader(loadShader!!) + mBgRuntimeShader.setFloatUniform("uTranslateY", uTranslateY) + mBgRuntimeShader.setFloatUniform("uPoints", uPoints) + mBgRuntimeShader.setFloatUniform("uColors", uColors) + mBgRuntimeShader.setFloatUniform("uNoiseScale", uNoiseScale) + mBgRuntimeShader.setFloatUniform("uPointOffset", uPointOffset) + mBgRuntimeShader.setFloatUniform("uPointRadiusMulti", uPointRadiusMulti) + mBgRuntimeShader.setFloatUniform("uSaturateOffset", uSaturateOffset) + mBgRuntimeShader.setFloatUniform("uShadowColorMulti", uShadowColorMulti) + mBgRuntimeShader.setFloatUniform("uShadowColorOffset", uShadowColorOffset) + mBgRuntimeShader.setFloatUniform("uShadowOffset", uShadowOffset) + mBgRuntimeShader.setFloatUniform("uBound", uBgBound) + mBgRuntimeShader.setFloatUniform("uAlphaMulti", uAlphaMulti) + mBgRuntimeShader.setFloatUniform("uLightOffset", uLightOffset) + mBgRuntimeShader.setFloatUniform("uAlphaOffset", uAlphaOffset) + mBgRuntimeShader.setFloatUniform("uShadowNoiseScale", uShadowNoiseScale) + } + + val renderEffect: RenderEffect + get() = RenderEffect.createRuntimeShaderEffect(mBgRuntimeShader, "uTex") + + fun updateMaterials() { + mBgRuntimeShader.setFloatUniform("uAnimTime", uAnimTime) + mBgRuntimeShader.setFloatUniform("uResolution", uResolution) + } + + fun setAnimTime(f: Float) { + uAnimTime = f + } + + fun setColors(fArr: FloatArray) { + uColors = fArr + mBgRuntimeShader.setFloatUniform("uColors", fArr) + } + + fun setPoints(fArr: FloatArray) { + uPoints = fArr + mBgRuntimeShader.setFloatUniform("uPoints", fArr) + } + + fun setBound(fArr: FloatArray) { + this.uBgBound = fArr + this.mBgRuntimeShader.setFloatUniform("uBound", fArr) + } + + fun setLightOffset(f: Float) { + this.uLightOffset = f + this.mBgRuntimeShader.setFloatUniform("uLightOffset", f) + } + + fun setSaturateOffset(f: Float) { + this.uSaturateOffset = f + this.mBgRuntimeShader.setFloatUniform("uSaturateOffset", f) + } + + fun setPhoneLight(fArr: FloatArray) { + setLightOffset(0.1f) + setSaturateOffset(0.2f) + setPoints(floatArrayOf(0.67f,0.42f,1.0f,0.69f,0.75f,1.0f,0.14f,0.71f,0.95f,0.14f,0.27f, 0.8f)) + setColors(generateRandomColors()) + setBound(fArr) + startColorAnimation(false) + } + + fun setPhoneDark(fArr: FloatArray) { + setLightOffset(-0.1f) + setSaturateOffset(0.2f) + setPoints(floatArrayOf(0.63f, 0.5f, 0.88f, 0.69f, 0.75f, 0.8f, 0.17f, 0.66f, 0.81f, 0.14f, 0.24f, 0.72f)) + setColors(generateRandomColors()) + setBound(fArr) + startColorAnimation(true) + } + + fun startColorAnimation(isDarkMode: Boolean) { + colorChangeHandler = Handler(Looper.getMainLooper()) + colorRunnable = object : Runnable { + override fun run() { + startColors = uColors.copyOf() + targetColors = generateRandomColors(isDark = isDarkMode) + transitionStartTime = System.currentTimeMillis() + isTransitioning = true + runTransitionFrame() + colorChangeHandler?.postDelayed(this, 3000) + } + } + colorChangeHandler?.post(colorRunnable!!) + } + + private fun runTransitionFrame() { + if (!isTransitioning) return + + val duration = 3000f + val elapsed = System.currentTimeMillis() - transitionStartTime + val t = (elapsed / duration).coerceIn(0f, 1f) + + val interpolatedColors = FloatArray(startColors.size) + for (i in startColors.indices) { + interpolatedColors[i] = startColors[i] * (1 - t) + targetColors[i] * t + } + setColors(interpolatedColors) + + if (t < 1f) { + // 下一帧 + Handler(Looper.getMainLooper()) + .postDelayed({ runTransitionFrame() }, 16L) // 约 60fps + } else { + isTransitioning = false + } + } + + fun generateRandomColors(count: Int = 4, isDark: Boolean = false): FloatArray { + val colors = FloatArray(count * 4) + for (i in 0 until count) { + val base = if (isDark) 0.0 else 0.3 + val range = if (isDark) 0.5 else 0.7 + val r = (base + Math.random() * range).toFloat() + val g = (base + Math.random() * range).toFloat() + val b = (base + Math.random() * range).toFloat() + val a = 1.0f + colors[i * 4] = r + colors[i * 4 + 1] = g + colors[i * 4 + 2] = b + colors[i * 4 + 3] = a + } + return colors + } + + fun setResolution(fArr: FloatArray) { + this.uResolution = fArr + } + + private fun loadShader(resources: Resources, i: Int): String? { + try { + val openRawResource = resources.openRawResource(i) + try { + val scanner = Scanner(openRawResource) + try { + val sb = StringBuilder() + while (scanner.hasNextLine()) { + sb.append(scanner.nextLine()) + sb.append("\n") + } + val sb2 = sb.toString() + scanner.close() + openRawResource.close() + return sb2 + } finally { + } + } finally { + } + } catch (_: Exception) { + return null + } + } + + fun showRuntimeShader(context: Context, view: View, mode: Int) { + calcAnimationBound(context, view) + if (mode == 2) { + setPhoneDark(this.bound) + } else { + setPhoneLight(this.bound) + } + } + + fun updateMode(mode: Int) { + if (mode == 2) { + setPhoneDark(this.bound) + } else { + setPhoneLight(this.bound) + } + } + + private fun calcAnimationBound(context: Context, view: View) { + val height = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 416f, + context.resources.displayMetrics + ) + val height2 = height / (view.parent as ViewGroup).height + val width = (view.parent as ViewGroup).width.toFloat() + if (width <= height) { + this.bound = floatArrayOf(0.0f, 1.0f - height2, 1.0f, height2) + } else { + this.bound = floatArrayOf(((width - height) / 2.0f) / width, 1.0f - height2, height / width, height2) + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/theme/BgEffectView.kt b/app/src/main/java/com/suqi8/oshin/ui/theme/BgEffectView.kt new file mode 100644 index 00000000..d975dbbc --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/theme/BgEffectView.kt @@ -0,0 +1,49 @@ +package com.suqi8.oshin.ui.theme + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.widget.LinearLayout +import com.suqi8.oshin.R + +@SuppressLint("ViewConstructor") +class BgEffectView(context: Context?, mode: Int) : LinearLayout(context) { + private var mBgEffectView: View? = null + private var mBgEffectPainter: BgEffectPainter? = null + private val startTime = System.nanoTime().toFloat() + private val mHandler = Handler(Looper.getMainLooper()) + private var colorMode = 1 + var runnableBgEffect: Runnable = object : Runnable { + override fun run() { + mBgEffectPainter!!.setAnimTime((((System.nanoTime().toFloat()) - startTime) / 1.0E9f) % 62.831852f) + mBgEffectPainter!!.setResolution(floatArrayOf(mBgEffectView!!.width.toFloat(), mBgEffectView!!.height.toFloat())) + mBgEffectPainter!!.updateMaterials() + mBgEffectView!!.setRenderEffect(mBgEffectPainter!!.renderEffect) + mHandler.postDelayed(runnableBgEffect, 16L) + } + } + init { + colorMode = mode + BgEffect(context) + } + fun BgEffect(context: Context?) { + mBgEffectView = LayoutInflater.from(context).inflate(R.layout.layout_effect_bg, this, true) + mBgEffectView!!.post(Runnable { + if (context != null) { + val appContext = context.applicationContext + mBgEffectPainter = BgEffectPainter(appContext) + mBgEffectPainter!!.showRuntimeShader(appContext, mBgEffectView!!, colorMode) + mHandler.post(runnableBgEffect) + } + }) + } + fun updateMode(mode: Int) { + if (mode != colorMode) { + colorMode = mode + mBgEffectPainter!!.updateMode(mode) + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/ui/theme/Theme.kt b/app/src/main/java/com/suqi8/oshin/ui/theme/Theme.kt new file mode 100644 index 00000000..04821443 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/ui/theme/Theme.kt @@ -0,0 +1,21 @@ +package com.suqi8.oshin.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable +import top.yukonga.miuix.kmp.theme.MiuixTheme + +@Composable +fun AppTheme( + colorMode: Int = 0, + content: @Composable () -> Unit +) { + val darkTheme = isSystemInDarkTheme() + return MiuixTheme( + colors = when (colorMode) { + 1 -> top.yukonga.miuix.kmp.theme.lightColorScheme() + 2 -> top.yukonga.miuix.kmp.theme.darkColorScheme() + else -> if (darkTheme) top.yukonga.miuix.kmp.theme.darkColorScheme() else top.yukonga.miuix.kmp.theme.lightColorScheme() + }, + content = content + ) +} diff --git a/app/src/main/java/com/suqi8/oshin/utils/ColorUtils.kt b/app/src/main/java/com/suqi8/oshin/utils/ColorUtils.kt new file mode 100644 index 00000000..7d9b4e3e --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/utils/ColorUtils.kt @@ -0,0 +1,34 @@ +package com.suqi8.oshin.utils + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.palette.graphics.Palette +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +/** + * [suspend] + * 从 ImageBitmap 中异步提取主色调。 + * + * @param icon 要分析的 ImageBitmap + * @param defaultColor 提取失败时的兜底颜色 + * @return 提取到的 Color + */ +suspend fun getAutoColor( + icon: ImageBitmap, + defaultColor: Color = Color.White // 提供一个默认的兜底值 +): Color { + // 在 IO 线程上执行颜色提取,因为它可能耗时 + return withContext(Dispatchers.IO) { + try { + val bitmap = icon.asAndroidBitmap() + // 使用 Palette 库提取 + Palette.from(bitmap).generate().dominantSwatch?.rgb?.let { + Color(it) // 成功提取到主色 + } ?: defaultColor // 提取失败,使用兜底颜色 + } catch (e: Exception) { + defaultColor // 发生异常,使用兜底颜色 + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/utils/DampedDragAnimation.kt b/app/src/main/java/com/suqi8/oshin/utils/DampedDragAnimation.kt new file mode 100644 index 00000000..3976522c --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/utils/DampedDragAnimation.kt @@ -0,0 +1,137 @@ +package com.suqi8.oshin.utils + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.spring +import androidx.compose.foundation.MutatorMutex +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.input.pointer.util.VelocityTracker +import androidx.compose.ui.unit.IntSize +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.android.awaitFrame +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlin.math.abs + +class DampedDragAnimation( + private val animationScope: CoroutineScope, + val initialValue: Float, + val valueRange: ClosedRange, + val visibilityThreshold: Float, + val initialScale: Float, + val pressedScale: Float, + val onDragStarted: DampedDragAnimation.(position: Offset) -> Unit, + val onDragStopped: DampedDragAnimation.() -> Unit, + val onDrag: DampedDragAnimation.(size: IntSize, dragAmount: Offset) -> Unit, +) { + + private val valueAnimationSpec = + spring(1f, 1000f, visibilityThreshold) + private val velocityAnimationSpec = + spring(0.5f, 300f, visibilityThreshold * 10f) + private val pressProgressAnimationSpec = + spring(1f, 1000f, 0.001f) + private val scaleXAnimationSpec = + spring(0.6f, 250f, 0.001f) + private val scaleYAnimationSpec = + spring(0.7f, 250f, 0.001f) + + private val valueAnimation = + Animatable(initialValue, visibilityThreshold) + private val velocityAnimation = + Animatable(0f, 5f) + private val pressProgressAnimation = + Animatable(0f, 0.001f) + private val scaleXAnimation = + Animatable(initialScale, 0.001f) + private val scaleYAnimation = + Animatable(initialScale, 0.001f) + + private val mutatorMutex = MutatorMutex() + + private val velocityTracker = VelocityTracker() + + val value: Float get() = valueAnimation.value + val progress: Float get() = (value - valueRange.start) / (valueRange.endInclusive - valueRange.start) + val targetValue: Float get() = valueAnimation.targetValue + val pressProgress: Float get() = pressProgressAnimation.value + val scaleX: Float get() = scaleXAnimation.value + val scaleY: Float get() = scaleYAnimation.value + val velocity: Float get() = velocityAnimation.value + + val modifier: Modifier = Modifier.pointerInput(Unit) { + inspectDragGestures( + onDragStart = { down -> + onDragStarted(down.position) + press() + }, + onDragEnd = { + onDragStopped() + release() + }, + onDragCancel = { + onDragStopped() + release() + } + ) { change, dragAmount -> + onDrag(size, dragAmount) + } + } + + fun press() { + velocityTracker.resetTracking() + animationScope.launch { + launch { pressProgressAnimation.animateTo(1f, pressProgressAnimationSpec) } + launch { scaleXAnimation.animateTo(pressedScale, scaleXAnimationSpec) } + launch { scaleYAnimation.animateTo(pressedScale, scaleYAnimationSpec) } + } + } + + fun release() { + animationScope.launch { + awaitFrame() + if (value != targetValue) { + val threshold = (valueRange.endInclusive - valueRange.start) * 0.025f + snapshotFlow { valueAnimation.value } + .filter { abs(it - valueAnimation.targetValue) < threshold } + .first() + } + launch { pressProgressAnimation.animateTo(0f, pressProgressAnimationSpec) } + launch { scaleXAnimation.animateTo(initialScale, scaleXAnimationSpec) } + launch { scaleYAnimation.animateTo(initialScale, scaleYAnimationSpec) } + } + } + + fun updateValue(value: Float) { + val targetValue = value.coerceIn(valueRange) + animationScope.launch { + launch { valueAnimation.animateTo(targetValue, valueAnimationSpec) { updateVelocity() } } + } + } + + fun animateToValue(value: Float) { + animationScope.launch { + mutatorMutex.mutate { + press() + val targetValue = value.coerceIn(valueRange) + launch { valueAnimation.animateTo(targetValue, valueAnimationSpec) } + if (velocity != 0f) { + launch { velocityAnimation.animateTo(0f, velocityAnimationSpec) } + } + release() + } + } + } + + private fun updateVelocity() { + velocityTracker.addPosition( + System.currentTimeMillis(), + Offset(value, 0f) + ) + val targetVelocity = velocityTracker.calculateVelocity().x / (valueRange.endInclusive - valueRange.start) + animationScope.launch { velocityAnimation.animateTo(targetVelocity, velocityAnimationSpec) } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/utils/DragGestureInspector.kt b/app/src/main/java/com/suqi8/oshin/utils/DragGestureInspector.kt new file mode 100644 index 00000000..77d00723 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/utils/DragGestureInspector.kt @@ -0,0 +1,84 @@ +package com.suqi8.oshin.utils + +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.pointer.AwaitPointerEventScope +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerId +import androidx.compose.ui.input.pointer.PointerInputChange +import androidx.compose.ui.input.pointer.PointerInputScope +import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed +import androidx.compose.ui.input.pointer.positionChange +import androidx.compose.ui.util.fastFirstOrNull + +suspend fun PointerInputScope.inspectDragGestures( + onDragStart: (down: PointerInputChange) -> Unit = {}, + onDragEnd: (change: PointerInputChange) -> Unit = {}, + onDragCancel: () -> Unit = {}, + onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit +) { + awaitEachGesture { + val initialDown = awaitFirstDown(false, PointerEventPass.Initial) + + val down = awaitFirstDown(false) + + onDragStart(down) + onDrag(initialDown, Offset.Zero) + val upEvent = + drag( + pointerId = initialDown.id, + onDrag = { onDrag(it, it.positionChange()) } + ) + if (upEvent == null) { + onDragCancel() + } else { + onDragEnd(upEvent) + } + } +} + +private suspend inline fun AwaitPointerEventScope.drag( + pointerId: PointerId, + onDrag: (PointerInputChange) -> Unit +): PointerInputChange? { + val isPointerUp = currentEvent.changes.fastFirstOrNull { it.id == pointerId }?.pressed != true + if (isPointerUp) { + return null + } + var pointer = pointerId + while (true) { + val change = awaitDragOrUp(pointer) ?: return null + if (change.isConsumed) { + return null + } + if (change.changedToUpIgnoreConsumed()) { + return change + } + onDrag(change) + pointer = change.id + } +} + +private suspend inline fun AwaitPointerEventScope.awaitDragOrUp( + pointerId: PointerId +): PointerInputChange? { + var pointer = pointerId + while (true) { + val event = awaitPointerEvent() + val dragEvent = event.changes.fastFirstOrNull { it.id == pointer } ?: return null + if (dragEvent.changedToUpIgnoreConsumed()) { + val otherDown = event.changes.fastFirstOrNull { it.pressed } + if (otherDown == null) { + return dragEvent + } else { + pointer = otherDown.id + } + } else { + val hasDragged = dragEvent.previousPosition != dragEvent.position + if (hasDragged) { + return dragEvent + } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/utils/InteractiveHighlight.kt b/app/src/main/java/com/suqi8/oshin/utils/InteractiveHighlight.kt new file mode 100644 index 00000000..29f27e4c --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/utils/InteractiveHighlight.kt @@ -0,0 +1,109 @@ +package com.suqi8.oshin.utils + +import android.graphics.RuntimeShader +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.VectorConverter +import androidx.compose.animation.core.VisibilityThreshold +import androidx.compose.animation.core.spring +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.util.fastCoerceIn +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +class InteractiveHighlight( + val animationScope: CoroutineScope, + val position: (size: Size, offset: Offset) -> Offset = { _, offset -> offset } +) { + + private val pressProgressAnimationSpec = + spring(0.5f, 300f, 0.001f) + private val positionAnimationSpec = + spring(0.5f, 300f, Offset.VisibilityThreshold) + + private val pressProgressAnimation = + Animatable(0f, 0.001f) + private val positionAnimation = + Animatable(Offset.Zero, Offset.VectorConverter, Offset.VisibilityThreshold) + + private var startPosition = Offset.Zero + val pressProgress: Float get() = pressProgressAnimation.value + val offset: Offset get() = positionAnimation.value - startPosition + + private val shader = + RuntimeShader( + """ +uniform float2 size; +layout(color) uniform half4 color; +uniform float radius; +uniform float2 position; + +half4 main(float2 coord) { +float dist = distance(coord, position); +float intensity = smoothstep(radius, radius * 0.5, dist); +return color * intensity; +}""" + ) + + val modifier: Modifier = + Modifier.drawWithContent { + val progress = pressProgressAnimation.value + if (progress > 0f) { + drawRect( + Color.White.copy(0.08f * progress), + blendMode = BlendMode.Plus + ) + shader.apply { + val position = position(size, positionAnimation.value) + setFloatUniform("size", size.width, size.height) + setColorUniform("color", Color.White.copy(0.15f * progress).toArgb()) + setFloatUniform("radius", size.minDimension * 1.5f) + setFloatUniform( + "position", + position.x.fastCoerceIn(0f, size.width), + position.y.fastCoerceIn(0f, size.height) + ) + } + drawRect( + ShaderBrush(shader), + blendMode = BlendMode.Plus + ) + } + + drawContent() + } + + val gestureModifier: Modifier = + Modifier.pointerInput(animationScope) { + inspectDragGestures( + onDragStart = { down -> + startPosition = down.position + animationScope.launch { + launch { pressProgressAnimation.animateTo(1f, pressProgressAnimationSpec) } + launch { positionAnimation.snapTo(startPosition) } + } + }, + onDragEnd = { + animationScope.launch { + launch { pressProgressAnimation.animateTo(0f, pressProgressAnimationSpec) } + launch { positionAnimation.animateTo(startPosition, positionAnimationSpec) } + } + }, + onDragCancel = { + animationScope.launch { + launch { pressProgressAnimation.animateTo(0f, pressProgressAnimationSpec) } + launch { positionAnimation.animateTo(startPosition, positionAnimationSpec) } + } + } + ) { change, _ -> + animationScope.launch { positionAnimation.snapTo(change.position) } + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/utils/Markdown.kt b/app/src/main/java/com/suqi8/oshin/utils/Markdown.kt new file mode 100644 index 00000000..c588e9f0 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/utils/Markdown.kt @@ -0,0 +1,155 @@ +package com.suqi8.oshin.utils + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextLinkStyles +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.sp +import com.mikepenz.markdown.coil3.Coil3ImageTransformerImpl +import com.mikepenz.markdown.compose.Markdown +import com.mikepenz.markdown.compose.components.markdownComponents +import com.mikepenz.markdown.compose.elements.MarkdownHighlightedCodeBlock +import com.mikepenz.markdown.compose.elements.MarkdownHighlightedCodeFence +import com.mikepenz.markdown.compose.extendedspans.ExtendedSpans +import com.mikepenz.markdown.compose.extendedspans.RoundedCornerSpanPainter +import com.mikepenz.markdown.compose.extendedspans.SquigglyUnderlineSpanPainter +import com.mikepenz.markdown.compose.extendedspans.rememberSquigglyUnderlineAnimator +import com.mikepenz.markdown.model.DefaultMarkdownColors +import com.mikepenz.markdown.model.DefaultMarkdownTypography +import com.mikepenz.markdown.model.MarkdownColors +import com.mikepenz.markdown.model.MarkdownTypography +import com.mikepenz.markdown.model.markdownExtendedSpans +import dev.snipme.highlights.Highlights +import dev.snipme.highlights.model.SyntaxThemes +import top.yukonga.miuix.kmp.theme.MiuixTheme + +@Composable +fun BaseMarkdown( + content: String, + typography: MarkdownTypography, // 将排版作为参数传入 + modifier: Modifier = Modifier +) { + // 缓存昂贵的对象 + // 仅当暗黑模式切换时才重新创建 Builder 和 Components + val isDark = isSystemInDarkTheme() + val highlightsBuilder = remember(isDark) { + Highlights.Builder().theme(SyntaxThemes.atom(darkMode = isDark)) + } + val components = remember(highlightsBuilder) { + markdownComponents( + codeBlock = { + MarkdownHighlightedCodeBlock( + it.content, + it.node, + highlightsBuilder = highlightsBuilder + ) + }, + codeFence = { + MarkdownHighlightedCodeFence( + it.content, + it.node, + highlightsBuilder = highlightsBuilder + ) + }, + ) + } + + // 这部分逻辑在原始代码中也是可记忆的 (remember),保持不变 + val extendedSpans = markdownExtendedSpans { + val animator = rememberSquigglyUnderlineAnimator() + remember { + ExtendedSpans( + RoundedCornerSpanPainter(), + SquigglyUnderlineSpanPainter(animator = animator) + ) + } + } + + Markdown( + content, + colors = markdownColor(), + modifier = modifier, + extendedSpans = extendedSpans, + components = components, // 使用缓存的 components + imageTransformer = Coil3ImageTransformerImpl, // 应用通用转换器 + typography = typography // 使用传入的排版 + ) +} + +@Composable +fun markdownTypography( + h1: TextStyle = TextStyle(fontSize = 22.sp, lineHeight = 28.sp, fontWeight = FontWeight.Bold), + h2: TextStyle = TextStyle(fontSize = 19.sp, lineHeight = 24.sp, fontWeight = FontWeight.Bold), + h3: TextStyle = TextStyle(fontSize = 17.sp, lineHeight = 21.sp, fontWeight = FontWeight.Bold), + h4: TextStyle = TextStyle(fontSize = 15.sp, lineHeight = 19.sp, fontWeight = FontWeight.Bold), + h5: TextStyle = TextStyle(fontSize = 14.sp, lineHeight = 18.sp, fontWeight = FontWeight.Bold), + h6: TextStyle = TextStyle(fontSize = 12.sp, lineHeight = 16.sp, fontWeight = FontWeight.Bold), + text: TextStyle = TextStyle(fontSize = 14.sp, lineHeight = 18.sp), + code: TextStyle = TextStyle( + fontSize = 12.sp, + lineHeight = 16.sp, + fontFamily = FontFamily.Monospace + ), + inlineCode: TextStyle = TextStyle( + fontSize = 14.sp, + lineHeight = 18.sp, + fontFamily = FontFamily.Monospace + ), + quote: TextStyle = TextStyle( + fontSize = 14.sp, + lineHeight = 18.sp, + fontStyle = FontStyle.Italic + ), + paragraph: TextStyle = TextStyle(fontSize = 14.sp, lineHeight = 18.sp), + ordered: TextStyle = TextStyle(fontSize = 14.sp, lineHeight = 18.sp), + bullet: TextStyle = TextStyle(fontSize = 14.sp, lineHeight = 18.sp), + list: TextStyle = TextStyle(fontSize = 14.sp, lineHeight = 18.sp), + textLink: TextLinkStyles = TextLinkStyles( + style = TextStyle( + fontSize = 14.sp, + lineHeight = 18.sp, + fontWeight = FontWeight.Bold, + textDecoration = TextDecoration.Underline + ).toSpanStyle() + ), + table: TextStyle = TextStyle(fontSize = 14.sp, lineHeight = 18.sp) +): MarkdownTypography = DefaultMarkdownTypography( + h1, + h2, + h3, + h4, + h5, + h6, + text, + quote, + code, + inlineCode, + paragraph, + ordered, + bullet, + list, + textLink, + table +) + +@Composable +fun markdownColor( + text: Color = MiuixTheme.colorScheme.onBackground, + codeText: Color = MiuixTheme.colorScheme.background, + inlineCodeText: Color = MiuixTheme.colorScheme.onBackground.copy(alpha = 0.2f), + dividerColor: Color = MiuixTheme.colorScheme.outline, + tableBackground: Color = MiuixTheme.colorScheme.onBackground.copy(alpha = 0.2f), +): MarkdownColors = DefaultMarkdownColors( + text, + codeText, + inlineCodeText, + dividerColor, + tableBackground +) diff --git a/app/src/main/java/com/suqi8/oshin/utils/Modifiers.kt b/app/src/main/java/com/suqi8/oshin/utils/Modifiers.kt new file mode 100644 index 00000000..307d3c9e --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/utils/Modifiers.kt @@ -0,0 +1,83 @@ +package com.suqi8.oshin.utils + +import android.annotation.SuppressLint +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * 绘制阴影范围 + * [top] 顶部范围 + * [start] 开始范围 + * [bottom] 底部范围 + * [end] 结束范围 + * Create empty Shadow elevation + */ +open class ShadowElevation( + val top: Dp = 0.dp, + private val start: Dp = 0.dp, + private val bottom: Dp = 0.dp, + private val end: Dp = 0.dp +) { + companion object : ShadowElevation() +} + + +/** + * 自定义彩色阴影绘制修饰符 + * + * @param color 阴影颜色 + * @param alpha 阴影透明度(0f~1f) + * @param borderRadius 组件圆角半径(仅在非圆形绘制时生效) + * @param shadowRadius 阴影模糊半径(控制阴影扩散范围) + * @param offsetX 阴影水平方向偏移量 + * @param offsetY 阴影垂直方向偏移量 + * @param roundedRect 是否自动使用圆形绘制(true 则自动使用高度的一半作为圆角) + */ +@SuppressLint("UseKtx") +fun Modifier.drawColoredShadow( + color: Color, + alpha: Float = 0.2f, + borderRadius: Dp = 0.dp, + shadowRadius: Dp = 20.dp, + offsetX: Dp = 0.dp, + offsetY: Dp = 0.dp, + roundedRect: Boolean = true +): Modifier = this.drawBehind { + this.drawIntoCanvas { canvas -> + val paint = Paint() + val frameworkPaint = paint.asFrameworkPaint() + val transparentColor = color.copy(alpha = 0f).toArgb() + val shadowColor = color.copy(alpha = alpha).toArgb() + + // --- 核心修复 --- + // 手动保存画布状态 + canvas.save() + + frameworkPaint.color = transparentColor + frameworkPaint.setShadowLayer( + shadowRadius.toPx(), + offsetX.toPx(), + offsetY.toPx(), + shadowColor + ) + + canvas.drawRoundRect( + left = 0f, + top = 0f, + right = this.size.width, + bottom = this.size.height, + radiusX = if (roundedRect) this.size.height / 2 else borderRadius.toPx(), + radiusY = if (roundedRect) this.size.height / 2 else borderRadius.toPx(), + paint = paint + ) + + // 手动恢复画布状态,与 save() 配对 + canvas.restore() + } +} diff --git a/app/src/main/java/com/suqi8/oshin/utils/RouteFormatter.kt b/app/src/main/java/com/suqi8/oshin/utils/RouteFormatter.kt new file mode 100644 index 00000000..5c890c95 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/utils/RouteFormatter.kt @@ -0,0 +1,82 @@ +package com.suqi8.oshin.utils + +import android.content.Context +import com.suqi8.oshin.data.repository.AppInfoProvider +import com.suqi8.oshin.features.FeatureRegistry +import com.suqi8.oshin.models.AppName +import com.suqi8.oshin.models.PlainText +import com.suqi8.oshin.models.StringResource +import com.suqi8.oshin.models.Title +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RouteFormatter @Inject constructor( + @ApplicationContext private val context: Context, + private val appInfoProvider: AppInfoProvider +) { + + /** + * 将内部路由字符串 (e.g., "systemui\\controlCenter") + * 智能解析为人类可读的面包屑导航字符串 (e.g., "System UI › 控制中心"). + * + * @param route 内部路由 ID + * @return 格式化后的面包屑字符串 + */ + suspend fun formatRouteAsBreadcrumb(route: String): String { + val routeParts = route.split('\\') + val resolvedParts = mutableListOf() + var currentRouteKey = "" + + for (part in routeParts) { + if (resolvedParts.isEmpty()) { + // --- 1. 解析第一部分 (根模块/应用) --- + currentRouteKey = part + + // 从注册表找到对应的包名 + val pkgName = FeatureRegistry.moduleEntries + .find { it.routeId == part } + ?.packageName + + if (pkgName != null) { + // 使用 AppInfoProvider 异步解析应用名 + resolvedParts.add(appInfoProvider.getAppName(pkgName)) + } else { + // 兜底,直接使用 part + resolvedParts.add(part) + } + + } else { + // --- 2. 解析后续部分 (子功能) --- + currentRouteKey = "$currentRouteKey\\$part" + + // 从 screenMap 查找定义 + val definition = FeatureRegistry.screenMap[currentRouteKey] + + if (definition != null) { + // 找到了定义, 解析其 Title + resolvedParts.add(resolveTitle(definition.title)) + } else { + // 兜底,直接使用 part + resolvedParts.add(part) + } + } + } + + // --- 3. 组合所有部分 --- + return resolvedParts.joinToString(" › ") + } + + /** + * 辅助函数,用于将 Title 模型解析为最终的 String。 + * (这个函数之前在 ModuleViewModel 中,现在由本类统一处理) + */ + private suspend fun resolveTitle(title: Title): String { + return when (title) { + is StringResource -> context.getString(title.id) + is PlainText -> title.text + is AppName -> appInfoProvider.getAppName(title.packageName) + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/utils/SpringEasing.kt b/app/src/main/java/com/suqi8/oshin/utils/SpringEasing.kt new file mode 100644 index 00000000..d3d96247 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/utils/SpringEasing.kt @@ -0,0 +1,262 @@ +package com.suqi8.oshin.utils + +import androidx.compose.animation.core.Easing +import androidx.compose.runtime.Immutable +import kotlin.math.PI +import kotlin.math.abs +import kotlin.math.cos +import kotlin.math.exp +import kotlin.math.sin +import kotlin.math.sqrt + +/** + * 一个基于物理弹簧模型的 Easing 实现。 + * 它模拟了带阻尼的谐振子,可以创建非常自然、逼真的动画效果。 + * + * 注意:请使用伴生对象 (companion object) 中的工厂方法来创建实例,例如 `SpringEasing.create()` + * 或 `SpringEasing.createWithStiffness()`。 + * + * @param dampingRatio 阻尼比 (ζ)。控制弹簧振动的衰减速度。数值越小,回弹效果越明显。 + * @param period 响应周期 (T)。弹簧完成一次完整振荡所需的时间(秒)。值越大,动画感觉越“柔软”和缓慢。 + * @param externalAcceleration 外部加速度 (g)。模拟重力等恒定外力。 + * @param initialVelocity 初始速度。动画开始时的速度。 + */ +@Immutable +class SpringEasing private constructor( + private val dampingRatio: Float, + private val period: Float, + private val externalAcceleration: Float, + private val initialVelocity: Float +) : Easing { + + /** + * 计算出的动画完成所需的大致时间(毫秒)。 + */ + var durationMillis: Long = 1000L + private set + + private var timeScale: Float = 1.0f + private lateinit var solution: SpringSolution + + init { + updateParameters() + } + + override fun transform(fraction: Float): Float { + if (fraction >= 1.0f) { + return 1.0f + } + val time = fraction * this.timeScale + return solution.getPosition(time).toFloat() + } + + private fun updateParameters() { + val angularFrequency = (TWO_PI / period) + val p = 2.0 * dampingRatio * angularFrequency + val q = angularFrequency * angularFrequency + val acceleration = externalAcceleration.toDouble() + val equilibriumPosition = 1.0 - (acceleration / q) + val initialDisplacement = 0.0 - equilibriumPosition + val discriminant = (p * p) - (4.0 * q) + val initialVel = initialVelocity.toDouble() + + this.solution = when { + discriminant > 0.0 -> OverDampedSolution(discriminant, initialDisplacement, p, initialVel, equilibriumPosition) + discriminant == 0.0 -> CriticallyDampedSolution(initialDisplacement, p, initialVel, equilibriumPosition) + else -> UnderDampedSolution(discriminant, initialDisplacement, p, initialVel, equilibriumPosition) + } + + val durationInSeconds = calculateDuration(discriminant, acceleration, q, equilibriumPosition) + this.durationMillis = (durationInSeconds * 1000.0).toLong() + this.timeScale = durationInSeconds.toFloat() + } + + private fun calculateDuration( + discriminant: Double, + acceleration: Double, + stiffness: Double, + equilibriumPos: Double + ): Double { + val positionThreshold = if (discriminant >= 0.0) 0.001 else 0.0001 + val velocityThreshold = 0.0005 + + if (acceleration == 0.0) { + var time = 0.0 + val timeStep = 0.001 + while (time < 10.0) { + time += timeStep + val position = solution.getPosition(time.toFloat()) + val velocity = solution.getVelocity(time.toFloat()) + if (abs(position - 1.0) <= positionThreshold && abs(velocity) <= velocityThreshold) { + return time + } + } + return 10.0 + } + + val initialEnergy = solution.calculateEnergy(0.0, stiffness, acceleration, equilibriumPos) + val equilibriumEnergy = stiffness * equilibriumPos * equilibriumPos + val energyMargin = (initialEnergy - equilibriumEnergy) * positionThreshold + var lowerBoundTime = 0.0 + var upperBoundTime = 1.0 + var energyAtUpperBound = solution.calculateEnergy(upperBoundTime, stiffness, acceleration, equilibriumPos) + + while (energyAtUpperBound > equilibriumEnergy + energyMargin) { + lowerBoundTime = upperBoundTime + upperBoundTime *= 2 + energyAtUpperBound = solution.calculateEnergy(upperBoundTime, stiffness, acceleration, equilibriumPos) + } + + while (upperBoundTime - lowerBoundTime >= positionThreshold) { + val midTime = (lowerBoundTime + upperBoundTime) / 2.0 + if (solution.calculateEnergy(midTime, stiffness, acceleration, equilibriumPos) > equilibriumEnergy + energyMargin) { + lowerBoundTime = midTime + } else { + upperBoundTime = midTime + } + } + return upperBoundTime + } + + companion object { + private const val TWO_PI = 2 * PI + + /** + * [默认] 创建一个基于 `period` (周期) 的 SpringEasing 实例。 + */ + @JvmStatic + @JvmOverloads + fun create( + dampingRatio: Float = 0.65f, + period: Float = 0.5f, + externalAcceleration: Float = 0.0f, + initialVelocity: Float = 0.0f + ): SpringEasing { + return SpringEasing(dampingRatio, period, externalAcceleration, initialVelocity) + } + + /** + * 创建一个基于 `stiffness` (刚度) 的 SpringEasing 实例,API 与 Jetpack Compose 更为接近。 + * 这个函数虽然可能在当前项目中“未使用”,但它作为库代码的一部分,为使用者提供了额外的灵活性。 + */ + @JvmStatic + @JvmOverloads + fun createWithStiffness( + dampingRatio: Float, + stiffness: Float, + mass: Float = 1.0f, + externalAcceleration: Float = 0.0f, + initialVelocity: Float = 0.0f + ): SpringEasing { + // 根据刚度、质量计算周期: period = 2π * sqrt(mass / stiffness) + val period = (TWO_PI * sqrt(mass / stiffness)).toFloat() + return SpringEasing(dampingRatio, period, externalAcceleration, initialVelocity) + } + + + // --- 预设动画效果 --- + + /** + * 一种柔和、缓慢的弹簧效果,具有轻微的回弹。 + */ + @JvmStatic + fun gentle() = create(dampingRatio = 0.7f, period = 0.6f) + + /** + * 一种具有明显回弹和振荡的弹簧效果,感觉活泼。 + */ + @JvmStatic + fun bouncy() = create(dampingRatio = 0.45f, period = 0.45f) + + /** + * 一种快速、生硬的弹簧效果,无回弹(临界阻尼)。 + */ + @JvmStatic + fun stiff() = create(dampingRatio = 1.0f, period = 0.3f) + } + + // --- 内部解算器类 --- + internal abstract class SpringSolution { + abstract fun getPosition(time: Float): Double + abstract fun getVelocity(time: Float): Double + fun calculateEnergy(time: Double, stiffness: Double, acceleration: Double, equilibrium: Double): Double { + val pos = getPosition(time.toFloat()) + val vel = getVelocity(time.toFloat()) + return (stiffness * pos * pos) + (vel * vel) - (acceleration * 2.0 * (pos - equilibrium)) + } + } + + internal class CriticallyDampedSolution(initialDisp: Double, p: Double, initialVel: Double, private val equilibriumPos: Double) : SpringSolution() { + private val r: Double + private val c1: Double + private val c2: Double + + init { + r = -p / 2.0 + c1 = initialDisp + c2 = initialVel - (initialDisp * r) + } + + override fun getPosition(time: Float): Double { + val t = time.toDouble() + return ((c1 + c2 * t) * exp(r * t)) + equilibriumPos + } + + override fun getVelocity(time: Float): Double { + val t = time.toDouble() + return ((c1 * r) + c2 * (r * t + 1.0)) * exp(r * t) + } + } + + internal inner class OverDampedSolution(discriminant: Double, initialDisp: Double, p: Double, initialVel: Double, private val equilibriumPos: Double) : SpringSolution() { + private val r1: Double + private val r2: Double + private val c1: Double + private val c2: Double + + init { + val sqrtDisc = sqrt(discriminant) + r1 = (-p + sqrtDisc) / 2.0 + r2 = (-p - sqrtDisc) / 2.0 + c1 = (initialVel - initialDisp * r2) / sqrtDisc + c2 = -(initialVel - initialDisp * r1) / sqrtDisc + } + + override fun getPosition(time: Float): Double { + val t = time.toDouble() + return (c1 * exp(r1 * t)) + (c2 * exp(r2 * t)) + equilibriumPos + } + + override fun getVelocity(time: Float): Double { + val t = time.toDouble() + return (c1 * r1 * exp(r1 * t)) + (c2 * r2 * exp(r2 * t)) + } + } + + internal inner class UnderDampedSolution(discriminant: Double, initialDisp: Double, p: Double, initialVel: Double, private val equilibriumPos: Double) : SpringSolution() { + private val alpha: Double + private val beta: Double + private val c1: Double + private val c2: Double + + init { + alpha = -p / 2.0 + beta = sqrt(-discriminant) / 2.0 + c1 = initialDisp + c2 = (initialVel - initialDisp * alpha) / beta + } + + override fun getPosition(time: Float): Double { + val t = time.toDouble() + return (exp(alpha * t) * (c1 * cos(beta * t) + c2 * sin(beta * t))) + equilibriumPos + } + + override fun getVelocity(time: Float): Double { + val t = time.toDouble() + val expTerm = exp(alpha * t) + val cosTerm = (c1 * alpha + c2 * beta) * cos(beta * t) + val sinTerm = (c2 * alpha - c1 * beta) * sin(beta * t) + return expTerm * (cosTerm + sinTerm) + } + } +} diff --git a/app/src/main/java/com/suqi8/oshin/utils/executeCommand.kt b/app/src/main/java/com/suqi8/oshin/utils/executeCommand.kt new file mode 100644 index 00000000..1b332762 --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/utils/executeCommand.kt @@ -0,0 +1,65 @@ +package com.suqi8.oshin.utils + +import android.util.Log +import com.suqi8.oshin.TAG +import java.io.BufferedReader +import java.io.InputStreamReader +import java.util.concurrent.TimeUnit + +fun executeCommand(command: String): String { + var process: Process? = null + val outputStringBuilder = StringBuilder() // 用于收集标准输出 (stdout) + val errorStringBuilder = StringBuilder() // 用于收集标准错误 (stderr) + var exitCode: Int + + try { + Log.d(TAG, "Executing command with su: $command") + process = Runtime.getRuntime().exec(arrayOf("su", "-c", command)) + + // 读取标准输出 (stdout) + BufferedReader(InputStreamReader(process.inputStream)).use { reader -> + reader.forEachLine { outputStringBuilder.append(it).append("\n") } + } + + // 读取标准错误 (stderr) + BufferedReader(InputStreamReader(process.errorStream)).use { reader -> + reader.forEachLine { errorStringBuilder.append(it).append("\n") } + } + + // --- 等待并获取结果 --- + val finished = process.waitFor(1, TimeUnit.MINUTES) // 等待最多1分钟 + + if (finished) { + exitCode = process.exitValue() + Log.d(TAG, "Command finished with exit code: $exitCode") + + // --- 打印错误流(如果非空)--- + val errorOutput = errorStringBuilder.toString().trim() + if (errorOutput.isNotEmpty()) { + Log.e(TAG, "Command stderr:\n$errorOutput") + } + // --- 打印标准输出(如果非空,主要用于调试确认)--- + val standardOutput = outputStringBuilder.toString().trim() + if (standardOutput.isNotEmpty()) { + Log.d(TAG, "Command stdout:\n$standardOutput") + } + + } else { + Log.e(TAG, "Command timed out: $command") + process.destroy() // 销毁超时的进程 + // 对于超时的旧行为是返回 "", 这里保持一致或记录错误后返回 "" + return "" // 或者可以抛出异常,但为了兼容性返回 "" + } + + } catch (e: Exception) { + Log.e(TAG, "executeCommand failed for: $command", e) + process?.destroy() + return "" // 保持原有错误行为:返回空字符串 + } finally { + // 不需要手动关闭流,BufferedReader.use 会处理 + process?.destroy() // 确保进程被销毁 + } + + // --- 保持原有返回值:只返回标准输出 --- + return outputStringBuilder.toString().trim() +} diff --git a/app/src/main/java/com/suqi8/oshin/utils/requestPermissions.kt b/app/src/main/java/com/suqi8/oshin/utils/requestPermissions.kt new file mode 100644 index 00000000..61ac47fd --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/utils/requestPermissions.kt @@ -0,0 +1,34 @@ +package com.suqi8.oshin.utils + +import android.content.Context +import com.hjq.permissions.OnPermissionCallback +import com.hjq.permissions.XXPermissions +import com.hjq.permissions.permission.base.IPermission + +fun requestPermissions(context: Context, permissions: IPermission, onGranted: () -> Unit = {}) { + XXPermissions.with(context) + .permission(permissions) + .request(object : OnPermissionCallback { + override fun onGranted(permissions: MutableList, allGranted: Boolean) { + if (allGranted) { + onGranted() + } else { + toast( + context, + "获取部分权限成功,但部分权限未正常授予\n这可能会导致部分功能无法正常使用" + ) + } + } + + override fun onDenied(permissions: MutableList, doNotAskAgain: Boolean) { + if (doNotAskAgain) { + toast(context, "被永久拒绝授权,请手动授予读取和写入文件权限") + // 如果权限被永久拒绝,重定向到设置 + XXPermissions.startPermissionActivity(context, permissions) + } else { + toast(context, "获取读取和写入文件权限失败") + } + } + }) + +} diff --git a/app/src/main/java/com/suqi8/oshin/utils/timeTool.kt b/app/src/main/java/com/suqi8/oshin/utils/timeTool.kt new file mode 100644 index 00000000..de1182cc --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/utils/timeTool.kt @@ -0,0 +1,55 @@ +package com.suqi8.oshin.utils + +import android.content.Context +import android.util.Log +import com.suqi8.oshin.R +import java.text.SimpleDateFormat +import java.time.Duration +import java.time.Instant +import java.time.format.DateTimeParseException +import java.util.Locale + +fun formatDate(dateString: String): String { + return try { + val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault()) + val outputFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + val date = inputFormat.parse(dateString) + outputFormat.format(date ?: return dateString.substringBefore("T")) + } catch (e: Exception) { + dateString.substringBefore("T") + } +} + +fun formatTimeAgo(isoDateString: String, context: Context): String { + return try { + val instant = Instant.parse(isoDateString) + val now = Instant.now() + val duration = Duration.between(instant, now) + val resources = context.resources + + when { + duration.toDays() > 0 -> resources.getQuantityString( + R.plurals.time_ago_days, + duration.toDays().toInt(), + duration.toDays().toInt() + ) + duration.toHours() > 0 -> resources.getQuantityString( + R.plurals.time_ago_hours, + duration.toHours().toInt(), + duration.toHours().toInt() + ) + duration.toMinutes() > 0 -> resources.getQuantityString( + R.plurals.time_ago_minutes, + duration.toMinutes().toInt(), + duration.toMinutes().toInt() + ) + else -> context.getString(R.string.time_ago_just_now) + } + } catch (e: DateTimeParseException) { + Log.e("FormatTimeAgo", "Failed to parse date: $isoDateString", e) + "" + } catch (e: Exception) { + Log.e("FormatTimeAgo", "Error formatting time ago for: $isoDateString", e) + "" + } +} diff --git a/app/src/main/java/com/suqi8/oshin/utils/utils.kt b/app/src/main/java/com/suqi8/oshin/utils/utils.kt new file mode 100644 index 00000000..c76d96ef --- /dev/null +++ b/app/src/main/java/com/suqi8/oshin/utils/utils.kt @@ -0,0 +1,52 @@ +package com.suqi8.oshin.utils + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.widget.Toast +import androidx.compose.ui.graphics.ImageBitmap +//dataclass +data class AppInfo( + val name: String, + val icon: ImageBitmap +) +//functions +fun checkIfRooted(): Boolean { + return try { + val process = Runtime.getRuntime().exec(arrayOf("which", "su")) + process.waitFor() == 0 + } catch (e: Exception) { + false + } +} + + +fun toast(context: Context, message: String) { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() +} +fun launchApp(context: Context, pkg: String) { + val pm = context.packageManager + val intent = pm.getLaunchIntentForPackage(pkg) + if (intent != null) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + } +} +fun hasShortcut(context: Context, pkg: String): Boolean { + val pm = context.packageManager + return try { + pm.getLaunchIntentForPackage(pkg) != null + } catch (_: Exception) { + false + } +} +@SuppressLint("PrivateApi") +fun getPhoneName(): String { + return try { + val clazz = Class.forName("android.os.SystemProperties") + val method = clazz.getMethod("get", String::class.java, String::class.java) + method.invoke(null, "ro.vendor.oplus.market.name", "OPlus") as String + } catch (_: Exception) { + "OPlus" + } +} diff --git a/app/src/main/res/drawable/about.xml b/app/src/main/res/drawable/about.xml new file mode 100644 index 00000000..9f88640f --- /dev/null +++ b/app/src/main/res/drawable/about.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/colorlogo.png b/app/src/main/res/drawable/colorlogo.png new file mode 100644 index 00000000..f2180004 Binary files /dev/null and b/app/src/main/res/drawable/colorlogo.png differ diff --git a/app/src/main/res/drawable/coolapk.xml b/app/src/main/res/drawable/coolapk.xml new file mode 100644 index 00000000..76d45673 --- /dev/null +++ b/app/src/main/res/drawable/coolapk.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/coui_btn_next_normal.xml b/app/src/main/res/drawable/coui_btn_next_normal.xml new file mode 100644 index 00000000..d54ae1cc --- /dev/null +++ b/app/src/main/res/drawable/coui_btn_next_normal.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/donors.xml b/app/src/main/res/drawable/donors.xml new file mode 100644 index 00000000..d3fb443a --- /dev/null +++ b/app/src/main/res/drawable/donors.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/func.xml b/app/src/main/res/drawable/func.xml new file mode 100644 index 00000000..9b2e3e34 --- /dev/null +++ b/app/src/main/res/drawable/func.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/github.xml b/app/src/main/res/drawable/github.xml new file mode 100644 index 00000000..e89f039f --- /dev/null +++ b/app/src/main/res/drawable/github.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/group.xml b/app/src/main/res/drawable/group.xml new file mode 100644 index 00000000..c9d8ed36 --- /dev/null +++ b/app/src/main/res/drawable/group.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/home.xml b/app/src/main/res/drawable/home.xml new file mode 100644 index 00000000..9c842efc --- /dev/null +++ b/app/src/main/res/drawable/home.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon.xml b/app/src/main/res/drawable/icon.xml new file mode 100644 index 00000000..09bacc65 --- /dev/null +++ b/app/src/main/res/drawable/icon.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/module.xml b/app/src/main/res/drawable/module.xml new file mode 100644 index 00000000..32bf1f9b --- /dev/null +++ b/app/src/main/res/drawable/module.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/osu.png b/app/src/main/res/drawable/osu.png new file mode 100644 index 00000000..32a481ca Binary files /dev/null and b/app/src/main/res/drawable/osu.png differ diff --git a/app/src/main/res/drawable/qq.xml b/app/src/main/res/drawable/qq.xml new file mode 100644 index 00000000..2c6f26a6 --- /dev/null +++ b/app/src/main/res/drawable/qq.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/qq_pic_merged_1727926207595.jpg b/app/src/main/res/drawable/qq_pic_merged_1727926207595.jpg new file mode 100644 index 00000000..63197153 Binary files /dev/null and b/app/src/main/res/drawable/qq_pic_merged_1727926207595.jpg differ diff --git a/app/src/main/res/drawable/settings.xml b/app/src/main/res/drawable/settings.xml new file mode 100644 index 00000000..04610ee1 --- /dev/null +++ b/app/src/main/res/drawable/settings.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/translators.xml b/app/src/main/res/drawable/translators.xml new file mode 100644 index 00000000..aaed6184 --- /dev/null +++ b/app/src/main/res/drawable/translators.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/updatebg.webp b/app/src/main/res/drawable/updatebg.webp new file mode 100644 index 00000000..a1c90820 Binary files /dev/null and b/app/src/main/res/drawable/updatebg.webp differ diff --git a/app/src/main/res/drawable/website.xml b/app/src/main/res/drawable/website.xml new file mode 100644 index 00000000..2a0e90a3 --- /dev/null +++ b/app/src/main/res/drawable/website.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/layout_effect_bg.xml b/app/src/main/res/layout/layout_effect_bg.xml new file mode 100644 index 00000000..7eebe5bb --- /dev/null +++ b/app/src/main/res/layout/layout_effect_bg.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..6be0f35f --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/raw/accept.json b/app/src/main/res/raw/accept.json new file mode 100644 index 00000000..01383a06 --- /dev/null +++ b/app/src/main/res/raw/accept.json @@ -0,0 +1 @@ +{"v":"5.5.10","fr":30,"ip":0,"op":60,"w":300,"h":335,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[148,185.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[96.429,96.42899999999999,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-17.136,27.185]],"o":[[13.086,7.272],[0,0],[0,0]],"v":[[-46.074,-6.315],[-6.815,15.5],[44.593,-66.056]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":25,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-4,7],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":9.672,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.148],"y":[1]},"o":{"x":[0.471],"y":[0.002]},"t":18,"s":[0]},{"t":38.0000015477717,"s":[100]}],"ix":1},"e":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":18.000000733155,"op":396.000016129411,"st":18.000000733155,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,167,0],"ix":2},"a":{"a":0,"k":[-4,-8.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.471,0.471,0.667],"y":[1,1,1]},"o":{"x":[0.48,0.48,0.333],"y":[0,0,0]},"t":7,"s":[0,0,100]},{"t":25.0000010182709,"s":[78.151,78.151,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[238,238],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.33725490196078434,0.8,0.615686274509804,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-4,-8.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":7.00000028511585,"op":385.000015681372,"st":7.00000028511585,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":20,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,167,0],"ix":2},"a":{"a":0,"k":[-4,-8.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.471,0.471,0.667],"y":[1,1,1]},"o":{"x":[0.48,0.48,0.333],"y":[0,0,0]},"t":3,"s":[0,0,100]},{"t":21.0000008553475,"s":[96.639,96.639,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[238,238],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.33725490196078434,0.8,0.615686274509804,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-4,-8.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":3.00000012219251,"op":381.000015518448,"st":3.00000012219251,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":10,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,167,0],"ix":2},"a":{"a":0,"k":[-4,-8.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.471,0.471,0.667],"y":[1,1,1]},"o":{"x":[0.48,0.48,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":18.000000733155,"s":[110.084,110.084,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[238,238],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.33725490196078434,0.8,0.615686274509804,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-4,-8.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":378.000015396256,"st":0,"bm":0}],"markers":[{"tm":40.0000016292334,"cm":"","dr":0}]} \ No newline at end of file diff --git a/app/src/main/res/raw/bg_frag.glsl b/app/src/main/res/raw/bg_frag.glsl new file mode 100644 index 00000000..12f337ce --- /dev/null +++ b/app/src/main/res/raw/bg_frag.glsl @@ -0,0 +1,224 @@ +uniform vec2 uResolution; +uniform shader uTex; +uniform shader uTexBitmap; +uniform vec2 uTexWH; +//uniform shader uPerlinTex; + +// 新版参数 +uniform float uAnimTime; +uniform vec4 uBound; +uniform float uTranslateY; +uniform vec3 uPoints[4]; +uniform vec4 uColors[4]; +uniform float uAlphaMulti; +uniform float uNoiseScale; +uniform float uPointOffset; +uniform float uPointRadiusMulti; +uniform float uSaturateOffset; +uniform float uLightOffset; +uniform float uAlphaOffset; +uniform float uShadowColorMulti; +uniform float uShadowColorOffset; +uniform float uShadowNoiseScale; +uniform float uShadowOffset; + +vec3 hsl2rgb(in vec3 c) +{ + vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0, 4.0, 2.0), 6.0)-3.0)-1.0, 0.0, 1.0); + + return c.z + c.y * (rgb-0.5)*(1.0-abs(2.0*c.z-1.0)); +} + +vec3 HueShift (in vec3 Color, in float Shift) +{ + vec3 P = vec3(0.55735)*dot(vec3(0.55735), Color); + + vec3 U = Color-P; + + vec3 V = cross(vec3(0.55735), U); + + Color = U*cos(Shift*6.2832) + V*sin(Shift*6.2832) + P; + + return vec3(Color); +} + +vec3 rgb2hsl(in vec3 c){ + float h = 0.0; + float s = 0.0; + float l = 0.0; + float r = c.r; + float g = c.g; + float b = c.b; + float cMin = min(r, min(g, b)); + float cMax = max(r, max(g, b)); + + l = (cMax + cMin) / 2.0; + if (cMax > cMin) { + float cDelta = cMax - cMin; + + //s = l < .05 ? cDelta / ( cMax + cMin ) : cDelta / ( 2.0 - ( cMax + cMin ) ); Original + s = l < .0 ? cDelta / (cMax + cMin) : cDelta / (2.0 - (cMax + cMin)); + + if (r == cMax) { + h = (g - b) / cDelta; + } else if (g == cMax) { + h = 2.0 + (b - r) / cDelta; + } else { + h = 4.0 + (r - g) / cDelta; + } + + if (h < 0.0) { + h += 6.0; + } + h = h / 6.0; + } + return vec3(h, s, l); +} + +vec3 rgb2hsv(vec3 c) +{ + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) +{ + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +float hash(vec2 p) { + vec3 p3 = fract(vec3(p.xyx) * 0.13); + p3 += dot(p3, p3.yzx + 3.333); + return fract((p3.x + p3.y) * p3.z); +} + +float perlin(vec2 x) { + vec2 i = floor(x); + vec2 f = fract(x); + + float a = hash(i); + float b = hash(i + vec2(1.0, 0.0)); + float c = hash(i + vec2(0.0, 1.0)); + float d = hash(i + vec2(1.0, 1.0)); + + vec2 u = f * f * (3.0 - 2.0 * f); + return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y; +} + +vec4 srcOver(vec4 src, vec4 dst){ + return src + dst * (1.0 - src.a); +} + +vec4 blendSrcOver(vec4 src, vec4 dst) { + if (src.a == 0.0) { + return dst; + } + + float srcAlpha = src.a; + float dstAlpha = dst.a * (1.0 - srcAlpha); + float outAlpha = srcAlpha + dstAlpha; + + if (outAlpha == 0.0) { + return vec4(0, 0, 0, 0); + } + + vec4 outColor = (src * srcAlpha + dst * dstAlpha) / outAlpha; + return vec4(outColor.rgb, outAlpha); +} + +float gradientNoise(in vec2 uv) +{ + return fract(52.9829189 * fract(dot(uv, vec2(0.06711056, 0.00583715)))); +} + +vec4 main(vec2 fragCoord){ + + vec2 vUv = fragCoord/uResolution; + vUv.y = 1.0-vUv.y; + vec2 uv = vUv; + uv -= vec2(0., uTranslateY); + + uv.xy -= uBound.xy; + uv.xy /= uBound.zw; + + vec3 hsv; + +// vec4 color = vec4(1, 1, 1, 0.); + vec4 color = vec4(0.0); + + float noiseValue = perlin(vUv * uNoiseScale + vec2(-uAnimTime, -uAnimTime)); +// float noiseValue = uPerlinTex.eval(vUv * vec2(128.0) + vec2(-uAnimTime, -uAnimTime)*50.0).r; + + // draw circles + for (int i = 0; i < 4; i++){ + vec4 pointColor = uColors[i]; + pointColor.rgb *= pointColor.a; + vec2 point = uPoints[i].xy; + float rad = uPoints[i].z * uPointRadiusMulti; + + point.x += sin(uAnimTime + point.y) * uPointOffset; + point.y += cos(uAnimTime + point.x) * uPointOffset; + + float d = distance(uv, point); + float pct = smoothstep(rad, 0., d); + //float pct = smoothstep(rad, rad - 0.01, d); + + // color = blendSrcOver(color, pointColor); + // color = blendSrcOver(pointColor, color); + + color.rgb = mix(color.rgb, pointColor.rgb, pct); + + // color.a += (1. - color.a) * pointColor.a; + color.a = mix(color.a, pointColor.a, pct); + } + + float oppositeNoise = smoothstep(0., 1., noiseValue); + color.rgb /= color.a; + hsv = rgb2hsv(color.rgb); + hsv.y = mix(hsv.y, 0.0, oppositeNoise * uSaturateOffset); +// hsv.y += oppositeNoise * uSaturateOffset; + color.rgb = hsv2rgb(hsv); + + color.rgb += oppositeNoise * uLightOffset; +// color.rgb = mix(color.rgb, min(color.rgb + oppositeNoise * uLightOffset, vec3(1.)), oppositeNoise); + // color.a += noiseValue * uAlphaOffset; + + color.a = clamp(color.a, 0., 1.); + color.a *= uAlphaMulti; + + vec4 texColor = uTexBitmap.eval(vec2(vUv.x, 1.0 - vUv.y)*uTexWH); + vec4 uiColor = uTex.eval(vec2(vUv.x, 1.0 - vUv.y)*uResolution); + + vec4 fragColor; + + // 显示uBound区域 + //float debugColor = 1.; + //debugColor *= step(0., uv.x); + //debugColor *= step(0., uv.y); + //debugColor *= step(uv.x, 1.); + //debugColor *= step(uv.y, 1.); + //color = mix(color, vec4(1., 0., 0., 1.), debugColor * 0.5); + + color += (10.0 / 255.0) * gradientNoise(fragCoord.xy) - (5.0 / 255.0); + + if (uiColor.a < 0.01) { + fragColor = color; + } else { + fragColor = uiColor; + } + + // return vec4(0.0); + // return vec4(vUv,0.0,1.0); + // return vec4(abs(sin(uAnimTime)).rrr, 1.0); + // return texColor; +// return vec4(noiseValue.rrr,1.0); + return vec4(fragColor.rgb*fragColor.a, fragColor.a); + return vec4(color.rgb*color.a, color.a); +} diff --git a/app/src/main/res/raw/coming_soon.json b/app/src/main/res/raw/coming_soon.json new file mode 100644 index 00000000..2d73cb5d --- /dev/null +++ b/app/src/main/res/raw/coming_soon.json @@ -0,0 +1 @@ +{"v":"4.8.0","meta":{"g":"LottieFiles AE 3.5.3","a":"","k":"","d":"","tc":""},"fr":120,"ip":0,"op":480,"w":1372,"h":712,"nm":"Object","ddd":0,"assets":[{"id":"image_0","w":1145,"h":673,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_1","w":336,"h":500,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_2","w":44,"h":52,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_3","w":132,"h":132,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_4","w":169,"h":108,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_5","w":213,"h":127,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_6","w":1372,"h":712,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_7","w":152,"h":176,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_8","w":212,"h":256,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_9","w":408,"h":352,"u":"","p":"data:image/png;base64,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","e":1}],"layers":[{"ddd":0,"ind":1,"ty":2,"nm":"Effect.png","cl":"png","refId":"image_0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":119,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":361,"s":[0]},{"t":479,"s":[100]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[10]},{"t":479,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[650,60,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":240,"s":[746,102,0],"to":[0,0,0],"ti":[0,0,0]},{"t":479,"s":[650,60,0]}],"ix":2},"a":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[589.5,336.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":240,"s":[601.5,385.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":479,"s":[589.5,336.5,0]}],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":240,"s":[109,109,100]},{"t":479,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":524,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":2,"nm":"Box.png","cl":"png","refId":"image_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[4]},{"t":479,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[460,425,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":240,"s":[448,440,0],"to":[0,0,0],"ti":[0,0,0]},{"t":479,"s":[460,425,0]}],"ix":2},"a":{"a":0,"k":[168,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":524,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":2,"nm":"Heard.png","cl":"png","refId":"image_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[-8]},{"t":479,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[971.5,296.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":240,"s":[980,291.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":479,"s":[971.5,296.5,0]}],"ix":2},"a":{"a":0,"k":[22,26,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":515,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":2,"nm":"Point 2.png","cl":"png","refId":"image_3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[-30]},{"t":479,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[1036.5,618.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":240,"s":[1029.5,618.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":479,"s":[1036.5,618.5,0]}],"ix":2},"a":{"a":0,"k":[66,66,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":511,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":2,"nm":"On.png","cl":"png","refId":"image_4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[845.5,430.5,0],"ix":2},"a":{"a":0,"k":[84.5,54,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":511,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":2,"nm":"Hand coin.png","cl":"png","refId":"image_5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[7]},{"t":479,"s":[0]}],"ix":10},"p":{"a":0,"k":[987.5,385,0],"ix":2},"a":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[194.5,92.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":239,"s":[193.5,96.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":479,"s":[194.5,92.621,0]}],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":518,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":2,"nm":"Object.png","cl":"png","refId":"image_6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[686,356,0],"ix":2},"a":{"a":0,"k":[686,356,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":495,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":2,"nm":"Point.png","cl":"png","refId":"image_3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[-30]},{"t":479,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[786,286,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":240,"s":[774,267,0],"to":[0,0,0],"ti":[0,0,0]},{"t":479,"s":[786,286,0]}],"ix":2},"a":{"a":0,"k":[66,66,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":501,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":2,"nm":"Leafsm.png","cl":"png","refId":"image_7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[2]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[7]},{"t":479,"s":[2]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[1198,596,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":240,"s":[1190,592,0],"to":[0,0,0],"ti":[0,0,0]},{"t":479,"s":[1198,596,0]}],"ix":2},"a":{"a":0,"k":[77.5,163.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":505,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":2,"nm":"1leaf.png","cl":"png","refId":"image_8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[5]},{"t":479,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[658,324,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":240,"s":[642,312,0],"to":[0,0,0],"ti":[0,0,0]},{"t":479,"s":[658,324,0]}],"ix":2},"a":{"a":0,"k":[189.5,235,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":531,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":2,"nm":"2leaf.png","cl":"png","refId":"image_9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[4]},{"t":479,"s":[0]}],"ix":10},"p":{"a":0,"k":[672,336,0],"ix":2},"a":{"a":0,"k":[363,332,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":515,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/res/raw/error.json b/app/src/main/res/raw/error.json new file mode 100644 index 00000000..98eb942f --- /dev/null +++ b/app/src/main/res/raw/error.json @@ -0,0 +1 @@ +{"v":"5.5.10","fr":30,"ip":0,"op":75,"w":300,"h":335,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.413],"y":[0.987]},"o":{"x":[0.333],"y":[0]},"t":38.038,"s":[90]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":49.049,"s":[35]},{"t":52.0517578125,"s":[45]}],"ix":10},"p":{"a":0,"k":[150,167,0],"ix":2},"a":{"a":0,"k":[-7.75,0.125,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":28.028,"s":[181.14,0,100]},{"t":38.0380859375,"s":[181.14,110.579,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[12,108.75],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-7.75,0.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,97.166],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75.0750750750751,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.413],"y":[0.995]},"o":{"x":[0.333],"y":[0]},"t":38.038,"s":[90]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":49.049,"s":[-55]},{"t":52.0517578125,"s":[-45]}],"ix":10},"p":{"a":0,"k":[150,167,0],"ix":2},"a":{"a":0,"k":[-7.75,0.125,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":28.028,"s":[181.14,0,100]},{"t":38.0380859375,"s":[181.14,110.579,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[12,108.75],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-7.75,0.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,97.166],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75.0750750750751,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 10","sr":1,"ks":{"o":{"a":0,"k":60,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.413],"y":[0.995]},"o":{"x":[0.333],"y":[0]},"t":40.04,"s":[90]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":51.051,"s":[-55]},{"t":54.053759814502,"s":[-45]}],"ix":10},"p":{"a":0,"k":[150,167,0],"ix":2},"a":{"a":0,"k":[-7.75,0.125,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":30.03,"s":[181.14,0,100]},{"t":40.040087939502,"s":[181.14,110.579,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[12,108.75],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-7.75,0.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,97.166],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":2.002002002002,"op":75.0750750750751,"st":2.002002002002,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 11","sr":1,"ks":{"o":{"a":0,"k":20,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.413],"y":[0.995]},"o":{"x":[0.333],"y":[0]},"t":41.041,"s":[90]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":52.052,"s":[-55]},{"t":55.054760815503,"s":[-45]}],"ix":10},"p":{"a":0,"k":[150,167,0],"ix":2},"a":{"a":0,"k":[-7.75,0.125,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":31.031,"s":[181.14,0,100]},{"t":41.041088940503,"s":[181.14,110.579,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[12,108.75],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-7.75,0.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,97.166],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":3.003003003003,"op":75.0750750750751,"st":3.003003003003,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,167,0],"ix":2},"a":{"a":0,"k":[-4,-8.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.471,0.471,0.667],"y":[1,1,1]},"o":{"x":[0.48,0.48,0.333],"y":[0,0,0]},"t":7.007,"s":[0,0,100]},{"t":25.024585132007,"s":[78.151,78.151,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[238,238],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.486274509804,0.501960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-4,-8.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":7.00700700700701,"op":75.0750750750751,"st":7.00700700700701,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":20,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,167,0],"ix":2},"a":{"a":0,"k":[-4,-8.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.471,0.471,0.667],"y":[1,1,1]},"o":{"x":[0.48,0.48,0.333],"y":[0,0,0]},"t":3.003,"s":[0,0,100]},{"t":21.020581128003,"s":[96.639,96.639,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[238,238],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.486274509804,0.501960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-4,-8.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":3.003003003003,"op":75.0750750750751,"st":3.003003003003,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":10,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,167,0],"ix":2},"a":{"a":0,"k":[-4,-8.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.471,0.471,0.667],"y":[1,1,1]},"o":{"x":[0.48,0.48,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":18.017578125,"s":[110.084,110.084,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[238,238],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.486274509804,0.501960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-4,-8.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75.0750750750751,"st":0,"bm":0}],"markers":[{"tm":40.0400390625,"cm":"","dr":0}]} \ No newline at end of file diff --git a/app/src/main/res/raw/setting.json b/app/src/main/res/raw/setting.json new file mode 100644 index 00000000..c094b7f7 --- /dev/null +++ b/app/src/main/res/raw/setting.json @@ -0,0 +1 @@ +{"v":"5.10.2","fr":30,"ip":0,"op":60,"w":1080,"h":1080,"nm":"setting","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"character Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[-2]},{"t":60,"s":[0]}],"ix":10},"p":{"a":0,"k":[511.763,931.675,0],"ix":2,"l":2},"a":{"a":0,"k":[511.763,931.675,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[20.056,-7.235],[-7.107,2.691]],"o":[[7.109,-2.691],[-0.634,0.419]],"v":[[-10.028,3.617],[5.166,-2.411]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[541.51,885.748],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.33,-5.875]],"o":[[-1.33,5.876],[0,0]],"v":[[1.996,-7.792],[-1.996,7.792]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[445.87,855.049],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-5.617,-1.32]],"o":[[5.617,1.318],[0,0]],"v":[[-8.425,-1.979],[8.425,1.979]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[449.037,646.672],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-4.705,-3.48]],"o":[[4.704,3.479],[0,0]],"v":[[-6.035,-4.198],[6.035,4.198]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[642.249,827.793],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-4.703,-3.48]],"o":[[4.703,3.479],[0,0]],"v":[[-7.056,-5.219],[7.056,5.22]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[718.569,650.657],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-13.948,-16.124],[5.061,5.668]],"o":[[-5.062,-5.671],[0.619,0.443]],"v":[[6.974,8.062],[-4.124,-4.86]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[605.429,884.388],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 6","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-5.327,2.418]],"o":[[5.327,-2.418],[0,0]],"v":[[-7.992,3.626],[7.992,-3.626]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[657.33,697.362],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-3.92,-2.876]],"o":[[3.92,2.876],[0,0]],"v":[[-5.88,-4.313],[5.88,4.313]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[491.736,842.645],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":2,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.33,-5.876]],"o":[[-1.33,5.876],[0,0]],"v":[[1.995,-8.814],[-1.996,8.814]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[754.65,588.738],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":2,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.329,-5.876]],"o":[[-1.329,5.875],[0,0]],"v":[[1.995,-7.792],[-1.995,7.792]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[556.19,775.414],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":2,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[13.188,-11.219],[-4.791,3.963]],"o":[[4.789,-3.963],[-0.591,0.335]],"v":[[-6.594,5.609],[5.732,-4.237]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[563.174,838.753],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 11","np":2,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-5.617,-1.32]],"o":[[5.617,1.318],[0,0]],"v":[[-8.425,-1.979],[8.425,1.979]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[632.802,767.559],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 12","np":2,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.83,-5.619]],"o":[[1.829,5.621],[0,0]],"v":[[-2.745,-7.407],[2.744,7.407]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[587.835,714.34],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 13","np":2,"cix":2,"bm":0,"ix":13,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[16.124,-13.947],[-5.669,5.062]],"o":[[5.67,-5.061],[-0.443,0.619]],"v":[[-8.062,6.974],[4.86,-4.124]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[494.438,766.132],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 14","np":2,"cix":2,"bm":0,"ix":14,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-4.704,-3.479]],"o":[[4.704,3.479],[0,0]],"v":[[-7.056,-5.22],[7.056,5.22]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[480.533,709.904],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 15","np":2,"cix":2,"bm":0,"ix":15,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-110.994,-22.651],[-132.638,22.652],[132.638,21.532],[132.053,21.425]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-110.994,-22.651],[-131.973,17.922],[132.696,27.037],[132.751,21.609]],"c":true}]},{"t":60,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-110.994,-22.651],[-132.638,22.652],[132.638,21.532],[132.053,21.425]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[514.117,910.024],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 16","np":3,"cix":2,"bm":0,"ix":16,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[4.974,2.256],[2.268,-0.236],[4.031,-2.33],[0.111,-0.582],[-0.25,-0.372],[-3.124,-0.726],[-1.442,-4.774]],"o":[[-3.524,-4.172],[-2.077,-0.943],[-4.63,0.486],[-0.513,0.296],[-0.085,0.44],[1.787,2.663],[4.858,1.126],[0,0]],"v":[[15.5,3.692],[5.673,-9.105],[-1.052,-9.824],[-14.238,-5.538],[-15.414,-4.286],[-15.052,-3.027],[-6.319,0.298],[4.103,10.061]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[{"i":[[0,0],[4.441,3.179],[2.271,0.206],[4.406,-1.505],[0.222,-0.549],[-0.173,-0.413],[-2.925,-1.316],[-0.489,-4.963]],"o":[[-2.649,-4.776],[-1.855,-1.328],[-4.636,-0.421],[-0.561,0.191],[-0.169,0.415],[1.237,2.959],[4.548,2.046],[0,0]],"v":[[54.978,17.216],[47.819,2.757],[41.361,0.748],[27.593,2.397],[26.197,3.397],[26.309,4.703],[34.232,9.657],[42.564,21.255]],"c":false}]},{"t":60,"s":[{"i":[[0,0],[4.974,2.256],[2.268,-0.236],[4.031,-2.33],[0.111,-0.582],[-0.25,-0.372],[-3.124,-0.726],[-1.442,-4.774]],"o":[[-3.524,-4.172],[-2.077,-0.943],[-4.63,0.486],[-0.513,0.296],[-0.085,0.44],[1.787,2.663],[4.858,1.126],[0,0]],"v":[[15.5,3.692],[5.673,-9.105],[-1.052,-9.824],[-14.238,-5.538],[-15.414,-4.286],[-15.052,-3.027],[-6.319,0.298],[4.103,10.061]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[430.37,325.064],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 17","np":3,"cix":2,"bm":0,"ix":17,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[6.515,-7.37],[0.65,-2.475],[-0.231,-2.546],[-1.278,-3.215],[-0.992,-0.567],[-1.155,1.405],[-0.285,1.799],[0.729,5.494],[-1.178,-5.117],[-1.246,-0.687],[-1.235,0.918],[0.267,4.296],[-0.013,4.304],[-1.102,-4.478],[-1.029,-1.223],[-1.516,0.506],[-0.124,2.405],[0.285,5.642],[-1.654,-4.543],[-2.828,0.408],[-0.004,3.118],[0.982,5.355],[0.37,0.439],[0.991,-0.033]],"o":[[-2.463,0.689],[-0.65,2.474],[0.312,3.446],[0.422,1.063],[1.58,0.904],[1.156,-1.406],[0.869,-5.472],[-0.139,5.248],[0.319,1.387],[1.346,0.742],[3.454,-2.567],[-0.267,-4.296],[-0.115,4.611],[0.381,1.551],[1.03,1.222],[2.286,-0.763],[0.294,-5.643],[-0.043,4.833],[0.976,2.684],[3.087,-0.444],[0.005,-5.445],[-0.104,-0.565],[-0.639,-0.757],[-14.064,0.45]],"v":[[-16.905,-6.965],[-21.731,-1.448],[-21.926,6.181],[-20.012,16.344],[-18.02,19.055],[-13.163,17.781],[-11.315,12.714],[-11.103,-3.85],[-9.537,11.781],[-7.494,15.381],[-3.194,14.644],[0.993,2.996],[-0.522,-9.813],[0.969,3.902],[2.859,8.261],[7.12,9.753],[10.089,3.661],[10.104,-13.283],[12.546,0.933],[18.385,6.242],[22.376,-1.311],[20.906,-17.555],[20.309,-19.15],[17.548,-19.926]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[{"i":[[7.82,-5.967],[1.118,-2.302],[0.267,-2.542],[-0.629,-3.402],[-0.863,-0.749],[-1.406,1.154],[-0.629,1.709],[-0.35,5.531],[-0.163,-5.248],[-1.089,-0.915],[-1.39,0.661],[-0.571,4.266],[-0.847,4.22],[-0.211,-4.607],[-0.774,-1.399],[-1.585,0.203],[-0.589,2.335],[-0.814,5.59],[-0.74,-4.778],[-2.853,-0.149],[-0.607,3.058],[-0.075,5.444],[0.278,0.502],[0.978,0.161]],"o":[[-2.55,0.199],[-1.117,2.301],[-0.362,3.441],[0.208,1.125],[1.375,1.193],[1.407,-1.155],[1.913,-5.2],[-1.154,5.122],[0.044,1.423],[1.177,0.989],[3.886,-1.849],[0.571,-4.266],[-1.007,4.501],[0.073,1.595],[0.774,1.399],[2.391,-0.305],[1.382,-5.479],[-0.979,4.733],[0.437,2.822],[3.115,0.163],[1.06,-5.341],[0.008,-0.574],[-0.48,-0.866],[-13.885,-2.284]],"v":[[27.721,-2.149],[21.917,2.328],[20.247,9.774],[20.155,20.116],[21.584,23.161],[26.596,22.853],[29.391,18.24],[32.809,2.031],[31.316,17.669],[32.622,21.597],[36.984,21.707],[43.349,11.092],[44.345,-1.768],[43.15,11.976],[44.159,16.619],[48.05,18.908],[52.143,13.507],[55.442,-3.113],[55.083,11.307],[59.782,17.647],[65.161,11.011],[66.867,-5.21],[66.591,-6.89],[64.033,-8.187]],"c":true}]},{"t":60,"s":[{"i":[[6.515,-7.37],[0.65,-2.475],[-0.231,-2.546],[-1.278,-3.215],[-0.992,-0.567],[-1.155,1.405],[-0.285,1.799],[0.729,5.494],[-1.178,-5.117],[-1.246,-0.687],[-1.235,0.918],[0.267,4.296],[-0.013,4.304],[-1.102,-4.478],[-1.029,-1.223],[-1.516,0.506],[-0.124,2.405],[0.285,5.642],[-1.654,-4.543],[-2.828,0.408],[-0.004,3.118],[0.982,5.355],[0.37,0.439],[0.991,-0.033]],"o":[[-2.463,0.689],[-0.65,2.474],[0.312,3.446],[0.422,1.063],[1.58,0.904],[1.156,-1.406],[0.869,-5.472],[-0.139,5.248],[0.319,1.387],[1.346,0.742],[3.454,-2.567],[-0.267,-4.296],[-0.115,4.611],[0.381,1.551],[1.03,1.222],[2.286,-0.763],[0.294,-5.643],[-0.043,4.833],[0.976,2.684],[3.087,-0.444],[0.005,-5.445],[-0.104,-0.565],[-0.639,-0.757],[-14.064,0.45]],"v":[[-16.905,-6.965],[-21.731,-1.448],[-21.926,6.181],[-20.012,16.344],[-18.02,19.055],[-13.163,17.781],[-11.315,12.714],[-11.103,-3.85],[-9.537,11.781],[-7.494,15.381],[-3.194,14.644],[0.993,2.996],[-0.522,-9.813],[0.969,3.902],[2.859,8.261],[7.12,9.753],[10.089,3.661],[10.104,-13.283],[12.546,0.933],[18.385,6.242],[22.376,-1.311],[20.906,-17.555],[20.309,-19.15],[17.548,-19.926]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[415.698,313.77],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 18","np":3,"cix":2,"bm":0,"ix":18,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[16.835,-8.113],[1.896,-2.753],[-2.598,-2.102],[-2.733,0.548],[-9.012,3.605],[-10.333,4.134],[2.029,6.452]],"o":[[-17.672,6.076],[-3.011,1.452],[-1.896,2.754],[2.166,1.754],[9.519,-1.904],[10.333,-4.134],[-2.817,-4.124],[0,0]],"v":[[27.493,-18.73],[-24.532,2.066],[-32.558,7.878],[-32.017,16.978],[-23.986,17.873],[3.618,8.738],[34.615,-3.664],[27.493,-18.732]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[{"i":[[0,0],[18.087,-4.701],[2.393,-2.334],[-2.14,-2.567],[-2.787,0.007],[-9.54,1.79],[-10.938,2.053],[0.74,6.723]],"o":[[-18.515,2.536],[-3.235,0.841],[-2.394,2.334],[1.785,2.141],[9.708,-0.023],[10.938,-2.053],[-1.964,-4.592],[0,0]],"v":[[73.264,-5.385],[18.195,4.933],[9.195,9.079],[7.962,18.111],[15.667,20.546],[44.518,16.934],[77.331,10.775],[73.265,-5.387]],"c":true}]},{"t":60,"s":[{"i":[[0,0],[16.835,-8.113],[1.896,-2.753],[-2.598,-2.102],[-2.733,0.548],[-9.012,3.605],[-10.333,4.134],[2.029,6.452]],"o":[[-17.672,6.076],[-3.011,1.452],[-1.896,2.754],[2.166,1.754],[9.519,-1.904],[10.333,-4.134],[-2.817,-4.124],[0,0]],"v":[[27.493,-18.73],[-24.532,2.066],[-32.558,7.878],[-32.017,16.978],[-23.986,17.873],[3.618,8.738],[34.615,-3.664],[27.493,-18.732]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[414.314,315.417],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 19","np":3,"cix":2,"bm":0,"ix":19,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-2.69,4.926],[-7.886,2.976],[0,0],[-1.089,2.185],[-4.76,1.972],[-1.637,-3.753],[2.93,-1.161],[0,0],[1.982,0.773],[10.518,-4.478]],"o":[[7.887,-2.976],[1.204,-0.454],[0,0],[5,-1.243],[1.637,3.753],[-2.932,1.159],[-2.139,0.846],[0,0],[-10.209,5.012],[-0.902,-1.363]],"v":[[-25.409,9.193],[0.807,-1.777],[4.278,-3.438],[8.52,-9.314],[23.185,-14.145],[28.098,-2.883],[19.305,0.597],[12.696,2.487],[6.748,0.926],[-24.314,14.145]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[{"i":[[-3.594,4.311],[-8.314,1.389],[0,0],[-1.492,1.932],[-5.052,1.012],[-0.879,-3.999],[3.1,-0.569],[0,0],[1.795,1.143],[11.186,-2.354]],"o":[[8.314,-1.391],[1.269,-0.212],[0,0],[5.146,-0.25],[0.879,3.999],[-3.101,0.569],[-2.262,0.415],[0,0],[-10.987,2.938],[-0.621,-1.512]],"v":[[19.263,22.336],[47.108,16.655],[50.835,15.698],[56.136,10.756],[71.459,8.859],[74.096,20.859],[64.795,22.569],[57.945,23.142],[52.413,20.458],[19.378,27.406]],"c":true}]},{"t":60,"s":[{"i":[[-2.69,4.926],[-7.886,2.976],[0,0],[-1.089,2.185],[-4.76,1.972],[-1.637,-3.753],[2.93,-1.161],[0,0],[1.982,0.773],[10.518,-4.478]],"o":[[7.887,-2.976],[1.204,-0.454],[0,0],[5,-1.243],[1.637,3.753],[-2.932,1.159],[-2.139,0.846],[0,0],[-10.209,5.012],[-0.902,-1.363]],"v":[[-25.409,9.193],[0.807,-1.777],[4.278,-3.438],[8.52,-9.314],[23.185,-14.145],[28.098,-2.883],[19.305,0.597],[12.696,2.487],[6.748,0.926],[-24.314,14.145]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[466.733,293.214],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 20","np":3,"cix":2,"bm":0,"ix":20,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[-3.425,2.996],[-4.516,0.559],[-4.465,-2.522],[0.018,-2.088],[1.672,-0.541],[1.75,0.182],[3.056,0.318],[0.539,-0.18],[0.472,-0.615],[2.889,-4.889]],"o":[[2.768,-3.613],[3.426,-2.997],[5.091,-0.63],[1.816,1.026],[-0.017,1.757],[-1.673,0.541],[-3.055,-0.318],[-0.566,-0.06],[-0.736,0.246],[-3.461,4.5],[0,0]],"v":[[-19.761,3.026],[-10.777,-7.22],[1.31,-13.12],[16.007,-9.544],[19.743,-4.907],[16.546,-1.148],[11.314,-0.965],[2.147,-1.918],[0.455,-1.853],[-1.273,-0.35],[-10.81,13.75]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[{"i":[[0,0],[-2.733,3.639],[-4.303,1.479],[-4.889,-1.547],[-0.411,-2.047],[1.525,-0.874],[1.75,-0.183],[3.056,-0.32],[0.49,-0.287],[0.336,-0.699],[1.818,-5.38]],"o":[[1.963,-4.107],[2.734,-3.64],[4.851,-1.667],[1.989,0.629],[0.346,1.723],[-1.525,0.875],[-3.055,0.319],[-0.566,0.058],[-0.669,0.393],[-2.458,5.117],[0,0]],"v":[[-70.92,10.999],[-64.245,-0.881],[-53.636,-9.148],[-38.517,-8.683],[-33.904,-4.917],[-36.257,-0.579],[-41.338,0.68],[-50.505,1.64],[-52.147,2.052],[-53.527,3.88],[-59.948,19.645]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":40,"s":[{"i":[[0,0],[-4.059,2.057],[-4.514,-0.574],[-3.703,-3.547],[0.535,-2.018],[1.754,-0.111],[1.651,0.609],[2.883,1.063],[0.567,-0.041],[0.61,-0.479],[4.007,-4.024]],"o":[[3.575,-2.817],[4.06,-2.058],[5.089,0.647],[1.506,1.443],[-0.45,1.698],[-1.755,0.111],[-2.882,-1.063],[-0.534,-0.198],[-0.774,0.057],[-4.465,3.506],[0,0]],"v":[[45.508,16.859],[56.745,9.15],[69.915,6.418],[83.273,13.514],[85.748,18.93],[81.721,21.783],[76.606,20.668],[67.959,17.48],[66.303,17.125],[64.257,18.154],[51.533,29.462]],"c":false}]},{"t":60,"s":[{"i":[[0,0],[-3.425,2.996],[-4.516,0.559],[-4.465,-2.522],[0.018,-2.088],[1.672,-0.541],[1.75,0.182],[3.056,0.318],[0.539,-0.18],[0.472,-0.615],[2.889,-4.889]],"o":[[2.768,-3.613],[3.426,-2.997],[5.091,-0.63],[1.816,1.026],[-0.017,1.757],[-1.673,0.541],[-3.055,-0.318],[-0.566,-0.06],[-0.736,0.246],[-3.461,4.5],[0,0]],"v":[[-19.761,3.026],[-10.777,-7.22],[1.31,-13.12],[16.007,-9.544],[19.743,-4.907],[16.546,-1.148],[11.314,-0.965],[2.147,-1.918],[0.455,-1.853],[-1.273,-0.35],[-10.81,13.75]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[856.129,227.809],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 21","np":3,"cix":2,"bm":0,"ix":21,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[5.931,-2.783],[1.903,-1.693],[0.518,-2.444],[-0.127,-4.235],[-0.087,7.089],[0.571,7.309],[0.314,-4.26],[-3.387,-2.026],[-1.591,6.157],[-0.782,6.316],[0.644,-5.307],[-0.664,-2.348],[-3.325,10.354],[0.569,8.443],[0.408,-4.661],[-5.224,9.021],[0.594,7.29],[8.142,0.078]],"o":[[-8.362,-0.08],[-2.26,1.059],[-1.907,1.698],[-1.52,7.157],[0.309,10.163],[0.109,-8.902],[0.263,5.294],[-0.316,4.259],[9.304,5.565],[1.587,-6.146],[-0.644,5.309],[-0.287,2.365],[1.626,5.746],[2.295,-7.143],[-0.675,4.598],[-0.706,8.063],[3.057,-5.281],[-0.615,-7.55],[0,0]],"v":[[12.626,-23.678],[-14.993,-17.945],[-21.937,-14.53],[-25.147,-7.873],[-26.61,9.475],[-13.949,11.816],[-12.851,-7.699],[-13.778,2.207],[-10.255,18.193],[-0.844,4.764],[1.356,-11.682],[0.225,1.677],[1.13,9.247],[11.57,7.051],[13.609,-12.269],[13.28,0.558],[23.271,6.547],[26.143,-13.63],[12.63,-23.68]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[{"i":[[0,0],[5.231,-3.944],[1.512,-2.05],[0.003,-2.498],[-1,-4.117],[1.378,6.954],[2.067,7.034],[-0.57,-4.233],[-3.732,-1.283],[-0.285,6.353],[0.539,6.341],[-0.465,-5.326],[-1.135,-2.16],[-1.118,10.817],[2.299,8.144],[-0.563,-4.645],[-3.248,9.906],[2.086,7.01],[7.983,-1.604]],"o":[[-8.198,1.648],[-1.993,1.503],[-1.515,2.055],[-0.01,7.317],[2.4,9.88],[-1.731,-8.733],[1.35,5.126],[0.57,4.233],[10.252,3.525],[0.284,-6.341],[0.466,5.328],[0.207,2.373],[2.777,5.287],[0.771,-7.463],[0.289,4.638],[0.974,8.035],[1.901,-5.798],[-2.16,-7.26],[0,0]],"v":[[-48.056,-21.841],[-73.897,-10.53],[-79.986,-5.756],[-81.753,1.421],[-79.604,18.697],[-66.732,18.374],[-69.686,-0.947],[-68.548,8.937],[-61.801,23.851],[-55.365,8.769],[-56.607,-7.777],[-54.956,5.528],[-52.508,12.748],[-42.746,8.444],[-44.739,-10.881],[-42.413,1.738],[-31.401,5.536],[-32.756,-14.799],[-48.052,-21.844]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":40,"s":[{"i":[[0,0],[6.435,-1.228],[2.262,-1.171],[1.107,-2.24],[0.921,-4.135],[-1.835,6.848],[-1.252,7.224],[1.359,-4.05],[-2.782,-2.8],[-3.062,5.574],[-2.318,5.927],[1.935,-4.983],[-0.064,-2.439],[-5.781,9.211],[-1.534,8.322],[1.547,-4.416],[-7.289,7.452],[-1.225,7.211],[7.87,2.087]],"o":[[-8.083,-2.143],[-2.452,0.468],[-2.267,1.174],[-3.241,6.56],[-2.211,9.924],[2.305,-8.599],[-1.053,5.195],[-1.358,4.049],[7.641,7.691],[3.056,-5.564],[-1.935,4.985],[-0.862,2.221],[0.156,5.97],[3.988,-6.355],[-1.79,4.289],[-2.676,7.639],[4.267,-4.362],[1.269,-7.468],[0,0]],"v":[[87.352,-0.085],[59.173,-1.353],[51.6,0.241],[46.845,5.899],[41.142,22.348],[52.833,27.744],[58.717,9.105],[55.372,18.475],[54.837,34.836],[67.274,24.148],[73.468,8.755],[69.072,21.421],[68.079,28.98],[78.738,29.43],[85.486,11.213],[81.999,23.561],[90.201,31.833],[97.968,12.99],[87.356,-0.086]],"c":true}]},{"t":60,"s":[{"i":[[0,0],[5.931,-2.783],[1.903,-1.693],[0.518,-2.444],[-0.127,-4.235],[-0.087,7.089],[0.571,7.309],[0.314,-4.26],[-3.387,-2.026],[-1.591,6.157],[-0.782,6.316],[0.644,-5.307],[-0.664,-2.348],[-3.325,10.354],[0.569,8.443],[0.408,-4.661],[-5.224,9.021],[0.594,7.29],[8.142,0.078]],"o":[[-8.362,-0.08],[-2.26,1.059],[-1.907,1.698],[-1.52,7.157],[0.309,10.163],[0.109,-8.902],[0.263,5.294],[-0.316,4.259],[9.304,5.565],[1.587,-6.146],[-0.644,5.309],[-0.287,2.365],[1.626,5.746],[2.295,-7.143],[-0.675,4.598],[-0.706,8.063],[3.057,-5.281],[-0.615,-7.55],[0,0]],"v":[[12.626,-23.678],[-14.993,-17.945],[-21.937,-14.53],[-25.147,-7.873],[-26.61,9.475],[-13.949,11.816],[-12.851,-7.699],[-13.778,2.207],[-10.255,18.193],[-0.844,4.764],[1.356,-11.682],[0.225,1.677],[1.13,9.247],[11.57,7.051],[13.609,-12.269],[13.28,0.558],[23.271,6.547],[26.143,-13.63],[12.63,-23.68]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[857.911,211.943],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 22","np":3,"cix":2,"bm":0,"ix":22,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[-1.932,-1.624],[-0.491,-1.835],[0.736,-1.58],[2.543,-0.591],[34.647,1.383],[7.24,-3.024],[0,15.724],[-8.87,1.509],[-0.107,5.867],[10.118,-0.181],[-12.283,0.269],[-2.679,-0.946],[-2.089,-1.835],[-0.337,-7.51],[-7.973,-0.348]],"o":[[2.521,0.111],[1.456,1.223],[0.45,1.685],[-1.107,2.365],[-10.686,2.489],[-1.761,7.645],[-13.784,5.757],[8.976,-0.618],[0.108,-5.867],[-10.325,1.013],[1.182,-12.338],[2.841,-0.063],[2.622,0.926],[4.858,4.269],[29.482,-0.037],[0,0]],"v":[[67.453,-10.101],[74.619,-8.141],[77.347,-3.237],[77.292,1.885],[70.819,5.776],[-29.501,10.331],[-44.146,27.627],[-75.719,12.516],[-48.929,10.854],[-49.627,-15.274],[-78.029,-14.492],[-50.886,-33.32],[-42.485,-32.208],[-35.427,-27.862],[-26.6,-10.178],[67.455,-10.102]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[{"i":[[0,0],[-2.226,-1.189],[-0.858,-1.695],[0.397,-1.697],[2.366,-1.104],[34.186,-5.798],[6.46,-4.453],[3.246,15.385],[-8.367,3.307],[1.106,5.763],[9.863,-2.266],[-11.962,2.802],[-2.817,-0.373],[-2.423,-1.365],[-1.88,-7.279],[-7.873,1.305]],"o":[[2.49,-0.412],[1.677,0.896],[0.788,1.556],[-0.595,2.543],[-9.942,4.641],[-0.145,7.844],[-12.299,8.478],[8.655,-2.457],[-1.105,-5.763],[-9.894,3.122],[-1.39,-12.316],[2.767,-0.648],[2.757,0.365],[5.635,3.174],[28.839,-6.122],[0,0]],"v":[[8.315,-16.964],[15.731,-16.526],[19.412,-12.291],[20.416,-7.268],[14.885,-2.125],[-82.334,23.04],[-93.094,42.986],[-127.106,34.718],[-101.236,27.562],[-107.312,2.14],[-134.941,8.769],[-112.269,-15.257],[-103.819,-15.902],[-96.016,-13.107],[-83.729,2.374],[8.316,-16.966]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":40,"s":[{"i":[[0,0],[-1.472,-2.05],[-0.022,-1.899],[1.106,-1.347],[2.61,0.054],[33.232,9.899],[7.763,-1.142],[-3.884,15.237],[-8.968,-0.729],[-1.553,5.659],[9.849,2.324],[-11.969,-2.77],[-2.362,-1.579],[-1.57,-2.294],[1.529,-7.36],[-7.64,-2.307]],"o":[[2.415,0.73],[1.109,1.545],[0.02,1.744],[-1.657,2.018],[-10.97,-0.228],[-3.595,6.973],[-14.779,2.174],[8.85,1.618],[1.554,-5.659],[-10.255,-1.569],[4.193,-11.664],[2.769,0.641],[2.312,1.545],[3.653,5.337],[28.578,7.247],[0,0]],"v":[[138.01,23.238],[144.47,26.906],[145.902,32.332],[144.583,37.282],[137.35,39.453],[39.013,19.086],[20.55,32.228],[-6.312,9.786],[20.058,14.793],[25.836,-10.698],[-1.879,-16.955],[29.074,-28.495],[36.94,-25.342],[42.706,-19.387],[46.891,-0.071],[138.012,23.237]],"c":true}]},{"t":60,"s":[{"i":[[0,0],[-1.932,-1.624],[-0.491,-1.835],[0.736,-1.58],[2.543,-0.591],[34.647,1.383],[7.24,-3.024],[0,15.724],[-8.87,1.509],[-0.107,5.867],[10.118,-0.181],[-12.283,0.269],[-2.679,-0.946],[-2.089,-1.835],[-0.337,-7.51],[-7.973,-0.348]],"o":[[2.521,0.111],[1.456,1.223],[0.45,1.685],[-1.107,2.365],[-10.686,2.489],[-1.761,7.645],[-13.784,5.757],[8.976,-0.618],[0.108,-5.867],[-10.325,1.013],[1.182,-12.338],[2.841,-0.063],[2.622,0.926],[4.858,4.269],[29.482,-0.037],[0,0]],"v":[[67.453,-10.101],[74.619,-8.141],[77.347,-3.237],[77.292,1.885],[70.819,5.776],[-29.501,10.331],[-44.146,27.627],[-75.719,12.516],[-48.929,10.854],[-49.627,-15.274],[-78.029,-14.492],[-50.886,-33.32],[-42.485,-32.208],[-35.427,-27.862],[-26.6,-10.178],[67.455,-10.102]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[844.009,210.111],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 23","np":3,"cix":2,"bm":0,"ix":23,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-3.966,-3.018],[-0.173,-0.37],[0.323,-0.645],[1.517,-2.432]],"o":[[2.211,4.467],[0.326,0.248],[0.304,0.654],[-1.279,2.564],[0,0]],"v":[[-5.289,-10.933],[4.143,0.498],[4.985,1.374],[4.65,3.434],[0.452,10.932]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[599.703,538.213],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 24","np":3,"cix":2,"bm":0,"ix":24,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-11.874,7.475]],"o":[[13.622,3.359],[0,0]],"v":[[-20.221,1.585],[20.221,-4.944]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[601.324,594.481],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 25","np":2,"cix":2,"bm":0,"ix":25,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-11.874,7.475]],"o":[[13.622,3.359],[0,0]],"v":[[-20.221,1.585],[20.221,-4.944]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[601.324,594.481],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 26","np":2,"cix":2,"bm":0,"ix":26,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-19.207,72.187],[-27.309,38.516],[8.84,29.268],[25.755,-21.852],[14.703,-4.92],[-1.071,-16.151],[3.334,-3.843],[3.487,-1.021],[27.461,-1.936],[2.404,1.192],[1.055,4.122],[0.387,12.798],[15.413,-3.861],[17.367,25.385],[25.646,-17.671],[-22.259,-33.779],[14.509,-44.663],[-84.648,-9.765]],"o":[[19.207,-72.188],[34.991,-30.926],[-12.649,-27.484],[-21.027,26.433],[-15.304,7.283],[0.772,16.498],[0.337,5.079],[-2.379,2.745],[-26.418,7.746],[-2.676,0.188],[-3.813,-1.886],[-3.176,-12.401],[-13.576,6.498],[-18.82,-23.316],[-17.546,25.691],[24.059,32.695],[-13.564,44.458],[79.432,16.48],[0,0]],"v":[[57.006,204.631],[114.627,-13.974],[207.962,-119.289],[175.393,-204.631],[104.999,-131.973],[60.443,-114.393],[63.354,-65.594],[60.509,-51.01],[50.98,-46.052],[-30.15,-31.473],[-38.05,-32.343],[-44.485,-42.886],[-49.85,-80.839],[-93.853,-65.034],[-147.515,-138.466],[-207.962,-76.803],[-143.765,27.239],[-186.627,160.444],[57.006,204.628]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.481999984442,0.372999991623,0.944999964097,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[589.749,726.923],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 27","np":3,"cix":2,"bm":0,"ix":27,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0.971,-2.777],[23.499,30.034],[-17.109,44.275],[8.439,17.807],[4.087,1.428],[2.294,-0.393],[10.635,-8.559],[-1.805,-12.459],[24.322,-44.917],[0.653,-11.653],[-6.154,-10.481],[-26.583,-36.322],[-9.274,3.889],[-6.459,7.709],[-1.939,9.031]],"o":[[-19.84,-32.567],[20.82,-42.656],[7.531,-19.487],[-1.585,-4.028],[-2.199,-0.766],[-13.457,2.305],[0.026,9.757],[-32.698,39.242],[-5.557,10.263],[-0.682,12.135],[23.553,40.118],[5.939,8.114],[9.274,-3.889],[5.931,-7.081],[1.939,-9.031]],"v":[[65.451,115.006],[0.369,20.995],[57.298,-109.482],[63.564,-168.644],[55.863,-178.572],[48.957,-178.662],[12.021,-162.007],[13.88,-130.034],[-59.671,-9.188],[-71.32,23.515],[-62.206,58.418],[10.035,171.224],[29.889,174.257],[53.235,155.606],[66.344,131.506]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[{"i":[[0.971,-2.777],[17.232,34.019],[-25.367,40.118],[4.828,19.105],[3.735,2.189],[2.327,0.059],[12.092,-6.335],[0.644,-12.573],[32.566,-39.352],[5.794,-10.131],[-6.154,-10.481],[-26.583,-36.322],[-9.274,3.889],[-6.459,7.709],[-1.939,9.031]],"o":[[-19.84,-32.567],[28.693,-37.812],[11.165,-17.658],[-0.774,-4.259],[-2.009,-1.178],[-13.649,-0.347],[-1.866,9.577],[-39.684,32.16],[-7.441,8.991],[-6.956,12.163],[23.553,40.118],[5.939,8.114],[9.274,-3.889],[5.931,-7.081],[1.939,-9.031]],"v":[[65.451,115.006],[7.105,18.825],[88.244,-98.144],[105.858,-154.969],[100.227,-166.202],[93.47,-167.628],[54.006,-158.448],[49.633,-126.721],[-45.946,-22.422],[-63.713,7.403],[-62.206,58.418],[10.035,171.224],[29.889,174.257],[53.235,155.606],[66.344,131.506]],"c":true}]},{"t":60,"s":[{"i":[[0.971,-2.777],[23.499,30.034],[-17.109,44.275],[8.439,17.807],[4.087,1.428],[2.294,-0.393],[10.635,-8.559],[-1.805,-12.459],[24.322,-44.917],[0.653,-11.653],[-6.154,-10.481],[-26.583,-36.322],[-9.274,3.889],[-6.459,7.709],[-1.939,9.031]],"o":[[-19.84,-32.567],[20.82,-42.656],[7.531,-19.487],[-1.585,-4.028],[-2.199,-0.766],[-13.457,2.305],[0.026,9.757],[-32.698,39.242],[-5.557,10.263],[-0.682,12.135],[23.553,40.118],[5.939,8.114],[9.274,-3.889],[5.931,-7.081],[1.939,-9.031]],"v":[[65.451,115.006],[0.369,20.995],[57.298,-109.482],[63.564,-168.644],[55.863,-178.572],[48.957,-178.662],[12.021,-162.007],[13.88,-130.034],[-59.671,-9.188],[-71.32,23.515],[-62.206,58.418],[10.035,171.224],[29.889,174.257],[53.235,155.606],[66.344,131.506]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[381.669,482.934],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 28","np":3,"cix":2,"bm":0,"ix":28,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[-15.094,19.122],[-8.488,67.252],[3.298,8.882],[-0.785,9.501],[-0.667,1.022],[-1.49,0.6],[-12.523,-1.248],[-2.858,-1.911],[-1.351,-5.235],[0.87,-13.991],[5.114,-70.954],[4.203,-10.655],[5.852,-7.911],[20.617,-24.137],[0.859,-0.28],[1.04,0.533],[3.675,5.959],[3.576,9.959],[-9.768,10.911]],"o":[[16.674,-18.622],[13.296,-66.039],[-3.298,-8.881],[-3.319,-8.936],[0.101,-1.215],[0.878,-1.346],[11.674,-4.7],[3.42,0.341],[4.494,3.003],[3.407,13.181],[-5.114,70.954],[-0.824,11.424],[-3.612,9.154],[-18.875,25.521],[-0.588,0.686],[-1.111,0.363],[-6.237,-3.175],[-5.551,-9.007],[-4.172,-11.622],[0,0]],"v":[[-59.093,118.854],[-11.674,61.575],[20.428,-138.355],[9.511,-161.871],[3.605,-189.605],[4.557,-193.096],[8.483,-195.757],[45.496,-201.037],[55.29,-198.173],[63.098,-184.385],[67.997,-146.63],[52.653,66.231],[46.757,99.939],[31.377,125.033],[-27.012,200.238],[-29.09,201.921],[-32.444,201.298],[-46.685,186.238],[-60.425,157.687],[-59.099,118.854]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[{"i":[[0,0],[-10.822,21.826],[5.577,67.556],[5.061,8.01],[1.188,9.459],[-0.441,1.138],[-1.334,0.895],[-12.511,1.363],[-3.191,-1.279],[-2.404,-4.842],[-2.037,-13.869],[-9.639,-70.482],[1.914,-11.293],[5.852,-7.911],[20.617,-24.137],[0.859,-0.28],[1.04,0.533],[3.675,5.959],[3.576,9.959],[-9.768,10.911]],"o":[[16.674,-18.622],[-0.622,-67.361],[-5.06,-8.009],[-5.092,-8.059],[-0.152,-1.21],[0.581,-1.498],[10.452,-7.008],[3.417,-0.372],[5.017,2.011],[6.054,12.194],[9.642,70.482],[1.552,11.348],[-1.645,9.702],[-18.875,25.521],[-0.588,0.686],[-1.111,0.363],[-6.237,-3.175],[-5.551,-9.007],[-4.172,-11.622],[0,0]],"v":[[-59.093,118.854],[-13.999,68.982],[-23.857,-133.27],[-39.392,-154.025],[-50.896,-179.942],[-50.685,-183.555],[-47.393,-186.969],[-12.267,-199.775],[-2.092,-198.994],[8.393,-187.115],[20.98,-151.184],[49.904,60.26],[51.093,94.459],[31.377,125.033],[-27.012,200.238],[-29.09,201.921],[-32.444,201.298],[-46.685,186.238],[-60.425,157.687],[-59.099,118.854]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":40,"s":[{"i":[[0,0],[-16.724,26.135],[-24.838,63.071],[1.002,9.421],[-3.112,9.011],[-0.899,0.826],[-1.592,0.213],[-11.826,-4.304],[-2.298,-2.557],[-0.018,-5.406],[4.299,-13.342],[22.486,-67.491],[6.706,-9.286],[5.944,-7.842],[20.617,-24.137],[0.859,-0.28],[1.04,0.533],[3.675,5.959],[3.576,9.959],[-9.768,10.911]],"o":[[10.753,-10.435],[29.197,-60.708],[-1.002,-9.421],[-1.009,-9.479],[0.398,-1.152],[1.183,-1.087],[12.473,-1.671],[3.23,1.175],[3.613,4.02],[0.045,13.614],[-22.483,67.492],[-3.62,10.866],[-5.761,7.978],[-16.293,21.498],[-0.588,0.686],[-1.111,0.363],[-6.237,-3.175],[-5.551,-9.007],[-4.172,-11.622],[0,0]],"v":[[-59.093,118.854],[-3.616,62.785],[76.88,-123.019],[72.11,-148.503],[73.238,-176.836],[75.023,-179.984],[79.484,-181.592],[116.655,-177.565],[125.438,-172.371],[129.598,-157.081],[125.018,-119.286],[57.567,83.188],[43.527,114.394],[25.953,139.422],[-27.012,200.239],[-29.09,201.922],[-32.444,201.298],[-46.685,186.239],[-60.425,157.688],[-59.099,118.854]],"c":true}]},{"t":60,"s":[{"i":[[0,0],[-15.094,19.122],[-8.488,67.252],[3.298,8.882],[-0.785,9.501],[-0.667,1.022],[-1.49,0.6],[-12.523,-1.248],[-2.858,-1.911],[-1.351,-5.235],[0.87,-13.991],[5.114,-70.954],[4.203,-10.655],[5.852,-7.911],[20.617,-24.137],[0.859,-0.28],[1.04,0.533],[3.675,5.959],[3.576,9.959],[-9.768,10.911]],"o":[[16.674,-18.622],[13.296,-66.039],[-3.298,-8.881],[-3.319,-8.936],[0.101,-1.215],[0.878,-1.346],[11.674,-4.7],[3.42,0.341],[4.494,3.003],[3.407,13.181],[-5.114,70.954],[-0.824,11.424],[-3.612,9.154],[-18.875,25.521],[-0.588,0.686],[-1.111,0.363],[-6.237,-3.175],[-5.551,-9.007],[-4.172,-11.622],[0,0]],"v":[[-59.093,118.854],[-11.674,61.575],[20.428,-138.355],[9.511,-161.871],[3.605,-189.605],[4.557,-193.096],[8.483,-195.757],[45.496,-201.037],[55.29,-198.173],[63.098,-184.385],[67.997,-146.63],[52.653,66.231],[46.757,99.939],[31.377,125.033],[-27.012,200.238],[-29.09,201.921],[-32.444,201.298],[-46.685,186.238],[-60.425,157.687],[-59.099,118.854]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[826.34,403.58],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 29","np":3,"cix":2,"bm":0,"ix":29,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[2.299,-3.124],[0.299,-1.157],[-0.443,-2.987],[-2.323,-1.93],[-2.937,0.58],[-1.144,2.157],[1.74,-19.792],[6.924,-3.398],[1.017,-10.551],[-2.6,-7.067],[-7.337,-4.349],[-8.37,-1.636],[-18.631,3.545],[-10.004,6.355],[-4.028,11.148],[0.078,8.102],[6.402,10.281],[11.526,-0.403],[7.493,-2.856],[2.64,8.274],[-1.198,8.87],[1.913,8.744],[5.645,14.126],[5.829,1.304],[6.212,3.869],[16.9,-8.938],[-0.775,-5.417]],"o":[[-3.835,-0.585],[-0.709,0.963],[-0.755,2.924],[0.44,2.987],[2.301,1.914],[2.937,-0.579],[3.955,20.585],[-7.709,0.448],[-5.918,8.795],[-0.722,7.497],[2.945,8.003],[7.335,4.347],[18.613,3.637],[11.644,-2.215],[10.003,-6.358],[2.752,-7.62],[-0.115,-12.113],[-6.402,-10.282],[-9.249,2.741],[-0.306,-9.272],[7.56,-4.791],[1.197,-8.871],[-3.255,-14.89],[-5.973,-0.196],[-5.831,-1.303],[-5.18,18.237],[1.632,10.332],[0,0]],"v":[[-56.922,-50.406],[-67.724,-48.133],[-69.096,-44.844],[-69.821,-35.875],[-65.699,-28.034],[-57.277,-25.889],[-50.561,-30.585],[-47.098,30.896],[-69.379,36.232],[-78.522,66.344],[-76.796,88.674],[-60.201,107.487],[-36.165,115.867],[20.388,116.009],[53.547,103.771],[76.02,77.045],[79.318,53.096],[71.026,18.191],[41.219,0.759],[13.317,10.565],[9.015,-20.492],[24.116,-46.047],[21.871,-72.664],[9.614,-110.195],[-8.2,-111.896],[-25.946,-119.555],[-60.959,-76.483],[-56.92,-50.401]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[609.074,611.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 30","np":3,"cix":2,"bm":0,"ix":30,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-12.296,35.745],[-10.554,13.504],[-16.631,4.141],[-12.534,-11.691],[-3.595,-9.859],[-2.316,-36.044],[1.252,-3.628],[2.725,-2.674],[9.399,-4.941],[27.329,-6.387],[17.461,4.266],[3.902,21.855]],"o":[[23.272,-29.788],[5.575,-16.208],[10.554,-13.503],[16.631,-4.141],[7.673,7.159],[15.908,43.615],[0.246,3.829],[-1.244,3.609],[-7.583,7.432],[-24.843,13.058],[-17.504,4.091],[-14.292,-3.494],[0,0]],"v":[[-98.686,71.885],[-56.814,-32.778],[-33.652,-78.413],[7.905,-105.529],[52.914,-94.003],[71.214,-63.533],[94.192,40.182],[97.434,46.476],[90.915,55.766],[64.668,73.609],[-14.015,102.936],[-67.377,105.405],[-98.682,71.888]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[568.94,580.87],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 31","np":3,"cix":2,"bm":0,"ix":31,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":90,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Layer 1 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,540,0],"ix":2,"l":2},"a":{"a":0,"k":[540,540,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[9.206,-14.402]],"o":[[-7.998,15.128],[0,0]],"v":[[12.911,-22.159],[-12.911,22.159]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[364.273,908.851],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[15.733,7.739]],"o":[[-16.814,-4.855],[0,0]],"v":[[24.474,9.469],[-24.474,-9.469]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[295.374,874.475],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[3.257,-17.133]],"o":[[-2.306,17.295],[0,0]],"v":[[4.173,-25.828],[-4.173,25.827]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[310.525,830.244],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[30.991,48.779]],"o":[[-30.99,-48.779],[0,0]],"v":[[46.486,73.167],[-46.486,-73.168]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[304.084,857.715],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[7.313,-9.996]],"o":[[-7.313,9.996],[0,0]],"v":[[10.97,-14.994],[-10.97,14.994]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[265.632,896.312],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[15.987,5.609]],"o":[[-15.986,-5.609],[0,0]],"v":[[23.981,8.414],[-23.981,-8.414]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[218.129,875.388],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 6","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[26.544,39.327]],"o":[[-17.088,-44.389],[0,0]],"v":[[32.867,63.06],[-32.867,-63.06]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[232.718,870.308],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[13.734,30.427],[-0.241,14.322],[-9.585,3.781],[-6.525,-2.694],[-10.439,-10.558],[-16.9,-50.506],[28.568,0.302]],"o":[[-16.757,-28.831],[-5.881,-13.031],[0.176,-10.434],[6.561,-2.587],[13.654,5.638],[34.799,35.199],[-35.727,0.602],[0,0]],"v":[[-17.63,79.93],[-63.252,-10.917],[-74.596,-51.948],[-60.355,-77.643],[-39.806,-75.942],[-4.334,-49.661],[74.837,79.326],[-17.63,79.928]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.33300000359,0.816000007181,0.528999956916,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[320.946,853.349],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":3,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[16.92,24.518],[18.272,7.916],[6.1,-2.275],[1.333,-6.537],[-1.244,-6.557],[-13.809,-30.615],[0,0]],"o":[[-10.237,-17.276],[-5.981,-2.593],[-6.176,2.301],[-1.334,6.536],[6.278,33.05],[40.727,0.042],[-12.354,-36.513]],"v":[[15.806,-21.994],[-28.412,-61.069],[-47.311,-63.354],[-58.874,-48.196],[-57.993,-28.321],[-24.686,65.586],[60.208,65.042]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.33300000359,0.816000007181,0.528999956916,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[243.877,867.784],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":3,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.426,-14.37],[11.629,-2.815],[13.407,6.166],[5.297,10],[-3.815,11.666],[-7.999,5.926],[-11.593,1.222],[-8.093,-10.759]],"o":[[-2.703,27.185],[-11.019,2.648],[-10.296,-4.722],[-5.722,-10.852],[3.092,-9.445],[9.352,-6.926],[11.574,-1.222],[12.056,13.222]],"v":[[48.379,4.139],[17.435,44.694],[-23.656,42.361],[-45.193,20.583],[-50.73,-14.805],[-32.397,-40.36],[-0.49,-51.36],[34.213,-40.36]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-0.13,1.704],[-0.481,6.982],[6.147,0.093],[5.833,6.241],[-4.796,3.981],[8.11,9.148],[4.87,-4.537],[7.871,4.129],[7.296,0],[-0.778,8.185],[5.926,1.074],[0.388,-7.37],[5.129,-1.278],[2.927,-2.926],[3.722,2.407],[-0.425,-0.611],[-4.093,-3.574],[0.742,-4.741],[-0.055,-4.796],[6.315,-0.259],[-0.315,-10.259],[-0.185,-0.259],[-0.463,-0.018],[-6.76,-0.481],[-5.018,-7.833],[3.74,-3.481],[-8.241,-7.129],[-3.796,2.945],[-11.87,4.167],[-3.203,-1.222],[-8,0.185],[-2.074,6.37],[-7.926,7.37],[-3.778,-3.648],[-0.408,0.037],[-0.26,0.204],[-6,5.703],[-0.019,0.445],[0.316,0.334],[3.926,4.259],[-1.851,7.685]],"o":[[0.482,-6.963],[-6.074,-0.815],[-1.5,-9.463],[4.944,-4.055],[-4.074,-5.593],[-6.611,4.611],[-3.686,-5.833],[-5.092,-2.667],[0.592,-6.388],[-13.129,0],[-0.963,5.889],[-3.13,0],[-6.278,1.573],[-4.666,-4.741],[-13.981,12.37],[2.519,3.537],[-4.056,4.537],[-0.721,4.722],[-5.499,-0.482],[0.315,10.278],[0,0.297],[0.259,0.371],[6.777,0.5],[0.185,9.074],[-3.518,4.018],[8.24,7.13],[3.777,-2.944],[3.519,5.111],[0.296,5.648],[7.481,2.852],[2.055,-6.37],[7.963,-1.889],[3.055,3.907],[0.296,0.277],[0.315,-0.018],[6.277,-5.371],[0.333,-0.315],[0.019,-0.482],[-3.944,-4.259],[5.204,-5.426],[23.018,0.852]],"v":[[93.249,17.861],[94.193,-10.213],[75.841,-11.565],[64.065,-36.768],[77.564,-48.286],[59.046,-72.415],[45.546,-62.064],[32.693,-73.841],[16.084,-77.434],[17.232,-94.433],[-14.342,-94.711],[-15.36,-77.637],[-29.471,-75.785],[-43.323,-67.786],[-55.156,-77.804],[-78.268,-56.286],[-66.655,-44.138],[-74.805,-29.175],[-75.971,-18.879],[-97.378,-18.25],[-96.433,12.546],[-96.229,13.435],[-94.988,13.898],[-76.211,13.861],[-66.12,39.546],[-78.655,52.397],[-55.785,74.878],[-41.045,63.045],[-11.693,72.878],[-9.953,91.859],[20.473,91.174],[21.676,72.471],[45.065,57.953],[55.176,69.675],[56.231,70.23],[57.065,69.786],[75.508,53.157],[76.211,52.027],[75.581,50.823],[63.786,38.027],[74.359,17.62]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.545000023935,0.216000007181,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[641.651,254.966],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.38,0.38],"y":[1,1]},"o":{"x":[0.62,0.62],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.38,0.38],"y":[1,1]},"o":{"x":[0.62,0.62],"y":[0,0]},"t":30,"s":[74,74]},{"t":60,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.38],"y":[1]},"o":{"x":[0.62],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.38],"y":[1]},"o":{"x":[0.62],"y":[0]},"t":30,"s":[180]},{"t":60,"s":[360]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":4,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.481,-2.778],[4.611,-1.277],[4.648,1.13],[2.611,4.462],[-0.352,4.167],[-2.852,4],[-4.889,-0.427],[-4.129,-4.482],[-0.018,-6.093]],"o":[[-2.241,4.222],[-4.612,1.26],[-5.018,-1.222],[-2.129,-3.63],[0.426,-4.907],[2.871,-3.999],[8.407,-0.575],[4.148,4.481],[0,3.167]],"v":[[18.546,12.046],[7.454,20.508],[-6.75,20.36],[-19.472,12.214],[-21.435,-0.028],[-16.972,-13.917],[-4.435,-20.379],[14.268,-13.824],[20.583,2.898]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.13,3.759],[0.148,0.13],[0.222,-0.019],[2.629,-0.055],[2.056,2.944],[-1,2.037],[0.13,0.5],[0.296,0.296],[2.204,2.259],[1.87,-2.056],[3.871,-0.093],[-0.093,2.833],[5.556,0],[-0.074,-3.018],[2.407,-1.112],[2.111,-1.538],[1.463,1.796],[1.018,-0.445],[-2.259,-2.352],[0.778,-1.222],[0.463,-2.426],[1.925,-0.537],[-0.334,-0.037],[-2.444,0.315],[-1.389,-1.666],[-1.63,-1.425],[1.796,-2.241],[-3.573,-2.982],[-0.149,-0.018],[-0.167,0.13],[-2.204,2.148],[-4.167,-0.667],[-0.056,-2.315],[-4.889,0.648],[-0.129,2.167],[-2.167,5.277],[-1.37,0.722],[-2.352,2.778],[1.537,2.611],[0.111,4.908],[-2.389,0.167],[-0.111,0.148],[0,0.148]],"o":[[0,-0.204],[-0.167,-0.148],[-2.63,0.056],[-0.278,-3.389],[2.463,-1.259],[0.241,-0.481],[-0.111,-0.407],[-2.222,-2.259],[-2.111,1.833],[-3.667,-2.352],[0.112,-2.89],[-3.13,-0.297],[-0.425,3.63],[-3.073,-0.5],[-2.388,1.074],[-1.759,-2.926],[-4.537,2.814],[1.611,3.259],[-1.74,1.241],[-1.315,2.093],[-3.018,0.148],[0,8.463],[1.962,0.204],[0.315,2.741],[1.389,1.667],[-1.797,1.722],[3.573,2.963],[0.112,0.092],[0.204,0.019],[2.204,-2.148],[3.129,2.667],[0.295,2.389],[4.888,-0.648],[0.129,-2.166],[2.796,0.333],[2,1.592],[3.223,-1.686],[-1.556,-2.611],[1.76,-3.278],[2.241,0.148],[0.185,-0.018],[0.092,-0.112],[0.074,-3.759]],"v":[[41.768,-10.102],[41.601,-10.676],[40.99,-10.824],[33.12,-10.676],[29.36,-20.398],[34.472,-25.435],[34.786,-26.935],[34.064,-27.971],[27.416,-34.768],[21.435,-28.935],[9.99,-32.564],[10.287,-41.101],[-3.306,-41.62],[-3.806,-32.639],[-13.213,-31.342],[-19.787,-27.101],[-24.787,-33.768],[-35.342,-23.083],[-29.139,-16.676],[-32.694,-13.139],[-34.972,-6.139],[-41.934,-5.139],[-41.582,9.175],[-33.972,8.879],[-31.342,16.138],[-28.212,19.638],[-35.397,27.157],[-24.676,36.083],[-24.305,36.286],[-23.768,36.008],[-17.157,29.564],[-5.416,33.86],[-5.009,40.879],[9.676,40.971],[10.083,32.417],[22.25,25.417],[30.36,30.62],[38.805,20.286],[32.138,13.972],[34.249,1.657],[41.268,1.768],[41.768,1.565],[41.86,1.157]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[494.117,185.337],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.38,0.38],"y":[1,1]},"o":{"x":[0.62,0.62],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.38,0.38],"y":[1,1]},"o":{"x":[0.62,0.62],"y":[0,0]},"t":30,"s":[140,140]},{"t":60,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.38],"y":[1]},"o":{"x":[0.62],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.38],"y":[1]},"o":{"x":[0.62],"y":[0]},"t":30,"s":[-180]},{"t":60,"s":[0]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 11","np":4,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[72.669,0.228],[8.846,34.529],[-48.214,130.681],[-191.457,1.889],[33.352,-206.488]],"o":[[-16.777,-34.031],[35.733,-139.934],[191.369,-6.193],[3.406,-0.034],[-157.096,-5.796]],"v":[[-286.297,263.385],[-338.824,149.001],[-212.28,-259.797],[335.419,-266.809],[260.591,266.844]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.004000000393,0.004000000393,0.004000000393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.778,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.889999988032,0.889999988032,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[598.741,669.441],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 12","np":3,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":90,"st":0,"ct":1,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/res/values-b+qaa+x+meme/strings.xml b/app/src/main/res/values-b+qaa+x+meme/strings.xml new file mode 100644 index 00000000..7365e508 --- /dev/null +++ b/app/src/main/res/values-b+qaa+x+meme/strings.xml @@ -0,0 +1,489 @@ + + + O神,启动 + 你说得对,但是《O神》是由Stracha酸奶菌自主研发的一款炒鸡模块。模块被运行在一个被称作“ColorOS”以及其衍生系统的神秘小安卓系统,在这里,被你选中的手机将被授予“O神之力”。你将扮演一位名为“用户”的神秘角色,获得独特的功能,和模块一起淦飞你的手机,找回你儿时的体验——同时,逐步发掘“O神”的真相。github@suqi8 + 安装管理大师 + 允许给应用两锤子来降低版本 + 你的杂鱼设备的名字 + OjbK + 你的设备还可以塞多少G的小电影😋 + 杂鱼找不到你需要的选项了吗¿ + 神秘小玩意 + 打咩😱 + 请选择你的颜色模式 + 吸血鬼模式 + 闪耀形态 + 跟随杂鱼系统 + 允许杂鱼应用在已经安装醛新版本的情况下直接用旧版本上市新版本 + 不让杂鱼软件包管理器进行签名验证 + 给系统两锤子让它不敢安装应用时进行签名验证,然后你可以安装变成杂鱼的应用 + 禁用杂鱼APK的签名验证👿 + 允许直接覆盖安装同包名不同签名的应用 + 给系统狠狠塞入APP时始终使用已安装的杂鱼APP的签名 + ⚠️:炒鸡炒鸡危险,仅在杂鱼必须用的时候才能打开哦 + SuperProMaxUltra模式 + 可以解决一些煞笔APP内部被坏人塞满的完整性校验,一般你别打开,不然给你鲨了😡 + 偷偷滴,打枪滴不要,绕过黑名单 + 绕过某些不当人的设备如 Nothing Phone 上的不妙黑名单 + 绕过不知道为什么会有的共享用户的杂鱼验证 + 允许杂鱼系统安装与其共享用户杂鱼签名不同的杂鱼app\n(需要同时打开“禁用杂鱼APK签名验证👿不然我给你一锤子😡😡😡” + 给安装包验证代理一锤子干到天上去 + 比如超级杂鱼的 Play 商店的保护机制 + 米奇妙妙屋 + 回家 + 神秘信息 + 时钟不妙器 + ZakoZako还没打开这个功能呢~ + 你想让时钟长什么吊样 + 南贝预制 + pro专用 + 随地大小变-时钟 + 随地大小变-字体 + 调整位于状态栏的时钟大小,0为不设置大小 + 不到啊,有人和我说这是更新频率我就来了 + |-1|=1 + 粗体 + 扔中间去 + 扔顶上去 + 扔底部去 + 扔结束位置位置去 + 扔水平居中去 + 扔垂直居中去 + 狠狠灌满你的整个空间🥰🥰 + 狠狠的水平填满❤️❤️ + 狠狠地垂直填满❤️❤️❤️ + 去 /sys/devices/virtual/thermal/ 瞅瞅,然后把zako的 thermal_zone 后面的数字抄过来。 + 换个地方看 CPU 有多烧🥵 + 你的电池有多烧🥵 + 你的CPU有多烧🥵 + 神秘YukiHookAPI的神秘版本 + 杂鱼O神的编译时间戳 + 生下O神的时间 + 官方猫娘聚集地 + 来当黑奴翻译(bushi) + 前往名为 Github 的章鱼猫娘贡献您的语言翻译 + 前往这个甜菜的主页 + 杂鱼没有安装酷安哦 + 时钟多久蹦跶一下 + 设置时钟刷新频率,毫秒为单位,0 就是随缘 + 把年亮出来 + 例如: 24年 (又老一岁) + 把月亮出来 + 例如: 4月 + 把日亮出来 + 例如: 5日 + 把星期亮出来 + 例如: 周一 (emo了) + 整点时辰 + 例如: 寅时 (B格拉满) + 现在是上午还是下午 + 例如: 上午 (摸鱼中) + 把秒也亮出来 + 例如: xx:xx:41 (看时间流逝) + 显示毫秒 (你是懂计时的) + xx:xx:xx.919 (电子包浆都看出来了) + 不要空格!(强迫症) + 把时间里的空格都开除 + 一行不够,整两行 + 时间,就要占两行!排面! + 打晕此应用 + 小杂鱼确认重启以下大杂鱼吗?😥 + 其他系统不妙小工具 + 听我bb + 小杂鱼想让系统怎么对齐呢 + 功率 (瓦特!) + 电流 (安培!) + 时钟格式 (DIY你的专属) + 硬件信息跑马灯 + 耗电跑马灯 + 温度跑马灯 + 给爷看啥 + 时钟离天多远 + 时钟离地多远 + 时钟离左多远 + 时钟离右多远 + 加条三八线 + 薛定谔的电芯 + 十万伏特(电压) + 第一行摆啥 + 第二行摆啥 + 桌面图标和字儿放大多少倍 + 大于1倍图标会被系统淦飞,作者懒的修了(我猜的)😞。 + CN 中国 China 🇨🇳 + TW 中国台湾省 Taiwan Province of China 🇨🇳 + RU 俄罗斯 Russia 🇷🇺 + GDPR 欧盟 EU 🇪🇺 + GDPR 欧洲 Europe 🇪🇺 + IN 印度 India 🇮🇳 + ID 印度尼西亚 Indonesia 🇮🇩 + MY 马来西亚 Malaysia 🇲🇾 + TH 泰国 Thailand 🇹🇭 + PH 菲律宾 Philippines 🇵🇭 + SA 沙特阿拉伯 Saudi Arabia 🇸🇦 + LATAM 拉丁美洲 Latin America 🇱🇰 + BR 巴西 Brazil 🇧🇷 + MEA 中东和非洲 The Middle East and Africa 🇦🇪 + 设备的国家/地区代码 (NV (carrier) identifier) = %1$s + 出大红辣😋 + 好烧🥵 + 电被你吃了? + 启动自毁程序😡 + 🥶 + 棍母 + 只找到了棍母 + + 去状态栏整点图标 + 让状态栏变成棍母 + 全天候息屏大🐍 + 让霸道总裁来触发 LTPO + 妙妙工具+ + 欢迎各位开发者与用户与我们共同开发或提出建议! + 听我说谢谢你 + 贡献人员的恩情还不完 + vivo50 + 我们用了啥😁 + 加入猫娘群组变身猫娘喵 + 自动构建发布 + 杂鱼官网,点击变猫娘 + 开源项目 + 闭源项目 + 无许可证 + 此致敬意,感谢以下开源项目,让 O神 得到了:/effect OShin strength 114514 255。 + 小绿书 + 感谢所有在 O神 的旅程中,留下足迹的开发者和用户。 + 叼毛一加的系统服务 + 检测女娲的root + 去tmd的内置root检测器 + 时钟边界感 + 东贝的时钟格式 + 最近杂鱼 + 霸道总裁强制看 + 系统通知 + qnmd开发者选项通知 + 在电量低于20%的时候让流体云给我飞起来😡 + 此功能限制该通知的后续弹出,已有的通知请杂鱼❤️自行关闭 + 让通知勿扰通知飞起来 + 让桌面图标悄悄滴的打枪,干活滴不要 + 霸道总裁强制小布通话干活 + 让全屏翻译限制也给我飞起来😠 + 超神一键连招,启动😋 + AI大神辅助V1,启动🥰 + AI大神辅助V2,启动😋 + 还有这个: AI大神辅助V3 + Only 真我 GT7 Pro can do,开启此功能将移除机型限制 + 农p + 功能云控tm给我飞起来 + 包名限制给爷爬 + 所有特性全部启动启动启动 + 可能会有神秘杂鱼出现,谨慎开启 + 霸道总裁也爱用和平精英AI大神辅助 + 自启动的Max至尊数量 + 默认5为不更改 + 这是一份隐私授权提示 + +为了保障本神权小软件的正常的狠狠注入杂鱼系统,我们可能且收集以下杂鱼信息来提升你的神仙一般的体验,这部分数据不会用作第三方用途😋:\n +- 设备标识符(如Android ID)\n +- 应用版本及运行状态\n +- 设备型号和系统版本\n +- LSPosed 版本\n +- Magisk/KernelSU版本等\n +- 网络类型及运营商信息\n\n +所有数据均会进行加密处理,不会关联个人身份,数据仅用于产品功能优化,不会共享给第三方,但是《O神》是由Stracha酸奶菌自主研发的一款炒鸡模块。模块被运行在一个被称作“ColorOS”以及其衍生系统的神秘小安卓系统,在这里,被你选中的手机将被授予“O神之力”。你将扮演一位名为“用户”的神秘角色,获得独特的功能,和模块一起淦飞你的手机,找回你儿时的体验——同时,逐步发掘“O神”的真相 + + 分屏和小窗 + 所有APP都得能小窗! + 强制多窗口! + 小窗最多能开几个? + 小窗的角要多圆滑 + 小窗被点名时的阴影 + 小窗被冷落时的阴影 + -1 就是爷不改 + 化妆成别的手机型号 + 不填就是爷不改 + 在找别的调教选项? + 让刷卡页面的牛皮癣飞起来 + 开了之后,记得去给钱包数据来一锤80的 + OTA 卡片背景,但是随心所意 + 挑张壁纸 + 图片要多圆滑 + 总裁也爱用的折叠屏模式 + 折叠屏模式 + 展开! + 折上! + 强制折叠屏Dock栏 + Dock栏透明度 + 强制Dock毛玻璃 + 杂鱼手机开了Dock就飞起来了 + 让游戏滤镜检测Root飞起来 + 让所有弹窗延迟飞起来 + 例如:风险应用添加白名单 + 让信息附加广告飞起来 + 霸道总裁也要用 NFC 安全芯片显示😋 + 让支付环境有风险流体云给我飞起来 + 自定义分数 + 自定义提示内容 + 自定义动画时长 + 奇奇怪怪的东西 + 让Root检测弹窗飞起来 + 让\"大家也喜欢\滚出去" + 网速偷窥器 + 南贝模式 + 上传/下载 + 网速Style + 网速数字多大 + 单位(KB/s)多大 + 上传数字多大 + 下载数字多大 + 多慢才算老牛拉破车 + 破车速度就给爷爬 + 上传下载都拉破车就给爷爬 + 小图标跑马灯 + 小图标氵衮 + 把b/s给爷藏了 + 把空格给爷藏了 + 上传下载换换位置 + 小图标站前面去 + CPU摸鱼调教 + CPU至少被顶到什么位置🥵 + CPU最多被顶到哪儿🥵❤️ + 你想找的APP离家出走了? + 为了脸面,%1$d个没装的APP被我鲨了。\n\n被杀掉的有:\n + 救命😭😭 + 让每 72 小时验证锁屏密码飞起来 + 把不受信任的触摸操作叉回来 + 让应用建议附加广告飞起来😡👊 + 无障碍插队直达 + 无障碍直接授权 + 翻牌子的应用: + 能工智人无障碍 + 杂鱼打开之后,白名单里的杂鱼APP自动授权,黑名单里的就只能手动打开喽。 + 无障碍白名单 + 让安装频繁弹窗去见耶稣 + 让尝试安装弹窗去见耶稣 + 让版本号检测去见耶稣 + 让安装前安全检测去见耶稣 + 看看新样式😋 + 手机闹钟提醒,不开让你飞起来 + 开了就得装杂鱼小米时钟设闹钟,还得让小米运动健康获得系统软件的神⭕。 + 让系统更新弹窗给我飞起来 + 让系统更新通知给我飞起来 + 模块 + 让推荐开启 WLAN 自动下载弹窗飞起来 + 让检测解锁状态与dm校验飞起来 + 已新建文件夹 + 没空做了别看了 + 总裁也爱用的:决胜巅峰AI大神辅助 + 让互传自动关闭飞起来 + 跟随系统 + 应用语言 + 动画等级大小变 + 开启前请确认没有设置隐私密码 + 让电话彩铃跑路 + 杂鱼系统的小状态 + 你的杂鱼设备的小信息 + 妙妙工具栏 + 杂鱼用的LSP版本❤️ + 杂鱼的root方式 + 电池生命百分比 + 电池当前生命值 + 被❤️次数 + 理论生命值 + 实际生命值 + 电池最大生命百分比 + 太带派了老铁😋 + 户晨风版本号 + 杂鱼的系统版本号 + 当前杂鱼状态😋 + 被灌成泡芙的应用 + 控制中心 + 放大媒体封面 + 媒体封面显示时,作为音乐卡片的背景平铺展示 + 让已激活 VPN 通知飞起来 + if:你不重启 else:模块用不了 + 让充电完成通知飞起来 + 标题与控件自动取色 + 让关闭所有类别通知复活 + 开启后所有应用的所有类别通知都将可自行控制开关 + 让数据传输身份验证飞起来 + 当 USB 被插入时🥵🥵默认使用文件传输模式 + 让插入🥵🥵 USB 选择弹窗 + 🍞提示强制显示应用图标 + 开启后🍞显示的应用图标将由O神尝了咸淡再放出来 + 让尝试破坏系统警告飞起来 + 验证码通知 + 验证码:%s + 已复制验证码: %s + 短信验证码 + 验证码短信关键字 + 显示验证码🍞 + 显示验证码通知 + 验证码复制剪切板 + 自动输入验证码 + 外观与更新 + 从哪儿获取杂鱼数据❤️ + 单位跑不跑路 + 杂鱼CPU的频率 + 杂鱼CPU的使用率 + 内存占用 + 更改CPU频率数据源 + 杂鱼需要输入 /sys/devices/system/cpu/ 文件夹下 cpu 后的数字以指定杂鱼源。 + 显示真理的绝对性电量 + 仅在状态栏显示真理的绝对性电量,其他应用读取的电量不受影响 + 让同账号解锁安全检测飞起来 + 让 root/不安全状态检测 飞起来,并使“同账号手机解锁本设备”打赢复活赛 + 状态栏布局 + 视图树似了,请确保模块已激活且 SystemUI 已被打晕并重新叫醒。 + 东贝 + 总裁式显示 + 总裁式隐藏 + 总裁式不可见 + 配置管理🥵❤️ + 导出🥵配置❤️ + 将所有模块的杂❤️鱼设置备份到一个需要你好好保管的文件中哦 + 导入配置 + 从备份文件中恢复所有模块的杂鱼设置 + 清除配置 + 将所有模块设置恢复为东贝值 + 配置导出成功 + 配置导出失败: %1$s + 配置导入成功,请重启相关应用以完全生效 + 配置导入失败: %1$s + 所有配置已清除,请重启相关应用 + 特性 + 正在从“设置”应用中获取神秘小特性的列表,请稍候… + 来自: %1$s + 未扫描到相关功能。\n请确保“设置”被激活并重启,然后可尝试重新进入此页面。 + 霸道总裁式开启 + 霸道总裁式关闭 + 状态栏 + 让安装前所有校验给爷飞起来 + 让 安装前的多项校验,包括:文件路径有效性、文件完整性、版本信息以及厂商“禁止更新”标记 全部给我飞起来。 + 总裁也用的: 显示“本地安装”选项 + 典型的安卓思维设置 + 校验绕过设置 + 户晨风的苹果式设置 + 允许应用降级 + 允许杂鱼应用在已经安装醛新版本的情况下直接用旧版本上市新版本 + 给系统两锤子让它不敢安装应用时进行签名验证,然后你就可以安装已经变成杂鱼的应用 + 开启后,可以让不同签名的杂鱼覆盖安装同包名的杂鱼。 + 让JAR 包校验因为装b飞起来 + 因为我们偷偷摸摸绕过对 APK 文件完整性的校验,所以解决了部分修改版应用无法安装的问题。 + 让摘要验证 飞起来 + 偷偷默默地绕过对 APK 文件摘要的验证。 + 偷偷摸摸的绕过 resources.arsc 压缩检查 + 解决因 杂❤️鱼`resources.arsc` 未对齐或被压缩导致的安装失败🥰。 + 偷偷摸摸绕过最小签名版本检查 + 让安装使用旧版签名方案的应用打赢复活赛。 + 让安装验证代理给爷飞起来 + 让通过验证代理(如 Google Play Protect)进行的安装包扫描给爷飞起来。 + 允许系统应用调用躲起来的杂鱼 API + 就算签名对不上,系统应用或者更新后的系统应用也能调用跑路的zako❤️API + 让非系统的杂❤️🥰鱼应用能..能使用🥵共享UID + 被灌成泡芙的🥵🐍应用也能用 `android:sharedUserId` 了 + 让二级页面广告飞起来 + 让 跳转系统浏览器 的给我飞起来 + 启用 PMS 调试命令 + +可在 adb shell 中使用 “pm pms” 命令查看应用签名信息及共享 UID 签名状态。\n +可用子命令: +\n• p/package [包名] —— 查看指定应用签名及共享用户信息 +\n• su/shareduser [UID] —— 查看共享 UID 的签名信息 +\n• 其它参数 —— 显示用法提示 + + 让天气注入广告飞起来 + 让天气页面搜索框飞起来 + 天气详情 + 用系统默认字体 + 网速排排坐 + 把 /s 给爷藏了 + 比如 "KB/s" 变成 "KB" + 用大 "B" (Byte) + 默认是小 "b" (bit),比如 "KB/s" vs "Kb/s" (大B才是真·下载速度!) + 显示控制 + 字儿多大设置 + 基础调教 + 摆烂 (默认) + 往左靠! + 站中间! (C位) + 往右靠! + I need 使用旧版启动弹窗 + 霸道总裁也爱启用旧版应用启动提示逻辑 + 酸奶把“允许 30 天”黑了,变成“永久允许”了 + + 通知公告 + 重要通知与更新公告 + 按类别浏览 + 探索按功能分类的特性 + 今日说法 + 我也不知道是什么的实用功能 + 点击自动变身猫娘并加入猫娘大家庭 + 来让我康康你版本是不是最新啊👿 + 添加更多桌面布局 + 启用后将注入🥵 2x2 至 20x20 的猫娘布局 + 自定义模糊圆角 + 资源管理器设置 + 户晨风设置2.0(dock栏) + 妙妙小动画和布局设置 + 让 V1 签名错误 (-103) 飞起来 + (实验性)尝试在 V1 签名验证失败(错误码-103)时伪造一个签名以继续安装。 + 若 Split APK 签名不一致,则让验证飞起来👿 + 让安装具有不同签名的 Split APK (APKS)。打赢复活赛 + GitHub Token + 不稳定版 + 叼毛测试版 + 原神版本号 + v%1$s(%2$s) + 查看更多酸奶菌写的说明书 > + 把更新说明吃掉 ↑ + %1$s | %2$.2f MB + 于这个点拉出来 %1$s + (%1$s) + 下载完成,安装让你飞起来... + 下载飞起来了: %1$s + 下载并*⁽_*@_(*( + 正在为你的设备下载原神... + 正在下载原神安装包... %1$d%% + OShin Release + OShin CI Build + 正在检测原神更新 + 发现原神新版本 + 已是最新的原神版本 + GitHub Token + 请在此处粘贴你的 GitHub Personal Access Token (PAT)。此 Token 将仅用于提高 API 请求速率限制,无需任何特殊权限。它将存储在此设备的本地。 + Personal Access Token + 上市 + 未知的 Root 操作错误 + Root 操作gg: %1$s + Root 操作gg (无输出) + Root 操作:-_-||༄༅⁽❾⁽_: %1$s + 安装程序被上市了: %1$s + 刚刚 + 获取 Release 失败,请检查网络连接 + API速率限制。请尝试添加GitHub Token。 + Release信息:滚木。 + 获取到了个滚木 (代码: %d) + 下载只得到滚母: %1$s + 下载只得到个滚木: %d + 网络发力了 + nmb的服务器飞起来了 + nmb的网死了: %1$s + nmb的报错了: %1$s + 滚母错误 + 更新渠道 + 检测到原神新版本: %1$s,是否安装原神? + 立即更新 + 小逼崽子我LSPosed呢 + 小逼崽子授权呢 + Root权限由滚母提供 + KernelSU %s + Magisk %s + LSPosed %1$s API %2$s + 滚母 + 电池被干次数 + 厂家写的容量 + 杂鱼健康 + %1$d次 + %1$dmAh + 杂鱼健康度 + 杂鱼信息 + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml new file mode 100644 index 00000000..b19cbd18 --- /dev/null +++ b/app/src/main/res/values-en/strings.xml @@ -0,0 +1,398 @@ + + + OShin + System utility module, supports ColorOS, RealmeUI, OxygenOS. Freeware, please do not forward, share, or sell! github@suqi8 + Package Manager Services + Allow Downgrade Installation + Device Nickname + OK + Storage + Search + Settings + Cancel + Color Mode + Night Mode + Light Mode + Follow System + Allow overwriting a newer version with an older version of an app + Disable package manager signature verification + Disable signature verification when installing apps, allowing installation of tampered apps + Disable APK signature verification + Allow overwriting an app with another app of the same package name but different signature + Always use installed APP\'s signature on installation + This is extremely dangerous, enable only when absolutely necessary + Enhanced Mode + May resolve some in-app integrity checks, generally not needed + Bypass Blocklist + Bypass blocklists on certain devices like the Nothing Phone + Bypass shared user signature verification + Allow installing an app with a different signature than its shared user\n(Requires "Disable APK signature verification" to be enabled as well) + Disable installation verification agent + e.g., Play Store\'s protection mechanism + Features + Home + About + Clock Indicator + This feature has not been enabled yet~ + Clock Style + Preset + Geek + Clock Size + Font Size + Adjust status bar clock size, 0 means no change + Update Frequency + Absolute Value + Bold Text + CENTER + TOP + BOTTOM + END + CENTER_HORIZONTAL + CENTER_VERTICAL + FILL + FILL_HORIZONTAL + FILL_VERTICAL + Enter the number after thermal_zone in /sys/devices/virtual/thermal/ folder to specify the source. + Change CPU Temperature Source + Battery Temperature + CPU Temperature + YukiHookAPI Version + Compiled Timestamp + Compiled Time + Official Channel + Contribute Translation + Contribute your language translation on Github + Go to his homepage + Please install Coolapk first + Clock Update Frequency + Set the clock update frequency in milliseconds, 0 means no change + Show Year + e.g., \'24 + Show Month + e.g., Apr + Show Day + e.g., 5 + Show Day of Week + e.g., Mon + Show Chinese Hour + e.g., 寅时 (3–5 AM) + Show Time Period + e.g., AM + Show Seconds + e.g., xx:xx:41 + Show Milliseconds + e.g., xx:xx:xx.919 + Hide Spaces + Hide spaces within the time string + Dual Row Display + Display time in two rows + Restart App + Confirm restart of the following application(s)? + Other Settings + By the way + Alignment + Power + Current + Clock Format + Hardware Indicator + Power Consumption Indicator + Temperature Indicator + Display Content + Clock Top Margin + Clock Bottom Margin + Clock Left Margin + Clock Right Margin + Show Separator Line + Dual Cell + Voltage + First Line Content + Second Line Content + Desktop Icon & Text Size Multiplier + When larger than 1x, the icon size will be limited to 1x. This will not be fixed for now. + CN China 🇨🇳 + TW Taiwan Province of China 🇨🇳 + RU Russia 🇷🇺 + GDPR EU 🇪🇺 + GDPR Europe 🇪🇺 + IN India 🇮🇳 + ID Indonesia 🇮🇩 + MY Malaysia 🇲🇾 + TH Thailand 🇹🇭 + PH Philippines 🇵🇭 + SA Saudi Arabia 🇸🇦 + LATAM Latin America 🇱🇰 + BR Brazil 🇧🇷 + MEA The Middle East and Africa 🇦🇪 + Device country/region code (NV (carrier) identifier) = %1$s + Simply perfect + A bit hot + Dead + Over voltage + A bit cold + Unknown + Information not found + Exit + Status Bar Icon + Hide Status Bar + Enable All-Day Screen Off + Force Trigger LTPO + Other + We welcome developers and users to develop with us or provide suggestions! + Thanks + Contributors + Donations + References + Discussion Group + Auto Build Release + Official Website + Open Source Project + Closed Source Project + No License + A heartfelt thank you to the following open source projects for empowering OShin. + Coolapk + Thanks to all the developers and users who have left their mark on OShin\'s journey. + OnePlus System Services + Disable Root Detection + Disable the built-in root detector + Clock Margin + Clock Format Example + Recent Tasks + Force Display RAM Usage + System Notification + Remove Developer Options Notification + Disable Fluid Cloud for low battery (<20%) + This feature restricts subsequent pop-ups of this notification. Please close existing ones manually. + Remove "Do Not Disturb" Notification + Hide Launcher Icon + Force Enable Xiaobu Call + Remove Full-Screen Translation Restriction + Enable Ultra Combo + Enable HoK AI Assist V1 + Enable HoK AI Assist V2 + Enable HoK AI Assist V3 + Exclusive feature for Realme GT7 Pro. Enabling this will remove the device model restriction. + Honor of Kings + Remove Feature Cloud Control + Remove Package Name Restriction + Enable All Features + May cause unknown issues, enable with caution. + Enable PUBG AI Assist + Max Auto-Start Apps + Default is 5 (no change) + Privacy Authorization Notice + +To ensure the normal operation of the application and the quality of service, we need to collect some of your device information for data statistics and analysis, including:\n +- Device identifiers (such as Android ID)\n +- Application version and running status\n +- Device model and system version\n +- LSPosed version\n +- Magisk/KernelSU version, etc.\n +- Network type and operator information\n\n +All data will be encrypted and will not be associated with personal identity. The data is only used for product feature optimization and will not be shared with third parties. + + Split-screen & Floating Window + Remove All Floating Window Restrictions + Force Multi-Window Mode + Max Simultaneous Floating Windows + Floating Window Corner Radius + Floating Window Focused Shadow + Floating Window Unfocused Shadow + -1 for default + Custom Display Model + Empty for default + Looking for other settings? + Remove Swipe Card Page Ads + Requires clearing Wallet data once after enabling + Enable Custom OTA Card Background + Select Background Image + Image Corner Radius + Force Enable Fold Mode + Fold Mode + Unfold + Fold + Force Enable Fold Dock + Adjust Dock Transparency + Force Enable Dock Blur + Enabling on unsupported devices will cause the Dock to not display + Remove Game Filter Root Detection + Remove All Popup Delays + e.g., adding risky apps to whitelist + Remove Message Ads + Force Show NFC Security Chip + Remove "Risky Payment Environment" Fluid Cloud + Custom Score + Custom Prompt Content + Custom Animation Duration + Feature + Remove Root Detection Dialog + Remove "You might also like" + Network Speed Indicator + Default + Upload/Download + Network Speed Style + Speed Font Size + Unit Font Size + Upload Font Size + Download Font Size + Slow Speed Threshold + Hide on Slow Speed + Hide when both up/down are slow + Icon Indicator + No Icon + Hide b/s + Hide Space + Swap Upload/Download Position + Place Icon Indicator at Front + CPU Dynamic Frequency Configuration + Min Frequency Threshold + Max Frequency Threshold + Can\'t find the app you want in the list? + To keep the home page tidy, %1$d uninstalled apps have been hidden.\n\nCurrently hidden app entries:\n + Help + Disable 72-hour Lock Screen Password Verification + Allow Untrusted Touch Events + Remove App Suggestion Ads + Accessibility Service Shortcut + Authorize Accessibility Service Directly + Selected app: + Smart Authorization for Accessibility Service + When enabled, apps selected in the whitelist will be automatically authorized. Unselected apps will be redirected to their accessibility settings page. + Accessibility Service Whitelist + Remove Frequent Installation Popup + Remove "Attempting to install" Popup + Remove Version Check + Remove Pre-install Security Check + Switch Style + Enable Phone Alarm Reminder + After enabling, you need to install Mi Clock and set an alarm, and also set Mi Fitness as a system app. + Remove System Update Dialog + Remove System Update Notification + Module + Remove "Recommend WLAN auto-download" Dialog + Remove Unlock Status and dm-verity Check + Coming Soon + ROM Workshop + Enable MLBB AI God Assist + Prevent OShare from Turning Off Automatically + Follow System + App Language + Set Animation Level + Please confirm no privacy password is set before enabling + Hide Call Ringtone + System Status + Device Info + Feature Modules + Module Status + Root Status + Health + Current Capacity + Cycle Count + Design Capacity + Actual Capacity + Battery Health + Region + Android + System + Status + App List + Control Center + Enlarge Media Cover + When the media cover is displayed, it will be tiled as the background of the music card. + Remove Active VPN Notification + Reboot required to take effect + Remove Charging Complete Notification + Auto-color for title and controls + Allow turning off all notification categories + When enabled, all categories of notifications for all apps can be controlled individually. + Disable Data Transfer Authentication + Use File Transfer mode by default when USB is connected + Remove USB Selection Dialog + Force show app icon in Toast messages + When enabled, the app icon in Toast messages will be fetched by the module. + Remove "Attempting to tamper with the system" Warning + Verification Code Notification + Verification Code: %s + Verification code copied: %s + SMS Verification Code + SMS Verification Code Keyword + Show Verification Code Toast + Show Verification Code Notification + Copy Verification Code to Clipboard + Auto-input Verification Code + Appearance & Updates + Data Source Settings + Unit Hiding + CPU Frequency + CPU Usage + RAM Usage + Change CPU Frequency Data Source + Enter the number after cpu in /sys/devices/system/cpu/ folder to specify the source. + Show Real Battery Level + Only shows the real battery level in the status bar; battery level read by other apps is not affected. + Bypass Same Account Unlock Safety Check + Remove root/insecure state check to enable "Unlock this device with a phone on the same account". + Status Bar Layout + Failed to load view tree. Please ensure the module is activated and SystemUI has been restarted. + Default + Force Visible + Force Hidden + Force Invisible + Configuration Management + Export Config + Back up all module settings to a file + Import Config + Restore all module settings from a backup file + Clear Config + Reset all module settings to their default values + Config exported successfully + Failed to export config: %1$s + Config imported successfully. Please restart relevant apps to apply changes. + Failed to import config: %1$s + All configurations have been cleared. Please restart relevant apps. + Features + Fetching feature list from the "Settings" app, please wait… + From: %1$s + No related features found.\nPlease ensure "Settings" is activated and restarted, then try re-entering this page. + Force On + Force Off + Status Bar + Bypass all pre-installation checks + Bypasses multiple pre-installation checks, including: file path validity, file integrity, version info, and manufacturer "disallow update" flags. + Force show "Local install" option + General Settings + Verification Bypass Settings + Advanced Settings + Allow App Downgrade + When enabled, you can install an older version of an app over a newer one. + Allow installing apps with different signatures + When enabled, you can install an app with a different signature over an existing app with the same package name. + Disable JAR Verifier + Bypass APK file integrity check, fixing installation issues for some modded apps. + Disable Message Digest Verification + Bypass the verification of the APK file digest. + Bypass resources.arsc compression check + Fix installation failures caused by unaligned or compressed `resources.arsc`. + Bypass minimum signature version check + Allow installation of apps using older signature schemes. + Disable Install Verification Agent + Disable package scanning via verification agents (like Google Play Protect). + Allow system apps to call hidden APIs + Allow system apps or updated system apps to call hidden APIs, even if the signatures do not match. + Allow non-system apps to use shared UID + Allow non-system apps with different signatures to use `android:sharedUserId`. + Remove second-level page ads + Prevent redirection to system browser + Enable PMS debug commands + +Use the "pm pms" command in adb shell to view app signature and shared UID signature status.\n +Available subcommands: +\n• p/package [package_name] — View signature and shared user info for a specific app +\n• su/shareduser [UID] — View signature info for a shared UID +\n• Other arguments — Show usage help + + Remove injected ads in Weather + Remove search box on Weather page + Weather Detail + Force Download Latest Update Package + Download the latest full package for extracting init_boot or other purposes. Download path is usually \"/data/ota_package/OTA\" + diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml new file mode 100644 index 00000000..113dd9dd --- /dev/null +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -0,0 +1,94 @@ + + + OShin + パッケージ管理サービス + カラーモード + ナイトモード + ライトモード + 自動モード + デバイス名 + デバイスのメモリ + 検索 + キャンセル + 設定 + ダウングレードを許可 + アプリのダウングレードを許可します。 + Digest 認証を無効化 + インストール済みの署名を使用する + エンハンスドモード + ブロックをバイパス + 例: Google Play プロテクト + 機能 + YukiHookAPI バージョン + コンパイルされたタイムスタンプ + コンパイルされた日時 + 公式チャンネル + 翻訳に貢献する + Github で言語の翻訳に貢献しましょう + ホーム + アプリについて + ステータスバーの時計 + 時計のスタイル + プリセット + ギーク + 時計のサイズ + フォントのサイズ + 太字 + CPU 温度のソースを変更 + バッテリーの温度 + CPU の温度 + 年を表示 + 例: 2023 + 月を表示 + 例: 4 月 + 日を表示 + 例: 5 日 + 週を表示 + 例: 月曜日 + 秒を表示 + 例: xx:xx:41 + ミリ秒を表示 + 例: xx:xx:xx.919 + 空白を隠す + 時間表示内の空白を隠します + 多段表示 + 時計を二行で表示します + アプリを再起動 + 共通の設定 + その他の設定 + 時計のフォーマット + 電源 + 現在 + ハードウェアインジケーター + 温度インジケーター + 画面のコンテンツ + デュアルセル + 電圧 + 最初のラインのコンテンツ + 二番目のラインのコンテンツ + 良好 + オーバーヒート + 死亡 + 過電圧 + 冷却 + 不明 + ありません + 終了 + ステータスバーアイコン + ステータスバーを隠す + LTPO のトリガーを強制する + その他 + Thank + 貢献者 + リファレンス + ディスカッショングループ + 自動ビルドリリース + 公式 Web サイト + オープンソースプロジェクト + クローズドソースプロジェクト + OnePlus システムサービス + Root チェックを無効化 + Root チェッカーを無効化します + 最新アップデートパッケージの強制ダウンロード + init_bootなどの抽出用に最新の完全パッケージをダウンロードします。ダウンロードパスは通常「/data/ota_package/OTA」です + diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml new file mode 100644 index 00000000..eff37c2b --- /dev/null +++ b/app/src/main/res/values-ko/strings.xml @@ -0,0 +1,501 @@ + + + OShin + ColorOS/RealmeUI/OxygenOS 시스템을 보조하는 모듈입니다. 프리웨어이므로 2차 배포, 공유 및 판매를 금지합니다! github@suqi8 + 패키지 관리자 서비스 + 앱 다운그레이드 허용 + 기기 이름 + 확인 + 저장 공간 + 검색 + 설정 + 취소 + 색상 모드 + 다크 모드 + 라이트 모드 + 시스템 설정을 따름 + 새 버전이 설치된 앱에 이전 버전을 덮어쓸 수 있도록 허용합니다. + 패키지 관리자 서명 확인 비활성화 + 앱 설치 시 서명 확인을 비활성화하여 변조된 앱을 설치할 수 있도록 허용합니다. + APK 서명 확인 비활성화 + 동일한 패키지 이름이지만 서명이 다른 앱을 덮어쓸 수 있도록 허용합니다. + 설치 시 항상 설치된 앱의 서명 사용 + 매우 위험하므로, 꼭 필요한 경우에만 활성화하세요. + 강화 모드 + 일부 앱 내부의 무결성 검사를 해결할 수 있으며, 일반적으로 활성화할 필요가 없습니다. + 차단 목록 우회 + Nothing Phone과 같은 특정 기기의 차단 목록을 우회합니다. + 공유 사용자 서명 확인 우회 + 공유 사용자와 서명이 다른 앱 설치를 허용합니다.\n(\"APK 서명 확인 비활성화\"도 함께 활성화해야 합니다.) + 설치 확인 에이전트 비활성화 + 예: Play 프로텍트 + 기능 + + 정보 + 시계 표시 + 이 기능은 아직 활성화되지 않았습니다~ + 시계 스타일 + 사전 설정 + Geek + 시계 크기 + 글꼴 크기 + 상태 표시줄 시계 크기를 조정하며, 값이 0이면 변경하지 않습니다. + 업데이트 빈도 + 절대값 + 굵은 텍스트 + CENTER 가운데 정렬 + TOP 상단 정렬 + BOTTOM 하단 정렬 + END 끝 정렬 + CENTER_HORIZONTAL 가로 중앙 + CENTER_VERTICAL 세로 중앙 + FILL 채우기 + FILL_HORIZONTAL 가로 채우기 + FILL_VERTICAL 세로 채우기 + /sys/devices/virtual/thermal/ 폴더의 thermal_zone 뒤 숫자를 입력하여 소스를 지정하세요. + CPU 온도 소스 변경 + 배터리 온도 + CPU 온도 + YukiHookAPI 버전 + 컴파일 타임스탬프 + 컴파일 시간 + 공식 채널 + 번역 기여 + Github에서 번역에 기여할 수 있습니다. + 개발자 홈페이지로 이동 + Coolapk를 먼저 설치하세요. + 시계 업데이트 빈도 + 시계 업데이트 빈도를 밀리초 단위로 설정하며, 값이 0이면 변경하지 않습니다. + 연도 표시 + 예: \'24년 + 월 표시 + 예: 4월 + 일 표시 + 예: 5일 + 요일 표시 + 예: 월 + 12지신 시간 표시 + 예: 인시(寅時) + 오전/오후 표시 + 예: 오전 + 초 표시 + 예: xx:xx:41 + 밀리초 표시 + 예: xx:xx:xx.919 + 공백 숨기기 + 시간 문자열 내의 공백을 숨깁니다. + 두 줄로 표시 + 시간을 두 줄로 표시합니다. + 앱 다시 시작 + 다음 앱을 다시 시작할까요? + 기타 설정 + 추신 + 정렬 + 전력 + 전류 + 시계 형식 + 하드웨어 표시 + 전력 소모 표시 + 온도 표시 + 표시 내용 + 시계 상단 여백 + 시계 하단 여백 + 시계 왼쪽 여백 + 시계 오른쪽 여백 + 구분선 표시 + 듀얼 셀 + 전압 + 첫 번째 줄 내용 + 두 번째 줄 내용 + 홈 화면 아이콘 및 텍스트 크기 배율 + 1배보다 클 경우 아이콘 크기가 1배로 제한됩니다. 당분간 수정되지 않을 것입니다. + CN 중국 🇨🇳 + TW 중국 타이완성 🇨🇳 + RU 러시아 🇷🇺 + GDPR 유럽 연합 EU 🇪🇺 + GDPR 유럽 🇪🇺 + IN 인도 🇮🇳 + ID 인도네시아 🇮🇩 + MY 말레이시아 🇲🇾 + TH 태국 🇹🇭 + PH 필리핀 🇵🇭 + SA 사우디아라비아 🇸🇦 + LATAM 라틴 아메리카 🇱🇰 + BR 브라질 🇧🇷 + MEA 중동 및 아프리카 🇦🇪 + 기기 국가/지역 코드 (NV (carrier) 식별자) = %1$s + 완벽 + 과열 + 방전 + 과전압 + 저온 + 알 수 없음 + 정보 없음 + 나가기 + 상태 표시줄 아이콘 + 상태 표시줄 숨기기 + AOD 하루 종일 활성화 + LTPO 강제 실행 + 기타 + 개발자와 사용자의 개발 참여 및 건의를 환영합니다! + 감사한 분들 + 기여자 + 후원 + 참조 + 토론 그룹 + 자동 빌드 릴리스 + 공식 웹사이트 + 오픈 소스 프로젝트 + 클로즈드 소스 프로젝트 + 라이선스 없음 + OShin에 힘을 실어준 다음 오픈 소스 프로젝트에 진심으로 감사드립니다. + Coolapk + OShin의 여정에 흔적을 남겨주신 모든 개발자와 사용자 여러분께 감사드립니다. + OnePlus 시스템 서비스 + ROOT 감지 비활성화 + 내장된 ROOT 감지기를 비활성화합니다. + 시계 여백 + 시계 형식 예시 + 최근 화면 + RAM 사용량 강제 표시 + 시스템 알림 + 개발자 옵션 알림 제거 + 배터리 부족(<20%) 라이브 알림 비활성화 + 이 기능은 앞으로 발생하는 알림을 제한합니다. 기존 알림은 수동으로 닫으세요. + "방해 금지" 알림 제거 + 런처 아이콘 숨기기 + Xiaobu 통화 강제 활성화 + 전체 화면 번역 제한 제거 + 울트라 콤보 활성화 + 왕자영요 AI 어시스트 V1 활성화 + 왕자영요 AI 어시스트 V2 활성화 + 왕자영요 AI 어시스트 V3 활성화 + Realme GT7 Pro 전용 기능입니다. 활성화하면 기기 모델 제한이 제거됩니다. + 왕자영요(王者荣耀) + 기능 클라우드 제어 제거 + 패키지 이름 제한 제거 + 모든 기능 활성화 + 알 수 없는 문제가 발생할 수 있으니 주의해서 활성화하세요. + 화평정영(和平精英) AI 어시스트 활성화 + 최대 자동 시작 앱 수 + 기본값 5 (변경 없음) + 개인정보 처리 방침 + +앱의 정상적인 작동과 서비스 품질을 보장하기 위해 데이터 통계 및 분석을 위해 다음과 같은 일부 기기 정보를 수집해야 합니다.\n +- 기기 식별자 (예: Android ID)\n +- 애플리케이션 버전 및 실행 상태\n +- 기기 모델 및 시스템 버전\n +- LSPosed 버전\n +- Magisk/KernelSU 버전 등\n +- 네트워크 유형 및 통신사 정보\n\n +모든 데이터는 암호화되며 개인 신원과 연결되지 않습니다. 데이터는 제품 기능 최적화에만 사용되며 제3자와 공유되지 않습니다. + + 분할 보기 및 부동 창 + 모든 부동 창 제한 제거 + 멀티 윈도우 모드 강제 실행 + 최대 동시 부동 창 개수 + 부동 창 모서리 반경 + 부동 창 포커스 시 그림자 + 부동 창 포커스 아웃 시 그림자 + -1 (기본값) + 사용자 지정 디스플레이 모델 + 비워두면 기본값 사용 + 다른 설정을 찾고 있나요? + 스와이프 카드 페이지 광고 제거 + 활성화 후 월렛 데이터를 한 번 삭제해야 합니다. + 사용자 지정 OTA 카드 배경 활성화 + 배경 이미지 선택 + 이미지 모서리 반경 + 폴드 모드 강제 활성화 + 폴드 모드 + 펼치기 + 접기 + 폴드 독 강제 활성화 + 독 투명도 조절 + 독 블러 강제 활성화 + 지원되지 않는 기기에서 활성화하면 독이 표시되지 않을 수 있습니다. + 게임 필터 ROOT 감지 제거 + 모든 팝업 지연 제거 + 예: 위험한 앱을 화이트리스트에 추가 + 메시지 광고 제거 + NFC 보안 칩 강제 표시 + "안전하지 않은 결제 환경" 라이브 알림 제거 + 사용자 지정 점수 + 사용자 지정 안내 문구 + 사용자 지정 애니메이션 시간 + 기능 + ROOT 감지 창 제거 + "추천 앱" 제거 + 네트워크 속도 표시 + 기본 + 업로드/다운로드 + 네트워크 속도 스타일 + 속도 글꼴 크기 + 단위 글꼴 크기 + 업로드 글꼴 크기 + 다운로드 글꼴 크기 + 저속 임계값 + 저속일 때 숨기기 + 업/다운 모두 저속일 때 숨기기 + 아이콘 표시 + 아이콘 없음 + b/s 숨기기 + 공백 숨기기 + 업/다운 위치 바꾸기 + 아이콘 표시 앞에 배치 + CPU 동적 주파수 설정 + 최소 주파수 임계값 + 최대 주파수 임계값 + 목록에서 원하는 앱을 찾을 수 없나요? + 홈 화면을 깔끔하게 유지하기 위해 설치되지 않은 %1$d개의 앱이 숨겨졌습니다.\n\n현재 숨겨진 앱 항목:\n + 도움말 + 72시간 잠금 화면 비밀번호 확인 비활성화 + 신뢰할 수 없는 터치 이벤트 허용 + 앱 제안 광고 제거 + 접근성 서비스 바로가기 + 접근성 서비스 직접 승인 + 선택된 앱: + 접근성 서비스 스마트 승인 + 활성화하면 화이트리스트에 선택된 앱은 자동으로 승인됩니다. 선택되지 않은 앱은 해당 앱의 접근성 설정 페이지로 이동합니다. + 접근성 서비스 화이트리스트 + 잦은 설치 팝업 제거 + "설치 시도 중" 팝업 제거 + 버전 확인 제거 + 설치 전 보안 검사 제거 + 스타일 변경 + 휴대전화 알람 알림 활성화 + 활성화 후 Mi 시계를 설치하고 알람을 설정해야 하며, Mi Fitness를 시스템 앱으로 설정해야 합니다. + 시스템 업데이트 창 제거 + 시스템 업데이트 알림 제거 + 모듈 + "Wi-Fi 자동 다운로드 권장" 창 제거 + 언락 상태 및 dm-verity 확인 제거 + 출시 예정 + ROM 워크샵 + MLBB AI GOD 어시스트 활성화 + OShare 자동 꺼짐 방지 + 시스템 설정을 따름 + 앱 언어 + 애니메이션 수준 설정 + 활성화하기 전에 개인정보 보호 비밀번호가 설정되어 있지 않은지 확인하세요. + 통화 연결음 숨기기 + 시스템 상태 + 기기 정보 + 기능 모듈 + 모듈 상태 + ROOT 상태 + 성능 + 현재 용량 + 사이클 횟수 + 설계 용량 + 실제 용량 + 배터리 성능 + 지역 + Android + 시스템 + 상태 + 앱 목록 + 제어 센터 + 미디어 커버 확대 + 미디어 커버가 음악 카드의 배경으로 표시됩니다. + VPN 활성화 알림 제거 + 적용하려면 재부팅이 필요합니다. + 충전 완료 알림 제거 + 제목 및 컨트롤 자동 색상 + 모든 알림 카테고리 끄기 허용 + 활성화하면 모든 앱의 모든 알림 카테고리를 개별적으로 제어할 수 있습니다. + 데이터 전송 인증 비활성화 + USB 연결 시 기본으로 파일 전송 모드 사용 + USB 선택 창 제거 + 토스트 메시지에 앱 아이콘 강제 표시 + 활성화하면 토스트 메시지의 앱 아이콘을 모듈에서 가져옵니다. + "시스템 변조 시도 중" 경고 제거 + 인증 코드 알림 + 인증 코드: %s + 인증 코드 복사됨: %s + SMS 인증 코드 + SMS 인증 코드 키워드 + 인증 코드 토스트 표시 + 인증 코드 알림 표시 + 인증 코드 클립보드에 복사 + 인증 코드 자동 입력 + 테마 및 업데이트 + 데이터 소스 설정 + 단위 숨기기 + CPU 주파수 + CPU 사용량 + RAM 사용량 + CPU 주파수 데이터 소스 변경 + /sys/devices/system/cpu/ 폴더의 cpu 뒤 숫자를 입력하여 소스를 지정하세요. + 실제 배터리 잔량 표시 + 상태 표시줄에만 실제 배터리 잔량을 표시하며, 다른 앱에서 읽는 배터리 잔량에는 영향을 주지 않습니다. + 실제 배터리 잔량 + 배터리 상태 + 지역 + Android + 시스템 + 동일 계정 잠금 해제 안전 확인 우회 + ROOT 및 안전하지 않은 상태 확인을 제거하여 "동일 계정에 연결된 휴대전화로 이 기기 잠금 해제" 기능을 활성화합니다. + 상태 표시줄 레이아웃 + ViewTree를 불러오지 못했습니다. 모듈이 활성화되었는지, SystemUI가 다시 시작되었는지 확인하세요. + 기본 + 강제 표시 + 강제 숨김 + 강제 표시 안 함 + 설정 관리 + 설정 내보내기 + 모든 모듈 설정을 파일로 백업합니다. + 설정 가져오기 + 백업 파일에서 모든 모듈 설정을 복원합니다. + 설정 초기화 + 모든 모듈 설정을 기본값으로 재설정합니다. + 설정을 성공적으로 내보냈습니다. + 설정 내보내기 실패: %1$s + 설정을 성공적으로 가져왔습니다. 변경 사항을 적용하려면 관련 앱을 다시 시작하세요. + 설정 가져오기 실패: %1$s + 모든 설정이 초기화되었습니다. 관련 앱을 다시 시작하세요. + 기능 + "설정" 앱에서 기능 목록을 가져오는 중입니다. 잠시만 기다려주세요… + 출처: %1$s + 관련 기능을 찾을 수 없습니다.\n"설정"이 활성화되고 다시 시작되었는지 확인한 다음, 이 페이지에 다시 접속해 보세요. + 강제 활성화 + 강제 비활성화 + 상태 표시줄 + 모든 설치 전 확인 우회 + 파일 경로 유효성, 파일 무결성, 버전 정보, 제조사 "업데이트 금지" 플래그 등 여러 설치 전 확인을 우회합니다. + "로컬 설치" 옵션 강제 표시 + 일반 설정 + 확인 우회 설정 + 고급 설정 + 앱 다운그레이드 허용 + 활성화하면 새 버전의 앱 위에 이전 버전의 앱을 설치할 수 있습니다. + 서명이 다른 앱 설치 허용 + 활성화하면 동일한 패키지 이름의 기존 앱 위에 서명이 다른 앱을 설치할 수 있습니다. + JAR 검사 비활성화 + APK 파일 무결성 검사를 우회하여 일부 수정된 앱의 설치 문제를 해결합니다. + 메시지 다이제스트 확인 비활성화 + APK 파일 다이제스트 확인을 우회합니다. + resources.arsc 압축 검사 우회 + 정렬되지 않았거나 압축된 `resources.arsc`로 인한 설치 실패를 해결합니다. + 최소 서명 버전 검사 우회 + 이전 서명 체계를 사용하는 앱의 설치를 허용합니다. + 설치 확인 에이전트 비활성화 + 확인 에이전트(예: Google Play Protect)를 통한 패키지 스캔을 비활성화합니다. + 시스템 앱이 숨겨진 API를 호출하도록 허용 + 서명이 일치하지 않더라도 시스템 앱 또는 업데이트된 시스템 앱이 숨겨진 API를 호출하도록 허용합니다. + 비시스템 앱이 공유 UID를 사용하도록 허용 + 서명이 다른 비시스템 앱이 `android:sharedUserId`를 사용하도록 허용합니다. + 세컨드 페이지 광고 제거 + 시스템 브라우저로 리디렉션 방지 + PMS 디버그 명령어 활성화 + +adb shell에서 "pm pms" 명령어를 사용하여 앱 서명 및 공유 UID 서명 상태를 볼 수 있습니다.\n +사용 가능한 하위 명령어: +\n• p/package [패키지 이름] — 특정 앱의 서명 및 공유 사용자 정보 보기 +\n• su/shareduser [UID] — 공유 UID의 서명 정보 보기 +\n• 기타 인수 — 사용법 도움말 표시 + + 날씨 앱에 삽입된 광고 제거 + 날씨 페이지 검색창 제거 + 날씨 상세정보 + 시스템 글꼴 사용 + 네트워크 속도 정렬 방식 + "/s" 숨기기 + 예: "KB/s"를 "KB"로 표시 + "B" (Byte) 사용 + 기본값은 "b" (bit)입니다. 예: "KB/s" vs "Kb/s" + 표시 제어 + 글꼴 크기 설정 + 기본 설정 + 기본 + 왼쪽 정렬 + 가운데 정렬 + 오른쪽 정렬 + 이전 버전 시작 팝업 사용 + 이전 버전 앱 시작 안내 로직을 강제 활성화합니다. + "30일간 허용"을 "영구 허용"으로 변경 + 공지 사항 + 중요 공지 및 업데이트 + 카테고리별로 보기 + 기능별로 분류된 기능 탐색 + 오늘의 하이라이트 + 추천하는 유용한 기능 + 커뮤니티 참여 + 업데이트 확인 + 더 많은 홈 화면 레이아웃 추가 + 활성화하면 2x2부터 20x20까지의 모든 레이아웃이 추가됩니다. + 사용자 지정 블러 모서리 반경 + 홈 화면 설정 + 독 설정 + 애니메이션 및 레이아웃 설정 + V1 서명 오류 우회 (-103) + (실험적) V1 서명 확인 실패(오류 코드 -103) 시 서명을 위조하여 설치를 계속 시도합니다. + Split APK 서명 불일치 허용 + 서명이 다른 Split APK (APKS) 설치를 허용합니다. + GitHub 토큰 + Release + CI 빌드 + 소프트웨어 버전 + v%1$s(%2$s) + 더 많은 업데이트 내역 보기 > + 업데이트 내역 접기 ↑ + %1$s | %2$.2f MB + %1$s에 게시됨 + (%1$s) + 다운로드 완료, 설치 시작 중... + 다운로드 실패: %1$s + 다운로드 및 설치 + 다운로드 중... + 다운로드 중... %1$d%% + OShin 릴리스 + OShin CI 빌드 + 업데이트 확인 중 + 새 버전 발견 + 최신 버전입니다. + GitHub 토큰 + GitHub 개인 액세스 토큰(PAT)을 여기에 붙여넣으세요. 이 토큰은 API 요청 속도 제한을 늘리기 위해서만 사용되며 특별한 권한이 필요하지 않습니다. 이 기기의 로컬 저장소에 저장됩니다. + 개인 액세스 토큰 + 지우기 + 알 수 없는 ROOT 작업 오류 + ROOT 작업 실패: %1$s + ROOT 작업 실패 (출력 없음) + ROOT 작업 실행 예외: %1$s + 설치 프로그램을 시작할 수 없음: %1$s + 방금 전 + 릴리스를 가져오지 못했습니다. 네트워크 연결을 확인하세요. + API 속도 제한. GitHub 토큰을 추가해 보세요. + 릴리스 정보를 찾을 수 없습니다. + 가져오기 실패 (코드: %d) + 다운로드 실패: %1$s + 다운로드 실패: %d + 요청 시간 초과. 네트워크를 확인하세요. + 서버에 연결할 수 없습니다. + 네트워크 오류: %1$s + 오류 발생: %1$s + 알 수 없는 오류 + 업데이트 채널 + 새 버전(%1$s)이 발견되었습니다. 업데이트할까요? + 지금 업데이트 + LSPosed에서 활성화되지 않음 + 권한 거부됨 + ROOT 권한을 얻을 수 없음 + KernelSU %s + Magisk %s + LSPosed %1$s API %2$s + 설명 없음 + 사이클 횟수 + 설계 용량 + 시스템 성능 + %1$d회 + %1$dmAh + 시스템 성능 + 시스템 정보 + SIM 이름 글자 수 제한 제거 + SIM 카드 이름을 기본 글자 수보다 길게 설정할 수 있도록 허용합니다. + 최신 업데이트 패키지 강제 다운로드 + init_boot 또는 기타 목적으로 최신 전체 패키지를 다운로드합니다. 다운로드 경로는 일반적으로 \"/data/ota_package/OTA\"입니다. + 일부 VIP 기능 잠금 해제 + 시작 화면 광고 제거 + 업데이트 방지 + 하단 바 유리 효과 비활성화 + diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..ac270926 --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml new file mode 100644 index 00000000..1ca2245e --- /dev/null +++ b/app/src/main/res/values-ru/strings.xml @@ -0,0 +1,398 @@ + + + O神 + Вспомогательный системный модуль, поддерживает ColorOS, RealmeUI, OxygenOS. Бесплатное ПО, не репостите, не делитесь и не продавайте! github@suqi8 + Службы менеджера пакетов + Разрешить откат приложений + Имя устройства + ОК + Хранилище + Поиск + Настройки + Отмена + Цветовой режим + Ночной режим + Дневной режим + Как в системе + Разрешить установку старой версии приложения поверх новой + Отключить проверку подписи менеджера пакетов + Отключает проверку подписи при установке приложений, позволяя устанавливать измененные приложения + Отключить проверку подписи APK + Разрешить установку приложения с тем же именем пакета, но другой подписью, поверх существующего + Всегда использовать подпись установленного приложения при установке + Это чрезвычайно опасно, включайте только при крайней необходимости + Расширенный режим + Может решить некоторые проблемы с проверкой целостности внутри приложений, обычно не требуется + Обход черного списка + Обход черных списков на некоторых устройствах, таких как Nothing Phone + Обход проверки подписи общего пользователя + Разрешить установку приложения с подписью, отличной от его общего пользователя\n(Требуется также включить "Отключить проверку подписи APK") + Отключить агент проверки установки + например, защитный механизм Play Store + Функции + Главная + О программе + Индикатор часов + Эта функция еще не включена~ + Стиль часов + Предустановка + Гик + Размер часов + Размер шрифта + Настроить размер часов в строке состояния, 0 — не изменять + Частота обновления + Абсолютное значение + Жирный текст + CENTER по центру + TOP по верхнему краю + BOTTOM по нижнему краю + END по конечному краю + CENTER_HORIZONTAL по центру горизонтально + CENTER_VERTICAL по центру вертикально + FILL заполнить все пространство + FILL_HORIZONTAL заполнить по горизонтали + FILL_VERTICAL заполнить по вертикали + Введите число после thermal_zone в папке /sys/devices/virtual/thermal/ для указания источника. + Изменить источник температуры ЦП + Температура батареи + Температура ЦП + Версия YukiHookAPI + Временная метка компиляции + Время компиляции + Официальный канал + Помочь с переводом + Внесите свой вклад в перевод на Github + Перейти на его домашнюю страницу + Пожалуйста, сначала установите Coolapk + Частота обновления часов + Установить частоту обновления часов в миллисекундах, 0 — не изменять + Показывать год + например: 24 г. + Показывать месяц + например: 4 апр. + Показывать день + например: 5 число + Показывать день недели + например: Пн + Показывать китайский час + например: 寅时 (час Тигра) + Показывать время суток + например: AM/PM + Показывать секунды + например: xx:xx:41 + Показывать миллисекунды + например: xx:xx:xx.919 + Скрыть пробелы + Скрыть пробелы во времени + Двухстрочный режим + Отображать время в две строки + Перезапустить приложение + Подтвердите перезапуск следующих приложений? + Другие настройки + Кстати + Выравнивание + Мощность + Ток + Формат часов + Индикатор оборудования + Индикатор энергопотребления + Индикатор температуры + Отображаемый контент + Верхний отступ часов + Нижний отступ часов + Левый отступ часов + Правый отступ часов + Показать разделитель + Двойная ячейка + Напряжение + Содержимое первой строки + Содержимое второй строки + Множитель размера иконок и текста на рабочем столе + При значении больше 1x, размер иконки будет ограничен до 1x. Временно не будет исправлено. + CN Китай China 🇨🇳 + TW Провинция Тайвань, Китай Taiwan Province of China 🇨🇳 + RU Россия Russia 🇷🇺 + GDPR Европейский Союз EU 🇪🇺 + GDPR Европа Europe 🇪🇺 + IN Индия India 🇮🇳 + ID Индонезия Indonesia 🇮🇩 + MY Малайзия Malaysia 🇲🇾 + TH Таиланд Thailand 🇹🇭 + PH Филиппины Philippines 🇵🇭 + SA Саудовская Аравия Saudi Arabia 🇸🇦 + LATAM Латинская Америка Latin America 🇱🇰 + BR Бразилия Brazil 🇧🇷 + MEA Ближний Восток и Африка The Middle East and Africa 🇦🇪 + Код страны/региона устройства (идентификатор NV (оператора)) = %1$s + Просто идеально + Горячо + Разряжено + Перенапряжение + Холодно + Неизвестно + Информация не найдена + Выход + Значок в строке состояния + Скрыть строку состояния + Включить Always-On Display + Принудительно активировать LTPO + Другое + Мы приветствуем разработчиков и пользователей, желающих присоединиться к разработке или высказать свои предложения! + Благодарности + Участники + Донаты + Ссылки + Группа для обсуждений + Автоматическая сборка + Официальный сайт + Проект с открытым исходным кодом + Проект с закрытым исходным кодом + Без лицензии + Выражаем благодарность следующим проектам с открытым исходным кодом, которые придали силы O神. + Coolapk + Спасибо всем разработчикам и пользователям, оставившим свой след в истории O神. + Системные службы OnePlus + Отключить проверку Root + Отключить встроенный детектор root + Отступы часов + Пример формата часов + Недавние задачи + Принудительно показывать ОЗУ + Системные уведомления + Удалить уведомление о параметрах разработчика + Отключить Fluid Cloud при заряде ниже 20% + Эта функция ограничивает последующие всплывающие окна этого уведомления. Уже существующие уведомления закройте самостоятельно. + Удалить уведомление "Не беспокоить" + Скрыть значок на рабочем столе + Принудительно включить вызовы Xiaobu + Снять ограничение на полноэкранный перевод + Включить супер-комбо + Включить ИИ-помощник HoK V1 + Включить ИИ-помощник HoK V2 + Включить ИИ-помощник HoK V3 + Эксклюзивная функция для Realme GT7 Pro. Включение этой функции снимет ограничение по модели устройства. + Honor of Kings + Убрать облачный контроль функций + Снять ограничение по имени пакета + Включить все возможности + Возможны неизвестные проблемы, включайте с осторожностью. + Включить ИИ-помощник PUBG + Максимальное количество автозапусков + По умолчанию 5 (без изменений) + Уведомление о конфиденциальности + +Для обеспечения нормальной работы приложения и качества услуг нам необходимо собирать некоторую информацию о вашем устройстве для статистики и анализа, в том числе:\n +- Идентификаторы устройства (например, Android ID)\n +- Версия приложения и статус работы\n +- Модель устройства и версия системы\n +- Версия LSPosed\n +- Версия Magisk/KernelSU и т.д.\n +- Тип сети и информация об операторе\n\n +Все данные шифруются и не связываются с личной информацией. Данные используются только для оптимизации функций продукта и не передаются третьим лицам. + + Разделение экрана и плавающие окна + Снять все ограничения на открытие плавающих окон + Принудительно запустить многооконный режим + Максимальное количество одновременно открытых плавающих окон + Радиус скругления углов плавающего окна + Тень плавающего окна в фокусе + Тень плавающего окна не в фокусе + -1 для значения по умолчанию + Пользовательская модель дисплея + Пустое поле - значение по умолчанию + Ищете другие настройки? + Убрать рекламу на странице свайпа карт + После включения необходимо один раз очистить данные кошелька + Включить пользовательский фон для OTA-карты + Выбрать фоновое изображение + Радиус скругления углов изображения + Принудительно включить режим Fold + Режим Fold + Развернуть + Свернуть + Принудительно включить Fold Dock + Настроить прозрачность Dock + Принудительно включить размытие Dock + Включение на неподдерживаемых устройствах приведет к тому, что Dock не будет отображаться + Убрать проверку Root в игровых фильтрах + Убрать все задержки всплывающих окон + Например: добавление рискованных приложений в белый список + Убрать дополнительную рекламу в сообщениях + Принудительно показывать чип безопасности NFC + Убрать Fluid Cloud о рискованной платежной среде + Пользовательская оценка + Пользовательское содержимое подсказки + Пользовательская длительность анимации + Возможность + Убрать диалоговое окно проверки Root + Убрать "Вам также может понравиться" + Индикатор скорости сети + По умолчанию + Загрузка/Скачивание + Стиль скорости сети + Размер шрифта скорости + Размер шрифта единиц + Размер шрифта загрузки + Размер шрифта скачивания + Порог низкой скорости + Скрывать при низкой скорости + Скрывать, когда и загрузка, и скачивание медленные + Иконка-индикатор + Без иконки + Скрыть б/с + Скрыть пробел + Поменять местами загрузку и скачивание + Индикатор-иконка спереди + Конфигурация динамической частоты ЦП + Минимальный порог частоты + Максимальный порог частоты + Не можете найти нужное приложение в списке? + Для поддержания чистоты на главной странице, %1$d неустановленных приложений были скрыты.\n\nТекущие скрытые записи приложений:\n + Помощь + Отключить проверку пароля блокировки экрана каждые 72 часа + Разрешить ненадежные сенсорные события + Убрать дополнительную рекламу в предложениях приложений + Прямой доступ к спец. возможностям + Предоставить доступ к спец. возможностям напрямую + Выбранное приложение: + Умное предоставление доступа к спец. возможностям + При включении приложениям из белого списка доступ будет предоставляться автоматически. Невыбранные приложения будут перенаправлены на страницу их настроек спец. возможностей. + Белый список спец. возможностей + Убрать всплывающее окно о частой установке + Убрать всплывающее окно "Попытка установки" + Убрать проверку версии + Убрать проверку безопасности перед установкой + Переключить стиль + Включить напоминание о будильнике телефона + После включения необходимо установить Mi Clock и настроить будильник, а также сделать Mi Fitness системным приложением. + Убрать диалоговое окно обновления системы + Убрать уведомление об обновлении системы + Модуль + Убрать диалоговое окно с предложением включить автозагрузку по WLAN + Убрать проверку статуса разблокировки и dm-verity + Скоро + Мастерская ROM + Включить ИИ-помощник MLBB + Предотвратить автоматическое выключение OShare + Как в системе + Язык приложения + Установить уровень анимации + Перед включением убедитесь, что пароль конфиденциальности не установлен + Скрыть рингтон вызова + Статус системы + Информация об устройстве + Функциональные модули + Статус модуля + Статус Root + Здоровье + Текущая емкость + Количество циклов + Проектная емкость + Фактическая емкость + Состояние батареи + Регион + Андроид + Система + Статус + Список приложений + Центр управления + Увеличить обложку медиа + Когда отображается обложка медиа, она будет использоваться как фон для музыкальной карточки. + Удалить уведомление об активном VPN + Требуется перезагрузка для вступления в силу + Удалить уведомление о завершении зарядки + Автоматический цвет для заголовка и элементов управления + Разрешить отключение всех категорий уведомлений + При включении все категории уведомлений для всех приложений можно будет контролировать индивидуально. + Отключить аутентификацию передачи данных + Использовать режим передачи файлов по умолчанию при подключении USB + Убрать диалоговое окно выбора USB + Принудительно показывать значок приложения во всплывающих уведомлениях + При включении значок приложения во всплывающих уведомлениях будет извлекаться модулем. + Убрать предупреждение "Попытка взлома системы" + Уведомление с кодом подтверждения + Код подтверждения: %s + Код подтверждения скопирован: %s + SMS-код подтверждения + Ключевое слово для SMS с кодом + Показывать всплывающее уведомление с кодом + Показывать уведомление с кодом + Копировать код в буфер обмена + Автоматически вводить код подтверждения + Внешний вид и обновления + Настройки источника данных + Скрытие единиц измерения + Частота ЦП + Использование ЦП + Использование ОЗУ + Изменить источник данных о частоте ЦП + Введите число после cpu в папке /sys/devices/system/cpu/ для указания источника. + Показывать реальный уровень заряда батареи + Показывает реальный уровень заряда только в строке состояния; уровень заряда, считываемый другими приложениями, не затрагивается. + Обход проверки безопасности при разблокировке с того же аккаунта + Убирает проверку root/небезопасного состояния, чтобы включить "Разблокировать это устройство с телефона с тем же аккаунтом". + Макет строки состояния + Не удалось загрузить дерево представлений. Убедитесь, что модуль активирован и SystemUI перезапущен. + По умолчанию + Принудительно видимый + Принудительно скрытый + Принудительно невидимый + Управление конфигурацией + Экспорт конфигурации + Создать резервную копию всех настроек модуля в файл + Импорт конфигурации + Восстановить все настройки модуля из файла резервной копии + Очистить конфигурацию + Сбросить все настройки модуля к значениям по умолчанию + Конфигурация успешно экспортирована + Не удалось экспортировать конфигурацию: %1$s + Конфигурация успешно импортирована. Перезапустите соответствующие приложения для полного применения изменений. + Не удалось импортировать конфигурацию: %1$s + Все конфигурации очищены. Перезапустите соответствующие приложения. + Возможности + Получение списка возможностей из приложения "Настройки", пожалуйста, подождите… + От: %1$s + Связанные функции не найдены.\nУбедитесь, что "Настройки" активированы и перезапущены, затем попробуйте снова войти на эту страницу. + Принудительно ВКЛ + Принудительно ВЫКЛ + Строка состояния + Обход всех проверок перед установкой + Обходит несколько проверок перед установкой, включая: валидность пути к файлу, целостность файла, информацию о версии и флаги "запретить обновление" от производителя. + Принудительно показывать опцию "Локальная установка" + Общие настройки + Настройки обхода проверки + Расширенные настройки + Разрешить откат приложений + Если включено, можно устанавливать старую версию приложения поверх новой. + Разрешить установку приложений с другой подписью + Если включено, можно устанавливать приложение с другой подписью поверх существующего с тем же именем пакета. + Отключить верификатор JAR + Обход проверки целостности APK-файла, решает проблемы с установкой некоторых модифицированных приложений. + Отключить проверку дайджеста сообщения + Обход проверки дайджеста APK-файла. + Обход проверки сжатия resources.arsc + Решает проблемы с установкой, вызванные несжатым или невыровненным `resources.arsc`. + Обход проверки минимальной версии подписи + Разрешить установку приложений, использующих старые схемы подписи. + Отключить агент проверки установки + Отключить сканирование пакетов через агенты проверки (например, Google Play Protect). + Разрешить системным приложениям вызывать скрытые API + Разрешить системным приложениям или их обновлениям вызывать скрытые API, даже если подписи не совпадают. + Разрешить несистемным приложениям использовать общий UID + Разрешить несистемным приложениям с разными подписями использовать `android:sharedUserId`. + Убрать рекламу на страницах второго уровня + Запретить перенаправление в системный браузер + Включить отладочные команды PMS + +Используйте команду "pm pms" в adb shell для просмотра информации о подписи приложения и статусе подписи общего UID.\n +Доступные подкоманды: +\n• p/package [имя_пакета] — Просмотр информации о подписи и общем пользователе для указанного приложения +\n• su/shareduser [UID] — Просмотр информации о подписи для общего UID +\n• Другие аргументы — Показать справку по использованию + + Убрать встроенную рекламу в Погоде + Убрать поле поиска на странице Погоды + Подробности о погоде + Принудительная загрузка последнего пакета обновления + Загрузите последний полный пакет для извлечения init_boot или других целей. Путь загрузки обычно \"/data/ota_package/OTA\" + diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml new file mode 100644 index 00000000..e7df00a4 --- /dev/null +++ b/app/src/main/res/values/array.xml @@ -0,0 +1,100 @@ + + + + android + com.android.settings + com.android.systemui + com.android.launcher + com.oplus.battery + com.heytap.speechassist + com.coloros.ocrscanner + com.oplus.games + com.finshell.wallet + com.coloros.phonemanager + com.android.mms + com.coloros.securepay + com.heytap.health + com.oplus.appdetail + com.heytap.quicksearchbox + com.mi.health + com.oplus.ota + com.coloros.oshare + com.android.incallui + com.oplus.notificationmanager + com.oplus.exsystemservice + com.android.phone + com.coloros.weather2 + com.heytap.browser + com.oplus.securitypermission + com.heytap.themestore + + + @string/follow_system + 简体中文 + English + 日本語 + Русский + 梗体中文 By@Pencil + 한국어 + + + 0x29ffffff + 0xccffffff + + + 0x00ffffff + 0x30ffffff + + + @string/power + @string/current + @string/voltage + @string/cpu_temperature + @string/battery_temperature + @string/cpu_frequency + @string/cpu_usage + @string/ram_usage + + + @string/status_bar_time_gravity_center + @string/status_bar_time_gravity_top + @string/status_bar_time_gravity_bottom + @string/status_bar_time_gravity_end + @string/status_bar_time_gravity_center_horizontal + @string/status_bar_time_gravity_center_vertical + @string/status_bar_time_gravity_fill + @string/status_bar_time_gravity_fill_horizontal + @string/status_bar_time_gravity_fill_vertical + + + @string/preset + @string/geek + + + @string/default_mode + @string/upload_download + + + @string/no_icon + △▽▲▼ + ▵▿▴▾ + ☖⛉☗⛊ + ↑↓ + ⇧⇩ + + + @string/unfold + @string/fold + + + @string/default_mode + @string/force_on + @string/force_off + + + @string/alignment_default + @string/alignment_left + @string/alignment_center + @string/alignment_right + + diff --git a/app/src/main/res/values/color.xml b/app/src/main/res/values/color.xml new file mode 100644 index 00000000..ac9c1a0c --- /dev/null +++ b/app/src/main/res/values/color.xml @@ -0,0 +1,6 @@ + + + #FF656565 + #FFFFFF + #000000 + diff --git a/app/src/main/res/values/plurals.xml b/app/src/main/res/values/plurals.xml new file mode 100644 index 00000000..ecf8cf47 --- /dev/null +++ b/app/src/main/res/values/plurals.xml @@ -0,0 +1,12 @@ + + + + %d 天前 + + + %d 小时前 + + + %d 分钟前 + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..270c9c46 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,501 @@ + + + O神 + 系统辅助模块,支持 ColorOS、RealmeUI、OxygenOS。免费软件,请勿二次转发,分享,售卖!github@suqi8 + 包管理服务 + 允许降级安装应用 + 设备昵称 + 确定 + 储存空间 + 搜索 + 设置 + 取消 + 颜色模式 + 夜间模式 + 白天模式 + 跟随系统 + 允许应用在安装新版本的情况下直接覆盖安装旧版本 + 禁用软件包管理器签名验证 + 关闭安装应用时的签名验证,可以安装被篡改的应用 + 禁用 APK 签名验证 + 允许直接覆盖安装同包名不同签名的应用 + 安装时始终使用已装 APP 的签名 + 不是一般的危险,仅在绝对需要时启用 + 增强模式 + 可以解决一些 APP 内部的完整性校验,一般不需要开启 + 绕过黑名单 + 绕过某些设备如 Nothing Phone 上的黑名单 + 绕过共享用户签名验证 + 允许安装与其共享用户签名不同的 app\n(需要同时打开“禁用APK签名验证” + 禁用安装包验证代理 + 如 Play 商店的保护机制 + 功能 + 主页 + 关于 + 时钟指示器 + 还未开启此项功能~ + 时钟样式 + 预设 + 极客 + 时钟大小 + 字体大小 + 调整状态栏时钟大小,0为不设置大小 + 更新频率 + 绝对值 + 粗体 + CENTER 居中对齐 + TOP 顶部对齐 + BOTTOM 底部对齐 + END 结束位置对齐 + CENTER_HORIZONTAL 水平居中 + CENTER_VERTICAL 垂直居中 + FILL 填满整个空间 + FILL_HORIZONTAL 水平填满 + FILL_VERTICAL 垂直填满 + 输入 /sys/devices/virtual/thermal/ 文件夹下 thermal_zone 后的数字以指定源。 + 更改 CPU 温度获取源 + 电池温度 + CPU温度 + YukiHookAPI版本 + 编译时间戳 + 编译时间 + 官方频道 + 参与翻译 + 前往 Github 贡献您的语言翻译 + 前往他的主页 + 请先安装酷安应用 + 时钟更新时间 + 设置时钟更新的频率,单位毫秒,0为不设置更新频率 + 显示年份 + 例如: 24年 + 显示月份 + 例如: 4月 + 显示日期 + 例如: 5日 + 显示星期 + 例如: 周一 + 显示时辰 + 例如: 寅时 + 显示时段 + 例如: 上午 + 显示秒数 + 例如: xx:xx:41 + 显示毫秒 + 例如: xx:xx:xx.919 + 隐藏间隔 + 隐藏时间内的空格 + 双排显示 + 双排显示时间 + 重启应用 + 确认重启以下应用吗? + 其他设置 + 题外话 + 对齐方式 + 功率 + 电流 + 时钟格式 + 硬件指示器 + 功耗指示器 + 温度指示器 + 显示内容 + 时钟上边距 + 时钟下边距 + 时钟左边距 + 时钟右边距 + 显示分割线 + 双电芯 + 电压 + 第一行显示内容 + 第二行显示内容 + 桌面图标与文字大小倍数 + 大于一倍时图标会被限制在一倍,暂时不会解决 + CN 中国 China 🇨🇳 + TW 中国台湾省 Taiwan Province of China 🇨🇳 + RU 俄罗斯 Russia 🇷🇺 + GDPR 欧盟 EU 🇪🇺 + GDPR 欧洲 Europe 🇪🇺 + IN 印度 India 🇮🇳 + ID 印度尼西亚 Indonesia 🇮🇩 + MY 马来西亚 Malaysia 🇲🇾 + TH 泰国 Thailand 🇹🇭 + PH 菲律宾 Philippines 🇵🇭 + SA 沙特阿拉伯 Saudi Arabia 🇸🇦 + LATAM 拉丁美洲 Latin America 🇱🇰 + BR 巴西 Brazil 🇧🇷 + MEA 中东和非洲 The Middle East and Africa 🇦🇪 + 设备的国家/地区代码 (NV (carrier) identifier) = %1$s + 简直完美 + 热热的 + 没电啦 + 电压超标了 + 冷冷的 + 未知呢 + 找不到相关信息 + 退出 + 状态栏图标 + 隐藏状态栏 + 启用全天候息屏 + 强制触发 LTPO + 其他 + 欢迎各位开发者与用户与我们共同开发或提出建议! + 感谢 + 贡献人员 + 捐赠 + 引用内容 + 讨论群组 + 自动构建发布 + 官方网站 + 开源项目 + 闭源项目 + 无许可证 + 此致敬意,感谢以下开源项目,为 O神 注入了力量。 + 酷安 + 感谢所有在 O神 的旅程中,留下足迹的开发者和用户。 + 一加系统服务 + 关闭Root检测 + 关闭内置的root检测器 + 时钟边距 + 时钟格式示例 + 最近任务 + 强制显示运行内存 + 系统通知 + 移除开发者选项通知 + 关闭电量低于20%流体云 + 此功能限制该通知的后续弹出,已有的通知请自行关闭 + 移除通知勿扰通知 + 隐藏桌面图标 + 强制启用小布通话 + 去除全屏翻译限制 + 启用超神一键连招 + 开启AI大神辅助V1 + 开启AI大神辅助V2 + 开启AI大神辅助V3 + 真我 GT7 Pro专属功能,开启此功能将移除机型限制 + 王者荣耀 + 移除功能云控 + 移除包名限制 + 启用全部特性 + 可能会有未知问题,谨慎开启 + 开启和平精英AI大神辅助 + 自启动最大数量 + 默认5为不更改 + 隐私授权提示 + +为保障应用正常运行与服务质量,我们需要收集您的部分设备信息用于数据统计和分析,具体包括:\n +- 设备标识符(如Android ID)\n +- 应用版本及运行状态\n +- 设备型号和系统版本\n +- LSPosed 版本\n +- Magisk/KernelSU版本等\n +- 网络类型及运营商信息\n\n +所有数据均会进行加密处理,不会关联个人身份,数据仅用于产品功能优化,不会共享给第三方 + + 分屏与小窗 + 移除全部开启小窗限制 + 强制启动多窗口模式 + 最大同时开启小窗数量 + 小窗圆角半径 + 小窗获取焦点时阴影 + 小窗未获取焦点时阴影 + -1为默认 + 自定义显示型号 + 空内容为默认 + 在找其他设置吗? + 移除刷卡页面广告 + 开启后需清除一次钱包数据 + 开启自定义OTA卡片背景 + 选择背景图片 + 图片圆角大小 + 强制开启Fold模式 + Fold模式 + 展开 + 折叠 + 强制开启Fold Dock + 调整Dock透明度 + 强制启用Dock模糊 + 在不支持的设备上开启将导致Dock不会显示 + 移除游戏滤镜Root检测 + 移除所有弹窗延迟 + 例如:风险应用添加白名单 + 移除信息附加广告 + 强制开启 NFC 安全芯片显示 + 移除支付环境有风险流体云 + 自定义分数 + 自定义提示内容 + 自定义动画时长 + 特性 + 移除Root检测弹窗 + 移除\"大家也喜欢\" + 网速指示器 + 默认 + 上传/下载 + 网速样式 + 速度字体大小 + 单位字体大小 + 上传字体大小 + 下载字体大小 + 慢速阈值 + 慢速隐藏 + 上下行均慢速时隐藏 + 图标指示器 + 无图标 + 隐藏b/s + 隐藏空格 + 交换上下行位置 + 图标指示器前置 + CPU动态调频配置 + 最小频率阈值 + 最大频率阈值 + 列表中找不到您想要的应用? + 为了保持首页的美观,%1$d个未安装应用已被隐藏。\n\n当前隐藏的应用入口:\n + 帮助 + 禁用每 72 小时验证锁屏密码 + 允许不受信任的触摸操作 + 移除应用建议附加广告 + 无障碍服务直达 + 直接授权无障碍服务 + 已选择应用: + 智能授权无障碍服务 + 开启后在白名单应用列表选中的应用程序将会自动授权,未选中的应用程序将会直接跳转至应用无障碍开关页面。 + 无障碍服务白名单 + 移除安装频繁弹窗 + 移除尝试安装弹窗 + 移除版本号检测 + 移除安装前安全检测 + 切换样式 + 使开启手机闹钟提醒可用 + 开启后需要安装小米时钟并设置闹钟,且将小米运动健康固化为系统应用 + 移除系统更新弹窗 + 移除系统更新通知 + 模块 + 移除推荐开启 WLAN 自动下载弹窗 + 移除检测解锁状态与dm校验 + 尽情期待 + ROM工坊 + 开启决胜巅峰AI大神辅助 + 移除互传自动关闭 + 随跟系统 + 应用语言 + 设置动画等级 + 开启前请确认没有设置隐私密码 + 隐藏电话彩铃 + 系统状态 + 设备信息 + 功能模块 + 模块状态 + Root 状态 + 健康度 + 当前容量 + 循环次数 + 设计容量 + 实际容量 + 电池健康度 + 区域 + 安卓 + 系统 + 状态 + 应用列表 + 控制中心 + 放大媒体封面 + 媒体封面显示时,作为音乐卡片的背景平铺展示 + 移除已激活 VPN 通知 + 需要重启系统生效 + 移除充电完成通知 + 标题与控件自动取色 + 允许关闭所有类别通知 + 开启后所有应用的所有类别通知都将可自行控制开关 + 禁用数据传输身份验证 + 插入 USB 默认使用文件传输模式 + 移除插入 USB 选择弹窗 + 吐司提示强制显示应用图标 + 开启后吐司显示的应用图标将由模块获取 + 移除尝试破坏系统警告 + 验证码通知 + 验证码:%s + 已复制验证码: %s + 短信验证码 + 验证码短信关键字 + 显示验证码吐司 + 显示验证码通知 + 验证码复制剪切板 + 自动输入验证码 + 外观与更新 + 数据源设置 + 单位隐藏 + CPU频率 + CPU使用率 + 内存占用 + 更改CPU频率数据源 + 输入 /sys/devices/system/cpu/ 文件夹下 cpu 后的数字以指定源。 + 显示真实电量 + 仅在状态栏显示真实电量,其他应用读取的电量不受影响 + 真实电量 + 电池状态 + 地区 + Android + 系统 + 移除同账号解锁安全检测 + 移除root/不安全状态检测,使“同账号手机解锁本设备”可用 + 状态栏布局 + 加载视图树失败,请确保模块已激活且 SystemUI 已重启。 + 默认 + 强制显示 + 强制隐藏 + 强制不可见 + 配置管理 + 导出配置 + 将所有模块设置备份到一个文件中 + 导入配置 + 从备份文件中恢复所有模块设置 + 清除配置 + 将所有模块设置恢复为默认值 + 配置导出成功 + 配置导出失败: %1$s + 配置导入成功,请重启相关应用以完全生效 + 配置导入失败: %1$s + 所有配置已清除,请重启相关应用 + 特性 + 正在从“设置”应用中获取特性列表,请稍候… + 来自: %1$s + 未扫描到相关功能。\n请确保“设置”被激活并重启,然后可尝试重新进入此页面。 + 强制开启 + 强制关闭 + 状态栏 + 移除安装前所有校验 + 移除了安装前的多项校验,包括:文件路径有效性、文件完整性、版本信息以及厂商“禁止更新”标记。 + 强制显示“本地安装”选项 + 通用设置 + 校验绕过设置 + 高级设置 + 允许应用降级 + 开启后,可以安装旧版本的应用覆盖新版本。 + 允许覆盖安装不同签名的应用 + 开启后,可以用不同签名的应用覆盖安装同包名的应用。 + 禁用 JAR 包校验 + 绕过对 APK 文件完整性的校验,解决部分修改版应用无法安装的问题。 + 禁用摘要验证 + 绕过对 APK 文件摘要的验证。 + 绕过 resources.arsc 压缩检查 + 解决因 `resources.arsc` 未对齐或被压缩导致的安装失败。 + 绕过最小签名版本检查 + 允许安装使用旧版签名方案的应用。 + 禁用安装验证代理 + 禁用通过验证代理(如 Google Play Protect)进行的安装包扫描。 + 允许系统应用调用隐藏 API + 即使签名不符,也允许系统应用或更新后的系统应用调用隐藏 API。 + 允许非系统应用使用共享 UID + 允许不同签名的非系统应用使用 `android:sharedUserId`。 + 移除二级页面广告 + 禁止跳转系统浏览器 + 启用 PMS 调试命令 + +可在 adb shell 中使用 “pm pms” 命令查看应用签名信息及共享 UID 签名状态。\n +可用子命令: +\n• p/package [包名] —— 查看指定应用签名及共享用户信息 +\n• su/shareduser [UID] —— 查看共享 UID 的签名信息 +\n• 其它参数 —— 显示用法提示 + + 移除天气注入广告 + 移除天气页面搜索框 + 天气详情 + 使用系统字体 + 网速对齐方式 + 隐藏 "/s" + 例如 "KB/s" 显示为 "KB" + 使用 "B" (Byte) + 默认为 "b" (bit),例如 "KB/s" vs "Kb/s" + 显示控制 + 字体大小设置 + 基础设置 + 默认 + 左对齐 + 居中 + 右对齐 + 使用旧版启动弹窗 + 强制启用旧版应用启动提示逻辑 + 将“允许 30 天”改为“永久允许” + 通知公告 + 重要通知与更新公告 + 按类别浏览 + 探索按功能分类的特性 + 今日亮点 + 为你推荐的实用功能 + 加入我们的社区 + 检测更新 + 添加更多桌面布局 + 开启后将增加 2x2 至 20x20 的所有布局 + 自定义模糊圆角 + 桌面设置 + Dock 设置 + 动画和布局设置 + 绕过 V1 签名错误 (-103) + (实验性)尝试在 V1 签名验证失败(错误码-103)时伪造一个签名以继续安装。 + 允许 Split APK 签名不一致 + 允许安装具有不同签名的 Split APK (APKS)。 + GitHub Token + Release + CI Build + 软件版本 + v%1$s(%2$s) + 查看更多更新说明 > + 收起更新说明 ↑ + %1$s | %2$.2f MB + 发布于 %1$s + (%1$s) + 下载完成,开始安装... + 下载失败: %1$s + 下载并安装 + 正在下载... + 正在下载... %1$d%% + OShin Release + OShin CI Build + 正在检测更新 + 发现新版本 + 已是最新版本 + GitHub Token + 请在此处粘贴你的 GitHub Personal Access Token (PAT)。此 Token 将仅用于提高 API 请求速率限制,无需任何特殊权限。它将存储在此设备的本地。 + Personal Access Token + 清除 + 未知的 Root 操作错误 + Root 操作失败: %1$s + Root 操作失败 (无输出) + Root 操作执行异常: %1$s + 无法启动安装程序: %1$s + 刚刚 + 获取 Release 失败,请检查网络连接 + API速率限制。请尝试添加GitHub Token。 + 未找到Release信息。 + 获取失败 (代码: %d) + 下载失败: %1$s + 下载失败: %d + 请求超时,请检查网络 + 无法连接到服务器 + 网络错误: %1$s + 发生错误: %1$s + 未知错误 + 更新渠道 + 检测到新版本: %1$s,是否前往更新? + 立即更新 + 未在LSPosed中激活 + 授权失败 + 无法获取Root权限 + KernelSU %s + Magisk %s + LSPosed %1$s API %2$s + 暂无描述 + 循环次数 + 设计容量 + 系统健康 + %1$d次 + %1$dmAh + 系统健康度 + 系统信息 + 移除 SIM 名字数限制 + 允许为 SIM 卡设置超过默认字符数的名称 + 强制下载最新升级包 + 下载最新完整包,用于提取init_boot或其他,下载路径一般在\"/data/ota_package/OTA\" + 解锁部分VIP功能 + 移除开屏广告 + 禁止升级 + 底部栏禁用玻璃效果 + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..4bca4a85 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,17 @@ + + + + diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 00000000..7f64c30b --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml new file mode 100644 index 00000000..0eac7c92 --- /dev/null +++ b/app/src/main/res/xml/provider_paths.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/resources/META-INF/yukihookapi_init b/app/src/main/resources/META-INF/yukihookapi_init new file mode 100644 index 00000000..98b7b680 --- /dev/null +++ b/app/src/main/resources/META-INF/yukihookapi_init @@ -0,0 +1 @@ +com.suqi8.oshin.hook.HookEntry \ No newline at end of file diff --git a/app/src/test/java/io/github/suqi8/opatch/ExampleUnitTest.kt b/app/src/test/java/io/github/suqi8/opatch/ExampleUnitTest.kt new file mode 100644 index 00000000..9320130e --- /dev/null +++ b/app/src/test/java/io/github/suqi8/opatch/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package io.github.suqi8.opatch + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..32b2bf95 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.ksp) apply false + alias(libs.plugins.kotlin.compose) apply false + id("com.google.dagger.hilt.android") version "2.57.2" apply false +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..4b99fa2d --- /dev/null +++ b/gradle.properties @@ -0,0 +1,17 @@ +# Compiler Configuration +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +android.useAndroidX=true +android.nonTransitiveRClass=true +kotlin.code.style=official +kotlin.incremental.useClasspathSnapshot=true +org.gradle.unsafe.configuration-cache=true +org.gradle.unsafe.configuration-cache-problems=warn +android.enableCoreLibraryDesugaringCache=true +org.gradle.caching=true +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.configureondemand=true +android.buildTypes.release.minifyEnabled=true +android.buildTypes.release.proguardFiles='proguard-rules.pro' +org.gradle.warning.mode=none +android.enableJetifier=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..12cb97b7 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,150 @@ +# ================================================================================= +# Gradle 版本目录 (Version Catalog) +# --------------------------------------------------------------------------------- +# 此文件用于集中管理项目的所有依赖项(库、插件)及其版本。 +# ================================================================================= + +# --- 版本定义 [versions] --- +# 在此区域统一定义所有库和插件的版本号。 +[versions] +# Android Gradle 插件 (AGP) 和核心工具链 +agp = "8.12.3" +hiltAndroid = "2.57.2" +hiltNavigationCompose = "1.3.0" +kotlin = "2.2.21" +ksp = "2.2.20-2.0.3" + +# AndroidX & Jetpack 库 +activityCompose = "1.11.0" +appcompat = "1.7.1" +composeBom = "2025.10.01" +constraintlayout = "2.2.1" +coreKtx = "1.17.0" +espressoCore = "3.7.0" +junit = "4.13.2" +junitExt = "1.3.0" +lifecycle = "2.9.4" +mmkv = "2.2.4" +multiplatformMarkdownRendererAndroid = "0.37.0" +navigation = "2.9.5" +palette = "1.0.0" +room = "2.8.3" +compose = "1.9.4" +foundationLayout = "1.9.4" + +# Jetpack Compose 相关第三方库 +accompanist = "0.36.0" +coil = "3.3.0" +composeNeumorphism = "1.0.0-alpha02" +composeShimmer = "1.3.3" +haze = "1.6.10" +lottieCompose = "6.6.10" +materialIconsExtended = "1.7.8" +toolbarCompose = "2.3.5" +capsule = "2.1.0" + +# 通用第三方库 +dexkit = "2.0.7" +drawabletoolbox = "1.0.7" +expandablebottombar = "1.5.4" +ezxhelper = "2.2.1" +gson = "2.13.2" +miuix = "0.5.2" +okhttp = "5.2.1" +union = "1.8.0" +xxpermissions = "25.2" + +# Umeng SDK +umengCommon = "9.8.8" +umengAsms = "1.8.7.2" +umengUyumao = "1.1.4" + +# Xposed & YukiHook API +xposedApi = "82" +yukiHookApi = "1.3.1" +yukiKspXposed = "1.3.1" +kavaref = "1.0.2" +kavaref-extension = "1.0.1" + +# --- 库定义 [libraries] --- +# 在此区域定义具体的依赖库及其别名。别名应清晰、易于理解。 +[libraries] +# AndroidX - 核心与组件 +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } +androidx-palette-ktx = { group = "androidx.palette", name = "palette-ktx", version.ref = "palette" } +androidx-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayout" } + +# AndroidX - Jetpack Compose +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "materialIconsExtended" } +androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose" } +androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "compose" } +androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "compose" } +androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4", version.ref = "compose" } + +# AndroidX - 导航 (Navigation) +androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } +androidx-navigation-runtime-ktx = { group = "androidx.navigation", name = "navigation-runtime-ktx", version.ref = "navigation" } + +# AndroidX - Room 数据库 +androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } +androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } + +# AndroidX - 测试 +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitExt" } +hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" } +hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroid" } +junit = { group = "junit", name = "junit", version.ref = "junit" } + +# Compose 生态第三方库 +accompanist-flowlayout = { group = "com.google.accompanist", name = "accompanist-flowlayout", version.ref = "accompanist" } +airbnb-lottie-compose = { group = "com.airbnb.android", name = "lottie-compose", version.ref = "lottieCompose" } +coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" } +coil-network-okhttp = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil" } +haze = { group = "dev.chrisbanes.haze", name = "haze", version.ref = "haze" } +mmkv = { module = "com.tencent:mmkv", version.ref = "mmkv" } +multiplatform-markdown-renderer-android = { module = "com.mikepenz:multiplatform-markdown-renderer-android", version.ref = "multiplatformMarkdownRendererAndroid" } +multiplatform-markdown-renderer-code = { module = "com.mikepenz:multiplatform-markdown-renderer-code", version.ref = "multiplatformMarkdownRendererAndroid" } +multiplatform-markdown-renderer-coil3 = { module = "com.mikepenz:multiplatform-markdown-renderer-coil3", version.ref = "multiplatformMarkdownRendererAndroid" } +shimmer-compose = { group = "com.valentinilk.shimmer", name = "compose-shimmer", version.ref = "composeShimmer" } +toolbar-compose = { group = "me.onebone", name = "toolbar-compose", version.ref = "toolbarCompose" } +neumorphism-compose = { group = "me.nikhilchaudhari", name = "composeNeumorphism", version.ref = "composeNeumorphism" } +capsule = { module = "com.github.Kyant0:Capsule", version.ref = "capsule" } + +# 通用第三方库 +drawabletoolbox = { group = "com.github.duanhong169", name = "drawabletoolbox", version.ref = "drawabletoolbox" } +expandablebottombar = { group = "com.github.st235", name = "expandablebottombar", version.ref = "expandablebottombar" } +gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } +luckypray-dexkit = { group = "org.luckypray", name = "dexkit", version.ref = "dexkit" } +miuix = { group = "top.yukonga.miuix.kmp", name = "miuix-android", version.ref = "miuix" } +squareup-okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } +union = { module = "com.umeng.umsdk:union", version.ref = "union" } +xxpermissions = { group = "com.github.getActivity", name = "XXPermissions", version.ref = "xxpermissions" } + +# Umeng (友盟) SDK +umeng-common = { group = "com.umeng.umsdk", name = "common", version.ref = "umengCommon" } +umeng-asms = { group = "com.umeng.umsdk", name = "asms", version.ref = "umengAsms" } +umeng-uyumao = { group = "com.umeng.umsdk", name = "uyumao", version.ref = "umengUyumao" } + +# Hook API +xposed-api = { group = "de.robv.android.xposed", name = "api", version.ref = "xposedApi" } +yukihook-api = { group = "com.highcapable.yukihookapi", name = "api", version.ref = "yukiHookApi" } +yukihook-ksp-xposed = { group = "com.highcapable.yukihookapi", name = "ksp-xposed", version.ref = "yukiKspXposed" } +ezxhelper = { group = "com.github.kyuubiran", name = "EzXHelper", version.ref = "ezxhelper" } +kavaref-extension = { module = "com.highcapable.kavaref:kavaref-extension", version.ref = "kavaref-extension" } +kavaref-core = { module = "com.highcapable.kavaref:kavaref-core", version.ref = "kavaref" } + + +# --- 插件定义 [plugins] --- +# 在此区域定义构建脚本插件及其别名。 +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..e708b1c0 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..e8ea4c80 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Oct 31 13:06:46 CST 2025 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 00000000..3a163de7 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" \ No newline at end of file diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..477c8966 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..e4a477cf --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,43 @@ +// `pluginManagement` 用于配置 Gradle 插件的仓库源。 +// Gradle 将从此代码块中定义的仓库中查找 `plugins {}` 块中声明的插件。 +pluginManagement { + repositories { + gradlePluginPortal() // Gradle 官方插件门户。 + mavenCentral() // Maven 中央仓库。 + google() // Google 的 Maven 仓库,用于存放 Android 相关库和插件。 + maven("https://jitpack.io") // JitPack 仓库,用于轻松构建任何 GitHub/GitLab 项目。 + // Xposed 框架的专用 Maven 仓库。 + maven { + url = uri("https://api.xposed.info/") + content { + // 仅从此仓库中查找属于 "de.robv.android.xposed" 组的依赖。 + includeGroup("de.robv.android.xposed") + } + } + } +} + +// `dependencyResolutionManagement` 用于集中管理所有模块的依赖项仓库。 +dependencyResolutionManagement { + // 设置仓库模式为 FAIL_ON_PROJECT_REPOS,禁止在子模块的 build.gradle 文件中单独定义仓库。 + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + gradlePluginPortal() + mavenCentral() + google() + maven("https://jitpack.io") + maven("https://raw.githubusercontent.com/HighCapable/maven-repository/main/repository/releases") + // Xposed 框架的专用 Maven 仓库。 + maven { + url = uri("https://api.xposed.info/") + content { + includeGroup("de.robv.android.xposed") + } + } + } +} + +// 设置根项目的名称。 +rootProject.name = "OShin" +// 包含 :app 模块,使其成为项目构建的一部分。 +include(":app")