├── .github └── workflows │ └── build-release.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── FAQ.md ├── LICENSE ├── README.md ├── README_EN.md ├── build.gradle.kts ├── composeApp ├── build.gradle.kts ├── compose-desktop.pro ├── launcher │ ├── icon.icns │ ├── icon.ico │ └── icon.png ├── libs │ ├── bcpkix-jdk18on-1.80.jar │ ├── bcprov-jdk18on-1.80.jar │ └── bcutil-jdk18on-1.80.jar ├── resources │ ├── common │ │ ├── honor_unsigned.apk │ │ ├── huawei_unsigned.apk │ │ ├── oppo_unsigned.apk │ │ ├── qq_unsigned.apk │ │ ├── vivo_unsigned.apk │ │ └── xiaomi_unsigned.apk │ ├── linux │ │ └── aapt2 │ ├── macos-arm64 │ │ └── aapt2 │ ├── macos-x64 │ │ └── aapt2 │ └── windows │ │ └── aapt2.exe └── src │ ├── commonMain │ ├── composeResources │ │ ├── drawable │ │ │ └── icon.png │ │ ├── files │ │ │ ├── lottie_loading_dark.json │ │ │ ├── lottie_loading_light.json │ │ │ ├── lottie_main_1_dark.json │ │ │ ├── lottie_main_1_light.json │ │ │ ├── lottie_main_2_dark.json │ │ │ ├── lottie_main_2_light.json │ │ │ ├── lottie_main_3_dark.json │ │ │ ├── lottie_main_3_light.json │ │ │ ├── lottie_main_4_dark.json │ │ │ ├── lottie_main_4_light.json │ │ │ └── upload.json │ │ ├── font │ │ │ └── ZCOOLKuaiLe-Regular.ttf │ │ ├── values-zh-rCN │ │ │ └── strings.xml │ │ └── values │ │ │ └── strings.xml │ └── kotlin │ │ ├── App.kt │ │ ├── constant │ │ └── ConfigConstant.kt │ │ ├── database │ │ └── PreferencesDataSource.kt │ │ ├── model │ │ ├── FileSelectorType.kt │ │ ├── IconFactoryData.kt │ │ ├── Model.kt │ │ ├── Sequence.kt │ │ └── UserData.kt │ │ ├── platform │ │ └── Expect.kt │ │ ├── theme │ │ ├── Color.kt │ │ ├── Theme.kt │ │ └── Type.kt │ │ ├── ui │ │ ├── ApkInformation.kt │ │ ├── ApkSignature.kt │ │ ├── ClearBuild.kt │ │ ├── IconFactory.kt │ │ ├── JunkCode.kt │ │ ├── SetUp.kt │ │ ├── SignatureGeneration.kt │ │ ├── SignatureInformation.kt │ │ └── UI.kt │ │ ├── utils │ │ ├── AndroidJunkGenerator.kt │ │ ├── Copy.kt │ │ ├── CoroutinesUtils.kt │ │ ├── ExternalCommand.kt │ │ ├── ImageRequest.kt │ │ ├── LottieAnimation.kt │ │ ├── Utils.kt │ │ └── update.kt │ │ └── vm │ │ └── MainViewModel.kt │ └── desktopMain │ └── kotlin │ ├── main.kt │ └── platform │ ├── DatabaseDriverFactory.kt │ └── RustFileDealWithFactory.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── rs ├── Cargo.toml ├── build.rs ├── src │ ├── lib.rs │ ├── srgb.rs │ └── toolkit.udl └── uniffi-bindgen.rs ├── screenshots ├── fail_to_quantize.png ├── screenshot_apk_information_1.png ├── screenshot_apk_information_1_en.png ├── screenshot_apk_signature_1.png ├── screenshot_apk_signature_1_en.png ├── screenshot_cache_clear_0.png ├── screenshot_cache_clear_0_en.png ├── screenshot_cache_clear_1.png ├── screenshot_cache_clear_1_en.png ├── screenshot_dark.png ├── screenshot_dark_en.png ├── screenshot_icon_factory_1.png ├── screenshot_icon_factory_1_en.png ├── screenshot_icon_factory_2.png ├── screenshot_icon_factory_2_en.png ├── screenshot_light.png ├── screenshot_light_en.png ├── screenshot_signature_generation_1.png ├── screenshot_signature_generation_1_en.png ├── screenshot_signature_information_1.png ├── screenshot_signature_information_1_en.png ├── screenshot_signature_information_2.png ├── screenshot_signature_information_2_en.png ├── screenshot_signature_information_3.png ├── screenshot_signature_information_3_en.png ├── unopen_1.png ├── unopen_2.png └── unopen_3.png └── settings.gradle.kts /.github/workflows/build-release.yml: -------------------------------------------------------------------------------- 1 | name: Build Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag_version: 7 | description: 'Specify the version tag (required)' 8 | required: true 9 | jobs: 10 | create-release-distribution: 11 | strategy: 12 | matrix: 13 | os: [ windows-latest , ubuntu-latest , macos-13 , macos-14 ] 14 | runs-on: ${{ matrix.os }} 15 | name: Create Release Distribution 16 | 17 | steps: 18 | - if: matrix.os != 'macos-14' 19 | name: Setup Jdk 20 | uses: actions/setup-java@v4 21 | with: 22 | distribution: "zulu" 23 | java-version: "17" 24 | architecture: x64 25 | 26 | - if: matrix.os == 'macos-14' 27 | name: Setup Jdk 28 | uses: actions/setup-java@v4 29 | with: 30 | distribution: "zulu" 31 | java-version: "17" 32 | architecture: aarch64 33 | 34 | - name: Checkout 35 | uses: actions/checkout@v4 36 | 37 | - name: PackageReleaseDistributionForCurrentOS 38 | run: ./gradlew packageReleaseDistributionForCurrentOS 39 | 40 | - if: matrix.os == 'windows-latest' 41 | name: Rename File 42 | run: | 43 | $ErrorActionPreference = "Stop" 44 | mv D:\a\AndroidToolKit\AndroidToolKit\composeApp\output\main-release\msi\AndroidToolKit-${{ inputs.tag_version }}.msi D:\a\AndroidToolKit\AndroidToolKit\composeApp\output\main-release\msi\AndroidToolKit-windows-x64.msi 45 | mv D:\a\AndroidToolKit\AndroidToolKit\composeApp\output\main-release\exe\AndroidToolKit-${{ inputs.tag_version }}.exe D:\a\AndroidToolKit\AndroidToolKit\composeApp\output\main-release\exe\AndroidToolKit-windows-x64.exe 46 | 47 | - if: matrix.os == 'ubuntu-latest' 48 | name: Rename File 49 | run: | 50 | mv /home/runner/work/AndroidToolKit/AndroidToolKit/composeApp/output/main-release/deb/androidtoolkit_${{ inputs.tag_version }}-1_amd64.deb /home/runner/work/AndroidToolKit/AndroidToolKit/composeApp/output/main-release/deb/AndroidToolKit-linux-amd64.deb 51 | mv /home/runner/work/AndroidToolKit/AndroidToolKit/composeApp/output/main-release/rpm/androidtoolkit-${{ inputs.tag_version }}-1.x86_64.rpm /home/runner/work/AndroidToolKit/AndroidToolKit/composeApp/output/main-release/rpm/AndroidToolKit-linux-x86_64.rpm 52 | 53 | - if: matrix.os == 'macos-13' 54 | name: Rename File 55 | run: | 56 | mv /Users/runner/work/AndroidToolKit/AndroidToolKit/composeApp/output/main-release/dmg/AndroidToolKit-${{ inputs.tag_version }}.dmg /Users/runner/work/AndroidToolKit/AndroidToolKit/composeApp/output/main-release/dmg/AndroidToolKit-macos-x64.dmg 57 | 58 | - if: matrix.os == 'macos-14' 59 | name: Rename File 60 | run: | 61 | mv /Users/runner/work/AndroidToolKit/AndroidToolKit/composeApp/output/main-release/dmg/AndroidToolKit-${{ inputs.tag_version }}.dmg /Users/runner/work/AndroidToolKit/AndroidToolKit/composeApp/output/main-release/dmg/AndroidToolKit-macos-arm64.dmg 62 | 63 | - if: matrix.os == 'windows-latest' 64 | name: Draft Release 65 | uses: ncipollo/release-action@v1 66 | with: 67 | draft: true 68 | allowUpdates: true 69 | tag: "v${{ inputs.tag_version }}" 70 | artifacts: "composeApp/output/main-release/msi/*,composeApp/output/main-release/exe/*" 71 | token: ${{ secrets.GH_TOKEN }} 72 | 73 | - if: matrix.os == 'ubuntu-latest' 74 | name: Draft Release 75 | uses: ncipollo/release-action@v1 76 | with: 77 | draft: true 78 | allowUpdates: true 79 | tag: "v${{ inputs.tag_version }}" 80 | artifacts: "composeApp/output/main-release/deb/*,composeApp/output/main-release/rpm/*" 81 | token: ${{ secrets.GH_TOKEN }} 82 | 83 | - if: matrix.os == 'macos-13' || matrix.os == 'macos-14' 84 | name: Draft Release 85 | uses: ncipollo/release-action@v1 86 | with: 87 | draft: true 88 | allowUpdates: true 89 | tag: "v${{ inputs.tag_version }}" 90 | artifacts: "composeApp/output/main-release/dmg/*" 91 | token: ${{ secrets.GH_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | **/build/ 4 | xcuserdata 5 | !src/**/build/ 6 | local.properties 7 | .idea 8 | .DS_Store 9 | captures 10 | .externalNativeBuild 11 | .cxx 12 | *.xcodeproj/* 13 | !*.xcodeproj/project.pbxproj 14 | !*.xcodeproj/xcshareddata/ 15 | !*.xcodeproj/project.xcworkspace/ 16 | !*.xcworkspace/contents.xcworkspacedata 17 | **/xcshareddata/WorkspaceSettings.xcsettings 18 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | lazyiones@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | ## 常见问题 2 | 3 | ## 1.无法打开 "AndroidToolKit。app",因为Apple无法检查其是否包含恶意软件。 4 | 出现此问题,按照图片依次点击解决 5 | 6 | | 截图 | 截图 | 截图 | 7 | |:-----------------------------------------------------------------:|:-----------------------------------------------------------------:|:-----------------------------------------------------------------:| 8 | | | | | 9 | 10 | ## 2.使用图标工厂出现报错 Fail to quantize: QUALITY_TOO_LOW 11 | 12 | 出现此问题,打开更多设置,调整为**无损压缩**,或将**PNG质量**项的**最低限度**下调即可 13 | ![Fail_To_Quantize](screenshots/fail_to_quantize.png) 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 LazyIonEs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## AndroidToolKit 4 | 5 |

6 | 7 | Static Badge 8 | 9 | 10 | 11 | 12 | 13 |

14 | 15 | 16 | 21 | 22 | 简体中文 | [English](./README_EN.md) 23 | 24 | 适用于安卓开发的桌面工具,支持 Windows、Mac 和 Linux :tada: 25 | 26 | > linux 平台未经测试,如有问题,请及时反馈 27 | 28 | ## 主要功能 29 | 30 | - [x] 签名信息 - 分析(APK/签名)的签名信息(modulus、md5、sha-1、sha-256等) 31 | - [x] APK信息 - 解析`AndroidManifest.xml`,提取部分信息 32 | - [x] APK签名 - 对APK进行签名 33 | - [x] 签名生成 - 生成签名证书 34 | - [x] 图标生成 - 一键生成多尺寸图标 35 | - [x] 缓存清理 - 清理Android项目缓存目录 36 | 37 | > 支持APK签名校验;单签名校验(需输入签名密码);文件拖拽;apk签名文件对齐;生成签名指定密钥类型,密钥大小;外观浅色深色模式。 38 | 39 | ## 下载 - [Releases](https://github.com/LazyIonEs/AndroidToolKit/releases/latest) 40 | 41 | | 设备 | 芯片 | 下载 | 42 | |:-------:|:-----------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| 43 | | macos | apple | [`macos-arm64.dmg`](https://github.com/LazyIonEs/AndroidToolKit/releases/latest/download/AndroidToolKit-macos-arm64.dmg) | 44 | | macos | intel | [`macos-x64.dmg`](https://github.com/LazyIonEs/AndroidToolKit/releases/latest/download/AndroidToolKit-macos-x64.dmg) | 45 | | windows | intel / amd | [`windows-x64.msi`](https://github.com/LazyIonEs/AndroidToolKit/releases/latest/download/AndroidToolKit-windows-x64.msi) **/** [`windows-x64.exe`](https://github.com/LazyIonEs/AndroidToolKit/releases/latest/download/AndroidToolKit-windows-x64.exe) | 46 | | linux | - | [`linux-amd64.deb`](https://github.com/LazyIonEs/AndroidToolKit/releases/latest/download/AndroidToolKit-linux-amd64.deb) **/** [`linux-x86_64.rpm`](https://github.com/LazyIonEs/AndroidToolKit/releases/latest/download/AndroidToolKit-linux-x86_64.rpm) | 47 | 48 | > [!CAUTION] 49 | > windows版不建议安装到C盘(默认安装路径是C盘 :clown_face:),可能会有权限等问题。遇到问题可以看看 [FAQ](FAQ.md) 50 | 51 | ## 截屏 52 | 53 | | 签名信息 | 签名信息 | 签名信息 | 54 | |:-------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------:| 55 | | | | | 56 | | APK信息 | APK签名 | 签名生成 | 57 | | | | | 58 | | 图标生成 | 缓存清理 | 黑白主题 | 59 | | | | | 60 | | 图标生成 | 缓存清理 | 黑白主题 | 61 | | | | | 62 | 63 | ## 技术栈 64 | 65 | - [Kotlin Multiplatform](https://kotlinlang.org/lp/multiplatform/) 66 | - [Kotlin Coroutines](https://github.com/Kotlin/kotlinx.coroutines) 67 | - [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/) 68 | - [Kotlinx Serialization](https://github.com/Kotlin/kotlinx.serialization) 69 | - [Rust](https://github.com/rust-lang/rust) 70 | - [mozjpeg](https://github.com/mozilla/mozjpeg) 71 | - [libimagequant](https://github.com/ImageOptim/libimagequant) 72 | - [uniffi-rs](https://github.com/mozilla/uniffi-rs) 73 | 74 | 有关所使用依赖项的完整列表,请查看 [catalog](/gradle/libs.versions.toml) 文件 75 | 76 | ## License 77 | 78 | ``` 79 | MIT License 80 | 81 | Copyright (c) 2024 LazyIonEs 82 | 83 | Permission is hereby granted, free of charge, to any person obtaining a copy 84 | of this software and associated documentation files (the "Software"), to deal 85 | in the Software without restriction, including without limitation the rights 86 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 87 | copies of the Software, and to permit persons to whom the Software is 88 | furnished to do so, subject to the following conditions: 89 | 90 | The above copyright notice and this permission notice shall be included in all 91 | copies or substantial portions of the Software. 92 | 93 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 94 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 95 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 96 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 97 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 98 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 99 | SOFTWARE. 100 | ``` 101 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## AndroidToolKit 4 | 5 |

6 | 7 | Static Badge 8 | 9 | 10 | 11 | 12 | 13 |

14 | 15 | 16 | 21 | 22 | 23 | [简体中文](./README.md) | English 24 | 25 | Desktop tools applicable to Android development, supporting Windows, Mac and Linux :tada: 26 | > The Linux platform has not been tested. If you have any questions, please give us feedback in time. 27 | 28 | ## Key Features 29 | 30 | - [x] Signature Information - Analyze the signature information of (APK/Signature) (modulus, md5, sha-1, sha-256, etc.) 31 | - [x] APK Information - Parse `AndroidManifest.xml` and extract some information 32 | - [x] APK Signature - Sign your APK 33 | - [x] Signature Generation - Generate a signed certificate 34 | - [x] Icon Generation - Generate icons of multiple sizes with one click 35 | - [x] Cache Cleaner - Cleans the Android project cache directory 36 | 37 | > Supports APK signature verification; single signature verification (signature password required); file dragging; apk 38 | > signature file alignment; generate signatures with specified key type and key size; appearance light and dark modes. 39 | 40 | ## Download - [Releases](https://github.com/LazyIonEs/AndroidToolKit/releases/latest) 41 | 42 | | device | chip | download | 43 | |:-------:|:-----------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| 44 | | macos | apple | [`macos-arm64.dmg`](https://github.com/LazyIonEs/AndroidToolKit/releases/latest/download/AndroidToolKit-macos-arm64.dmg) | 45 | | macos | intel | [`macos-x64.dmg`](https://github.com/LazyIonEs/AndroidToolKit/releases/latest/download/AndroidToolKit-macos-x64.dmg) | 46 | | windows | intel / amd | [`windows-x64.msi`](https://github.com/LazyIonEs/AndroidToolKit/releases/latest/download/AndroidToolKit-windows-x64.msi) **/** [`windows-x64.exe`](https://github.com/LazyIonEs/AndroidToolKit/releases/latest/download/AndroidToolKit-windows-x64.exe) | 47 | | linux | - | [`linux-amd64.deb`](https://github.com/LazyIonEs/AndroidToolKit/releases/latest/download/AndroidToolKit-linux-amd64.deb) **/** [`linux-x86_64.rpm`](https://github.com/LazyIonEs/AndroidToolKit/releases/latest/download/AndroidToolKit-linux-x86_64.rpm) | 48 | 49 | > [!CAUTION] 50 | > It is not recommended to install the Windows version to the C drive (the default installation path is the C drive : 51 | > clown_face:), which may cause problems such as permissions. If you encounter any problems, please refer 52 | > to [FAQ](FAQ.md) 53 | 54 | ## screenshot 55 | 56 | | Signature Information | Signature Information | Signature Information | 57 | |:----------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------:| 58 | | | | | 59 | | APK Information | APK Signature | Signature generation | 60 | | | | | 61 | | Icon Generation | Cache Cleaner | Black and white theme | 62 | | | | | 63 | | Icon Generation | Cache Cleaner | Black and white theme | 64 | | | | | 65 | 66 | ## Technology Stack 67 | 68 | - [Kotlin Multiplatform](https://kotlinlang.org/lp/multiplatform/) 69 | - [Kotlin Coroutines](https://github.com/Kotlin/kotlinx.coroutines) 70 | - [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/) 71 | - [Kotlinx Serialization](https://github.com/Kotlin/kotlinx.serialization) 72 | - [Rust](https://github.com/rust-lang/rust) 73 | - [mozjpeg](https://github.com/mozilla/mozjpeg) 74 | - [libimagequant](https://github.com/ImageOptim/libimagequant) 75 | - [uniffi-rs](https://github.com/mozilla/uniffi-rs) 76 | 77 | For a complete list of dependencies used, check the [catalog](/gradle/libs.versions.toml) file 78 | 79 | ## License 80 | 81 | ``` 82 | MIT License 83 | 84 | Copyright (c) 2024 LazyIonEs 85 | 86 | Permission is hereby granted, free of charge, to any person obtaining a copy 87 | of this software and associated documentation files (the "Software"), to deal 88 | in the Software without restriction, including without limitation the rights 89 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 90 | copies of the Software, and to permit persons to whom the Software is 91 | furnished to do so, subject to the following conditions: 92 | 93 | The above copyright notice and this permission notice shall be included in all 94 | copies or substantial portions of the Software. 95 | 96 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 97 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 98 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 99 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 100 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 101 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 102 | SOFTWARE. 103 | ``` 104 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | // this is necessary to avoid the plugins to be loaded multiple times 3 | // in each subproject's classloader 4 | alias(libs.plugins.jetbrainsCompose) apply false 5 | alias(libs.plugins.kotlinMultiplatform) apply false 6 | alias(libs.plugins.githubBuildconfig) apply false 7 | alias(libs.plugins.compose.compiler) apply false 8 | alias(libs.plugins.about.libraries) apply false 9 | alias(libs.plugins.hot.reload) apply false 10 | } -------------------------------------------------------------------------------- /composeApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat 2 | import org.jetbrains.compose.reload.ComposeHotRun 3 | import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag 4 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 5 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 6 | import java.io.FileOutputStream 7 | import java.nio.file.Files 8 | 9 | plugins { 10 | alias(libs.plugins.kotlinMultiplatform) 11 | alias(libs.plugins.jetbrainsCompose) 12 | alias(libs.plugins.githubBuildconfig) 13 | alias(libs.plugins.kotlinSerialization) 14 | alias(libs.plugins.compose.compiler) 15 | alias(libs.plugins.about.libraries) 16 | alias(libs.plugins.hot.reload) 17 | } 18 | 19 | val javaLanguageVersion = JavaLanguageVersion.of(17) 20 | val linuxArmTarget = "aarch64-unknown-linux-gnu" 21 | val linuxX64Target = "x86_64-unknown-linux-gnu" 22 | 23 | val kitVersion by extra("1.5.5") 24 | val kitPackageName = "AndroidToolKit" 25 | val kitDescription = "Desktop tools applicable to Android development, supporting Windows, Mac and Linux" 26 | val kitCopyright = "Copyright (c) 2024 LazyIonEs" 27 | val kitVendor = "LazyIonEs" 28 | val kitLicenseFile = project.rootProject.file("LICENSE") 29 | 30 | val useCross = (properties.getOrDefault("useCross", "false") as String).toBoolean() 31 | val isLinuxAarch64 = (properties.getOrDefault("isLinuxAarch64", "false") as String).toBoolean() 32 | 33 | val rustGeneratedSource = "${layout.buildDirectory.get()}/generated/source/uniffi/main/org/tool/kit/kotlin" 34 | 35 | val aboutLibrariesSource = "src/commonMain/composeResources/files/aboutlibraries.json" 36 | 37 | group = "org.tool.kit" 38 | version = kitVersion 39 | 40 | configurations.commonMainApi { 41 | // com.android.tools:sdk-common 42 | exclude(group = "org.bouncycastle", module = "bcpkix-jdk18on") 43 | exclude(group = "org.bouncycastle", module = "bcprov-jdk18on") 44 | exclude(group = "org.bouncycastle", module = "bcutil-jdk18on") 45 | } 46 | 47 | kotlin { 48 | jvmToolchain { 49 | languageVersion.set(javaLanguageVersion) 50 | } 51 | 52 | jvm("desktop") 53 | jvmToolchain(17) 54 | 55 | sourceSets { 56 | val desktopMain by getting 57 | 58 | desktopMain.kotlin.srcDir(rustGeneratedSource) 59 | 60 | commonMain.dependencies { 61 | implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) 62 | implementation(compose.runtime) 63 | implementation(compose.foundation) 64 | implementation(compose.material) 65 | implementation(compose.ui) 66 | implementation(compose.components.resources) 67 | implementation(compose.material3) 68 | implementation(compose.materialIconsExtended) 69 | implementation(compose.foundation) 70 | implementation(libs.slf4j.api) 71 | implementation(libs.slf4j.simple) 72 | implementation(libs.android.apksig) 73 | implementation(libs.android.sdk.common) 74 | implementation(libs.android.binary.resources) 75 | implementation(libs.commons.codec) 76 | implementation(libs.asm) 77 | implementation(libs.lifecycle.viewmodel.compose) 78 | implementation(libs.jna) 79 | implementation(libs.filekit.core) 80 | implementation(libs.filekit.dialogs) 81 | implementation(libs.filekit.dialogs.compose) 82 | implementation(libs.multiplatform.settings) 83 | implementation(libs.multiplatform.settings.coroutines) 84 | implementation(libs.multiplatform.settings.serialization) 85 | implementation(libs.kotlinx.serialization.json) 86 | implementation(libs.kotlinx.datetime) 87 | implementation("com.jetbrains.intellij.platform:util:243.26053.20") { 88 | exclude(group = "com.fasterxml", module = "aalto-xml") 89 | exclude(group = "com.github.ben-manes.caffeine", module = "caffeine") 90 | exclude(group = "com.intellij.platform", module = "kotlinx-coroutines-core-jvm") 91 | exclude(group = "com.intellij.platform", module = "kotlinx-coroutines-debug") 92 | exclude(group = "com.jetbrains.intellij.platform", module = "util-jdom") 93 | exclude(group = "com.jetbrains.intellij.platform", module = "util-class-loader") 94 | exclude(group = "com.jetbrains.intellij.platform", module = "util-xml-dom") 95 | exclude(group = "commons-codec", module = "commons-codec") 96 | exclude(group = "commons-io", module = "commons-io") 97 | exclude(group = "net.java.dev.jna", module = "jna-platform") 98 | exclude(group = "org.apache.commons", module = "commons-compress") 99 | exclude(group = "org.jetbrains.intellij.deps.fastutil", module = "intellij-deps-fastutil") 100 | exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib") 101 | exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-core-jvm") 102 | exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json-jvm") 103 | exclude(group = "org.lz4", module = "lz4-java") 104 | exclude(group = "org.slf4j", module = "log4j-over-slf4j") 105 | exclude(group = "oro", module = "oro") 106 | } 107 | runtimeOnly(libs.kotlinx.coroutines.swing) 108 | implementation(libs.about.libraries.core) 109 | implementation(libs.about.libraries.compose.m3) 110 | implementation(libs.coil.compose) 111 | implementation(libs.zoomimage.compose.coil3) 112 | } 113 | desktopMain.dependencies { 114 | implementation(compose.desktop.currentOs) 115 | } 116 | } 117 | } 118 | 119 | tasks.withType { 120 | compilerOptions.jvmTarget = JvmTarget.JVM_17 121 | compilerOptions.freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn") 122 | } 123 | 124 | tasks.withType { 125 | javaLauncher.set(javaToolchains.launcherFor { 126 | languageVersion.set(javaLanguageVersion) 127 | }) 128 | } 129 | 130 | compose.desktop { 131 | application { 132 | mainClass = "MainKt" 133 | 134 | jvmArgs += listOf("-Dapple.awt.application.appearance=system") 135 | 136 | this@application.dependsOn("rustTasks") 137 | 138 | sourceSets.forEach { 139 | it.java.srcDir(rustGeneratedSource) 140 | } 141 | 142 | nativeDistributions { 143 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Exe, TargetFormat.Deb, TargetFormat.Rpm) 144 | packageName = kitPackageName 145 | packageVersion = kitVersion 146 | description = kitDescription 147 | copyright = kitCopyright 148 | vendor = kitVendor 149 | licenseFile.set(kitLicenseFile) 150 | 151 | modules( 152 | "java.compiler", 153 | "java.instrument", 154 | "java.naming", 155 | "java.prefs", 156 | "java.rmi", 157 | "java.sql", 158 | "jdk.management", 159 | "jdk.security.auth", 160 | "jdk.unsupported" 161 | ) 162 | 163 | outputBaseDir.set(project.layout.projectDirectory.dir("output")) 164 | appResourcesRootDir.set(project.layout.projectDirectory.dir("resources")) 165 | 166 | linux { 167 | debPackageVersion = packageVersion 168 | rpmPackageVersion = packageVersion 169 | debMaintainer = "lazyiones@gmail.com" 170 | iconFile.set(project.file("launcher/icon.png")) 171 | } 172 | macOS { 173 | dmgPackageVersion = packageVersion 174 | pkgPackageVersion = packageVersion 175 | 176 | packageBuildVersion = packageVersion 177 | dmgPackageBuildVersion = packageVersion 178 | pkgPackageBuildVersion = packageVersion 179 | bundleID = "org.tool.kit" 180 | 181 | dockName = kitPackageName 182 | iconFile.set(project.file("launcher/icon.icns")) 183 | } 184 | windows { 185 | msiPackageVersion = packageVersion 186 | exePackageVersion = packageVersion 187 | menuGroup = packageName 188 | dirChooser = true 189 | perUserInstall = true 190 | shortcut = true 191 | menu = true 192 | upgradeUuid = "2B0C6D0B-BEB7-4E64-807E-BEE0F91C7B04" 193 | iconFile.set(project.file("launcher/icon.ico")) 194 | } 195 | } 196 | buildTypes.release.proguard { 197 | obfuscate.set(true) 198 | optimize.set(true) 199 | joinOutputJars.set(true) 200 | configurationFiles.from(project.file("compose-desktop.pro")) 201 | } 202 | } 203 | } 204 | 205 | task("rustTasks") { 206 | runBuildRust() 207 | } 208 | 209 | tasks.getByName("compileKotlinDesktop").doLast { runBuildRust() } 210 | 211 | // 执行导出收集依赖项详细信息json文件 212 | tasks.getByName("copyNonXmlValueResourcesForCommonMain").dependsOn("exportLibraryDefinitions") 213 | 214 | buildConfig { 215 | className("BuildConfig") 216 | packageName("org.tool.kit") 217 | buildConfigField("APP_NAME", kitPackageName) 218 | buildConfigField("APP_VERSION", kitVersion) 219 | buildConfigField("APP_DESCRIPTION", kitDescription) 220 | buildConfigField("APP_COPYRIGHT", kitCopyright) 221 | buildConfigField("APP_VENDOR", kitVendor) 222 | buildConfigField("APP_LICENSE", "MIT License") 223 | buildConfigField("APP_LICENSE_URI", uri("https://opensource.org/license/mit")) 224 | buildConfigField("APP_LICENSE_FILE", kitLicenseFile) 225 | buildConfigField("AUTHOR_GITHUB_URI", uri("https://github.com/LazyIonEs")) 226 | buildConfigField("APP_GITHUB_URI", uri("https://github.com/LazyIonEs/AndroidToolKit")) 227 | } 228 | 229 | enum class OS { 230 | LINUX, 231 | WINDOWS, 232 | MAC 233 | } 234 | 235 | fun currentOs(): OS { 236 | val os = System.getProperty("os.name") 237 | return when { 238 | os.equals("Mac OS X", ignoreCase = true) -> OS.MAC 239 | os.startsWith("Win", ignoreCase = true) -> OS.WINDOWS 240 | os.startsWith("Linux", ignoreCase = true) -> OS.LINUX 241 | else -> error("Unknown OS name: $os") 242 | } 243 | } 244 | 245 | fun runBuildRust() { 246 | val destinyLibFile = getRustDestinyLibFile() 247 | val destinyKtFile = getRustDestinyKtFile() 248 | if (destinyLibFile.exists() && destinyKtFile.exists()) { 249 | // 已存在,不重新编译 250 | return 251 | } 252 | buildRust() 253 | copyRustBuild() 254 | generateKotlinFromUdl() 255 | } 256 | 257 | fun buildRust() { 258 | providers.exec { 259 | println("Build rs called") 260 | val binary = if (currentOs() == OS.LINUX && useCross) { 261 | "cross" 262 | } else { 263 | "cargo" 264 | } 265 | 266 | val params = mutableListOf( 267 | binary, "build", "--release", "--features=uniffi/cli", 268 | ) 269 | 270 | if (currentOs() == OS.LINUX && useCross) { 271 | if (isLinuxAarch64) { 272 | params.add("--target=$linuxArmTarget") 273 | } else { 274 | params.add("--target=$linuxX64Target") 275 | } 276 | } 277 | 278 | workingDir = File(rootDir, "rs") 279 | commandLine = params 280 | }.result.get() 281 | } 282 | 283 | fun copyRustBuild() { 284 | val workingDirPath = if (currentOs() == OS.LINUX && useCross) { 285 | if (isLinuxAarch64) { 286 | "rs/target/$linuxArmTarget/release" 287 | } else { 288 | "rs/target/$linuxX64Target/release" 289 | } 290 | } else { 291 | "rs/target/release" 292 | } 293 | 294 | val workingDir = File(rootDir, workingDirPath) 295 | 296 | val originLib = when (currentOs()) { 297 | OS.LINUX -> "libtoolkit_rs.so" 298 | OS.WINDOWS -> "toolkit_rs.dll" 299 | OS.MAC -> "libtoolkit_rs.dylib" 300 | } 301 | 302 | val originFile = File(workingDir, originLib) 303 | val destinyFile = getRustDestinyLibFile() 304 | 305 | Files.copy(originFile.toPath(), FileOutputStream(destinyFile)) 306 | println("Copy rs build completed") 307 | } 308 | 309 | fun getRustDestinyLibFile(): File { 310 | val outputDir = "${layout.buildDirectory.asFile.get().absolutePath}/classes/kotlin/desktop/main" 311 | val directory = File(outputDir) 312 | directory.mkdirs() 313 | val destinyLib = when (currentOs()) { 314 | OS.LINUX -> "libuniffi_toolkit.so" 315 | OS.WINDOWS -> "uniffi_toolkit.dll" 316 | OS.MAC -> "libuniffi_toolkit.dylib" 317 | } 318 | val destinyFile = File(directory, destinyLib) 319 | return destinyFile 320 | } 321 | 322 | fun getRustDestinyKtFile() = 323 | File(rustGeneratedSource + File.separator + "uniffi" + File.separator + "toolkit", "toolkit.kt") 324 | 325 | fun generateKotlinFromUdl() { 326 | providers.exec { 327 | workingDir = File(rootDir, "rs") 328 | commandLine = listOf( 329 | "cargo", "run", "--features=uniffi/cli", 330 | "--bin", "uniffi-bindgen", "generate", "src/toolkit.udl", 331 | "--language", "kotlin", 332 | "--out-dir", rustGeneratedSource 333 | ) 334 | }.result.get() 335 | } 336 | 337 | aboutLibraries { 338 | export { 339 | outputFile = file(aboutLibrariesSource) 340 | } 341 | } 342 | 343 | tasks.withType().configureEach { 344 | mainClass.set("MainKt") 345 | } 346 | 347 | composeCompiler { 348 | featureFlags.add(ComposeFeatureFlag.OptimizeNonSkippingGroups) 349 | } -------------------------------------------------------------------------------- /composeApp/compose-desktop.pro: -------------------------------------------------------------------------------- 1 | -ignorewarnings 2 | # -verbose 3 | 4 | -dontnote java.awt.* 5 | -dontnote java.nio.* 6 | -dontnote java.util.* 7 | -dontnote com.intellij.** 8 | -dontnote org.jdom.** 9 | -dontnote com.google.** 10 | -dontnote org.apache.** 11 | -dontnote com.sun.** 12 | -dontnote javax.activation.** 13 | -dontnote org.intellij.** 14 | -dontnote org.jetbrains.** 15 | 16 | -dontwarn java.awt.* 17 | -dontwarn java.nio.* 18 | -dontwarn java.util.* 19 | -dontwarn com.intellij.** 20 | -dontwarn org.jdom.** 21 | -dontwarn com.google.** 22 | -dontwarn org.apache.** 23 | 24 | -keep enum org.jetbrains.nav_cupcake.** { *; } 25 | -keep class com.android.ddmlib.** { *; } 26 | -keep class org.lwjgl.** { *; } 27 | -keep class org.bouncycastle.** { *; } 28 | -keep class com.android.apksig.** { *; } 29 | -keep class org.sqlite.** { *; } 30 | -keep class org.slf4j.** { *; } 31 | #-keep class java.nio.** { *; } 32 | #-keep class java.util.concurrent.** { *; } 33 | -keep class uniffi.toolkit.** { *; } 34 | -keep class androidx.datastore.** { *; } 35 | -keep class com.sun.jna.** { *; } 36 | -keep class * implements com.sun.jna.** { *; } 37 | -keepclassmembers class * extends com.sun.jna.* { public *; } 38 | -keepclassmembers class androidx.datastore.** { *; } 39 | -keep class kotlinx.coroutines.swing.SwingDispatcherFactory 40 | -keep class androidx.datastore.preferences.protobuf.** { *; } 41 | -keepclassmembers class androidx.datastore.preferences.protobuf.** { *; } 42 | -keepattributes Signature,LineNumberTable,RuntimeVisibleAnnotations,AnnotationDefault,*Annotation*,InnerClasses 43 | 44 | # coil3 45 | -keep class okio.** { *; } 46 | -keep class * extends coil3.util.DecoderServiceLoaderTarget { *; } 47 | -keep class * extends coil3.util.FetcherServiceLoaderTarget { *; } -------------------------------------------------------------------------------- /composeApp/launcher/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/launcher/icon.icns -------------------------------------------------------------------------------- /composeApp/launcher/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/launcher/icon.ico -------------------------------------------------------------------------------- /composeApp/launcher/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/launcher/icon.png -------------------------------------------------------------------------------- /composeApp/libs/bcpkix-jdk18on-1.80.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/libs/bcpkix-jdk18on-1.80.jar -------------------------------------------------------------------------------- /composeApp/libs/bcprov-jdk18on-1.80.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/libs/bcprov-jdk18on-1.80.jar -------------------------------------------------------------------------------- /composeApp/libs/bcutil-jdk18on-1.80.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/libs/bcutil-jdk18on-1.80.jar -------------------------------------------------------------------------------- /composeApp/resources/common/honor_unsigned.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/resources/common/honor_unsigned.apk -------------------------------------------------------------------------------- /composeApp/resources/common/huawei_unsigned.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/resources/common/huawei_unsigned.apk -------------------------------------------------------------------------------- /composeApp/resources/common/oppo_unsigned.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/resources/common/oppo_unsigned.apk -------------------------------------------------------------------------------- /composeApp/resources/common/qq_unsigned.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/resources/common/qq_unsigned.apk -------------------------------------------------------------------------------- /composeApp/resources/common/vivo_unsigned.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/resources/common/vivo_unsigned.apk -------------------------------------------------------------------------------- /composeApp/resources/common/xiaomi_unsigned.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/resources/common/xiaomi_unsigned.apk -------------------------------------------------------------------------------- /composeApp/resources/linux/aapt2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/resources/linux/aapt2 -------------------------------------------------------------------------------- /composeApp/resources/macos-arm64/aapt2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/resources/macos-arm64/aapt2 -------------------------------------------------------------------------------- /composeApp/resources/macos-x64/aapt2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/resources/macos-x64/aapt2 -------------------------------------------------------------------------------- /composeApp/resources/windows/aapt2.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/resources/windows/aapt2.exe -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/src/commonMain/composeResources/drawable/icon.png -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/files/lottie_main_3_dark.json: -------------------------------------------------------------------------------- 1 | {"v":"4.5.9","fr":29.9700012207031,"ip":0,"op":87.0000035435826,"w":350,"h":350,"ddd":0,"assets":[],"layers":[{"ddd":0,"ind":0,"ty":4,"nm":"Earth Outline","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[175,175,0]},"a":{"a":0,"k":[30,30,0]},"s":{"a":0,"k":[235,235,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[11.238,-3.576],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0.491,2.648],[-0.322,0.244],[-0.809,1.455],[0.254,0.981],[-0.086,1.297],[0.058,0.483],[-0.104,1.657],[0.728,0.741],[-5.105,2.693],[-0.016,-0.329],[0,0],[0,0],[-0.578,0.534],[-0.53,0.196],[0.038,-0.214],[1.318,-0.692],[-0.184,-2.341],[0,0],[0,0],[-0.923,0],[-1.77,1.353],[-1.962,0],[-1.56,0.67],[-0.514,0.064],[0.172,-0.579],[0.69,0],[0.224,0.228],[1.738,-1.812],[-0.082,-1.981],[0,0],[0.12,0.054],[0.902,0],[0.163,-0.244],[-0.296,-0.703],[0.728,-1.222],[-0.433,-0.761],[-0.73,0],[-1.555,0.37],[-0.268,0],[-0.336,-0.928],[-0.862,-0.664],[-0.46,0],[-0.188,0.611],[-0.018,0.06],[-0.021,-0.232],[-0.47,0],[-1.023,0.474],[-0.257,0.005],[-0.042,-0.254],[-0.51,-0.775],[-3.249,0],[-1.154,1.791],[-0.17,1.174],[-0.397,0.274],[-1.196,-1.563],[-0.853,0],[0,0],[-0.269,0.306]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-1.183,-2.332],[0.317,-0.291],[0.807,-0.609],[0.734,-1.321],[-0.156,-0.594],[0.113,-1.697],[-0.049,-0.419],[0.119,-1.901],[2.634,-5.139],[-0.179,0.521],[0,0],[0,0],[0.409,0],[2.67,-2.462],[-0.004,0.12],[-0.193,1.072],[-1.65,0.869],[0,0],[0,0],[0.985,0.385],[2.54,0],[1.597,-1.22],[1.045,0],[0.909,-0.391],[1.124,0.896],[-0.513,1.709],[-0.1,0],[-1.647,-1.646],[-1.305,1.36],[0,0],[-0.123,-0.055],[-1.414,-0.644],[-0.784,0],[-0.475,0.717],[0.695,1.661],[-0.524,0.878],[0.269,0.473],[0.632,0],[0.396,-0.096],[0.546,0],[0.254,0.701],[0.535,0.41],[1.19,0],[0.015,-0.053],[0.083,0.09],[0.106,1.292],[0.458,0],[0.901,-0.411],[0.065,0.163],[0.115,0.676],[1.587,2.4],[1.945,0],[0.277,-0.431],[0.118,-0.819],[0.005,0],[0.48,0.629],[0,0],[0.472,0],[-0.103,12.386]],"v":[[8.474,26.686],[8.91,24.086],[12.306,21.33],[14.25,15.266],[6.25,15.233],[4.691,12.411],[-1.113,8.062],[-4.88,8.209],[-8.29,7.136],[-10.107,5.883],[-9.594,5.712],[-10.536,5.587],[-11.44,4.964],[-12.821,5.284],[-15.189,4.969],[-18.473,6.561],[-18.79,6.666],[-18.772,6.731],[-21.627,8.089],[-24.985,12.614],[-27.523,5.116],[-26.574,4.333],[-24.124,1.639],[-23.757,-1.405],[-24.001,-3.966],[-24.004,-6.647],[-24,-9.157],[-24.931,-12.72],[-13.021,-24.776],[-13.279,-23.558],[-13.234,-22.604],[-12.28,-22.604],[-11.136,-23.282],[-6.733,-26.666],[-6.792,-26.172],[-9.549,-23.751],[-13.067,-19.463],[-13.018,-18.839],[-12.435,-18.61],[-9.559,-18.028],[-3.418,-20.782],[1.648,-23.154],[5.726,-24.458],[8.374,-25.439],[8.867,-22.059],[6.511,-19.073],[6.027,-19.327],[0.197,-19.007],[-1.757,-13.668],[-1.724,-13.2],[-2.087,-13.365],[-5.626,-14.564],[-6.96,-13.911],[-6.856,-11.59],[-7.794,-7.187],[-8.338,-4.787],[-6.833,-4.075],[-3.672,-4.61],[-2.674,-4.753],[-1.711,-3.74],[-0.28,-1.504],[1.18,-0.9],[2.907,-2.652],[2.96,-2.827],[3.074,-2.386],[4.455,-0.899],[6.517,-1.559],[8.12,-2.06],[8.289,-1.279],[9.054,1.013],[17.078,5.995],[21.816,3.257],[22.365,1.019],[23.192,-2.251],[24.901,-0.644],[26.897,0.708],[26.898,0.708],[27.995,0.233]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[0,13.734],[0.671,2.488],[0,0],[0,0],[0.012,0.044],[0.271,0.731],[0.006,0.021],[0.301,0.657],[0.054,0.114],[0.337,0.6],[0.088,0.152],[0.382,0.563],[0.069,0.1],[0.398,0.499],[0.075,0.09],[0.489,0.518],[0.076,0.079],[6.459,1.095],[0.042,0.006],[0.76,0.067],[0.124,0.008],[0.768,0],[0.513,-0.026],[0.007,-0.001],[3.244,-1.383],[0,0],[0,0],[0,-12.178],[-3.848,-5.044],[0,0],[0,0],[-0.147,-0.181],[-0.08,-0.095],[-0.105,-0.121],[0,0],[0,0],[-4.412,-1.778],[0,0],[0,0],[-0.059,-0.022],[-0.183,-0.065],[-0.259,-0.083],[-0.208,-0.06],[-0.246,-0.065],[-0.213,-0.052],[-0.253,-0.056],[-0.211,-0.041],[-0.268,-0.045],[-0.203,-0.028],[-0.287,-0.033],[-0.192,-0.019],[-0.323,-0.02],[-0.16,-0.007],[-0.487,0],[-0.577,0.032],[-0.182,0.013],[-0.395,0.045],[-0.21,0.03],[-0.364,0.063],[-0.215,0.042],[-0.39,0.093],[-0.148,0.038],[0,0],[0,0]],"o":[[0,-2.698],[0,0],[0,0],[-0.013,-0.046],[-0.214,-0.756],[-0.008,-0.021],[-0.255,-0.682],[-0.053,-0.115],[-0.295,-0.625],[-0.086,-0.152],[-0.346,-0.59],[-0.07,-0.101],[-0.366,-0.524],[-0.073,-0.09],[-0.45,-0.552],[-0.075,-0.081],[-4.359,-4.53],[-0.042,-0.008],[-0.744,-0.123],[-0.123,-0.011],[-0.753,-0.057],[-0.516,0],[-0.006,0],[-3.521,0.181],[0,0],[0,0],[-10.449,4.697],[0,6.821],[0,0],[0,0],[0.141,0.183],[0.077,0.097],[0.103,0.124],[0,0],[0,0],[3.086,3.523],[0,0],[0,0],[0.059,0.022],[0.183,0.068],[0.257,0.09],[0.205,0.065],[0.246,0.073],[0.212,0.058],[0.252,0.063],[0.209,0.045],[0.265,0.052],[0.202,0.034],[0.284,0.042],[0.19,0.023],[0.32,0.03],[0.16,0.011],[0.486,0.024],[0.585,0],[0.18,-0.012],[0.399,-0.031],[0.211,-0.025],[0.366,-0.05],[0.215,-0.038],[0.395,-0.079],[0.149,-0.037],[0,0],[0,0],[12.613,-3.546]],"v":[[30,0],[28.965,-7.8],[28.966,-7.808],[28.906,-8.027],[28.864,-8.159],[28.142,-10.392],[28.119,-10.454],[27.279,-12.461],[27.123,-12.805],[26.17,-14.642],[25.908,-15.098],[24.821,-16.831],[24.609,-17.128],[23.463,-18.663],[23.246,-18.938],[21.835,-20.54],[21.608,-20.779],[5.031,-29.569],[4.906,-29.591],[2.651,-29.876],[2.281,-29.903],[0,-30],[-1.541,-29.96],[-1.563,-29.959],[-11.746,-27.61],[-12.25,-27.396],[-12.253,-27.373],[-30,0],[-23.86,18.157],[-23.854,18.184],[-23.717,18.36],[-23.276,18.899],[-23.041,19.187],[-22.734,19.561],[-22.596,19.723],[-22.564,19.738],[-11.172,27.834],[-11.17,27.851],[-10.62,28.06],[-10.442,28.123],[-9.893,28.317],[-9.12,28.579],[-8.5,28.766],[-7.761,28.976],[-7.124,29.138],[-6.366,29.315],[-5.737,29.444],[-4.937,29.587],[-4.331,29.683],[-3.474,29.792],[-2.903,29.858],[-1.938,29.931],[-1.46,29.963],[0,30],[1.741,29.947],[2.282,29.905],[3.474,29.793],[4.107,29.712],[5.202,29.54],[5.848,29.424],[7.022,29.163],[7.472,29.062],[8.103,28.901],[8.108,28.878]],"c":true}},"nm":"Path 2","mn":"ADBE Vector Shape - Group"},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge"},{"ty":"mm","mm":4,"nm":"Merge Paths 2","mn":"ADBE Vector Filter - Merge"},{"ty":"fl","c":{"a":0,"k":[0.6941,0.8196,0.5412,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[30,30],"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":5,"mn":"ADBE Vector Group"}],"ip":0,"op":900.000036657751,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":1,"ty":4,"nm":"Plane Outline","ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":0,"s":[0],"e":[359]},{"t":87.0000035435826}]},"p":{"a":0,"k":[175,175,0]},"a":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":16,"s":[174.42,246.5,0],"e":[174.42,232.5,0],"to":[0,-2.33333325386047,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":43,"s":[174.42,232.5,0],"e":[174.42,246.5,0],"to":[0,0,0],"ti":[0,-2.33333325386047,0]},{"t":69.0000028104276}]},"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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":16,"s":[150,150,100],"e":[186,186,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":43,"s":[186,186,100],"e":[150,150,100]},{"t":69.0000028104276}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[-10.521,3.811],[-0.007,5.58],[5.236,1.909],[11.201,0],[0,0],[0,0],[0,0],[0,0],[0,0],[1.132,0],[0,0],[0.676,-0.952],[0,-0.745],[-0.134,-0.383],[0,0],[0,0],[0,0],[1.458,0],[0,0],[0.673,-1.006],[-0.466,-1.118],[0,0],[0,0],[0,0],[0,0],[0,-0.469],[-0.401,-0.604],[-1.208,0],[0,0],[-0.561,1.346],[0,0],[0,0],[0,0],[0,-0.395],[-0.445,-0.618],[-1.168,0],[0,0],[-0.687,0.905]],"o":[[0,0],[0,0],[0,0],[11.125,0],[5.248,-1.903],[0,-5.588],[-10.514,-3.838],[0,0],[0,0],[0,0],[0,0],[0,0],[-0.687,-0.901],[0,0],[-1.168,0],[-0.445,0.622],[0,0.394],[0,0],[0,0],[0,0],[-0.561,-1.346],[0,0],[-1.208,0],[-0.676,1.009],[0,0],[0,0],[0,0],[0,0],[-0.188,0.448],[0,0.703],[0.673,1.005],[0,0],[1.458,0],[0,0],[0,0],[0,0],[-0.134,0.383],[0,0.741],[0.676,0.956],[0,0],[1.132,0],[0,0]],"v":[[-6.09,90.416],[21.346,54.25],[48.785,18.076],[67.356,18.083],[99.946,12.348],[108.593,0.033],[99.975,-12.3],[67.252,-18.083],[48.778,-18.09],[21.346,-54.25],[10.944,-68.716],[-6.09,-90.417],[-18.724,-107.071],[-21.606,-108.5],[-43.306,-108.5],[-46.25,-106.984],[-46.923,-104.883],[-46.724,-103.704],[-17.212,-18.112],[-68.641,-15.053],[-90.751,-44.792],[-94.09,-47.017],[-104.788,-47.017],[-107.799,-45.407],[-108.128,-42.007],[-94.462,-5.212],[-97.408,0.5],[-94.408,6.5],[-108.128,42.008],[-108.406,43.399],[-107.799,45.407],[-104.788,47.017],[-94.09,47.017],[-90.751,44.792],[-68.641,15.396],[-17.19,18.055],[-46.724,103.704],[-46.923,104.883],[-46.25,106.98],[-43.306,108.5],[-21.606,108.5],[-18.724,107.067]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":0,"k":[0.2915,0.3948,0.1652,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[174.407,174.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[13.48,13.48],"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,"mn":"ADBE Vector Group"}],"ip":0,"op":900.000036657751,"st":0,"bm":0,"sr":1}]} -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/files/lottie_main_3_light.json: -------------------------------------------------------------------------------- 1 | {"v":"4.5.9","fr":29.9700012207031,"ip":0,"op":87.0000035435826,"w":350,"h":350,"ddd":0,"assets":[],"layers":[{"ddd":0,"ind":0,"ty":4,"nm":"Earth Outline","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[175,175,0]},"a":{"a":0,"k":[30,30,0]},"s":{"a":0,"k":[235,235,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[11.238,-3.576],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0.491,2.648],[-0.322,0.244],[-0.809,1.455],[0.254,0.981],[-0.086,1.297],[0.058,0.483],[-0.104,1.657],[0.728,0.741],[-5.105,2.693],[-0.016,-0.329],[0,0],[0,0],[-0.578,0.534],[-0.53,0.196],[0.038,-0.214],[1.318,-0.692],[-0.184,-2.341],[0,0],[0,0],[-0.923,0],[-1.77,1.353],[-1.962,0],[-1.56,0.67],[-0.514,0.064],[0.172,-0.579],[0.69,0],[0.224,0.228],[1.738,-1.812],[-0.082,-1.981],[0,0],[0.12,0.054],[0.902,0],[0.163,-0.244],[-0.296,-0.703],[0.728,-1.222],[-0.433,-0.761],[-0.73,0],[-1.555,0.37],[-0.268,0],[-0.336,-0.928],[-0.862,-0.664],[-0.46,0],[-0.188,0.611],[-0.018,0.06],[-0.021,-0.232],[-0.47,0],[-1.023,0.474],[-0.257,0.005],[-0.042,-0.254],[-0.51,-0.775],[-3.249,0],[-1.154,1.791],[-0.17,1.174],[-0.397,0.274],[-1.196,-1.563],[-0.853,0],[0,0],[-0.269,0.306]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-1.183,-2.332],[0.317,-0.291],[0.807,-0.609],[0.734,-1.321],[-0.156,-0.594],[0.113,-1.697],[-0.049,-0.419],[0.119,-1.901],[2.634,-5.139],[-0.179,0.521],[0,0],[0,0],[0.409,0],[2.67,-2.462],[-0.004,0.12],[-0.193,1.072],[-1.65,0.869],[0,0],[0,0],[0.985,0.385],[2.54,0],[1.597,-1.22],[1.045,0],[0.909,-0.391],[1.124,0.896],[-0.513,1.709],[-0.1,0],[-1.647,-1.646],[-1.305,1.36],[0,0],[-0.123,-0.055],[-1.414,-0.644],[-0.784,0],[-0.475,0.717],[0.695,1.661],[-0.524,0.878],[0.269,0.473],[0.632,0],[0.396,-0.096],[0.546,0],[0.254,0.701],[0.535,0.41],[1.19,0],[0.015,-0.053],[0.083,0.09],[0.106,1.292],[0.458,0],[0.901,-0.411],[0.065,0.163],[0.115,0.676],[1.587,2.4],[1.945,0],[0.277,-0.431],[0.118,-0.819],[0.005,0],[0.48,0.629],[0,0],[0.472,0],[-0.103,12.386]],"v":[[8.474,26.686],[8.91,24.086],[12.306,21.33],[14.25,15.266],[6.25,15.233],[4.691,12.411],[-1.113,8.062],[-4.88,8.209],[-8.29,7.136],[-10.107,5.883],[-9.594,5.712],[-10.536,5.587],[-11.44,4.964],[-12.821,5.284],[-15.189,4.969],[-18.473,6.561],[-18.79,6.666],[-18.772,6.731],[-21.627,8.089],[-24.985,12.614],[-27.523,5.116],[-26.574,4.333],[-24.124,1.639],[-23.757,-1.405],[-24.001,-3.966],[-24.004,-6.647],[-24,-9.157],[-24.931,-12.72],[-13.021,-24.776],[-13.279,-23.558],[-13.234,-22.604],[-12.28,-22.604],[-11.136,-23.282],[-6.733,-26.666],[-6.792,-26.172],[-9.549,-23.751],[-13.067,-19.463],[-13.018,-18.839],[-12.435,-18.61],[-9.559,-18.028],[-3.418,-20.782],[1.648,-23.154],[5.726,-24.458],[8.374,-25.439],[8.867,-22.059],[6.511,-19.073],[6.027,-19.327],[0.197,-19.007],[-1.757,-13.668],[-1.724,-13.2],[-2.087,-13.365],[-5.626,-14.564],[-6.96,-13.911],[-6.856,-11.59],[-7.794,-7.187],[-8.338,-4.787],[-6.833,-4.075],[-3.672,-4.61],[-2.674,-4.753],[-1.711,-3.74],[-0.28,-1.504],[1.18,-0.9],[2.907,-2.652],[2.96,-2.827],[3.074,-2.386],[4.455,-0.899],[6.517,-1.559],[8.12,-2.06],[8.289,-1.279],[9.054,1.013],[17.078,5.995],[21.816,3.257],[22.365,1.019],[23.192,-2.251],[24.901,-0.644],[26.897,0.708],[26.898,0.708],[27.995,0.233]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[0,13.734],[0.671,2.488],[0,0],[0,0],[0.012,0.044],[0.271,0.731],[0.006,0.021],[0.301,0.657],[0.054,0.114],[0.337,0.6],[0.088,0.152],[0.382,0.563],[0.069,0.1],[0.398,0.499],[0.075,0.09],[0.489,0.518],[0.076,0.079],[6.459,1.095],[0.042,0.006],[0.76,0.067],[0.124,0.008],[0.768,0],[0.513,-0.026],[0.007,-0.001],[3.244,-1.383],[0,0],[0,0],[0,-12.178],[-3.848,-5.044],[0,0],[0,0],[-0.147,-0.181],[-0.08,-0.095],[-0.105,-0.121],[0,0],[0,0],[-4.412,-1.778],[0,0],[0,0],[-0.059,-0.022],[-0.183,-0.065],[-0.259,-0.083],[-0.208,-0.06],[-0.246,-0.065],[-0.213,-0.052],[-0.253,-0.056],[-0.211,-0.041],[-0.268,-0.045],[-0.203,-0.028],[-0.287,-0.033],[-0.192,-0.019],[-0.323,-0.02],[-0.16,-0.007],[-0.487,0],[-0.577,0.032],[-0.182,0.013],[-0.395,0.045],[-0.21,0.03],[-0.364,0.063],[-0.215,0.042],[-0.39,0.093],[-0.148,0.038],[0,0],[0,0]],"o":[[0,-2.698],[0,0],[0,0],[-0.013,-0.046],[-0.214,-0.756],[-0.008,-0.021],[-0.255,-0.682],[-0.053,-0.115],[-0.295,-0.625],[-0.086,-0.152],[-0.346,-0.59],[-0.07,-0.101],[-0.366,-0.524],[-0.073,-0.09],[-0.45,-0.552],[-0.075,-0.081],[-4.359,-4.53],[-0.042,-0.008],[-0.744,-0.123],[-0.123,-0.011],[-0.753,-0.057],[-0.516,0],[-0.006,0],[-3.521,0.181],[0,0],[0,0],[-10.449,4.697],[0,6.821],[0,0],[0,0],[0.141,0.183],[0.077,0.097],[0.103,0.124],[0,0],[0,0],[3.086,3.523],[0,0],[0,0],[0.059,0.022],[0.183,0.068],[0.257,0.09],[0.205,0.065],[0.246,0.073],[0.212,0.058],[0.252,0.063],[0.209,0.045],[0.265,0.052],[0.202,0.034],[0.284,0.042],[0.19,0.023],[0.32,0.03],[0.16,0.011],[0.486,0.024],[0.585,0],[0.18,-0.012],[0.399,-0.031],[0.211,-0.025],[0.366,-0.05],[0.215,-0.038],[0.395,-0.079],[0.149,-0.037],[0,0],[0,0],[12.613,-3.546]],"v":[[30,0],[28.965,-7.8],[28.966,-7.808],[28.906,-8.027],[28.864,-8.159],[28.142,-10.392],[28.119,-10.454],[27.279,-12.461],[27.123,-12.805],[26.17,-14.642],[25.908,-15.098],[24.821,-16.831],[24.609,-17.128],[23.463,-18.663],[23.246,-18.938],[21.835,-20.54],[21.608,-20.779],[5.031,-29.569],[4.906,-29.591],[2.651,-29.876],[2.281,-29.903],[0,-30],[-1.541,-29.96],[-1.563,-29.959],[-11.746,-27.61],[-12.25,-27.396],[-12.253,-27.373],[-30,0],[-23.86,18.157],[-23.854,18.184],[-23.717,18.36],[-23.276,18.899],[-23.041,19.187],[-22.734,19.561],[-22.596,19.723],[-22.564,19.738],[-11.172,27.834],[-11.17,27.851],[-10.62,28.06],[-10.442,28.123],[-9.893,28.317],[-9.12,28.579],[-8.5,28.766],[-7.761,28.976],[-7.124,29.138],[-6.366,29.315],[-5.737,29.444],[-4.937,29.587],[-4.331,29.683],[-3.474,29.792],[-2.903,29.858],[-1.938,29.931],[-1.46,29.963],[0,30],[1.741,29.947],[2.282,29.905],[3.474,29.793],[4.107,29.712],[5.202,29.54],[5.848,29.424],[7.022,29.163],[7.472,29.062],[8.103,28.901],[8.108,28.878]],"c":true}},"nm":"Path 2","mn":"ADBE Vector Shape - Group"},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge"},{"ty":"mm","mm":4,"nm":"Merge Paths 2","mn":"ADBE Vector Filter - Merge"},{"ty":"fl","c":{"a":0,"k":[0.3451,0.3843,0.2863,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[30,30],"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":5,"mn":"ADBE Vector Group"}],"ip":0,"op":900.000036657751,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":1,"ty":4,"nm":"Plane Outline","ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":0,"s":[0],"e":[359]},{"t":87.0000035435826}]},"p":{"a":0,"k":[175,175,0]},"a":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":16,"s":[174.42,246.5,0],"e":[174.42,232.5,0],"to":[0,-2.33333325386047,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":43,"s":[174.42,232.5,0],"e":[174.42,246.5,0],"to":[0,0,0],"ti":[0,-2.33333325386047,0]},{"t":69.0000028104276}]},"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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":16,"s":[150,150,100],"e":[186,186,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":43,"s":[186,186,100],"e":[150,150,100]},{"t":69.0000028104276}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[-10.521,3.811],[-0.007,5.58],[5.236,1.909],[11.201,0],[0,0],[0,0],[0,0],[0,0],[0,0],[1.132,0],[0,0],[0.676,-0.952],[0,-0.745],[-0.134,-0.383],[0,0],[0,0],[0,0],[1.458,0],[0,0],[0.673,-1.006],[-0.466,-1.118],[0,0],[0,0],[0,0],[0,0],[0,-0.469],[-0.401,-0.604],[-1.208,0],[0,0],[-0.561,1.346],[0,0],[0,0],[0,0],[0,-0.395],[-0.445,-0.618],[-1.168,0],[0,0],[-0.687,0.905]],"o":[[0,0],[0,0],[0,0],[11.125,0],[5.248,-1.903],[0,-5.588],[-10.514,-3.838],[0,0],[0,0],[0,0],[0,0],[0,0],[-0.687,-0.901],[0,0],[-1.168,0],[-0.445,0.622],[0,0.394],[0,0],[0,0],[0,0],[-0.561,-1.346],[0,0],[-1.208,0],[-0.676,1.009],[0,0],[0,0],[0,0],[0,0],[-0.188,0.448],[0,0.703],[0.673,1.005],[0,0],[1.458,0],[0,0],[0,0],[0,0],[-0.134,0.383],[0,0.741],[0.676,0.956],[0,0],[1.132,0],[0,0]],"v":[[-6.09,90.416],[21.346,54.25],[48.785,18.076],[67.356,18.083],[99.946,12.348],[108.593,0.033],[99.975,-12.3],[67.252,-18.083],[48.778,-18.09],[21.346,-54.25],[10.944,-68.716],[-6.09,-90.417],[-18.724,-107.071],[-21.606,-108.5],[-43.306,-108.5],[-46.25,-106.984],[-46.923,-104.883],[-46.724,-103.704],[-17.212,-18.112],[-68.641,-15.053],[-90.751,-44.792],[-94.09,-47.017],[-104.788,-47.017],[-107.799,-45.407],[-108.128,-42.007],[-94.462,-5.212],[-97.408,0.5],[-94.408,6.5],[-108.128,42.008],[-108.406,43.399],[-107.799,45.407],[-104.788,47.017],[-94.09,47.017],[-90.751,44.792],[-68.641,15.396],[-17.19,18.055],[-46.724,103.704],[-46.923,104.883],[-46.25,106.98],[-43.306,108.5],[-21.606,108.5],[-18.724,107.067]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":0,"k":[0.298,0.4,0.1686,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[174.407,174.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[13.48,13.48],"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,"mn":"ADBE Vector Group"}],"ip":0,"op":900.000036657751,"st":0,"bm":0,"sr":1}]} -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/font/ZCOOLKuaiLe-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/composeApp/src/commonMain/composeResources/font/ZCOOLKuaiLe-Regular.ttf -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 签名信息 3 | APK信息 4 | APK签名 5 | 签名生成 6 | 图标生成 7 | 垃圾代码 8 | 缓存清理 9 | 设置 10 | 11 | 签名信息 12 | APK信息 13 | APK签名 14 | 签名生成 15 | 图标生成 16 | 垃圾代码 17 | 缓存清理 18 | 设置 19 | 20 | %1$s,%2$s 21 | 22 | 愣着干嘛,还不松手 23 | 上传APK 24 | 25 | 应用名称: 26 | 版本: 27 | 版本号: 28 | 包名: 29 | 编译SDK版本: 30 | 最小SDK版本: 31 | 目标SDK版本: 32 | ABIs: 33 | 文件MD5: 34 | 大小: 35 | 应用权限列表: 36 | 37 | 输出路径 38 | 密钥文件路径 39 | 密钥库密码 40 | 密钥别名 41 | 密钥密码 42 | APK文件 43 | 签名策略: 44 | 使用 V2 Only 签名的APK包仅支持Android 7及更高版本的系统安装和使用,请注意 45 | 开始签名 46 | APK Signature Scheme V1 47 | APK Signature Scheme V2,包含V1 48 | APK Signature Scheme V2,不包含V1 49 | APK Signature Scheme V3,包含V1和V2 50 | 51 | 请检查Error项 52 | 请检查空项 53 | 54 | 选择文件夹 55 | 总存储空间 56 | 已使用空间 57 | 可用空间 58 | 已扫描文件夹 59 | 已选择文件夹 60 | 个文件夹 61 | 占总存储空间 62 | 快速清理缓存目录 63 | 快速、深度扫描 Android 项目构建缓存目录\n批量删除构建缓存,释放磁盘空间\n选择 Android 项目或更上层目录 64 | yyyy年MM月dd日 65 | 请先选择需要删除的目录 66 | 日期(从新到旧) 67 | 日期(从旧到新) 68 | 大小(从大到小) 69 | 大小(从小到大) 70 | 名称(从 A 到 Z) 71 | 名称(从 Z 到 A) 72 | 确认删除缓存目录? 73 | 此操作将永久清除该目录下的所有文件,删除后数据将无法恢复,且可能导致下次构建时间延长。请确保您已备份所有重要数据。 74 | 确认删除 75 | 取消 76 | 77 | 一键生成应用图标 78 | 支持png、jpg、jpeg文件\n上传 1024 x 1024 像素的图片以获得最佳效果 79 | 更多设置 80 | 图标输出路径 81 | 图标名称 82 | 收起 83 | 展开 84 | 压缩自定义 85 | 无损压缩 86 | 有损压缩 87 | 压缩速度 88 | 快速 89 | 疯狂 90 | 外部目录名称 91 | Android目录 92 | PNG质量 93 | JPEG质量 94 | 最低限度:%1$d 95 | 目标:%1$d 96 | PNG 缩放算法 97 | JPEG 缩放算法 98 | 上传图片 99 | 开始制作 100 | 101 | AAR输出路径 102 | AAR 名称 103 | 包的数量 104 | 每个包里 activity 的数量 105 | 资源前缀 106 | 包名 107 | 后缀 108 | 开始生成 109 | 110 | APK签名 111 | 签名后缀 112 | 签名后缀: Apk签名后输出名称(比如:输入Apk名称为apk_unsign.apk,则输入Apk名称为apk_unsign%1$s.apk 113 | 是否删除重名输出文件 114 | 注意:输出文件重名后无法成功签名,会提示输出文件已存在 115 | 启用文件对齐 116 | 注意:目标 R+(版本 30 及更高版本)要求已安装 APK 内的文件未压缩存储并在 4 字节边界上对齐 117 | 118 | 签名生成 119 | 目标密钥类型 120 | 注意:JKS 密钥库使用专用格式。建议使用行业标准格式 PKCS12。 121 | 目标密钥大小 122 | 注意:生成的证书 使用的 1024 位 RSA 密钥 被视为存在安全风险。此密钥大小将在未来的更新中被禁用。 123 | 124 | 常规 125 | 默认输出路径 126 | 外观 127 | 跟随系统 128 | 亮色模式 129 | 暗色模式 130 | 131 | ToolKit拓展 132 | 启用拓展选项 133 | 启用垃圾代码生成选项 134 | 135 | 关于 136 | 应用名称 137 | 应用版本 138 | 应用描述 139 | 应用版权 140 | 应用作者 141 | 开源协议 142 | 源代码 143 | 作者 144 | 许可证 145 | 开源许可证 146 | 147 | 密钥输出路径 148 | 密钥文件名称 149 | 密钥 150 | 有效期(单位:年) 151 | 证书 152 | 作者名称 153 | 组织单位 154 | 组织 155 | 城市或地区 156 | 省份 157 | 国家编码 158 | 别名密码 159 | 创建密钥库 160 | 确认密码 161 | 162 | 上传(APK/签名)文件 163 | 复制MD5、SHA1和SHA-256 164 | 快捷复制 165 | 密钥库密码验证 166 | 密钥库密码错误 167 | 确认 168 | 169 | 已开启ToolKit扩展模式 170 | 输出文件已存在:%1$s 171 | APK签名成功,点击跳转至已签名文件 172 | 跳转 173 | 签名失败,请联系开发者排查问题 174 | 执行命令出现错误 175 | APK解析失败 176 | 创建签名成功,点击跳转至签名文件 177 | 签名制作失败,请检查输入项是否合法。 178 | 签名验证失败 179 | APK签名验证失败 180 | 构建结束:成功,文件大小:%1$s,点击跳转至构建文件 181 | 构建失败 182 | 图标制作失败 183 | 图标生成完成。点击跳转至输出目录 184 | 未扫描到缓存目录 185 | 清理完成,已为您清理%1$s 186 | %1$d个文件删除异常 187 | 已复制到剪切板 188 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Signature 3 | APK 4 | Sign 5 | KeyStore 6 | Icon 7 | Garbage 8 | Cache 9 | Setting 10 | 11 | Signature Information 12 | APK Information 13 | APK Signature 14 | Signature Generation 15 | Icon Generation 16 | Garbage Code 17 | Cache Cleanup 18 | Setting 19 | 20 | %1$s, %2$s 21 | 22 | LetGo 23 | Upload APK 24 | 25 | App Name: 26 | Version: 27 | Version Code: 28 | Package Name: 29 | Compile SDK: 30 | Minimum SDK: 31 | Target SDK: 32 | ABIs: 33 | File MD5: 34 | Size: 35 | Permissions: 36 | 37 | Output Path 38 | Key Store File Path 39 | Key Store Password 40 | Key Alias 41 | Key Password 42 | APK File 43 | Signature Strategy: 44 | The APK package signed with V2 Only supports Android 7 and higher system installation and use. Please note 45 | Start Signing 46 | APK Signature Scheme V1 47 | APK Signature Scheme V2, including V1 48 | APK Signature Scheme V2, not including V1 49 | APK Signature Scheme V3, including V1 and V2 50 | 51 | Please check the Error item 52 | Please check for empty items 53 | 54 | SELECT A FOLDER 55 | Total Storage Space 56 | Used Space 57 | Available Space 58 | Scanned Folders 59 | Selected Folders 60 | Folders 61 | Percentage of total storage space 62 | Quickly clear cache directories 63 | Quickly and thoroughly scan the build‑cache directories of an Android project.\nBatch‑delete build caches to free up disk space.\nSelect an Android project or a higher‑level directory. 64 | MMM dd, yyyy 65 | Please first select the directory to delete 66 | Newest date first 67 | Oldest date first 68 | Largest first 69 | Smallest first 70 | Name A -> Z 71 | Name Z -> A 72 | Delete selected folders? 73 | This operation will permanently delete all files in the selected directory. Once removed, the data cannot be recovered and subsequent build times may be longer. Please ensure you have backed up all important data. 74 | Confirm deletion 75 | Cancel 76 | 77 | One‑Click App Icon Generation 78 | Supports PNG, JPG, and JPEG files. \nUpload a 1024 × 1024 pixel image for best results. 79 | More Settings 80 | Icon Output Path 81 | Icon Name 82 | Close 83 | Expand 84 | Compress Custom 85 | Lossless Compression 86 | Lossy Compression 87 | Compression Speed 88 | Fast 89 | Crazy 90 | External Directory Name 91 | Android Directory 92 | PNG Quality 93 | JPEG Quality 94 | Minimum: %1$d 95 | Target: %1$d 96 | PNG scaling Algorithm 97 | JPEG scaling Algorithm 98 | Upload Image 99 | Start Making 100 | 101 | AAR Output Path 102 | AAR Name 103 | Number of packages 104 | The Number Of Activities In Each Package 105 | Resource Prefix 106 | Package Name 107 | Suffix 108 | Start Generating 109 | 110 | APK Signature 111 | Signature Suffix 112 | Signature Suffix: The output name of the Apk after signing (for example, if the input Apk name is apk_unsign.apk, then the input Apk name is apk_unsign%1$s.apk 113 | Whether to delete output files with duplicate names 114 | Note: If the output file has the same name, it cannot be signed successfully, and the system will prompt that the output file already exists. 115 | Enable File Alignment 116 | Note: Target R+ (version 30 and higher) requires that files within the installed APK be stored uncompressed and aligned on 4-byte boundaries. 117 | 118 | Signature Generation 119 | Target Key Type 120 | Note: JKS keystores use a proprietary format. The industry standard format PKCS12 is recommended. 121 | Target Key Size 122 | Note: The generated certificates use 1024-bit RSA keys which are considered a security risk. This key size will be disabled in a future update. 123 | 124 | Conventional 125 | Default Output Path 126 | Appearance 127 | Auto 128 | Light 129 | Dark 130 | 131 | ToolKit Expand 132 | Enable Extended Options 133 | Enable garbage code generation option 134 | 135 | About 136 | Application Name 137 | Application Version 138 | Application Description 139 | Application Copyright 140 | App Author 141 | Open Source Agreement 142 | Source Code 143 | Author 144 | License 145 | Open Source Licenses 146 | 147 | Key Output Path 148 | Key File Name 149 | Key 150 | Validity (years) 151 | 证书 152 | First and Last Name 153 | Organizational Unit 154 | Organization 155 | City or Locality 156 | State or Province 157 | Country Code (XX) 158 | Alias Password 159 | Create Key Store 160 | Confirm Password 161 | 162 | Upload APK/Signature File 163 | Copy MD5, SHA1, and SHA-256 164 | Quick Copy 165 | Password Verification 166 | Wrong key store password 167 | Confirm 168 | 169 | ToolKit extension mode is enabled 170 | Output file already exists: %1$s 171 | APK is signed successfully, click to jump to the signed file 172 | Jump 173 | Signing failed, please contact the developer to troubleshoot the problem 174 | An error occurred while executing the command 175 | APK parsing failed 176 | The signature is created successfully, click to jump to the signature file 177 | Signature creation failed, please check whether the input items are legal. 178 | Signature verification failed 179 | APK signature verification failed 180 | Build completed: Success, file size: %1$s, click to jump to the build file 181 | Build failure 182 | Icon creation failed 183 | The icon generation is complete. Click to jump to the output directory 184 | The cache directory was not scanned. 185 | Cleaning completed, %1$s has been cleaned for you 186 | %1$d files deleted abnormally 187 | Copied to clipboard 188 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/App.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.animation.AnimatedVisibility 2 | import androidx.compose.animation.Crossfade 3 | import androidx.compose.animation.expandVertically 4 | import androidx.compose.animation.fadeIn 5 | import androidx.compose.animation.fadeOut 6 | import androidx.compose.animation.shrinkVertically 7 | import androidx.compose.foundation.isSystemInDarkTheme 8 | import androidx.compose.foundation.layout.Arrangement 9 | import androidx.compose.foundation.layout.Column 10 | import androidx.compose.foundation.layout.Row 11 | import androidx.compose.foundation.layout.fillMaxHeight 12 | import androidx.compose.foundation.layout.fillMaxSize 13 | import androidx.compose.foundation.layout.padding 14 | import androidx.compose.material.icons.Icons 15 | import androidx.compose.material.icons.automirrored.outlined.Article 16 | import androidx.compose.material.icons.automirrored.rounded.Article 17 | import androidx.compose.material.icons.outlined.Assessment 18 | import androidx.compose.material.icons.outlined.Cookie 19 | import androidx.compose.material.icons.outlined.DesignServices 20 | import androidx.compose.material.icons.outlined.FolderDelete 21 | import androidx.compose.material.icons.outlined.Settings 22 | import androidx.compose.material.icons.outlined.Verified 23 | import androidx.compose.material.icons.outlined.VpnKey 24 | import androidx.compose.material.icons.rounded.Assessment 25 | import androidx.compose.material.icons.rounded.Cookie 26 | import androidx.compose.material.icons.rounded.DesignServices 27 | import androidx.compose.material.icons.rounded.FolderDelete 28 | import androidx.compose.material.icons.rounded.Settings 29 | import androidx.compose.material.icons.rounded.Verified 30 | import androidx.compose.material.icons.rounded.VpnKey 31 | import androidx.compose.material3.ExperimentalMaterial3Api 32 | import androidx.compose.material3.Icon 33 | import androidx.compose.material3.MaterialTheme 34 | import androidx.compose.material3.NavigationRail 35 | import androidx.compose.material3.NavigationRailItem 36 | import androidx.compose.material3.PlainTooltip 37 | import androidx.compose.material3.Scaffold 38 | import androidx.compose.material3.SnackbarHost 39 | import androidx.compose.material3.SnackbarHostState 40 | import androidx.compose.material3.SnackbarResult 41 | import androidx.compose.material3.Text 42 | import androidx.compose.material3.TooltipBox 43 | import androidx.compose.material3.rememberTooltipState 44 | import androidx.compose.runtime.Composable 45 | import androidx.compose.runtime.LaunchedEffect 46 | import androidx.compose.runtime.collectAsState 47 | import androidx.compose.runtime.getValue 48 | import androidx.compose.runtime.remember 49 | import androidx.compose.runtime.rememberCoroutineScope 50 | import androidx.compose.ui.Alignment 51 | import androidx.compose.ui.Modifier 52 | import androidx.compose.ui.graphics.vector.ImageVector 53 | import androidx.compose.ui.platform.LocalDensity 54 | import androidx.compose.ui.unit.IntOffset 55 | import androidx.compose.ui.unit.IntRect 56 | import androidx.compose.ui.unit.IntSize 57 | import androidx.compose.ui.unit.LayoutDirection 58 | import androidx.compose.ui.unit.dp 59 | import androidx.compose.ui.window.PopupPositionProvider 60 | import androidx.lifecycle.viewmodel.compose.viewModel 61 | import com.russhwolf.settings.ExperimentalSettingsApi 62 | import kotlinx.coroutines.Dispatchers 63 | import kotlinx.coroutines.flow.drop 64 | import kotlinx.coroutines.flow.first 65 | import kotlinx.coroutines.launch 66 | import model.DarkThemeConfig 67 | import org.jetbrains.compose.resources.StringResource 68 | import org.jetbrains.compose.resources.stringResource 69 | import org.tool.kit.composeapp.generated.resources.Res 70 | import org.tool.kit.composeapp.generated.resources.apk_information_rail 71 | import org.tool.kit.composeapp.generated.resources.apk_information_tooltip 72 | import org.tool.kit.composeapp.generated.resources.apk_signature_rail 73 | import org.tool.kit.composeapp.generated.resources.apk_signature_tooltip 74 | import org.tool.kit.composeapp.generated.resources.cache_cleanup_rail 75 | import org.tool.kit.composeapp.generated.resources.cache_cleanup_tooltip 76 | import org.tool.kit.composeapp.generated.resources.garbage_code_rail 77 | import org.tool.kit.composeapp.generated.resources.garbage_code_tooltip 78 | import org.tool.kit.composeapp.generated.resources.icon_generation_rail 79 | import org.tool.kit.composeapp.generated.resources.icon_generation_tooltip 80 | import org.tool.kit.composeapp.generated.resources.setting_rail 81 | import org.tool.kit.composeapp.generated.resources.setting_tooltip 82 | import org.tool.kit.composeapp.generated.resources.signature_generation_rail 83 | import org.tool.kit.composeapp.generated.resources.signature_generation_tooltip 84 | import org.tool.kit.composeapp.generated.resources.signature_information_rail 85 | import org.tool.kit.composeapp.generated.resources.signature_information_tooltip 86 | import platform.createFlowSettings 87 | import theme.AppTheme 88 | import ui.ApkInformation 89 | import ui.ApkSignature 90 | import ui.ClearBuild 91 | import ui.ClearBuildBottom 92 | import ui.IconFactory 93 | import ui.JunkCode 94 | import ui.LoadingAnimate 95 | import ui.SetUp 96 | import ui.SignatureGeneration 97 | import ui.SignatureInformation 98 | import vm.MainViewModel 99 | import vm.UIState 100 | 101 | @OptIn(ExperimentalSettingsApi::class) 102 | @Composable 103 | fun App() { 104 | val viewModel = viewModel { MainViewModel(settings = createFlowSettings()) } 105 | val themeConfig by viewModel.themeConfig.collectAsState() 106 | val useDarkTheme = when (themeConfig) { 107 | DarkThemeConfig.LIGHT -> false 108 | DarkThemeConfig.DARK -> true 109 | DarkThemeConfig.FOLLOW_SYSTEM -> isSystemInDarkTheme() 110 | } 111 | AppTheme(useDarkTheme) { 112 | MainContentScreen(viewModel) 113 | } 114 | } 115 | 116 | /** 117 | * 主要模块 118 | */ 119 | @OptIn(ExperimentalMaterial3Api::class) 120 | @Composable 121 | fun MainContentScreen(viewModel: MainViewModel) { 122 | val scope = rememberCoroutineScope() 123 | val snackbarHostState = remember { SnackbarHostState() } 124 | scope.launch(Dispatchers.IO) { 125 | collectOutputPath(viewModel) 126 | } 127 | Scaffold(bottomBar = { 128 | AnimatedVisibility( 129 | visible = viewModel.uiPageIndex == Page.CLEAR_BUILD && viewModel.fileClearUIState == UIState.WAIT && viewModel.pendingDeletionFileList.isNotEmpty(), 130 | enter = fadeIn() + expandVertically(), 131 | exit = shrinkVertically() + fadeOut() 132 | ) { 133 | ClearBuildBottom(viewModel) 134 | } 135 | }, snackbarHost = { 136 | SnackbarHost(hostState = snackbarHostState) 137 | }) { innerPadding -> 138 | Row( 139 | modifier = Modifier.fillMaxSize().padding(innerPadding), verticalAlignment = Alignment.CenterVertically 140 | ) { 141 | val junkCode by viewModel.junkCode.collectAsState() 142 | val pages = if (junkCode) { 143 | Page.entries.toMutableList() 144 | } else { 145 | Page.entries.toMutableList().also { list -> list.remove(Page.JUNK_CODE) } 146 | } 147 | // 导航栏 148 | AnimatedVisibility(viewModel.pendingDeletionFileList.isEmpty()) { 149 | NavigationRail(Modifier.fillMaxHeight()) { 150 | Column( 151 | modifier = Modifier.fillMaxHeight(), 152 | verticalArrangement = Arrangement.Center, 153 | horizontalAlignment = Alignment.CenterHorizontally 154 | ) { 155 | pages.forEachIndexed { _, page -> 156 | TooltipBox( 157 | positionProvider = rememberRichTooltipPositionProvider(), tooltip = { 158 | PlainTooltip { 159 | Text( 160 | stringResource(page.tooltip), style = MaterialTheme.typography.bodySmall 161 | ) 162 | } 163 | }, state = rememberTooltipState(), enableUserInput = viewModel.uiPageIndex != page 164 | ) { 165 | val icon = if (viewModel.uiPageIndex == page) page.selectedIcon else page.unSelectedIcon 166 | NavigationRailItem( 167 | label = { Text(stringResource(page.title)) }, 168 | icon = { Icon(icon, contentDescription = stringResource(page.title)) }, 169 | selected = viewModel.uiPageIndex == page, 170 | onClick = { viewModel.updateUiState(page) }, 171 | alwaysShowLabel = false, 172 | ) 173 | } 174 | } 175 | } 176 | } 177 | } 178 | // 主界面 179 | val content: @Composable (Page) -> Unit = { page -> 180 | when (page) { 181 | Page.SIGNATURE_INFORMATION -> SignatureInformation(viewModel) 182 | Page.APK_INFORMATION -> ApkInformation(viewModel) 183 | Page.APK_SIGNATURE -> ApkSignature(viewModel) 184 | Page.SIGNATURE_GENERATION -> SignatureGeneration(viewModel) 185 | Page.JUNK_CODE -> JunkCode(viewModel) 186 | Page.ICON_FACTORY -> IconFactory(viewModel) 187 | Page.CLEAR_BUILD -> ClearBuild(viewModel) 188 | Page.SET_UP -> SetUp(viewModel) 189 | } 190 | } 191 | // 淡入淡出切换页面 192 | Crossfade(targetState = viewModel.uiPageIndex, modifier = Modifier.fillMaxSize(), content = content) 193 | } 194 | } 195 | val snackbarVisuals by viewModel.snackbarVisuals.collectAsState() 196 | LaunchedEffect(snackbarVisuals) { 197 | if (snackbarVisuals.message.isBlank()) return@LaunchedEffect 198 | val snackbarResult = snackbarHostState.showSnackbar(snackbarVisuals) 199 | when (snackbarResult) { 200 | SnackbarResult.ActionPerformed -> snackbarVisuals.action?.invoke() 201 | SnackbarResult.Dismissed -> Unit 202 | } 203 | } 204 | LoadingAnimate(isShowLoading(viewModel), viewModel, scope) 205 | } 206 | 207 | private fun isShowLoading(viewModel: MainViewModel) = 208 | viewModel.junkCodeUIState == UIState.Loading || viewModel.iconFactoryUIState == UIState.Loading 209 | || viewModel.apkSignatureUIState == UIState.Loading || viewModel.apkInformationState == UIState.Loading 210 | || viewModel.keyStoreInfoUIState == UIState.Loading || viewModel.verifierState == UIState.Loading 211 | || (viewModel.fileClearUIState == UIState.Loading && viewModel.isClearing) 212 | 213 | suspend fun collectOutputPath(viewModel: MainViewModel) { 214 | val userData = viewModel.userData.drop(0).first() 215 | val outputPath = userData.defaultOutputPath 216 | viewModel.apply { 217 | updateApkSignature(viewModel.apkSignatureState.copy(outputPath = outputPath)) 218 | updateSignatureGenerate(viewModel.keyStoreInfoState.copy(keyStorePath = outputPath)) 219 | updateJunkCodeInfo(viewModel.junkCodeInfoState.copy(outputPath = outputPath)) 220 | updateIconFactoryInfo(viewModel.iconFactoryInfoState.copy(outputPath = outputPath)) 221 | } 222 | } 223 | 224 | enum class Page( 225 | val title: StringResource, 226 | val tooltip: StringResource, 227 | val selectedIcon: ImageVector, 228 | val unSelectedIcon: ImageVector 229 | ) { 230 | SIGNATURE_INFORMATION( 231 | Res.string.signature_information_rail, 232 | Res.string.signature_information_tooltip, 233 | Icons.AutoMirrored.Rounded.Article, 234 | Icons.AutoMirrored.Outlined.Article 235 | ), 236 | APK_INFORMATION( 237 | Res.string.apk_information_rail, 238 | Res.string.apk_information_tooltip, 239 | Icons.Rounded.Assessment, 240 | Icons.Outlined.Assessment 241 | ), 242 | APK_SIGNATURE( 243 | Res.string.apk_signature_rail, 244 | Res.string.apk_signature_tooltip, 245 | Icons.Rounded.VpnKey, 246 | Icons.Outlined.VpnKey 247 | ), 248 | SIGNATURE_GENERATION( 249 | Res.string.signature_generation_rail, 250 | Res.string.signature_generation_tooltip, 251 | Icons.Rounded.Verified, 252 | Icons.Outlined.Verified 253 | ), 254 | JUNK_CODE( 255 | Res.string.garbage_code_rail, 256 | Res.string.garbage_code_tooltip, 257 | Icons.Rounded.Cookie, 258 | Icons.Outlined.Cookie 259 | ), 260 | ICON_FACTORY( 261 | Res.string.icon_generation_rail, 262 | Res.string.icon_generation_tooltip, 263 | Icons.Rounded.DesignServices, 264 | Icons.Outlined.DesignServices 265 | ), 266 | CLEAR_BUILD( 267 | Res.string.cache_cleanup_rail, 268 | Res.string.cache_cleanup_tooltip, 269 | Icons.Rounded.FolderDelete, 270 | Icons.Outlined.FolderDelete 271 | ), 272 | SET_UP( 273 | Res.string.setting_rail, 274 | Res.string.setting_tooltip, 275 | Icons.Rounded.Settings, 276 | Icons.Outlined.Settings 277 | ) 278 | } 279 | 280 | @Composable 281 | private fun rememberRichTooltipPositionProvider(): PopupPositionProvider { 282 | val tooltipAnchorSpacing = with(LocalDensity.current) { 4.dp.roundToPx() } 283 | return remember(tooltipAnchorSpacing) { 284 | object : PopupPositionProvider { 285 | override fun calculatePosition( 286 | anchorBounds: IntRect, windowSize: IntSize, layoutDirection: LayoutDirection, popupContentSize: IntSize 287 | ): IntOffset { 288 | var x = anchorBounds.right 289 | if (x + popupContentSize.width > windowSize.width) { 290 | x = anchorBounds.left - popupContentSize.width 291 | if (x < 0) x = anchorBounds.left + (anchorBounds.width - popupContentSize.width) / 2 292 | } 293 | x -= tooltipAnchorSpacing 294 | val y = anchorBounds.top + (anchorBounds.height - popupContentSize.height) / 2 295 | return IntOffset(x, y) 296 | } 297 | } 298 | } 299 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/constant/ConfigConstant.kt: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import model.JpegAlgorithm 4 | import model.PngAlgorithm 5 | import model.UnsignedApk 6 | import utils.resourcesDirWithCommon 7 | import java.io.File 8 | 9 | 10 | object ConfigConstant { 11 | 12 | /** 13 | * 未签名APK列表 14 | */ 15 | val unsignedApkList = listOf( 16 | UnsignedApk("oppo", File(resourcesDirWithCommon, "oppo_unsigned.apk").absolutePath), 17 | UnsignedApk("vivo", File(resourcesDirWithCommon, "vivo_unsigned.apk").absolutePath), 18 | UnsignedApk("huawei", File(resourcesDirWithCommon, "huawei_unsigned.apk").absolutePath), 19 | UnsignedApk("xiaomi", File(resourcesDirWithCommon, "xiaomi_unsigned.apk").absolutePath), 20 | UnsignedApk("qq", File(resourcesDirWithCommon, "qq_unsigned.apk").absolutePath), 21 | UnsignedApk("honor", File(resourcesDirWithCommon, "honor_unsigned.apk").absolutePath) 22 | ) 23 | 24 | /** 25 | * Android 图标目录 26 | */ 27 | val ANDROID_ICON_DIR_LIST = listOf("mipmap", "drawable") 28 | 29 | /** 30 | * ICON 对应 文件/尺寸 31 | */ 32 | val ICON_FILE_LIST = listOf("mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi") 33 | val ICON_SIZE_LIST = listOf(48.toUInt(), 72.toUInt(), 96.toUInt(), 144.toUInt(), 192.toUInt()) 34 | 35 | /** 36 | * ICON PNG 缩放算法 37 | */ 38 | val ICON_PNG_ALGORITHM = listOf( 39 | PngAlgorithm.Triangle, 40 | PngAlgorithm.Catrom, 41 | PngAlgorithm.Mitchell, 42 | PngAlgorithm.Lanczos3, 43 | ) 44 | 45 | /** 46 | * ICON JPEG 缩放算法 47 | */ 48 | val ICON_JPEG_ALGORITHM = listOf( 49 | JpegAlgorithm.Bilinear, 50 | JpegAlgorithm.Hamming, 51 | JpegAlgorithm.CatmullRom, 52 | JpegAlgorithm.Mitchell, 53 | JpegAlgorithm.Gaussian, 54 | JpegAlgorithm.Lanczos3, 55 | ) 56 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/database/PreferencesDataSource.kt: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import com.russhwolf.settings.ExperimentalSettingsApi 4 | import com.russhwolf.settings.coroutines.FlowSettings 5 | import com.russhwolf.settings.coroutines.toBlockingSettings 6 | import com.russhwolf.settings.serialization.decodeValue 7 | import com.russhwolf.settings.serialization.encodeValue 8 | import kotlinx.coroutines.flow.MutableStateFlow 9 | import kotlinx.coroutines.flow.asStateFlow 10 | import kotlinx.coroutines.flow.map 11 | import kotlinx.serialization.ExperimentalSerializationApi 12 | import model.DarkThemeConfig 13 | import model.DestStoreSize 14 | import model.DestStoreType 15 | import model.IconFactoryData 16 | import model.JpegAlgorithm 17 | import model.PngAlgorithm 18 | import model.UserData 19 | import utils.getDownloadDirectory 20 | 21 | /** 22 | * @Author : LazyIonEs 23 | * @CreateDate : 2024/2/20 19:36 24 | * @Description : 用户偏好设置 25 | * @Version : 1.0 26 | */ 27 | @OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class) 28 | class PreferencesDataSource @OptIn(ExperimentalSettingsApi::class) constructor(private val settings: FlowSettings) { 29 | 30 | private val blockingSettings = settings.toBlockingSettings() 31 | 32 | companion object { 33 | private const val THEME_CONFIG = "theme_config" 34 | val DEFAULT_THEME_CONFIG = DarkThemeConfig.FOLLOW_SYSTEM 35 | private const val USER_DATA = "user_data" 36 | val DEFAULT_USER_DATA = UserData( 37 | defaultOutputPath = getDownloadDirectory(), 38 | duplicateFileRemoval = true, 39 | defaultSignerSuffix = "_sign", 40 | alignFileSize = true, 41 | destStoreType = DestStoreType.JKS, 42 | destStoreSize = DestStoreSize.TWO_THOUSAND_FORTY_EIGHT 43 | ) 44 | private const val ICON_FACTORY_DATA = "icon_factory_data" 45 | val DEFAULT_ICON_FACTORY_DATA = IconFactoryData( 46 | pngTypIdx = PngAlgorithm.Lanczos3, 47 | jpegTypIdx = JpegAlgorithm.Lanczos3, 48 | lossless = true, 49 | minimum = 70, 50 | target = 100, 51 | speed = 1, 52 | preset = 6, 53 | percentage = 1f, 54 | quality = 85f 55 | ) 56 | private const val JUNK_CODE = "junk_code" 57 | private const val DEVELOPER_MODE = "developer_mode" 58 | } 59 | 60 | private val _userData = MutableStateFlow(DEFAULT_USER_DATA) 61 | val userData = _userData.asStateFlow() 62 | 63 | private val _iconFactoryData = MutableStateFlow(DEFAULT_ICON_FACTORY_DATA) 64 | val iconFactoryData = _iconFactoryData.asStateFlow() 65 | 66 | val themeConfig = settings.getStringOrNullFlow(THEME_CONFIG).map { string -> 67 | string?.let { 68 | when (it) { 69 | DarkThemeConfig.FOLLOW_SYSTEM.name -> DarkThemeConfig.FOLLOW_SYSTEM 70 | DarkThemeConfig.DARK.name -> DarkThemeConfig.DARK 71 | else -> DarkThemeConfig.LIGHT 72 | } 73 | } ?: let { 74 | DEFAULT_THEME_CONFIG 75 | } 76 | } 77 | 78 | init { 79 | _userData.value = blockingSettings.decodeValue(UserData.serializer(), USER_DATA, DEFAULT_USER_DATA) 80 | _iconFactoryData.value = 81 | blockingSettings.decodeValue(IconFactoryData.serializer(), ICON_FACTORY_DATA, DEFAULT_ICON_FACTORY_DATA) 82 | } 83 | 84 | suspend fun saveThemeConfig(themeConfig: DarkThemeConfig) { 85 | settings.putString(THEME_CONFIG, themeConfig.name) 86 | } 87 | 88 | fun saveUserData(userData: UserData) { 89 | blockingSettings.encodeValue(UserData.serializer(), USER_DATA, userData) 90 | _userData.value = userData 91 | } 92 | 93 | fun saveIconFactoryData(iconFactoryData: IconFactoryData) { 94 | blockingSettings.encodeValue(IconFactoryData.serializer(), ICON_FACTORY_DATA, iconFactoryData) 95 | _iconFactoryData.value = iconFactoryData 96 | } 97 | 98 | val junkCode = settings.getBooleanOrNullFlow(JUNK_CODE).map { mode -> mode == true } 99 | 100 | suspend fun saveJunkCode(show: Boolean) { 101 | settings.putBoolean(JUNK_CODE, show) 102 | } 103 | 104 | val developerMode = settings.getBooleanOrNullFlow(DEVELOPER_MODE).map { mode -> mode == true } 105 | 106 | suspend fun saveDeveloperMode(show: Boolean) { 107 | settings.putBoolean(DEVELOPER_MODE, show) 108 | } 109 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/model/FileSelectorType.kt: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | /** 4 | * 文件选择类型 5 | */ 6 | enum class FileSelectorType { 7 | APK, 8 | KEY, 9 | IMAGE, 10 | EXECUTE 11 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/model/IconFactoryData.kt: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class IconFactoryData( 7 | val pngTypIdx: PngAlgorithm, 8 | val jpegTypIdx: JpegAlgorithm, 9 | val lossless: Boolean, 10 | val minimum: Int, 11 | val target: Int, 12 | val speed: Int, 13 | val preset: Int, 14 | val percentage: Float, 15 | val quality: Float 16 | ) 17 | 18 | enum class PngAlgorithm(val typIdx: Int) { 19 | Triangle(0), Catrom(1), Mitchell(2), Lanczos3(3) 20 | } 21 | 22 | enum class JpegAlgorithm(val typIdx: Int) { 23 | Bilinear(0), Hamming(1), CatmullRom(2), Mitchell(3), Gaussian(4), Lanczos3(5) 24 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/model/Model.kt: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import androidx.compose.material3.SnackbarDuration 4 | import androidx.compose.material3.SnackbarVisuals 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.setValue 8 | import androidx.compose.ui.graphics.ImageBitmap 9 | import org.jetbrains.compose.resources.StringResource 10 | import org.tool.kit.composeapp.generated.resources.Res 11 | import org.tool.kit.composeapp.generated.resources.apk_signature_v1 12 | import org.tool.kit.composeapp.generated.resources.apk_signature_v2 13 | import org.tool.kit.composeapp.generated.resources.apk_signature_v2_only 14 | import org.tool.kit.composeapp.generated.resources.apk_signature_v3 15 | import java.io.File 16 | 17 | /** 18 | * @Author : LazyIonEs 19 | * @CreateDate : 2024/2/1 20:23 20 | * @Description : 描述 21 | * @Version : 1.0 22 | */ 23 | /** 24 | * Apk签名信息结果 25 | */ 26 | data class VerifierResult( 27 | val isSuccess: Boolean, // 验证是否成功 28 | val isApk: Boolean, // 是否是apk验证 29 | val path: String, // 文件路径 30 | val name: String, // 文件名称 31 | val data: ArrayList // 验证信息 32 | ) 33 | 34 | /** 35 | * 签名信息结果 36 | */ 37 | data class Verifier( 38 | val version: Int, 39 | val subject: String, 40 | val validFrom: String, 41 | val validUntil: String, 42 | val publicKeyType: String, 43 | val modulus: String, 44 | val signatureType: String, 45 | var md5: String, 46 | var sha1: String, 47 | var sha256: String 48 | ) 49 | 50 | /** 51 | * Apk签名,存储页面信息,viewModel中 52 | */ 53 | data class ApkSignature( 54 | var apkPath: String = "", // apk路径 55 | var outputPath: String = "", // apk输出路径 56 | var keyStorePolicy: SignaturePolicy = SignaturePolicy.V2, // 密钥策略 57 | private var _keyStorePath: String = "", // 密钥 58 | var keyStorePassword: String = "", // 密钥密码 59 | var keyStoreAlisaList: ArrayList? = null, // 别名列表 60 | var keyStoreAlisaIndex: Int = 0, // 别名选中下标 61 | var keyStoreAlisaPassword: String = "" // 别名密码 62 | ) { 63 | var keyStorePath: String 64 | get() = _keyStorePath 65 | set(value) { 66 | if (_keyStorePath != value) { 67 | this.keyStorePassword = "" 68 | this.keyStoreAlisaList = null 69 | this.keyStoreAlisaIndex = 0 70 | this.keyStoreAlisaPassword = "" 71 | } 72 | _keyStorePath = value 73 | } 74 | } 75 | 76 | /** 77 | * APK签名策略 78 | */ 79 | enum class SignaturePolicy(val title: String, val value: StringResource) { 80 | V1("V1", Res.string.apk_signature_v1), 81 | V2("V2", Res.string.apk_signature_v2), 82 | V2Only("V2 Only", Res.string.apk_signature_v2_only), 83 | V3("V3", Res.string.apk_signature_v3) 84 | } 85 | 86 | /** 87 | * Apk信息 88 | */ 89 | data class ApkInformation( 90 | var label: String = "", // 名称 91 | var icon: ImageBitmap? = null, // 图标 92 | var size: Long = 0L, // 大小 93 | var md5: String = "", // 文件md5 94 | var packageName: String = "", // 包名 95 | var versionCode: String = "", // 版本号 96 | var versionName: String = "", // 版本 97 | var compileSdkVersion: String = "", // 编译版本 98 | var minSdkVersion: String = "", // 最小版本 99 | var targetSdkVersion: String = "", // 目标版本 100 | var usesPermissionList: ArrayList? = null, // 权限列表 101 | var nativeCode: String = "" // 架构 102 | ) { 103 | fun isBlank(): Boolean { 104 | return label.isBlank() && packageName.isBlank() && versionCode.isBlank() && versionName.isBlank() 105 | } 106 | } 107 | 108 | /** 109 | * 签名信息,存储页面信息,viewModel中 110 | */ 111 | data class KeyStoreInfo( 112 | var keyStorePath: String = "", // 密钥路径 113 | var keyStoreName: String = "sign.jks", // 密钥名称 114 | var keyStorePassword: String = "", // 密钥密码 115 | var keyStoreConfirmPassword: String = "", // 密钥确认密码 116 | var keyStoreAlisa: String = "", // 别名 117 | var keyStoreAlisaPassword: String = "", // 别名密码 118 | var keyStoreAlisaConfirmPassword: String = "", // 别名确认密码 119 | var validityPeriod: String = "25", // 密码有效期 120 | var authorName: String = "", // 作者名称 121 | var organizationalUnit: String = "", // 组织单位 122 | var organizational: String = "", // 组织 123 | var city: String = "", // 城市 124 | var province: String = "", // 省份 125 | var countryCode: String = "" // 国家编码 126 | ) 127 | 128 | /** 129 | * 垃圾代码生成信息,存储页面信息,viewModel中 130 | */ 131 | data class JunkCodeInfo( 132 | var outputPath: String = "", // 输出路径 133 | var aarName: String = "junk_com_dev_junk_plugin_TT2.0.0.aar", // aar名称 134 | private var _packageName: String = "com.dev.junk", // 包名 135 | private var _suffix: String = "plugin", // 后缀 136 | var packageCount: String = "50", // 包数量 137 | var activityCountPerPackage: String = "30", // 每个包里 activity 的数量 138 | var resPrefix: String = "junk_", // 资源前缀 139 | ) { 140 | var packageName: String 141 | get() = _packageName 142 | set(value) { 143 | _packageName = value 144 | aarName = "junk_" + packageName.replace(".", "_") + "_" + this.suffix + "_TT2.0.0.aar" 145 | } 146 | 147 | var suffix: String 148 | get() = _suffix 149 | set(value) { 150 | _suffix = value 151 | aarName = "junk_" + packageName.replace(".", "_") + "_" + this.suffix + "_TT2.0.0.aar" 152 | } 153 | } 154 | 155 | 156 | /** 157 | * 未签名APK 158 | */ 159 | data class UnsignedApk(val title: String, val path: String) 160 | 161 | /** 162 | * 图标工厂信息,存储页面信息,viewModel中 163 | */ 164 | data class IconFactoryInfo( 165 | var icon: File? = null, // 图标文件 166 | var outputPath: String = "", // 输出路径 167 | var fileDir: String = "res", // 顶级目录 168 | var iconDir: String = "mipmap", // Android目录 169 | var iconName: String = "ic_launcher", // icon名称 170 | var result: MutableList? = null // 生成的结果 171 | ) 172 | 173 | /** 174 | * Snackbar信息 175 | * @param action 需要执行的操作 176 | */ 177 | data class SnackbarVisualsData( 178 | override var message: String = "", 179 | override var actionLabel: String? = null, 180 | override var withDismissAction: Boolean = false, 181 | override var duration: SnackbarDuration = SnackbarDuration.Short, 182 | var timestamp: Long = System.currentTimeMillis(), 183 | var action: (() -> Unit)? = null 184 | ) : SnackbarVisuals { 185 | 186 | fun reset(): SnackbarVisualsData { 187 | actionLabel = null 188 | withDismissAction = false 189 | duration = SnackbarDuration.Short 190 | action = null 191 | timestamp = System.currentTimeMillis() 192 | return this 193 | } 194 | } 195 | 196 | class PendingDeletionFile( 197 | val directoryPath: String, 198 | val file: File, 199 | val filePath: String, 200 | val fileLength: Long, 201 | val fileLastModified: Long, 202 | deleteExceptions: Boolean = false, 203 | initialChecked: Boolean = true, 204 | ) { 205 | var checked by mutableStateOf(initialChecked) 206 | var exception by mutableStateOf(deleteExceptions) 207 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/model/Sequence.kt: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | /** 4 | * 列表排序 5 | */ 6 | enum class Sequence { 7 | DATE_NEW_TO_OLD, // 日期从新到旧 8 | DATE_OLD_TO_NEW, // 日期从旧到新 9 | SIZE_LARGE_TO_SMALL, // 大小从大到小 10 | SIZE_SMALL_TO_LARGE, // 大小从小到大 11 | NAME_A_TO_Z, // 名称从A到Z 12 | NAME_Z_TO_A, // 名称从Z到A 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/model/UserData.kt: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import kotlinx.serialization.Serializable 4 | import org.jetbrains.compose.resources.StringResource 5 | import org.tool.kit.composeapp.generated.resources.Res 6 | import org.tool.kit.composeapp.generated.resources.dark_mode 7 | import org.tool.kit.composeapp.generated.resources.follow_the_system 8 | import org.tool.kit.composeapp.generated.resources.light_mode 9 | 10 | @Serializable 11 | data class UserData( 12 | val defaultOutputPath: String, // 默认输出路径 13 | val duplicateFileRemoval: Boolean, // 重复文件删除 14 | val defaultSignerSuffix: String, // 默认签名后缀 15 | val alignFileSize: Boolean, // 文件对齐 16 | val destStoreType: DestStoreType, // 目标密钥类型 17 | val destStoreSize: DestStoreSize, // 目标密钥大小 18 | ) 19 | 20 | enum class DarkThemeConfig(val resource: StringResource) { 21 | FOLLOW_SYSTEM(Res.string.follow_the_system), 22 | LIGHT(Res.string.light_mode), 23 | DARK(Res.string.dark_mode) 24 | } 25 | 26 | enum class DestStoreSize(val size: Int) { 27 | ONE_THOUSAND_TWENTY_FOUR(1024), TWO_THOUSAND_FORTY_EIGHT(2048) 28 | } 29 | 30 | enum class DestStoreType { 31 | JKS, PKCS12 32 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/platform/Expect.kt: -------------------------------------------------------------------------------- 1 | package platform 2 | 3 | import androidx.annotation.FloatRange 4 | import androidx.annotation.IntRange 5 | import com.russhwolf.settings.ExperimentalSettingsApi 6 | import com.russhwolf.settings.coroutines.FlowSettings 7 | 8 | @OptIn(ExperimentalSettingsApi::class) 9 | expect fun createFlowSettings(): FlowSettings 10 | 11 | /** 12 | * 缩放图片 13 | * @param inputPath 输入路径 14 | * @param outputPath 输出路径 15 | * @param width 宽度 16 | * @param height 高度 17 | * @param typIdx 缩放算法 0 Triangle 1 Catrom 2 Mitchell 3 Lanczos3 18 | */ 19 | @Throws(RustException::class) 20 | expect fun resizePng(inputPath: String, outputPath: String, width: UInt, height: UInt, typIdx: UByte = 3u) 21 | 22 | /** 23 | * 缩放图片 24 | * @param inputPath 输入路径 25 | * @param outputPath 输出路径 26 | * @param width 宽度 27 | * @param height 高度 28 | * @param typIdx 缩放算法 0 Bilinear 1 Hamming 2 CatmullRom 3 Mitchell 4 Gaussian 5 Lanczos3 29 | */ 30 | @Throws(RustException::class) 31 | expect fun resizeFir(inputPath: String, outputPath: String, width: UInt, height: UInt, typIdx: UByte = 5u) 32 | 33 | /** 34 | * 量化(有损)并压缩图片 35 | * @param inputPath 输入路径 36 | * @param outputPath 输出路径 37 | * @param minimum 最低限度 38 | * @param target 目标质量 如果不能满足最低质量,量化将因错误而中止。默认值为最小值 0,最大值 100,表示尽力而为,并且永不中止该过程。 39 | * 如果最大值小于 100,则库将尝试使用较少的颜色。颜色较少的图像并不总是较小,因为它会导致抖动增加。 40 | * @param speed 速度 1 - 10 更快的速度会生成质量较低的图像,但可能对于实时生成图像有用 41 | * @param preset 预设 42 | */ 43 | @Throws(RustException::class) 44 | expect fun quantize( 45 | inputPath: String, 46 | outputPath: String, 47 | @IntRange(from = 0, to = 100) minimum: Int = 70, 48 | @IntRange(from = 30, to = 100) target: Int = 100, 49 | @IntRange(from = 1, to = 10) speed: Int = 1, 50 | @IntRange(from = 0, to = 6) preset: Int = 6 51 | ) 52 | 53 | /** 54 | * 无损压缩PNG 55 | * @param inputPath 输入路径 56 | * @param outputPath 输出路径 57 | * @param preset 预设 1 - 6 预设越高、速度越慢、压缩效果越好 58 | */ 59 | @Throws(RustException::class) 60 | expect fun oxipng( 61 | inputPath: String, 62 | outputPath: String, 63 | @IntRange(from = 0, to = 6) preset: Int = 6 64 | ) 65 | 66 | /** 67 | * 压缩图片 68 | * @param inputPath 输入路径 69 | * @param outputPath 输出路径 70 | * @param quality 图像质量。建议值为 60-80 71 | */ 72 | @Throws(RustException::class) 73 | expect fun mozJpeg(inputPath: String, outputPath: String, @FloatRange(from = 0.0, to = 100.0) quality: Float = 85f) 74 | 75 | class RustException( 76 | message: String? 77 | ) : Exception(message) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package theme 2 | import androidx.compose.ui.graphics.Color 3 | 4 | val primaryLight = Color(0xFF4C662B) 5 | val onPrimaryLight = Color(0xFFFFFFFF) 6 | val primaryContainerLight = Color(0xFFCDEDA3) 7 | val onPrimaryContainerLight = Color(0xFF102000) 8 | val secondaryLight = Color(0xFF586249) 9 | val onSecondaryLight = Color(0xFFFFFFFF) 10 | val secondaryContainerLight = Color(0xFFDCE7C8) 11 | val onSecondaryContainerLight = Color(0xFF151E0B) 12 | val tertiaryLight = Color(0xFF386663) 13 | val onTertiaryLight = Color(0xFFFFFFFF) 14 | val tertiaryContainerLight = Color(0xFFBCECE7) 15 | val onTertiaryContainerLight = Color(0xFF00201E) 16 | val errorLight = Color(0xFFBA1A1A) 17 | val onErrorLight = Color(0xFFFFFFFF) 18 | val errorContainerLight = Color(0xFFFFDAD6) 19 | val onErrorContainerLight = Color(0xFF410002) 20 | val backgroundLight = Color(0xFFF9FAEF) 21 | val onBackgroundLight = Color(0xFF1A1C16) 22 | val surfaceLight = Color(0xFFF9FAEF) 23 | val onSurfaceLight = Color(0xFF1A1C16) 24 | val surfaceVariantLight = Color(0xFFE1E4D5) 25 | val onSurfaceVariantLight = Color(0xFF44483D) 26 | val outlineLight = Color(0xFF75796C) 27 | val outlineVariantLight = Color(0xFFC5C8BA) 28 | val scrimLight = Color(0xFF000000) 29 | val inverseSurfaceLight = Color(0xFF2F312A) 30 | val inverseOnSurfaceLight = Color(0xFFF1F2E6) 31 | val inversePrimaryLight = Color(0xFFB1D18A) 32 | val surfaceDimLight = Color(0xFFDADBD0) 33 | val surfaceBrightLight = Color(0xFFF9FAEF) 34 | val surfaceContainerLowestLight = Color(0xFFFFFFFF) 35 | val surfaceContainerLowLight = Color(0xFFF3F4E9) 36 | val surfaceContainerLight = Color(0xFFEEEFE3) 37 | val surfaceContainerHighLight = Color(0xFFE8E9DE) 38 | val surfaceContainerHighestLight = Color(0xFFE2E3D8) 39 | 40 | val primaryLightMediumContrast = Color(0xFF314A12) 41 | val onPrimaryLightMediumContrast = Color(0xFFFFFFFF) 42 | val primaryContainerLightMediumContrast = Color(0xFF617D3F) 43 | val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF) 44 | val secondaryLightMediumContrast = Color(0xFF3C462F) 45 | val onSecondaryLightMediumContrast = Color(0xFFFFFFFF) 46 | val secondaryContainerLightMediumContrast = Color(0xFF6E785E) 47 | val onSecondaryContainerLightMediumContrast = Color(0xFFFFFFFF) 48 | val tertiaryLightMediumContrast = Color(0xFF1A4A47) 49 | val onTertiaryLightMediumContrast = Color(0xFFFFFFFF) 50 | val tertiaryContainerLightMediumContrast = Color(0xFF4F7D79) 51 | val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF) 52 | val errorLightMediumContrast = Color(0xFF8C0009) 53 | val onErrorLightMediumContrast = Color(0xFFFFFFFF) 54 | val errorContainerLightMediumContrast = Color(0xFFDA342E) 55 | val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF) 56 | val backgroundLightMediumContrast = Color(0xFFF9FAEF) 57 | val onBackgroundLightMediumContrast = Color(0xFF1A1C16) 58 | val surfaceLightMediumContrast = Color(0xFFF9FAEF) 59 | val onSurfaceLightMediumContrast = Color(0xFF1A1C16) 60 | val surfaceVariantLightMediumContrast = Color(0xFFE1E4D5) 61 | val onSurfaceVariantLightMediumContrast = Color(0xFF404439) 62 | val outlineLightMediumContrast = Color(0xFF5D6155) 63 | val outlineVariantLightMediumContrast = Color(0xFF787C70) 64 | val scrimLightMediumContrast = Color(0xFF000000) 65 | val inverseSurfaceLightMediumContrast = Color(0xFF2F312A) 66 | val inverseOnSurfaceLightMediumContrast = Color(0xFFF1F2E6) 67 | val inversePrimaryLightMediumContrast = Color(0xFFB1D18A) 68 | val surfaceDimLightMediumContrast = Color(0xFFDADBD0) 69 | val surfaceBrightLightMediumContrast = Color(0xFFF9FAEF) 70 | val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF) 71 | val surfaceContainerLowLightMediumContrast = Color(0xFFF3F4E9) 72 | val surfaceContainerLightMediumContrast = Color(0xFFEEEFE3) 73 | val surfaceContainerHighLightMediumContrast = Color(0xFFE8E9DE) 74 | val surfaceContainerHighestLightMediumContrast = Color(0xFFE2E3D8) 75 | 76 | val primaryLightHighContrast = Color(0xFF142700) 77 | val onPrimaryLightHighContrast = Color(0xFFFFFFFF) 78 | val primaryContainerLightHighContrast = Color(0xFF314A12) 79 | val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF) 80 | val secondaryLightHighContrast = Color(0xFF1C2511) 81 | val onSecondaryLightHighContrast = Color(0xFFFFFFFF) 82 | val secondaryContainerLightHighContrast = Color(0xFF3C462F) 83 | val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF) 84 | val tertiaryLightHighContrast = Color(0xFF002725) 85 | val onTertiaryLightHighContrast = Color(0xFFFFFFFF) 86 | val tertiaryContainerLightHighContrast = Color(0xFF1A4A47) 87 | val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF) 88 | val errorLightHighContrast = Color(0xFF4E0002) 89 | val onErrorLightHighContrast = Color(0xFFFFFFFF) 90 | val errorContainerLightHighContrast = Color(0xFF8C0009) 91 | val onErrorContainerLightHighContrast = Color(0xFFFFFFFF) 92 | val backgroundLightHighContrast = Color(0xFFF9FAEF) 93 | val onBackgroundLightHighContrast = Color(0xFF1A1C16) 94 | val surfaceLightHighContrast = Color(0xFFF9FAEF) 95 | val onSurfaceLightHighContrast = Color(0xFF000000) 96 | val surfaceVariantLightHighContrast = Color(0xFFE1E4D5) 97 | val onSurfaceVariantLightHighContrast = Color(0xFF21251C) 98 | val outlineLightHighContrast = Color(0xFF404439) 99 | val outlineVariantLightHighContrast = Color(0xFF404439) 100 | val scrimLightHighContrast = Color(0xFF000000) 101 | val inverseSurfaceLightHighContrast = Color(0xFF2F312A) 102 | val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF) 103 | val inversePrimaryLightHighContrast = Color(0xFFD6F7AC) 104 | val surfaceDimLightHighContrast = Color(0xFFDADBD0) 105 | val surfaceBrightLightHighContrast = Color(0xFFF9FAEF) 106 | val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF) 107 | val surfaceContainerLowLightHighContrast = Color(0xFFF3F4E9) 108 | val surfaceContainerLightHighContrast = Color(0xFFEEEFE3) 109 | val surfaceContainerHighLightHighContrast = Color(0xFFE8E9DE) 110 | val surfaceContainerHighestLightHighContrast = Color(0xFFE2E3D8) 111 | 112 | val primaryDark = Color(0xFFB1D18A) 113 | val onPrimaryDark = Color(0xFF1F3701) 114 | val primaryContainerDark = Color(0xFF354E16) 115 | val onPrimaryContainerDark = Color(0xFFCDEDA3) 116 | val secondaryDark = Color(0xFFBFCBAD) 117 | val onSecondaryDark = Color(0xFF2A331E) 118 | val secondaryContainerDark = Color(0xFF404A33) 119 | val onSecondaryContainerDark = Color(0xFFDCE7C8) 120 | val tertiaryDark = Color(0xFFA0D0CB) 121 | val onTertiaryDark = Color(0xFF003735) 122 | val tertiaryContainerDark = Color(0xFF1F4E4B) 123 | val onTertiaryContainerDark = Color(0xFFBCECE7) 124 | val errorDark = Color(0xFFFFB4AB) 125 | val onErrorDark = Color(0xFF690005) 126 | val errorContainerDark = Color(0xFF93000A) 127 | val onErrorContainerDark = Color(0xFFFFDAD6) 128 | val backgroundDark = Color(0xFF12140E) 129 | val onBackgroundDark = Color(0xFFE2E3D8) 130 | val surfaceDark = Color(0xFF12140E) 131 | val onSurfaceDark = Color(0xFFE2E3D8) 132 | val surfaceVariantDark = Color(0xFF44483D) 133 | val onSurfaceVariantDark = Color(0xFFC5C8BA) 134 | val outlineDark = Color(0xFF8F9285) 135 | val outlineVariantDark = Color(0xFF44483D) 136 | val scrimDark = Color(0xFF000000) 137 | val inverseSurfaceDark = Color(0xFFE2E3D8) 138 | val inverseOnSurfaceDark = Color(0xFF2F312A) 139 | val inversePrimaryDark = Color(0xFF4C662B) 140 | val surfaceDimDark = Color(0xFF12140E) 141 | val surfaceBrightDark = Color(0xFF383A32) 142 | val surfaceContainerLowestDark = Color(0xFF0C0F09) 143 | val surfaceContainerLowDark = Color(0xFF1A1C16) 144 | val surfaceContainerDark = Color(0xFF1E201A) 145 | val surfaceContainerHighDark = Color(0xFF282B24) 146 | val surfaceContainerHighestDark = Color(0xFF33362E) 147 | 148 | val primaryDarkMediumContrast = Color(0xFFB5D58E) 149 | val onPrimaryDarkMediumContrast = Color(0xFF0C1A00) 150 | val primaryContainerDarkMediumContrast = Color(0xFF7D9A59) 151 | val onPrimaryContainerDarkMediumContrast = Color(0xFF000000) 152 | val secondaryDarkMediumContrast = Color(0xFFC4CFB1) 153 | val onSecondaryDarkMediumContrast = Color(0xFF101907) 154 | val secondaryContainerDarkMediumContrast = Color(0xFF8A9579) 155 | val onSecondaryContainerDarkMediumContrast = Color(0xFF000000) 156 | val tertiaryDarkMediumContrast = Color(0xFFA4D4D0) 157 | val onTertiaryDarkMediumContrast = Color(0xFF001A19) 158 | val tertiaryContainerDarkMediumContrast = Color(0xFF6B9995) 159 | val onTertiaryContainerDarkMediumContrast = Color(0xFF000000) 160 | val errorDarkMediumContrast = Color(0xFFFFBAB1) 161 | val onErrorDarkMediumContrast = Color(0xFF370001) 162 | val errorContainerDarkMediumContrast = Color(0xFFFF5449) 163 | val onErrorContainerDarkMediumContrast = Color(0xFF000000) 164 | val backgroundDarkMediumContrast = Color(0xFF12140E) 165 | val onBackgroundDarkMediumContrast = Color(0xFFE2E3D8) 166 | val surfaceDarkMediumContrast = Color(0xFF12140E) 167 | val onSurfaceDarkMediumContrast = Color(0xFFFBFCF0) 168 | val surfaceVariantDarkMediumContrast = Color(0xFF44483D) 169 | val onSurfaceVariantDarkMediumContrast = Color(0xFFC9CCBE) 170 | val outlineDarkMediumContrast = Color(0xFFA1A497) 171 | val outlineVariantDarkMediumContrast = Color(0xFF818578) 172 | val scrimDarkMediumContrast = Color(0xFF000000) 173 | val inverseSurfaceDarkMediumContrast = Color(0xFFE2E3D8) 174 | val inverseOnSurfaceDarkMediumContrast = Color(0xFF282B24) 175 | val inversePrimaryDarkMediumContrast = Color(0xFF364F17) 176 | val surfaceDimDarkMediumContrast = Color(0xFF12140E) 177 | val surfaceBrightDarkMediumContrast = Color(0xFF383A32) 178 | val surfaceContainerLowestDarkMediumContrast = Color(0xFF0C0F09) 179 | val surfaceContainerLowDarkMediumContrast = Color(0xFF1A1C16) 180 | val surfaceContainerDarkMediumContrast = Color(0xFF1E201A) 181 | val surfaceContainerHighDarkMediumContrast = Color(0xFF282B24) 182 | val surfaceContainerHighestDarkMediumContrast = Color(0xFF33362E) 183 | 184 | val primaryDarkHighContrast = Color(0xFFF4FFDF) 185 | val onPrimaryDarkHighContrast = Color(0xFF000000) 186 | val primaryContainerDarkHighContrast = Color(0xFFB5D58E) 187 | val onPrimaryContainerDarkHighContrast = Color(0xFF000000) 188 | val secondaryDarkHighContrast = Color(0xFFF4FFDF) 189 | val onSecondaryDarkHighContrast = Color(0xFF000000) 190 | val secondaryContainerDarkHighContrast = Color(0xFFC4CFB1) 191 | val onSecondaryContainerDarkHighContrast = Color(0xFF000000) 192 | val tertiaryDarkHighContrast = Color(0xFFEAFFFC) 193 | val onTertiaryDarkHighContrast = Color(0xFF000000) 194 | val tertiaryContainerDarkHighContrast = Color(0xFFA4D4D0) 195 | val onTertiaryContainerDarkHighContrast = Color(0xFF000000) 196 | val errorDarkHighContrast = Color(0xFFFFF9F9) 197 | val onErrorDarkHighContrast = Color(0xFF000000) 198 | val errorContainerDarkHighContrast = Color(0xFFFFBAB1) 199 | val onErrorContainerDarkHighContrast = Color(0xFF000000) 200 | val backgroundDarkHighContrast = Color(0xFF12140E) 201 | val onBackgroundDarkHighContrast = Color(0xFFE2E3D8) 202 | val surfaceDarkHighContrast = Color(0xFF12140E) 203 | val onSurfaceDarkHighContrast = Color(0xFFFFFFFF) 204 | val surfaceVariantDarkHighContrast = Color(0xFF44483D) 205 | val onSurfaceVariantDarkHighContrast = Color(0xFFF9FCED) 206 | val outlineDarkHighContrast = Color(0xFFC9CCBE) 207 | val outlineVariantDarkHighContrast = Color(0xFFC9CCBE) 208 | val scrimDarkHighContrast = Color(0xFF000000) 209 | val inverseSurfaceDarkHighContrast = Color(0xFFE2E3D8) 210 | val inverseOnSurfaceDarkHighContrast = Color(0xFF000000) 211 | val inversePrimaryDarkHighContrast = Color(0xFF1A3000) 212 | val surfaceDimDarkHighContrast = Color(0xFF12140E) 213 | val surfaceBrightDarkHighContrast = Color(0xFF383A32) 214 | val surfaceContainerLowestDarkHighContrast = Color(0xFF0C0F09) 215 | val surfaceContainerLowDarkHighContrast = Color(0xFF1A1C16) 216 | val surfaceContainerDarkHighContrast = Color(0xFF1E201A) 217 | val surfaceContainerHighDarkHighContrast = Color(0xFF282B24) 218 | val surfaceContainerHighestDarkHighContrast = Color(0xFF33362E) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.darkColorScheme 6 | import androidx.compose.material3.lightColorScheme 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.runtime.Immutable 9 | import androidx.compose.ui.graphics.Color 10 | 11 | private val lightScheme = lightColorScheme( 12 | primary = primaryLight, 13 | onPrimary = onPrimaryLight, 14 | primaryContainer = primaryContainerLight, 15 | onPrimaryContainer = onPrimaryContainerLight, 16 | secondary = secondaryLight, 17 | onSecondary = onSecondaryLight, 18 | secondaryContainer = secondaryContainerLight, 19 | onSecondaryContainer = onSecondaryContainerLight, 20 | tertiary = tertiaryLight, 21 | onTertiary = onTertiaryLight, 22 | tertiaryContainer = tertiaryContainerLight, 23 | onTertiaryContainer = onTertiaryContainerLight, 24 | error = errorLight, 25 | onError = onErrorLight, 26 | errorContainer = errorContainerLight, 27 | onErrorContainer = onErrorContainerLight, 28 | background = backgroundLight, 29 | onBackground = onBackgroundLight, 30 | surface = surfaceLight, 31 | onSurface = onSurfaceLight, 32 | surfaceVariant = surfaceVariantLight, 33 | onSurfaceVariant = onSurfaceVariantLight, 34 | outline = outlineLight, 35 | outlineVariant = outlineVariantLight, 36 | scrim = scrimLight, 37 | inverseSurface = inverseSurfaceLight, 38 | inverseOnSurface = inverseOnSurfaceLight, 39 | inversePrimary = inversePrimaryLight, 40 | surfaceDim = surfaceDimLight, 41 | surfaceBright = surfaceBrightLight, 42 | surfaceContainerLowest = surfaceContainerLowestLight, 43 | surfaceContainerLow = surfaceContainerLowLight, 44 | surfaceContainer = surfaceContainerLight, 45 | surfaceContainerHigh = surfaceContainerHighLight, 46 | surfaceContainerHighest = surfaceContainerHighestLight, 47 | ) 48 | 49 | private val darkScheme = darkColorScheme( 50 | primary = primaryDark, 51 | onPrimary = onPrimaryDark, 52 | primaryContainer = primaryContainerDark, 53 | onPrimaryContainer = onPrimaryContainerDark, 54 | secondary = secondaryDark, 55 | onSecondary = onSecondaryDark, 56 | secondaryContainer = secondaryContainerDark, 57 | onSecondaryContainer = onSecondaryContainerDark, 58 | tertiary = tertiaryDark, 59 | onTertiary = onTertiaryDark, 60 | tertiaryContainer = tertiaryContainerDark, 61 | onTertiaryContainer = onTertiaryContainerDark, 62 | error = errorDark, 63 | onError = onErrorDark, 64 | errorContainer = errorContainerDark, 65 | onErrorContainer = onErrorContainerDark, 66 | background = backgroundDark, 67 | onBackground = onBackgroundDark, 68 | surface = surfaceDark, 69 | onSurface = onSurfaceDark, 70 | surfaceVariant = surfaceVariantDark, 71 | onSurfaceVariant = onSurfaceVariantDark, 72 | outline = outlineDark, 73 | outlineVariant = outlineVariantDark, 74 | scrim = scrimDark, 75 | inverseSurface = inverseSurfaceDark, 76 | inverseOnSurface = inverseOnSurfaceDark, 77 | inversePrimary = inversePrimaryDark, 78 | surfaceDim = surfaceDimDark, 79 | surfaceBright = surfaceBrightDark, 80 | surfaceContainerLowest = surfaceContainerLowestDark, 81 | surfaceContainerLow = surfaceContainerLowDark, 82 | surfaceContainer = surfaceContainerDark, 83 | surfaceContainerHigh = surfaceContainerHighDark, 84 | surfaceContainerHighest = surfaceContainerHighestDark, 85 | ) 86 | 87 | private val mediumContrastLightColorScheme = lightColorScheme( 88 | primary = primaryLightMediumContrast, 89 | onPrimary = onPrimaryLightMediumContrast, 90 | primaryContainer = primaryContainerLightMediumContrast, 91 | onPrimaryContainer = onPrimaryContainerLightMediumContrast, 92 | secondary = secondaryLightMediumContrast, 93 | onSecondary = onSecondaryLightMediumContrast, 94 | secondaryContainer = secondaryContainerLightMediumContrast, 95 | onSecondaryContainer = onSecondaryContainerLightMediumContrast, 96 | tertiary = tertiaryLightMediumContrast, 97 | onTertiary = onTertiaryLightMediumContrast, 98 | tertiaryContainer = tertiaryContainerLightMediumContrast, 99 | onTertiaryContainer = onTertiaryContainerLightMediumContrast, 100 | error = errorLightMediumContrast, 101 | onError = onErrorLightMediumContrast, 102 | errorContainer = errorContainerLightMediumContrast, 103 | onErrorContainer = onErrorContainerLightMediumContrast, 104 | background = backgroundLightMediumContrast, 105 | onBackground = onBackgroundLightMediumContrast, 106 | surface = surfaceLightMediumContrast, 107 | onSurface = onSurfaceLightMediumContrast, 108 | surfaceVariant = surfaceVariantLightMediumContrast, 109 | onSurfaceVariant = onSurfaceVariantLightMediumContrast, 110 | outline = outlineLightMediumContrast, 111 | outlineVariant = outlineVariantLightMediumContrast, 112 | scrim = scrimLightMediumContrast, 113 | inverseSurface = inverseSurfaceLightMediumContrast, 114 | inverseOnSurface = inverseOnSurfaceLightMediumContrast, 115 | inversePrimary = inversePrimaryLightMediumContrast, 116 | surfaceDim = surfaceDimLightMediumContrast, 117 | surfaceBright = surfaceBrightLightMediumContrast, 118 | surfaceContainerLowest = surfaceContainerLowestLightMediumContrast, 119 | surfaceContainerLow = surfaceContainerLowLightMediumContrast, 120 | surfaceContainer = surfaceContainerLightMediumContrast, 121 | surfaceContainerHigh = surfaceContainerHighLightMediumContrast, 122 | surfaceContainerHighest = surfaceContainerHighestLightMediumContrast, 123 | ) 124 | 125 | private val highContrastLightColorScheme = lightColorScheme( 126 | primary = primaryLightHighContrast, 127 | onPrimary = onPrimaryLightHighContrast, 128 | primaryContainer = primaryContainerLightHighContrast, 129 | onPrimaryContainer = onPrimaryContainerLightHighContrast, 130 | secondary = secondaryLightHighContrast, 131 | onSecondary = onSecondaryLightHighContrast, 132 | secondaryContainer = secondaryContainerLightHighContrast, 133 | onSecondaryContainer = onSecondaryContainerLightHighContrast, 134 | tertiary = tertiaryLightHighContrast, 135 | onTertiary = onTertiaryLightHighContrast, 136 | tertiaryContainer = tertiaryContainerLightHighContrast, 137 | onTertiaryContainer = onTertiaryContainerLightHighContrast, 138 | error = errorLightHighContrast, 139 | onError = onErrorLightHighContrast, 140 | errorContainer = errorContainerLightHighContrast, 141 | onErrorContainer = onErrorContainerLightHighContrast, 142 | background = backgroundLightHighContrast, 143 | onBackground = onBackgroundLightHighContrast, 144 | surface = surfaceLightHighContrast, 145 | onSurface = onSurfaceLightHighContrast, 146 | surfaceVariant = surfaceVariantLightHighContrast, 147 | onSurfaceVariant = onSurfaceVariantLightHighContrast, 148 | outline = outlineLightHighContrast, 149 | outlineVariant = outlineVariantLightHighContrast, 150 | scrim = scrimLightHighContrast, 151 | inverseSurface = inverseSurfaceLightHighContrast, 152 | inverseOnSurface = inverseOnSurfaceLightHighContrast, 153 | inversePrimary = inversePrimaryLightHighContrast, 154 | surfaceDim = surfaceDimLightHighContrast, 155 | surfaceBright = surfaceBrightLightHighContrast, 156 | surfaceContainerLowest = surfaceContainerLowestLightHighContrast, 157 | surfaceContainerLow = surfaceContainerLowLightHighContrast, 158 | surfaceContainer = surfaceContainerLightHighContrast, 159 | surfaceContainerHigh = surfaceContainerHighLightHighContrast, 160 | surfaceContainerHighest = surfaceContainerHighestLightHighContrast, 161 | ) 162 | 163 | private val mediumContrastDarkColorScheme = darkColorScheme( 164 | primary = primaryDarkMediumContrast, 165 | onPrimary = onPrimaryDarkMediumContrast, 166 | primaryContainer = primaryContainerDarkMediumContrast, 167 | onPrimaryContainer = onPrimaryContainerDarkMediumContrast, 168 | secondary = secondaryDarkMediumContrast, 169 | onSecondary = onSecondaryDarkMediumContrast, 170 | secondaryContainer = secondaryContainerDarkMediumContrast, 171 | onSecondaryContainer = onSecondaryContainerDarkMediumContrast, 172 | tertiary = tertiaryDarkMediumContrast, 173 | onTertiary = onTertiaryDarkMediumContrast, 174 | tertiaryContainer = tertiaryContainerDarkMediumContrast, 175 | onTertiaryContainer = onTertiaryContainerDarkMediumContrast, 176 | error = errorDarkMediumContrast, 177 | onError = onErrorDarkMediumContrast, 178 | errorContainer = errorContainerDarkMediumContrast, 179 | onErrorContainer = onErrorContainerDarkMediumContrast, 180 | background = backgroundDarkMediumContrast, 181 | onBackground = onBackgroundDarkMediumContrast, 182 | surface = surfaceDarkMediumContrast, 183 | onSurface = onSurfaceDarkMediumContrast, 184 | surfaceVariant = surfaceVariantDarkMediumContrast, 185 | onSurfaceVariant = onSurfaceVariantDarkMediumContrast, 186 | outline = outlineDarkMediumContrast, 187 | outlineVariant = outlineVariantDarkMediumContrast, 188 | scrim = scrimDarkMediumContrast, 189 | inverseSurface = inverseSurfaceDarkMediumContrast, 190 | inverseOnSurface = inverseOnSurfaceDarkMediumContrast, 191 | inversePrimary = inversePrimaryDarkMediumContrast, 192 | surfaceDim = surfaceDimDarkMediumContrast, 193 | surfaceBright = surfaceBrightDarkMediumContrast, 194 | surfaceContainerLowest = surfaceContainerLowestDarkMediumContrast, 195 | surfaceContainerLow = surfaceContainerLowDarkMediumContrast, 196 | surfaceContainer = surfaceContainerDarkMediumContrast, 197 | surfaceContainerHigh = surfaceContainerHighDarkMediumContrast, 198 | surfaceContainerHighest = surfaceContainerHighestDarkMediumContrast, 199 | ) 200 | 201 | private val highContrastDarkColorScheme = darkColorScheme( 202 | primary = primaryDarkHighContrast, 203 | onPrimary = onPrimaryDarkHighContrast, 204 | primaryContainer = primaryContainerDarkHighContrast, 205 | onPrimaryContainer = onPrimaryContainerDarkHighContrast, 206 | secondary = secondaryDarkHighContrast, 207 | onSecondary = onSecondaryDarkHighContrast, 208 | secondaryContainer = secondaryContainerDarkHighContrast, 209 | onSecondaryContainer = onSecondaryContainerDarkHighContrast, 210 | tertiary = tertiaryDarkHighContrast, 211 | onTertiary = onTertiaryDarkHighContrast, 212 | tertiaryContainer = tertiaryContainerDarkHighContrast, 213 | onTertiaryContainer = onTertiaryContainerDarkHighContrast, 214 | error = errorDarkHighContrast, 215 | onError = onErrorDarkHighContrast, 216 | errorContainer = errorContainerDarkHighContrast, 217 | onErrorContainer = onErrorContainerDarkHighContrast, 218 | background = backgroundDarkHighContrast, 219 | onBackground = onBackgroundDarkHighContrast, 220 | surface = surfaceDarkHighContrast, 221 | onSurface = onSurfaceDarkHighContrast, 222 | surfaceVariant = surfaceVariantDarkHighContrast, 223 | onSurfaceVariant = onSurfaceVariantDarkHighContrast, 224 | outline = outlineDarkHighContrast, 225 | outlineVariant = outlineVariantDarkHighContrast, 226 | scrim = scrimDarkHighContrast, 227 | inverseSurface = inverseSurfaceDarkHighContrast, 228 | inverseOnSurface = inverseOnSurfaceDarkHighContrast, 229 | inversePrimary = inversePrimaryDarkHighContrast, 230 | surfaceDim = surfaceDimDarkHighContrast, 231 | surfaceBright = surfaceBrightDarkHighContrast, 232 | surfaceContainerLowest = surfaceContainerLowestDarkHighContrast, 233 | surfaceContainerLow = surfaceContainerLowDarkHighContrast, 234 | surfaceContainer = surfaceContainerDarkHighContrast, 235 | surfaceContainerHigh = surfaceContainerHighDarkHighContrast, 236 | surfaceContainerHighest = surfaceContainerHighestDarkHighContrast, 237 | ) 238 | 239 | @Immutable 240 | data class ColorFamily( 241 | val color: Color, 242 | val onColor: Color, 243 | val colorContainer: Color, 244 | val onColorContainer: Color 245 | ) 246 | 247 | val unspecified_scheme = ColorFamily( 248 | Color.Unspecified, Color.Unspecified, Color.Unspecified, Color.Unspecified 249 | ) 250 | 251 | @Composable 252 | fun AppTheme(useDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { 253 | val colors = if (!useDarkTheme) { 254 | lightScheme 255 | } else { 256 | darkScheme 257 | } 258 | MaterialTheme( 259 | colorScheme = colors, 260 | typography = AppTypography, 261 | content = content 262 | ) 263 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | val AppTypography = Typography() 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ui/ApkInformation.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.animation.fadeIn 5 | import androidx.compose.animation.fadeOut 6 | import androidx.compose.foundation.BorderStroke 7 | import androidx.compose.foundation.ExperimentalFoundationApi 8 | import androidx.compose.foundation.draganddrop.dragAndDropTarget 9 | import androidx.compose.foundation.isSystemInDarkTheme 10 | import androidx.compose.foundation.layout.Box 11 | import androidx.compose.foundation.layout.Column 12 | import androidx.compose.foundation.layout.Row 13 | import androidx.compose.foundation.layout.fillMaxSize 14 | import androidx.compose.foundation.layout.height 15 | import androidx.compose.foundation.layout.padding 16 | import androidx.compose.foundation.layout.size 17 | import androidx.compose.foundation.lazy.LazyColumn 18 | import androidx.compose.foundation.onClick 19 | import androidx.compose.material3.Card 20 | import androidx.compose.material3.MaterialTheme 21 | import androidx.compose.material3.Surface 22 | import androidx.compose.material3.Text 23 | import androidx.compose.runtime.Composable 24 | import androidx.compose.runtime.collectAsState 25 | import androidx.compose.runtime.getValue 26 | import androidx.compose.runtime.mutableStateOf 27 | import androidx.compose.runtime.remember 28 | import androidx.compose.runtime.rememberCoroutineScope 29 | import androidx.compose.runtime.setValue 30 | import androidx.compose.ui.Alignment 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.graphics.asSkiaBitmap 33 | import androidx.compose.ui.unit.DpSize 34 | import androidx.compose.ui.unit.dp 35 | import androidx.compose.ui.window.Window 36 | import androidx.compose.ui.window.rememberWindowState 37 | import coil3.compose.AsyncImage 38 | import com.github.panpf.zoomimage.CoilZoomAsyncImage 39 | import kotlinx.coroutines.CoroutineScope 40 | import model.ApkInformation 41 | import model.DarkThemeConfig 42 | import model.FileSelectorType 43 | import org.jetbrains.compose.resources.painterResource 44 | import org.jetbrains.compose.resources.stringResource 45 | import org.tool.kit.composeapp.generated.resources.ABIs 46 | import org.tool.kit.composeapp.generated.resources.Res 47 | import org.tool.kit.composeapp.generated.resources.app_name 48 | import org.tool.kit.composeapp.generated.resources.compile_sdk_version 49 | import org.tool.kit.composeapp.generated.resources.file_md5 50 | import org.tool.kit.composeapp.generated.resources.icon 51 | import org.tool.kit.composeapp.generated.resources.let_go 52 | import org.tool.kit.composeapp.generated.resources.minimum_sdk_version 53 | import org.tool.kit.composeapp.generated.resources.package_name 54 | import org.tool.kit.composeapp.generated.resources.permissions 55 | import org.tool.kit.composeapp.generated.resources.size 56 | import org.tool.kit.composeapp.generated.resources.target_sdk_version 57 | import org.tool.kit.composeapp.generated.resources.upload_apk 58 | import org.tool.kit.composeapp.generated.resources.version 59 | import org.tool.kit.composeapp.generated.resources.version_code 60 | import theme.AppTheme 61 | import utils.LottieAnimation 62 | import utils.copy 63 | import utils.formatFileSize 64 | import utils.getImageRequest 65 | import utils.isApk 66 | import vm.MainViewModel 67 | import vm.UIState 68 | import kotlin.io.path.pathString 69 | 70 | /** 71 | * @Author : LazyIonEs 72 | * @CreateDate : 2024/2/8 16:13 73 | * @Description : APK信息 74 | * @Version : 1.0 75 | */ 76 | @Composable 77 | fun ApkInformation(viewModel: MainViewModel) { 78 | val scope = rememberCoroutineScope() 79 | if (viewModel.apkInformationState == UIState.WAIT) { 80 | ApkInformationLottie(viewModel, scope) 81 | } 82 | ApkInformationBox(viewModel) 83 | ApkDraggingBox(viewModel, scope) 84 | } 85 | 86 | /** 87 | * 主页动画 88 | */ 89 | @Composable 90 | private fun ApkInformationLottie(viewModel: MainViewModel, scope: CoroutineScope) { 91 | val themeConfig by viewModel.themeConfig.collectAsState() 92 | val useDarkTheme = when (themeConfig) { 93 | DarkThemeConfig.LIGHT -> false 94 | DarkThemeConfig.DARK -> true 95 | DarkThemeConfig.FOLLOW_SYSTEM -> isSystemInDarkTheme() 96 | } 97 | Box( 98 | modifier = Modifier.padding(6.dp), contentAlignment = Alignment.Center 99 | ) { 100 | if (useDarkTheme) { 101 | LottieAnimation(scope, "files/lottie_main_2_dark.json") 102 | } else { 103 | LottieAnimation(scope, "files/lottie_main_2_light.json") 104 | } 105 | } 106 | } 107 | 108 | @OptIn(ExperimentalFoundationApi::class) 109 | @Composable 110 | private fun ApkDraggingBox(viewModel: MainViewModel, scope: CoroutineScope) { 111 | var dragging by remember { mutableStateOf(false) } 112 | UploadAnimate(dragging, scope) 113 | Box( 114 | modifier = Modifier.fillMaxSize() 115 | .dragAndDropTarget(shouldStartDragAndDrop = accept@{ true }, target = dragAndDropTarget(dragging = { 116 | dragging = it 117 | }, onFinish = { result -> 118 | result.onSuccess { fileList -> 119 | fileList.firstOrNull()?.let { 120 | val path = it.toAbsolutePath().pathString 121 | if (path.isApk) { 122 | viewModel.apkInformation(path) 123 | } 124 | } 125 | } 126 | })) 127 | ) { 128 | Box( 129 | modifier = Modifier.align(Alignment.BottomEnd) 130 | ) { 131 | FileButton( 132 | value = if (dragging) { 133 | stringResource(Res.string.let_go) 134 | } else { 135 | stringResource(Res.string.upload_apk) 136 | }, expanded = viewModel.apkInformationState == UIState.WAIT, FileSelectorType.APK 137 | ) { path -> 138 | viewModel.apkInformation(path) 139 | } 140 | } 141 | } 142 | } 143 | 144 | @OptIn(ExperimentalFoundationApi::class) 145 | @Composable 146 | private fun ApkInformationBox( 147 | viewModel: MainViewModel 148 | ) { 149 | val uiState = viewModel.apkInformationState 150 | AnimatedVisibility( 151 | visible = uiState is UIState.Success, enter = fadeIn(), exit = fadeOut() 152 | ) { 153 | Card( 154 | modifier = Modifier.fillMaxSize().padding(top = 14.dp, bottom = 14.dp, end = 14.dp), 155 | border = BorderStroke(2.dp, MaterialTheme.colorScheme.outline) 156 | ) { 157 | Box( 158 | modifier = Modifier.fillMaxSize().padding(vertical = 12.dp) 159 | ) { 160 | if (uiState is UIState.Success) { 161 | val apkInformation = uiState.result as ApkInformation 162 | LazyColumn { 163 | item { 164 | AppInfoItem(stringResource(Res.string.app_name), apkInformation.label, viewModel) 165 | } 166 | item { 167 | AppInfoItem(stringResource(Res.string.version), apkInformation.versionName, viewModel) 168 | } 169 | item { 170 | AppInfoItem(stringResource(Res.string.version_code), apkInformation.versionCode, viewModel) 171 | } 172 | item { 173 | AppInfoItem(stringResource(Res.string.package_name), apkInformation.packageName, viewModel) 174 | } 175 | item { 176 | AppInfoItem( 177 | stringResource(Res.string.compile_sdk_version), 178 | apkInformation.compileSdkVersion, 179 | viewModel 180 | ) 181 | } 182 | item { 183 | AppInfoItem( 184 | stringResource(Res.string.minimum_sdk_version), 185 | apkInformation.minSdkVersion, 186 | viewModel 187 | ) 188 | } 189 | item { 190 | AppInfoItem( 191 | stringResource(Res.string.target_sdk_version), 192 | apkInformation.targetSdkVersion, 193 | viewModel 194 | ) 195 | } 196 | item { 197 | AppInfoItem(stringResource(Res.string.ABIs), apkInformation.nativeCode, viewModel) 198 | } 199 | item { 200 | AppInfoItem(stringResource(Res.string.file_md5), apkInformation.md5, viewModel) 201 | } 202 | item { 203 | AppInfoItem( 204 | stringResource(Res.string.size), 205 | apkInformation.size.formatFileSize(scale = 1, withInterval = true), 206 | viewModel 207 | ) 208 | } 209 | item { 210 | PermissionsList(apkInformation.usesPermissionList) 211 | } 212 | } 213 | apkInformation.icon?.let { image -> 214 | var isOpenImage by remember { mutableStateOf(false) } 215 | if (isOpenImage) { 216 | val windowState = rememberWindowState(size = DpSize(450.dp, 450.dp)) 217 | Window( 218 | onCloseRequest = { isOpenImage = false }, 219 | state = windowState, 220 | title = "Zoom Image", 221 | icon = painterResource(Res.drawable.icon), 222 | alwaysOnTop = true 223 | ) { 224 | val themeConfig by viewModel.themeConfig.collectAsState() 225 | val useDarkTheme = when (themeConfig) { 226 | DarkThemeConfig.LIGHT -> false 227 | DarkThemeConfig.DARK -> true 228 | DarkThemeConfig.FOLLOW_SYSTEM -> isSystemInDarkTheme() 229 | } 230 | AppTheme(useDarkTheme) { 231 | Surface(color = MaterialTheme.colorScheme.background) { 232 | CoilZoomAsyncImage( 233 | model = getImageRequest(image.asSkiaBitmap()), 234 | contentDescription = "zoom image", 235 | modifier = Modifier.fillMaxSize(), 236 | ) 237 | } 238 | } 239 | } 240 | } 241 | AsyncImage( 242 | model = getImageRequest(image.asSkiaBitmap()), 243 | contentDescription = "app icon", 244 | modifier = Modifier.align(Alignment.TopEnd) 245 | .padding(top = 6.dp, end = 18.dp) 246 | .size(128.dp) 247 | .onClick { 248 | isOpenImage = !isOpenImage 249 | } 250 | ) 251 | } 252 | } 253 | } 254 | } 255 | } 256 | } 257 | 258 | @Composable 259 | private fun AppInfoItem(title: String, value: String, viewModel: MainViewModel) { 260 | Card(modifier = Modifier.padding(horizontal = 12.dp).height(36.dp), onClick = { 261 | copy(value, viewModel) 262 | }) { 263 | Row( 264 | modifier = Modifier.fillMaxSize().padding(horizontal = 24.dp) 265 | ) { 266 | Text( 267 | title, 268 | style = MaterialTheme.typography.titleMedium, 269 | modifier = Modifier.weight(1.2f).align(Alignment.CenterVertically) 270 | ) 271 | Text( 272 | value, 273 | style = MaterialTheme.typography.bodyMedium, 274 | modifier = Modifier.weight(4f).align(Alignment.CenterVertically) 275 | ) 276 | } 277 | } 278 | } 279 | 280 | @Composable 281 | private fun PermissionsList(permissions: ArrayList?) { 282 | permissions?.let { 283 | Column( 284 | modifier = Modifier.padding(horizontal = 12.dp), 285 | ) { 286 | Row( 287 | modifier = Modifier.fillMaxSize().padding(horizontal = 24.dp) 288 | ) { 289 | Text( 290 | stringResource(Res.string.permissions), 291 | modifier = Modifier.weight(1.2f), 292 | style = MaterialTheme.typography.titleMedium 293 | ) 294 | Column( 295 | modifier = Modifier.weight(4f) 296 | ) { 297 | it.forEach { permission -> 298 | Text(text = permission, style = MaterialTheme.typography.bodyMedium) 299 | } 300 | } 301 | } 302 | } 303 | } 304 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ui/JunkCode.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import androidx.compose.foundation.layout.Row 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.foundation.layout.size 9 | import androidx.compose.foundation.lazy.LazyColumn 10 | import androidx.compose.material3.Button 11 | import androidx.compose.material3.Card 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.material3.OutlinedTextField 14 | import androidx.compose.material3.Text 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.ui.Alignment 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.unit.dp 19 | import org.jetbrains.compose.resources.stringResource 20 | import org.tool.kit.composeapp.generated.resources.Res 21 | import org.tool.kit.composeapp.generated.resources.aar_name 22 | import org.tool.kit.composeapp.generated.resources.aar_output_path 23 | import org.tool.kit.composeapp.generated.resources.check_empty 24 | import org.tool.kit.composeapp.generated.resources.check_error 25 | import org.tool.kit.composeapp.generated.resources.junk_package_name 26 | import org.tool.kit.composeapp.generated.resources.number_of_activities 27 | import org.tool.kit.composeapp.generated.resources.number_of_packages 28 | import org.tool.kit.composeapp.generated.resources.resource_prefix 29 | import org.tool.kit.composeapp.generated.resources.start_generating 30 | import org.tool.kit.composeapp.generated.resources.suffix 31 | import vm.MainViewModel 32 | import java.io.File 33 | 34 | /** 35 | * @Author : LazyIonEs 36 | * @CreateDate : 2024/4/1 20:07 37 | * @Description : 垃圾代码生成页面 38 | * @Version : 1.0 39 | */ 40 | @Composable 41 | fun JunkCode(viewModel: MainViewModel) { 42 | JunkCodeBox(viewModel) 43 | } 44 | 45 | @Composable 46 | private fun JunkCodeBox(viewModel: MainViewModel) { 47 | Card( 48 | modifier = Modifier.fillMaxSize().padding(top = 20.dp, bottom = 20.dp, end = 14.dp) 49 | ) { 50 | val outputPathError = 51 | viewModel.junkCodeInfoState.outputPath.isNotBlank() && !File(viewModel.junkCodeInfoState.outputPath).isDirectory 52 | LazyColumn( 53 | modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally 54 | ) { 55 | item { 56 | Spacer(Modifier.size(16.dp)) 57 | FolderInput( 58 | value = viewModel.junkCodeInfoState.outputPath, 59 | label = stringResource(Res.string.aar_output_path), 60 | isError = outputPathError, 61 | onValueChange = { path -> 62 | viewModel.updateJunkCodeInfo(viewModel.junkCodeInfoState.copy(outputPath = path)) 63 | }) 64 | } 65 | item { 66 | Spacer(Modifier.size(8.dp)) 67 | StringInput( 68 | value = viewModel.junkCodeInfoState.aarName, 69 | label = stringResource(Res.string.aar_name), 70 | isError = false, 71 | realOnly = true, 72 | onValueChange = { }) 73 | } 74 | item { 75 | Spacer(Modifier.size(8.dp)) 76 | PackageName(viewModel) 77 | } 78 | item { 79 | Spacer(Modifier.size(8.dp)) 80 | IntInput( 81 | value = viewModel.junkCodeInfoState.packageCount, 82 | label = stringResource(Res.string.number_of_packages), 83 | isError = viewModel.junkCodeInfoState.packageCount.isBlank(), 84 | onValueChange = { packageCount -> 85 | viewModel.updateJunkCodeInfo(viewModel.junkCodeInfoState.copy(packageCount = packageCount)) 86 | }) 87 | } 88 | item { 89 | Spacer(Modifier.size(8.dp)) 90 | IntInput( 91 | value = viewModel.junkCodeInfoState.activityCountPerPackage, 92 | label = stringResource(Res.string.number_of_activities), 93 | isError = viewModel.junkCodeInfoState.activityCountPerPackage.isBlank(), 94 | onValueChange = { activityCountPerPackage -> 95 | viewModel.updateJunkCodeInfo(viewModel.junkCodeInfoState.copy(activityCountPerPackage = activityCountPerPackage)) 96 | }) 97 | } 98 | item { 99 | Spacer(Modifier.size(8.dp)) 100 | StringInput( 101 | value = viewModel.junkCodeInfoState.resPrefix, 102 | label = stringResource(Res.string.resource_prefix), 103 | isError = viewModel.junkCodeInfoState.resPrefix.isBlank(), 104 | onValueChange = { resPrefix -> 105 | viewModel.updateJunkCodeInfo(viewModel.junkCodeInfoState.copy(resPrefix = resPrefix)) 106 | }) 107 | } 108 | item { 109 | Spacer(Modifier.size(12.dp)) 110 | Generate( 111 | viewModel, outputPathError 112 | ) 113 | Spacer(Modifier.size(24.dp)) 114 | } 115 | } 116 | } 117 | } 118 | 119 | @Composable 120 | private fun PackageName(viewModel: MainViewModel) { 121 | Row( 122 | modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 64.dp), 123 | verticalAlignment = Alignment.CenterVertically 124 | ) { 125 | OutlinedTextField( 126 | modifier = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 3.dp).weight(3f), 127 | value = viewModel.junkCodeInfoState.packageName, 128 | onValueChange = { packageName -> 129 | val junkCodeInfo = viewModel.junkCodeInfoState.copy() 130 | junkCodeInfo.packageName = packageName 131 | viewModel.updateJunkCodeInfo(junkCodeInfo) 132 | }, 133 | label = { Text(text = stringResource(Res.string.junk_package_name), style = MaterialTheme.typography.labelLarge) }, 134 | singleLine = true, 135 | isError = viewModel.junkCodeInfoState.packageName.isBlank() 136 | ) 137 | Text( 138 | ".", 139 | style = MaterialTheme.typography.labelLarge, 140 | modifier = Modifier.align(Alignment.Bottom).padding(bottom = 3.dp) 141 | ) 142 | OutlinedTextField( 143 | modifier = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 3.dp).weight(2f), 144 | value = viewModel.junkCodeInfoState.suffix, 145 | onValueChange = { suffix -> 146 | val junkCodeInfo = viewModel.junkCodeInfoState.copy() 147 | junkCodeInfo.suffix = suffix 148 | viewModel.updateJunkCodeInfo(junkCodeInfo) 149 | }, 150 | label = { Text(text = stringResource(Res.string.suffix), style = MaterialTheme.typography.labelLarge) }, 151 | singleLine = true, 152 | isError = viewModel.junkCodeInfoState.suffix.isBlank() 153 | ) 154 | } 155 | } 156 | 157 | @Composable 158 | private fun Generate( 159 | viewModel: MainViewModel, outputPathError: Boolean 160 | ) { 161 | Button(onClick = { 162 | if (outputPathError) { 163 | viewModel.updateSnackbarVisuals(Res.string.check_error) 164 | return@Button 165 | } 166 | if (viewModel.junkCodeInfoState.outputPath.isBlank() || viewModel.junkCodeInfoState.packageName.isBlank() || viewModel.junkCodeInfoState.suffix.isBlank() || viewModel.junkCodeInfoState.packageCount.isBlank() || viewModel.junkCodeInfoState.activityCountPerPackage.isEmpty() || viewModel.junkCodeInfoState.resPrefix.isBlank()) { 167 | viewModel.updateSnackbarVisuals(Res.string.check_empty) 168 | return@Button 169 | } 170 | viewModel.generateJunkCode() 171 | }) { 172 | Text( 173 | text = stringResource(Res.string.start_generating), 174 | style = MaterialTheme.typography.titleMedium, 175 | modifier = Modifier.padding(horizontal = 48.dp) 176 | ) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/utils/Copy.kt: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import org.tool.kit.composeapp.generated.resources.Res 4 | import org.tool.kit.composeapp.generated.resources.copied_to_clipboard 5 | import vm.MainViewModel 6 | import java.awt.Toolkit 7 | import java.awt.datatransfer.Clipboard 8 | import java.awt.datatransfer.StringSelection 9 | 10 | /** 11 | * @Author : LazyIonEs 12 | * @CreateDate : 2024/2/3 15:56 13 | * @Description : 描述 14 | * @Version : 1.0 15 | */ 16 | 17 | fun copy(value: String, viewModel: MainViewModel) { 18 | copy(value) 19 | viewModel.updateSnackbarVisuals(Res.string.copied_to_clipboard) 20 | } 21 | 22 | /** 23 | * 复制到剪切板 24 | */ 25 | private fun copy(value: String) { 26 | val stringSelection = StringSelection(value) 27 | val clipboard: Clipboard = Toolkit.getDefaultToolkit().systemClipboard 28 | clipboard.setContents(stringSelection, null) 29 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/utils/CoroutinesUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package utils 18 | 19 | import kotlinx.coroutines.flow.SharingStarted 20 | import kotlinx.coroutines.flow.StateFlow 21 | 22 | private const val StopTimeoutMillis: Long = 5000 23 | 24 | /** 25 | * A [SharingStarted] meant to be used with a [StateFlow] to expose data to the UI. 26 | * 27 | * When the UI stops observing, upstream flows stay active for some time to allow the system to 28 | * come back from a short-lived configuration change (such as rotations). If the UI stops 29 | * observing for longer, the cache is kept but the upstream flows are stopped. When the UI comes 30 | * back, the latest value is replayed and the upstream flows are executed again. This is done to 31 | * save resources when the app is in the background but let users switch between apps quickly. 32 | */ 33 | val WhileUiSubscribed: SharingStarted = SharingStarted.WhileSubscribed(StopTimeoutMillis) 34 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/utils/ExternalCommand.kt: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import java.io.File 4 | import java.io.IOException 5 | import java.io.InputStream 6 | import java.io.OutputStream 7 | import java.util.concurrent.TimeUnit 8 | 9 | /** 10 | * Executes a command with ability to pass `stdin` and capture `stdout` and `stderr`. 11 | */ 12 | class ExternalCommand(private val executable: String) { 13 | 14 | @Throws(IOException::class) 15 | fun execute( 16 | args: List, 17 | stdin: InputStream, 18 | stdout: OutputStream, 19 | stderr: OutputStream, 20 | timeout: Long = 60000L, 21 | unit: TimeUnit = TimeUnit.MILLISECONDS 22 | ): Int { 23 | val command: MutableList = ArrayList() 24 | val exe = File(executable) 25 | command.add(exe.absolutePath) 26 | command.addAll(args) 27 | val pb = ProcessBuilder(command) 28 | val process = pb.start() 29 | val inToProcess = PipeConnector(stdin, process.outputStream) 30 | val processToOut = PipeConnector(process.inputStream, stdout) 31 | val processToErr = PipeConnector(process.errorStream, stderr) 32 | inToProcess.start() 33 | processToOut.start() 34 | processToErr.start() 35 | var code = 255 36 | try { 37 | val finished = process.waitFor(timeout, unit) 38 | if (!finished) { 39 | process.destroyForcibly() 40 | } else { 41 | code = process.exitValue() 42 | } 43 | processToOut.join() 44 | stdin.close() 45 | inToProcess.join() 46 | } catch (e: InterruptedException) { 47 | println(e) 48 | } 49 | return code 50 | } 51 | 52 | private class PipeConnector(private val input: InputStream, private val output: OutputStream) : Thread() { 53 | override fun run() { 54 | try { 55 | val buffer = ByteArray(8192) 56 | var read: Int 57 | while (input.read(buffer).also { read = it } > 0) { 58 | output.write(buffer, 0, read) 59 | output.flush() 60 | } 61 | } catch (e: IOException) { 62 | // Ignore and exit the thread 63 | } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/utils/ImageRequest.kt: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import coil3.ImageLoader 4 | import coil3.asImage 5 | import coil3.decode.DataSource 6 | import coil3.fetch.FetchResult 7 | import coil3.fetch.Fetcher 8 | import coil3.fetch.ImageFetchResult 9 | import coil3.request.ImageRequest 10 | import coil3.request.Options 11 | import coil3.size.Size 12 | import org.jetbrains.skia.Bitmap 13 | 14 | fun getImageRequest(data: Any?) = 15 | ImageRequest.Builder(coil3.PlatformContext.INSTANCE) 16 | .data(data = data) 17 | .size(Size.ORIGINAL) 18 | .fetcherFactory(SkiaBitmapFetcher.Factory()) 19 | .build() 20 | 21 | class SkiaBitmapFetcher( 22 | private val data: Bitmap 23 | ) : Fetcher { 24 | override suspend fun fetch(): FetchResult { 25 | return ImageFetchResult( 26 | image = data.asImage(), 27 | isSampled = false, 28 | dataSource = DataSource.MEMORY, 29 | ) 30 | } 31 | 32 | class Factory : Fetcher.Factory { 33 | override fun create(data: Bitmap, options: Options, imageLoader: ImageLoader): Fetcher { 34 | return SkiaBitmapFetcher(data) 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/utils/LottieAnimation.kt: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import androidx.compose.animation.core.* 4 | import androidx.compose.foundation.Canvas 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.runtime.* 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.graphics.drawscope.drawIntoCanvas 9 | import androidx.compose.ui.graphics.nativeCanvas 10 | import kotlinx.coroutines.CoroutineScope 11 | import kotlinx.coroutines.launch 12 | import org.jetbrains.compose.resources.ExperimentalResourceApi 13 | import org.jetbrains.skia.Rect 14 | import org.jetbrains.skia.skottie.Animation 15 | import org.jetbrains.skia.sksg.InvalidationController 16 | import org.tool.kit.composeapp.generated.resources.Res 17 | import kotlin.math.roundToInt 18 | 19 | /** 20 | * @Author : LazyIonEs 21 | * @CreateDate : 2024/3/7 16:54 22 | * @Description : 加载json动画 23 | * @Version : 1.0 24 | */ 25 | @OptIn(ExperimentalResourceApi::class) 26 | @Composable 27 | fun LottieAnimation(scope: CoroutineScope, path: String, modifier: Modifier = Modifier) { 28 | var animation by remember { mutableStateOf(null) } 29 | scope.launch { 30 | val json = Res.readBytes(path).decodeToString() 31 | animation = Animation.makeFromString(json) 32 | } 33 | animation?.let { InfiniteAnimation(it, modifier.fillMaxSize()) } 34 | } 35 | 36 | @Composable 37 | private fun InfiniteAnimation(animation: Animation, modifier: Modifier) { 38 | val infiniteTransition = rememberInfiniteTransition() 39 | val time by infiniteTransition.animateFloat( 40 | initialValue = 0f, targetValue = animation.duration, animationSpec = infiniteRepeatable( 41 | animation = tween((animation.duration * 1000).roundToInt(), easing = LinearEasing), 42 | repeatMode = RepeatMode.Restart 43 | ) 44 | ) 45 | val invalidationController = remember { InvalidationController() } 46 | animation.seekFrameTime(time, invalidationController) 47 | Canvas(modifier) { 48 | drawIntoCanvas { 49 | animation.render( 50 | canvas = it.nativeCanvas, dst = Rect.makeWH(size.width, size.height) 51 | ) 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/utils/update.kt: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import androidx.compose.runtime.MutableState 4 | 5 | inline fun MutableState.update( 6 | transform: (T) -> T 7 | ): T = run { 8 | transform(this.value).also { this.value = it } 9 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/main.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.window.Window 2 | import androidx.compose.ui.window.application 3 | import org.jetbrains.compose.resources.painterResource 4 | import org.tool.kit.composeapp.generated.resources.Res 5 | import org.tool.kit.composeapp.generated.resources.icon 6 | 7 | fun main() = application { 8 | Window( 9 | onCloseRequest = ::exitApplication, title = "AndroidToolKit", icon = painterResource(Res.drawable.icon) 10 | ) { 11 | App() 12 | } 13 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/platform/DatabaseDriverFactory.kt: -------------------------------------------------------------------------------- 1 | package platform 2 | 3 | import com.russhwolf.settings.ExperimentalSettingsApi 4 | import com.russhwolf.settings.PreferencesSettings 5 | import com.russhwolf.settings.coroutines.FlowSettings 6 | import com.russhwolf.settings.coroutines.toFlowSettings 7 | import kotlinx.coroutines.Dispatchers 8 | 9 | /** 10 | * @Author : LazyIonEs 11 | * @CreateDate : 2024/2/20 17:52 12 | * @Description : 数据库驱动工厂 13 | * @Version : 1.0 14 | */ 15 | @OptIn(ExperimentalSettingsApi::class) 16 | actual fun createFlowSettings(): FlowSettings = PreferencesSettings.Factory().create("toolkit").toFlowSettings(Dispatchers.IO) -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/platform/RustFileDealWithFactory.kt: -------------------------------------------------------------------------------- 1 | package platform 2 | 3 | import androidx.annotation.FloatRange 4 | import androidx.annotation.IntRange 5 | import uniffi.toolkit.ToolKitRustException 6 | 7 | /** 8 | * 缩放图片 9 | * @param inputPath 输入路径 10 | * @param outputPath 输出路径 11 | * @param width 宽度 12 | * @param height 高度 13 | * @param typIdx 缩放算法 0 Triangle 1 Catrom 2 Mitchell 3 Lanczos3 14 | */ 15 | @Throws(RustException::class) 16 | actual fun resizePng(inputPath: String, outputPath: String, width: UInt, height: UInt, typIdx: UByte) { 17 | try { 18 | uniffi.toolkit.resizePng( 19 | inputPath = inputPath, outputPath = outputPath, dstWidth = width, dstHeight = height, typIdx = typIdx 20 | ) 21 | } catch (e: ToolKitRustException) { 22 | e.printStackTrace() 23 | throw RustException(e.message) 24 | } 25 | } 26 | 27 | /** 28 | * 缩放图片 29 | * @param inputPath 输入路径 30 | * @param outputPath 输出路径 31 | * @param width 宽度 32 | * @param height 高度 33 | */ 34 | @Throws(RustException::class) 35 | actual fun resizeFir(inputPath: String, outputPath: String, width: UInt, height: UInt, typIdx: UByte) { 36 | try { 37 | uniffi.toolkit.resizeFir( 38 | inputPath = inputPath, outputPath = outputPath, dstWidth = width, dstHeight = height, typIdx = typIdx 39 | ) 40 | } catch (e: ToolKitRustException) { 41 | e.printStackTrace() 42 | throw RustException(e.message) 43 | } 44 | } 45 | 46 | /** 47 | * 量化(有损)并压缩图片 48 | * @param inputPath 输入路径 49 | * @param outputPath 输出路径 50 | * @param minimum 最低限度 51 | * @param target 目标质量 如果不能满足最低质量,量化将因错误而中止。默认值为最小值 0,最大值 100,表示尽力而为,并且永不中止该过程。 52 | * 如果最大值小于 100,则库将尝试使用较少的颜色。颜色较少的图像并不总是较小,因为它会导致抖动增加。 53 | * @param speed 速度 1 - 10 更快的速度会生成质量较低的图像,但可能对于实时生成图像有用 54 | * @param preset 预设 1 - 6 预设越高、速度越慢、压缩效果越好 55 | */ 56 | @Throws(RustException::class) 57 | actual fun quantize( 58 | inputPath: String, 59 | outputPath: String, 60 | @IntRange(from = 0, to = 100) minimum: Int, 61 | @IntRange(from = 30, to = 100) target: Int, 62 | @IntRange(from = 1, to = 10) speed: Int, 63 | @IntRange(from = 0, to = 6) preset: Int 64 | ) { 65 | try { 66 | uniffi.toolkit.quantize( 67 | inputPath = inputPath, 68 | outputPath = outputPath, 69 | minimum = minimum.toUByte(), 70 | target = target.toUByte(), 71 | speed = speed, 72 | preset = preset.toUByte() 73 | ) 74 | } catch (e: ToolKitRustException) { 75 | e.printStackTrace() 76 | throw RustException(e.message) 77 | } 78 | } 79 | 80 | /** 81 | * 无损压缩PNG 82 | * @param inputPath 输入路径 83 | * @param outputPath 输出路径 84 | * @param preset 预设 1 - 6 预设越高、速度越慢、压缩效果越好 85 | */ 86 | @Throws(RustException::class) 87 | actual fun oxipng( 88 | inputPath: String, 89 | outputPath: String, 90 | @IntRange(from = 0, to = 6) preset: Int 91 | ) { 92 | try { 93 | uniffi.toolkit.oxipng( 94 | inputPath = inputPath, 95 | outputPath = outputPath, 96 | preset = preset.toUByte() 97 | ) 98 | } catch (e: ToolKitRustException) { 99 | e.printStackTrace() 100 | throw RustException(e.message) 101 | } 102 | } 103 | 104 | 105 | /** 106 | * 压缩图片 107 | * @param inputPath 输入路径 108 | * @param outputPath 输出路径 109 | * @param quality 图像质量。建议值为 60-80 110 | */ 111 | @Throws(RustException::class) 112 | actual fun mozJpeg(inputPath: String, outputPath: String, @FloatRange(from = 0.0, to = 100.0) quality: Float) { 113 | try { 114 | uniffi.toolkit.mozJpeg( 115 | inputPath = inputPath, outputPath = outputPath, quality = quality 116 | ) 117 | } catch (e: ToolKitRustException) { 118 | e.printStackTrace() 119 | throw RustException(e.message) 120 | } 121 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | #Gradle 3 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" 4 | 5 | #Development 6 | development=true -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | compose = "1.6.11" 3 | compose-compiler = "1.5.8" 4 | compose-plugin = "1.8.1" 5 | junit = "4.13.2" 6 | kotlin = "2.1.21" 7 | apksig = "8.9.1" 8 | tools = "31.9.1" 9 | slf4j = "2.0.17" 10 | buildconfig = "5.6.2" 11 | commons-codec = "1.18.0" 12 | asm = "9.8" 13 | jna = "5.17.0" 14 | logging = "7.0.0" 15 | lifecycle = "2.8.4" 16 | coroutines = "1.10.2" 17 | filekit = "0.10.0-beta04" 18 | multiplatform-settings = "1.3.0" 19 | kotlinx-serialization-json = "1.8.1" 20 | kotlinx-datetime = "0.6.2" 21 | about-libraries = "12.1.2" 22 | hot-reload = "1.0.0-alpha10" 23 | foojay = "0.10.0" 24 | coil = "3.2.0" 25 | zoomimage = "1.3.0" 26 | 27 | [libraries] 28 | kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } 29 | kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } 30 | junit = { group = "junit", name = "junit", version.ref = "junit" } 31 | compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" } 32 | compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } 33 | compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } 34 | compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" } 35 | compose-material = { module = "androidx.compose.material:material", version.ref = "compose" } 36 | compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose" } 37 | compose-material-icons-extended-desktop = { module = "androidx.compose.material:material-icons-extended-desktop", version.ref = "compose" } 38 | # https://mvnrepository.com/artifact/org.jetbrains.androidx.lifecycle/lifecycle-viewmodel-compose 39 | lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" } 40 | # https://mvnrepository.com/artifact/com.android.tools.build/apksig 41 | android-apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" } 42 | # https://mvnrepository.com/artifact/com.android.tools/sdk-common 43 | android-sdk-common = { module = "com.android.tools:sdk-common", version.ref = "tools" } 44 | # https://mvnrepository.com/artifact/com.android.tools.apkparser/binary-resources 45 | android-binary-resources = { module = "com.android.tools.apkparser:binary-resources", version.ref = "tools" } 46 | # https://mvnrepository.com/artifact/org.slf4j/slf4j-api 47 | slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } 48 | slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } 49 | # https://mvnrepository.com/artifact/commons-codec/commons-codec 50 | commons-codec = { module = "commons-codec:commons-codec", version.ref = "commons-codec" } 51 | # https://mvnrepository.com/artifact/org.ow2.asm/asm 52 | asm = { module = "org.ow2.asm:asm", version.ref = "asm" } 53 | # https://mvnrepository.com/artifact/net.java.dev.jna/jna 54 | jna = { module = "net.java.dev.jna:jna", version.ref = "jna" } 55 | logging = { module = "io.github.oshai:kotlin-logging-jvm", version.ref = "logging" } 56 | # https://github.com/Kotlin/kotlinx.coroutines 57 | kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" } 58 | # https://github.com/vinceglb/FileKit 59 | filekit-core = { module = "io.github.vinceglb:filekit-core", version.ref = "filekit" } 60 | filekit-dialogs = { module = "io.github.vinceglb:filekit-dialogs", version.ref = "filekit" } 61 | filekit-dialogs-compose = { module = "io.github.vinceglb:filekit-dialogs-compose", version.ref = "filekit" } 62 | # https://github.com/russhwolf/multiplatform-settings 63 | multiplatform-settings = { module = "com.russhwolf:multiplatform-settings", version.ref = "multiplatform-settings" } 64 | multiplatform-settings-coroutines = { module = "com.russhwolf:multiplatform-settings-coroutines", version.ref = "multiplatform-settings" } 65 | multiplatform-settings-serialization = { module = "com.russhwolf:multiplatform-settings-serialization", version.ref = "multiplatform-settings" } 66 | # https://github.com/Kotlin/kotlinx.serialization 67 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } 68 | # https://github.com/Kotlin/kotlinx-datetime 69 | kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } 70 | # https://github.com/mikepenz/AboutLibraries 71 | about-libraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "about-libraries" } 72 | about-libraries-compose-m3 = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "about-libraries" } 73 | # https://github.com/coil-kt/coil 74 | coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } 75 | # https://github.com/panpf/zoomimage 76 | zoomimage-compose-coil3 = { module = "io.github.panpf.zoomimage:zoomimage-compose-coil3", version.ref = "zoomimage" } 77 | 78 | [plugins] 79 | jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } 80 | kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 81 | # https://github.com/gmazzo/gradle-buildconfig-plugin 82 | githubBuildconfig = { id = "com.github.gmazzo.buildconfig", version.ref = "buildconfig" } 83 | kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 84 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 85 | about-libraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "about-libraries" } 86 | # https://github.com/JetBrains/compose-hot-reload 87 | hot-reload = { id = "org.jetbrains.compose.hot-reload", version.ref = "hot-reload" } 88 | foojay-resolver-convention = { id = "org.gradle.toolchains.foojay-resolver-convention", version.ref = "foojay" } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | if ! command -v java >/dev/null 2>&1 134 | then 135 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 136 | 137 | Please set the JAVA_HOME variable in your environment to match the 138 | location of your Java installation." 139 | fi 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | 201 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 202 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 203 | 204 | # Collect all arguments for the java command; 205 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 206 | # shell script including quotes and variable substitutions, so put them in 207 | # double quotes to make sure that they get re-expanded; and 208 | # * put everything else in single quotes, so that it's not re-expanded. 209 | 210 | set -- \ 211 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 212 | -classpath "$CLASSPATH" \ 213 | org.gradle.wrapper.GradleWrapperMain \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "toolkit-rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | name = "toolkit_rs" 9 | 10 | [dependencies] 11 | uniffi = { version = "0.29.2", features = [ "cli" ] } 12 | image = "0.25.6" 13 | imagequant = "4.3.4" 14 | thiserror = "2.0.12" 15 | lodepng = "3.11.0" 16 | fast_image_resize = { version = "5.1.4", features = ["image"] } 17 | mozjpeg = "0.10.13" 18 | oxipng = { version = "9.1.5", features = ["parallel", "zopfli", "filetime"], default-features = false } 19 | rgb = { version = "0.8.50", default-features = false } 20 | resize = "0.8.8" 21 | 22 | [build-dependencies] 23 | uniffi = { version = "0.29.2", features = ["build"] } 24 | 25 | [[bin]] 26 | name = "uniffi-bindgen" 27 | path = "uniffi-bindgen.rs" 28 | -------------------------------------------------------------------------------- /rs/build.rs: -------------------------------------------------------------------------------- 1 | include!("./src/srgb.rs"); 2 | 3 | use std::io::Write; 4 | 5 | fn main() -> std::io::Result<()> { 6 | let mut srgb_to_linear_lut = String::from("static SRGB_TO_LINEAR_LUT: [f32; 256] = ["); 7 | for i in 0..256 { 8 | srgb_to_linear_lut.push_str(&format!("{0:.7}", srgb_to_linear((i as f32) / 255.0))); 9 | srgb_to_linear_lut.push_str(","); 10 | } 11 | srgb_to_linear_lut.pop().unwrap(); 12 | srgb_to_linear_lut.push_str("];"); 13 | 14 | let mut file = std::fs::File::create("src/lut.inc")?; 15 | file.write_all(srgb_to_linear_lut.as_bytes())?; 16 | 17 | uniffi::generate_scaffolding("src/toolkit.udl").unwrap(); 18 | 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /rs/src/srgb.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | pub trait Clamp: PartialOrd + Sized { 3 | fn clamp(self, min: Self, max: Self) -> Self { 4 | if self.lt(&min) { 5 | min 6 | } else if self.gt(&max) { 7 | max 8 | } else { 9 | self 10 | } 11 | } 12 | } 13 | 14 | impl Clamp for f32 {} 15 | 16 | #[allow(dead_code)] 17 | pub fn srgb_to_linear(v: f32) -> f32 { 18 | if v < 0.04045 { 19 | v / 12.92 20 | } else { 21 | ((v + 0.055) / 1.055).powf(2.4).clamp(0.0, 1.0) 22 | } 23 | } 24 | 25 | pub fn linear_to_srgb(v: f32) -> f32 { 26 | if v < 0.0031308 { 27 | v * 12.92 28 | } else { 29 | (1.055 * v.powf(1.0 / 2.4) - 0.055).clamp(0.0, 1.0) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /rs/src/toolkit.udl: -------------------------------------------------------------------------------- 1 | namespace toolkit { 2 | [Throws=ToolKitRustError] 3 | void resize_fir(string input_path, string output_path, u32 dst_width, u32 dst_height, u8 typ_idx); 4 | [Throws=ToolKitRustError] 5 | void resize_png(string input_path, string output_path, u32 dst_width, u32 dst_height, u8 typ_idx); 6 | [Throws=ToolKitRustError] 7 | void quantize(string input_path, string output_path, u8 minimum, u8 target, i32 speed, u8 preset); 8 | [Throws=ToolKitRustError] 9 | void oxipng(string input_path, string output_path, u8 preset); 10 | [Throws=ToolKitRustError] 11 | void moz_jpeg(string input_path, string output_path, float quality); 12 | }; 13 | 14 | [Error] 15 | enum ToolKitRustError { 16 | "Error" 17 | }; -------------------------------------------------------------------------------- /rs/uniffi-bindgen.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | uniffi::uniffi_bindgen_main() 3 | } 4 | -------------------------------------------------------------------------------- /screenshots/fail_to_quantize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/fail_to_quantize.png -------------------------------------------------------------------------------- /screenshots/screenshot_apk_information_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_apk_information_1.png -------------------------------------------------------------------------------- /screenshots/screenshot_apk_information_1_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_apk_information_1_en.png -------------------------------------------------------------------------------- /screenshots/screenshot_apk_signature_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_apk_signature_1.png -------------------------------------------------------------------------------- /screenshots/screenshot_apk_signature_1_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_apk_signature_1_en.png -------------------------------------------------------------------------------- /screenshots/screenshot_cache_clear_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_cache_clear_0.png -------------------------------------------------------------------------------- /screenshots/screenshot_cache_clear_0_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_cache_clear_0_en.png -------------------------------------------------------------------------------- /screenshots/screenshot_cache_clear_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_cache_clear_1.png -------------------------------------------------------------------------------- /screenshots/screenshot_cache_clear_1_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_cache_clear_1_en.png -------------------------------------------------------------------------------- /screenshots/screenshot_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_dark.png -------------------------------------------------------------------------------- /screenshots/screenshot_dark_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_dark_en.png -------------------------------------------------------------------------------- /screenshots/screenshot_icon_factory_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_icon_factory_1.png -------------------------------------------------------------------------------- /screenshots/screenshot_icon_factory_1_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_icon_factory_1_en.png -------------------------------------------------------------------------------- /screenshots/screenshot_icon_factory_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_icon_factory_2.png -------------------------------------------------------------------------------- /screenshots/screenshot_icon_factory_2_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_icon_factory_2_en.png -------------------------------------------------------------------------------- /screenshots/screenshot_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_light.png -------------------------------------------------------------------------------- /screenshots/screenshot_light_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_light_en.png -------------------------------------------------------------------------------- /screenshots/screenshot_signature_generation_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_signature_generation_1.png -------------------------------------------------------------------------------- /screenshots/screenshot_signature_generation_1_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_signature_generation_1_en.png -------------------------------------------------------------------------------- /screenshots/screenshot_signature_information_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_signature_information_1.png -------------------------------------------------------------------------------- /screenshots/screenshot_signature_information_1_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_signature_information_1_en.png -------------------------------------------------------------------------------- /screenshots/screenshot_signature_information_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_signature_information_2.png -------------------------------------------------------------------------------- /screenshots/screenshot_signature_information_2_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_signature_information_2_en.png -------------------------------------------------------------------------------- /screenshots/screenshot_signature_information_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_signature_information_3.png -------------------------------------------------------------------------------- /screenshots/screenshot_signature_information_3_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/screenshot_signature_information_3_en.png -------------------------------------------------------------------------------- /screenshots/unopen_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/unopen_1.png -------------------------------------------------------------------------------- /screenshots/unopen_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/unopen_2.png -------------------------------------------------------------------------------- /screenshots/unopen_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazyIonEs/AndroidToolKit/e5257457e5eac74789798ad3e0d0c42aaacd83dc/screenshots/unopen_3.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "AndroidToolKit" 2 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 3 | 4 | pluginManagement { 5 | plugins { 6 | // https://github.com/JetBrains/compose-hot-reload 7 | id("org.jetbrains.compose.hot-reload") version "1.0.0-alpha09" 8 | } 9 | repositories { 10 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 11 | google() 12 | gradlePluginPortal() 13 | mavenCentral() 14 | } 15 | } 16 | 17 | plugins { 18 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.10.0" 19 | } 20 | 21 | dependencyResolutionManagement { 22 | repositories { 23 | google() 24 | mavenCentral() 25 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 26 | maven("https://www.jetbrains.com/intellij-repository/releases") 27 | } 28 | } 29 | 30 | include(":composeApp") --------------------------------------------------------------------------------