Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
377ea183a7 | ||
|
|
72361ab8bf | ||
|
|
f708e583c3 | ||
|
|
d753e1dc48 | ||
|
|
315a8a3805 | ||
|
|
129fed9c9f | ||
|
|
0baccb7621 | ||
|
|
842a8aa45a | ||
|
|
d17843479c | ||
|
|
0d70cc8e58 | ||
|
|
4e6cacb206 | ||
|
|
52514ba35b | ||
|
|
4d59ce435e | ||
|
|
b3b7fa6f4d | ||
|
|
c057c16391 | ||
|
|
dee7cc6f2b | ||
|
|
3d0d87cb0c | ||
|
|
6b66d9b3f8 | ||
|
|
a301d94858 | ||
|
|
01199470f2 | ||
|
|
9e7ea19567 | ||
|
|
cdc6a6cb4a | ||
|
|
bb2d8fd7e0 | ||
|
|
2b6d418fe6 | ||
|
|
d8b1126b96 | ||
|
|
2eeddcfa80 | ||
|
|
59e3675a36 | ||
|
|
bc386f080d | ||
|
|
2dc1377154 | ||
|
|
610852e2f2 | ||
|
|
15b19bb8ce | ||
|
|
4a598b1837 | ||
|
|
caee2417d6 | ||
|
|
349ca36d4e | ||
|
|
ec86f5caf2 | ||
|
|
b5a5cdfcd2 | ||
|
|
72d799e065 | ||
|
|
d06f22dcd0 | ||
|
|
cb90630f27 | ||
|
|
59ad9204d0 | ||
|
|
cb97c16f5e | ||
|
|
69b48d5345 | ||
|
|
45ed4708c9 | ||
|
|
f3c77bdb3b | ||
|
|
dc0eb9eec1 | ||
|
|
83dd6443cb | ||
|
|
3d77f2d135 | ||
|
|
1ea219bddc | ||
|
|
39adba62d1 | ||
|
|
3526e84e04 | ||
|
|
bfdb706b60 | ||
|
|
a297e07055 |
10
.github/workflows/build-manager.yml
vendored
10
.github/workflows/build-manager.yml
vendored
@@ -119,6 +119,8 @@ jobs:
|
|||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
- target: x86_64-linux-android
|
- target: x86_64-linux-android
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
|
- target: armv7-linux-androideabi
|
||||||
|
os: ubuntu-latest
|
||||||
uses: ./.github/workflows/ksud.yml
|
uses: ./.github/workflows/ksud.yml
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
@@ -198,12 +200,20 @@ jobs:
|
|||||||
name: ksud-x86_64-linux-android
|
name: ksud-x86_64-linux-android
|
||||||
path: .
|
path: .
|
||||||
|
|
||||||
|
- name: Download arm ksud
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ksud-armv7-linux-androideabi
|
||||||
|
path: .
|
||||||
|
|
||||||
- name: Copy ksud to app jniLibs
|
- name: Copy ksud to app jniLibs
|
||||||
run: |
|
run: |
|
||||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
mkdir -p app/src/main/jniLibs/arm64-v8a
|
||||||
mkdir -p app/src/main/jniLibs/x86_64
|
mkdir -p app/src/main/jniLibs/x86_64
|
||||||
|
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
||||||
cp -f ../aarch64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/arm64-v8a/libzakozako.so
|
cp -f ../aarch64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/arm64-v8a/libzakozako.so
|
||||||
cp -f ../x86_64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/x86_64/libzakozako.so
|
cp -f ../x86_64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/x86_64/libzakozako.so
|
||||||
|
cp -f ../armv7-linux-androideabi/release/zakozako ../manager/app/src/main/jniLibs/armeabi-v7a/libzakozako.so
|
||||||
|
|
||||||
- name: Copy kpmmgr to app jniLibs
|
- name: Copy kpmmgr to app jniLibs
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
3
.github/workflows/gki-kernel.yml
vendored
3
.github/workflows/gki-kernel.yml
vendored
@@ -198,6 +198,9 @@ jobs:
|
|||||||
- name: Make working directory clean to avoid dirty
|
- name: Make working directory clean to avoid dirty
|
||||||
working-directory: android-kernel
|
working-directory: android-kernel
|
||||||
run: |
|
run: |
|
||||||
|
if [ -e common/BUILD.bazel ]; then
|
||||||
|
sed -i '/^[[:space:]]*"protected_exports_list"[[:space:]]*:[[:space:]]*"android\/abi_gki_protected_exports_aarch64",$/d' common/BUILD.bazel
|
||||||
|
fi
|
||||||
rm common/android/abi_gki_protected_exports_* || echo "No protected exports!"
|
rm common/android/abi_gki_protected_exports_* || echo "No protected exports!"
|
||||||
git config --global user.email "bot@kernelsu.org"
|
git config --global user.email "bot@kernelsu.org"
|
||||||
git config --global user.name "KernelSUBot"
|
git config --global user.name "KernelSUBot"
|
||||||
|
|||||||
@@ -2,49 +2,64 @@
|
|||||||
|
|
||||||
**English** | [简体中文](README.md) | [日本語](README-ja.md)
|
**English** | [简体中文](README.md) | [日本語](README-ja.md)
|
||||||
|
|
||||||
|
|
||||||
Android device root solution based on [KernelSU](https://github.com/tiann/KernelSU)
|
Android device root solution based on [KernelSU](https://github.com/tiann/KernelSU)
|
||||||
|
|
||||||
**Experimental! Use at your own risk!** This solution is based on [KernelSU](https://github.com/tiann/KernelSU) and is experimental!
|
**Experimental! Use at your own risk!** This solution is based on [KernelSU](https://github.com/tiann/KernelSU) and is experimental!
|
||||||
|
|
||||||
>
|
|
||||||
> This is an unofficial fork. All rights are reserved to [@tiann](https://github.com/tiann)
|
> This is an unofficial fork. All rights are reserved to [@tiann](https://github.com/tiann)
|
||||||
> However, we will be a separately maintained branch of KSU in the future
|
|
||||||
>
|
>
|
||||||
|
> However, we will be a separately maintained branch of KSU in the future
|
||||||
- Fully adapted for non-GKI devices (susfs-dev and unsusfs-patched dev branches only)
|
|
||||||
|
|
||||||
## How to add
|
## How to add
|
||||||
|
|
||||||
Use the susfs-stable or susfs-dev branch (integrated susfs with support for non-GKI devices)
|
Using main branching (non-GKI device builds are not supported)
|
||||||
|
|
||||||
```
|
|
||||||
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Use the main branch
|
|
||||||
```
|
```
|
||||||
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main
|
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Using branches that support non-GKI devices
|
||||||
|
```
|
||||||
|
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s nongki
|
||||||
|
```
|
||||||
|
|
||||||
## How to use integrated susfs
|
## How to use integrated susfs
|
||||||
|
|
||||||
1. Use the susfs-dev branch directly without any patching
|
1. Use the susfs-dev branch directly without any patching
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## KPM Support
|
||||||
|
|
||||||
## KPM support
|
- Based on KernelPatch, we have removed duplicates of KSU and kept only KPM support.
|
||||||
|
|
||||||
- We have removed duplicate KSU functions based on KernelPatch and retained KPM support.
|
|
||||||
- We will introduce more APatch-compatible functions to ensure the integrity of KPM functionality.
|
- We will introduce more APatch-compatible functions to ensure the integrity of KPM functionality.
|
||||||
|
|
||||||
|
We will introduce more APatch-compatible functions to ensure the completeness of KPM functionality.
|
||||||
|
|
||||||
Open source address: https://github.com/ShirkNeko/SukiSU_KernelPatch_patch
|
KPM templates: https://github.com/udochina/KPM-Build-Anywhere
|
||||||
|
|
||||||
|
> [!Note]
|
||||||
|
> 1. `CONFIG_KPM=y` needs to be added.
|
||||||
|
> 2. Non-GKI devices need to add `CONFIG_KALLSYMS=y` and `CONFIG_KALLSYMS_ALL=y` as well.
|
||||||
|
> 3. Some kernel source code below `4.19` also needs to be backport from `4.19` to the header file `set_memory.h`.
|
||||||
|
|
||||||
|
## How to do a system update to retain ROOT
|
||||||
|
- After OTA, don't reboot first, go to the manager flashing/patching kernel interface, find `GKI/non_GKI install` and select the Anykernel3 kernel zip file that needs to be flashed, select the slot that is opposite to the current running slot of the system for flashing, and then reboot to retain the GKI mode update (This method is not supported for all non-GKI devices, so please try it yourself. It is the safest way to use TWRP for non-GKI devices.)
|
||||||
|
- Or use LKM mode to install to the unused slot (after OTA).
|
||||||
|
|
||||||
|
## Compatibility Status
|
||||||
|
- KernelSU (versions prior to v1.0.0) officially supports Android GKI 2.0 devices (kernel 5.10+)
|
||||||
|
|
||||||
|
- Older kernels (4.4+) are also compatible, but the kernel must be built manually
|
||||||
|
|
||||||
|
- KernelSU can support 3.x kernels (3.4-3.18) through additional reverse ports
|
||||||
|
|
||||||
|
- Currently supports `arm64-v8a`, `armeabi-v7a (bare)` and some `X86_64`
|
||||||
|
|
||||||
KPM template address: https://github.com/udochina/KPM-Build-Anywhere
|
|
||||||
|
|
||||||
## More links
|
## More links
|
||||||
|
|
||||||
Projects compiled based on Sukisu and susfs
|
Projects compiled based on Sukisu and susfs
|
||||||
- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
|
- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
|
||||||
- [OnePlus](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS)
|
- [OnePlus](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS)
|
||||||
@@ -53,32 +68,37 @@ Projects compiled based on Sukisu and susfs
|
|||||||
- This method references the hook method from (https://github.com/rsuntk/KernelSU)
|
- This method references the hook method from (https://github.com/rsuntk/KernelSU)
|
||||||
|
|
||||||
1. **KPROBES hook:**
|
1. **KPROBES hook:**
|
||||||
- This method only supports GKI (5.10 - 6.x) kernels, and all non-GKI kernels must use manual hooks.
|
- Also used for Loadable Kernel Module (LKM)
|
||||||
- For Loadable Kernel Modules (LKM)
|
- Default hook method on GKI kernels.
|
||||||
- Default hooking method for GKI kernels
|
- Need `CONFIG_KPROBES=y`
|
||||||
- Requires `CONFIG_KPROBES=y`.
|
|
||||||
2. **Manual hooks:**
|
|
||||||
- For GKI (5.10 - 6.x) kernels, add `CONFIG_KSU_MANUAL_HOOK=y` to the kernel defconfig and make sure to protect KernelSU hooks by using `#ifdef CONFIG_KSU_MANUAL_HOOK` instead of `#ifdef CONFIG_KSU`.
|
|
||||||
- Standard KernelSU hooks: https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source
|
|
||||||
- backslashxx syscall hooks: https://github.com/backslashxx/KernelSU/issues/5
|
|
||||||
- Some non-GKI devices that manually integrate KPROBES do not require the manual VFS hook `new_hook.patch` patch
|
|
||||||
|
|
||||||
|
2. **Manual hook:**
|
||||||
|
- Standard KernelSU hook: https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source
|
||||||
|
- backslashxx's syscall manual hook: https://github.com/backslashxx/KernelSU/issues/5
|
||||||
|
- Default hook method on Non-GKI kernels.
|
||||||
|
- Need `CONFIG_KSU_MANUAL_HOOK=y`
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
### GKI
|
|
||||||
1. such as Xiaomi, Redmi, Samsung, and other devices (does not include manufacturers that modified the kernel like Meizu, OnePlus, RealMe, and OPPO)
|
### Universal GKI
|
||||||
2. Use the prebuilt GKI kernel, the ones with their name ending with AnyKernel3, mentioned in the 'More Links' section, and then flash it with recoveries like TWRP
|
|
||||||
3. Generally, packages with a plain .zip suffix are universal. However, if your device has a MediaTek processor, you should use the ones with .gz suffix, and packages with .lz4 suffix are dedicated to Google devices.
|
Please **all** refer to https://kernelsu.org/zh_CN/guide/installation.html
|
||||||
|
|
||||||
|
> [!Note]
|
||||||
|
> 1. for devices with GKI 2.0 such as Xiaomi, Redmi, Samsung, etc. (excludes kernel-modified manufacturers such as Meizu, OnePlus, Zenith, and oppo)
|
||||||
|
> 2. Find the GKI build in [more links](#%E6%9B%B4%E5%A4%9A%E9%93%BE%E6%8E%A5). Find the device kernel version. Then download it and use TWRP or kernel flashing tool to flash the zip file with AnyKernel3 suffix.
|
||||||
|
> 3. The .zip archive without suffix is uncompressed, the gz suffix is the compression used by Tenguet models.
|
||||||
|
|
||||||
|
|
||||||
### OnePlus
|
### OnePlus
|
||||||
|
|
||||||
1. Use the link mentioned in the 'More Links' section to create a customized build with your device information, and then flash the zip file with the AnyKernel3 suffix.
|
1. Use the link mentioned in the 'More Links' section to create a customized build with your device information, and then flash the zip file with the AnyKernel3 suffix.
|
||||||
|
|
||||||
> [!Note]
|
> [!Note]
|
||||||
> - You only need to fill in the first two parts of kernel versions, such as 5.10, 5.15, 6.1, or 6.6.
|
> - You only need to fill in the first two parts of kernel versions, such as 5.10, 5.15, 6.1, or 6.6.
|
||||||
> - Please search for the processor codename by yourself, usually it is all English without numbers.
|
> - Please search for the processor codename by yourself, usually it is all English without numbers.
|
||||||
> - You can find the branch and configuration files from the OnePlus open-source kernel repository.
|
> - You can find the branch and configuration files from the OnePlus open-source kernel repository.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
1. Kernel-based `su` and root access management.
|
1. Kernel-based `su` and root access management.
|
||||||
@@ -88,23 +108,19 @@ Projects compiled based on Sukisu and susfs
|
|||||||
5. More customization
|
5. More customization
|
||||||
6. Support for KPM kernel modules
|
6. Support for KPM kernel modules
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
- The file in the “kernel” directory is under [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) license.
|
- The file in the “kernel” directory is under [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) license.
|
||||||
- All other parts except the “kernel” directory are under [GPL-3.0 or later](https://www.gnu.org/licenses/gpl-3.0.html) license.
|
- All other parts except the “kernel” directory are under [GPL-3.0 or later](https://www.gnu.org/licenses/gpl-3.0.html) license.
|
||||||
|
|
||||||
## Sponsorship list
|
## Sponsorship list
|
||||||
|
|
||||||
- [Ktouls](https://github.com/Ktouls) Thanks so much for bringing me support
|
- [Ktouls](https://github.com/Ktouls) Thanks so much for bringing me support
|
||||||
- [zaoqi123](https://github.com/zaoqi123) It's not a bad idea to buy me a milk tea
|
- [zaoqi123](https://github.com/zaoqi123) It's not a bad idea to buy me a milk tea
|
||||||
- [wswzgdg](https://github.com/wswzgdg) Many thanks for supporting this project
|
- [wswzgdg](https://github.com/wswzgdg) Many thanks for supporting this project
|
||||||
- [yspbwx2010](https://github.com/yspbwx2010) Many thanks
|
- [yspbwx2010](https://github.com/yspbwx2010) Many thanks
|
||||||
- [DARKWWEE](https://github.com/DARKWWEE) Thanks for the 100 USDT Lao
|
- [DARKWWEE](https://github.com/DARKWWEE) Thanks for the 100 USDT Lao
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the above list does not have your name, I will update it as soon as possible, and thanks again for your support!
|
If the above list does not have your name, I will update it as soon as possible, and thanks again for your support!
|
||||||
|
|
||||||
## Contributions
|
## Contributions
|
||||||
|
|||||||
@@ -7,71 +7,98 @@
|
|||||||
**試験中なビルドです!自己責任で使用してください!**<br>
|
**試験中なビルドです!自己責任で使用してください!**<br>
|
||||||
このソリューションは [KernelSU](https://github.com/tiann/KernelSU) に基づいていますが、試験中なビルドです。
|
このソリューションは [KernelSU](https://github.com/tiann/KernelSU) に基づいていますが、試験中なビルドです。
|
||||||
|
|
||||||
>
|
|
||||||
> これは非公式なフォークです。すべての権利は [@tiann](https://github.com/tiann) に帰属します。
|
> これは非公式なフォークです。すべての権利は [@tiann](https://github.com/tiann) に帰属します。
|
||||||
>
|
>
|
||||||
>ただし、将来的には KSU とは別に管理されるブランチとなる予定です。
|
> ただし、将来的には KSU とは別に管理されるブランチとなる予定です。
|
||||||
|
|
||||||
- GKI 非対応なデバイスに完全に適応 (susfs-dev と unsusfs-patched dev ブランチのみ)
|
|
||||||
|
|
||||||
## 追加方法
|
## 追加方法
|
||||||
susfs-stable または susfs-dev ブランチ (GKI 非対応デバイスに対応する統合された susfs) 使用してください。
|
|
||||||
|
|
||||||
|
メイン分岐の使用(GKI デバイス以外のビルドはサポートされていません。)
|
||||||
|
```
|
||||||
|
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main
|
||||||
|
```
|
||||||
|
|
||||||
|
GKI以外のデバイスをサポートするブランチを使用する
|
||||||
|
```
|
||||||
|
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s nongki
|
||||||
|
```
|
||||||
|
|
||||||
|
## 統合された susfs の使い方
|
||||||
|
|
||||||
|
1. パッチを当てずに susfs-dev ブランチを直接使用してください。
|
||||||
```
|
```
|
||||||
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
|
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
メインブランチを使用する場合
|
|
||||||
```
|
|
||||||
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main
|
|
||||||
```
|
|
||||||
## 統合された susfs の使い方
|
|
||||||
1. パッチを当てずに susfs-dev ブランチを直接使用してください。
|
|
||||||
|
|
||||||
## KPM に対応
|
## KPM に対応
|
||||||
|
|
||||||
- KernelPatch に基づいて重複した KSU の機能を削除、KPM の対応を維持させています。
|
- KernelPatch に基づいて重複した KSU の機能を削除、KPM の対応を維持させています。
|
||||||
- KPM 機能の整合性を確保するために、APatch の互換機能を更に向上させる予定です。
|
- KPM 機能の整合性を確保するために、APatch の互換機能を更に向上させる予定です。
|
||||||
|
|
||||||
|
|
||||||
オープンソースアドレス: https://github.com/ShirkNeko/SukiSU_KernelPatch_patch
|
オープンソースアドレス: https://github.com/ShirkNeko/SukiSU_KernelPatch_patch
|
||||||
|
|
||||||
|
|
||||||
KPM テンプレートのアドレス: https://github.com/udochina/KPM-Build-Anywhere
|
KPM テンプレートのアドレス: https://github.com/udochina/KPM-Build-Anywhere
|
||||||
|
|
||||||
|
> [!Note]
|
||||||
|
> 1. `CONFIG_KPM=y` が必要である。
|
||||||
|
> 2.非 GKI デバイスには `CONFIG_KALLSYMS=y` と `CONFIG_KALLSYMS_ALL=y` も必要です。
|
||||||
|
> 3.いくつかのカーネル `4.19` およびそれ以降のソースコードでは、 `4.19` からバックポートされた `set_memory.h` ヘッダーファイルも必要です。
|
||||||
|
|
||||||
|
|
||||||
|
## ROOT を保持するシステムアップデートの方法
|
||||||
|
- OTAの後、最初に再起動せず、マネージャのフラッシュ/パッチカーネルインターフェイスに移動し、`GKI/non_GKI 取り付け`を見つけ、フラッシュする必要があるAnykernel3カーネルzipファイルを選択し、フラッシュするためにシステムの現在の実行スロットと反対のスロットを選択し、GKIモードアップデートを保持するために再起動します(この方法は、現時点ではすべてのnon_GKIデバイスでサポートされていませんので、各自でお試しください。 (この方法は、すべての非GKIデバイスでサポートされていませんので、ご自身でお試しください)。
|
||||||
|
- または、LKMモードを使用して未使用のスロットにインストールします(OTA後)。
|
||||||
|
|
||||||
|
## 互換性ステータス
|
||||||
|
- KernelSU(v1.0.0より前のバージョン)はAndroid GKI 2.0デバイス(カーネル5.10以上)を公式にサポートしています。
|
||||||
|
|
||||||
|
- 古いカーネル(4.4+)も互換性がありますが、カーネルは手動でビルドする必要があります。
|
||||||
|
|
||||||
|
- KernelSU は追加のリバースポートを通じて 3.x カーネル (3.4-3.18) をサポートしています。
|
||||||
|
|
||||||
|
- 現在は `arm64-v8a`、`armeabi-v7a (bare)`、いくつかの `X86_64` をサポートしています。
|
||||||
|
|
||||||
## その他のリンク
|
## その他のリンク
|
||||||
|
|
||||||
SukiSU と susfs をベースにコンパイルされたプロジェクトです。
|
SukiSU と susfs をベースにコンパイルされたプロジェクトです。
|
||||||
|
|
||||||
- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
|
- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
|
||||||
- [OnePlus](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS)
|
- [OnePlus](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS)
|
||||||
|
|
||||||
## フックの方式
|
## フックの方式
|
||||||
|
|
||||||
- この方式は (https://github.com/rsuntk/KernelSU) のフック方式を参照してください。
|
- この方式は (https://github.com/rsuntk/KernelSU) のフック方式を参照してください。
|
||||||
|
|
||||||
1. **KPROBES フック:**
|
1. **KPROBES フック:**
|
||||||
- この方式は GKI (5.10 - 6.x) のカーネルのみに対応しています。GKI 以外のカーネルは手動でフックを使用する必要があります。
|
|
||||||
- 読み込み可能なカーネルモジュールの場合 (LKM)
|
- 読み込み可能なカーネルモジュールの場合 (LKM)
|
||||||
- GKI カーネルのデフォルトとなるフック方式
|
- GKI カーネルのデフォルトとなるフック方式
|
||||||
- `CONFIG_KPROBES=y` が必要です。
|
- `CONFIG_KPROBES=y` が必要です
|
||||||
|
|
||||||
2. **手動でフック:**
|
2. **手動でフック:**
|
||||||
- GKI (5.10 - 6.x) のカーネルの場合、カーネルの defconfig に `CONFIG_KSU_MANUAL_HOOK=y` を追加して `#ifdef CONFIG_KSU` ではなく `#ifdef CONFIG_KSU_MANUAL_HOOK` を使用して KernelSU フックを保護するようにしてください。
|
|
||||||
- 標準の KernelSU フック: https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source
|
- 標準の KernelSU フック: https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source
|
||||||
- backslashxx syscall フック: https://github.com/backslashxx/KernelSU/issues/5
|
- backslashxx syscall フック: https://github.com/backslashxx/KernelSU/issues/5
|
||||||
- KPROBES を手動で統合する一部の非 GKI デバイスでは手動の VFS フック `new_hook.patch` パッチは不要です。
|
- 非 GKI カーネル用のデフォルトフッキングメソッド
|
||||||
|
- `CONFIG_KSU_MANUAL_HOOK=y` が必要です
|
||||||
|
|
||||||
## 使い方
|
## 使い方
|
||||||
### GKI
|
|
||||||
1. Xiaomi、Redmi、Samsung などのデバイス (Meizu、OnePlus、Realme、OPPO などのカーネルを変更したメーカー以外)
|
|
||||||
2. `その他のリンク`の項目で言及されているカーネル名が、AnyKernel3 で終わるビルド済みの GKI カーネルを TWRP などのリカバリーでフラッシュします。
|
|
||||||
3. 一般的な .zip の接頭辞を持つパッケージは汎用的になります。ただし、デバイスに MediaTek 製の SoC が搭載されている場合は、.gz の接頭辞を持つパッケージを使用する必要があります。その他に .lz4 の接頭辞を持つパッケージは Google 製デバイス専用です。
|
|
||||||
|
|
||||||
|
### ユニバーサルGKI
|
||||||
|
|
||||||
|
https://kernelsu.org/zh_CN/guide/installation.html をご参照ください。
|
||||||
|
|
||||||
|
> [!Note]
|
||||||
|
> 1.Xiaomi、Redmi、Samsung などの GKI 2.0 を搭載したデバイス用 (Meizu、Yiga、Zenith、oppo などのマジックカーネルを搭載したメーカーは除く)。
|
||||||
|
> 2. [more links](#%E6%9B%B4%E5%A4%9A%E9%93%BE%E6%8E%A5) で GKI ビルドを検索します。 デバイスのカーネルバージョンを検索します。 次に、それをダウンロードし、TWRPまたはカーネルフラッシングツールを使用して、AnyKernel3の接尾辞が付いたzipファイルをフラッシュします。
|
||||||
|
> 接尾辞なしの.zipアーカイブは非圧縮で、接尾辞gzはTenguetモデルで使用されている圧縮方法です。
|
||||||
### OnePlus
|
### OnePlus
|
||||||
|
|
||||||
1. `その他のリンク`の項目に記載されているリンクを開き、デバイス情報を使用してカスタマイズされたカーネルをビルドし、AnyKernel3 の接頭辞を持つ .zip ファイルをフラッシュします。
|
1. `その他のリンク`の項目に記載されているリンクを開き、デバイス情報を使用してカスタマイズされたカーネルをビルドし、AnyKernel3 の接頭辞を持つ .zip ファイルをフラッシュします。
|
||||||
|
|
||||||
> [!Note]
|
> [!Note]
|
||||||
> - 5.10、5.15、6.1、6.6 などのカーネルバージョンの最初の 2 文字のみを入力する必要があります。
|
> - 5.10、5.15、6.1、6.6 などのカーネルバージョンの最初の 2 文字のみを入力する必要があります。
|
||||||
> - SoC のコードネームは自分で検索してください。通常は、数字がなく英語表記のみです。
|
> - SoC のコードネームは自分で検索してください。通常は、数字がなく英語表記のみです。
|
||||||
> - ブランチと構成ファイルは、OnePlus オープンソースカーネルリポジトリから見つけることができます。
|
> - ブランチと構成ファイルは、OnePlus オープンソースカーネルリポジトリから見つけることができます。
|
||||||
|
|
||||||
|
|
||||||
## 機能
|
## 機能
|
||||||
|
|
||||||
1. カーネルベースな `su` および root アクセスの管理。
|
1. カーネルベースな `su` および root アクセスの管理。
|
||||||
@@ -81,23 +108,19 @@ SukiSU と susfs をベースにコンパイルされたプロジェクトです
|
|||||||
5. その他のカスタマイズ
|
5. その他のカスタマイズ
|
||||||
6. KPM カーネルモジュールに対応
|
6. KPM カーネルモジュールに対応
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## ライセンス
|
## ライセンス
|
||||||
|
|
||||||
- “kernel” ディレクトリ内のファイルは [GPL-2.0](https://www.gnu.org/licenses/old-licenses/gpl-2.0.ja.html) のみライセンス下にあります。
|
- “kernel” ディレクトリ内のファイルは [GPL-2.0](https://www.gnu.org/licenses/old-licenses/gpl-2.0.ja.html) のみライセンス下にあります。
|
||||||
- “kernel” ディレクトリを除くその他すべての部分は [GPL-3.0 またはそれ以降](https://www.gnu.org/licenses/gpl-3.0.html) のライセンス下にあります。
|
- “kernel” ディレクトリを除くその他すべての部分は [GPL-3.0 またはそれ以降](https://www.gnu.org/licenses/gpl-3.0.html) のライセンス下にあります。
|
||||||
|
|
||||||
## スポンサーシップの一覧
|
## スポンサーシップの一覧
|
||||||
|
|
||||||
- [Ktouls](https://github.com/Ktouls) 応援をしてくれたことに感謝。
|
- [Ktouls](https://github.com/Ktouls) 応援をしてくれたことに感謝。
|
||||||
- [zaoqi123](https://github.com/zaoqi123) ミルクティーを買ってあげるのも良い考えですね。
|
- [zaoqi123](https://github.com/zaoqi123) ミルクティーを買ってあげるのも良い考えですね。
|
||||||
- [wswzgdg](https://github.com/wswzgdg) このプロジェクトを支援していただき、ありがとうございます。
|
- [wswzgdg](https://github.com/wswzgdg) このプロジェクトを支援していただき、ありがとうございます。
|
||||||
- [yspbwx2010](https://github.com/yspbwx2010) どうもありがとう。
|
- [yspbwx2010](https://github.com/yspbwx2010) どうもありがとう。
|
||||||
- [DARKWWEE](https://github.com/DARKWWEE) ラオウ100USDTありがとう!
|
- [DARKWWEE](https://github.com/DARKWWEE) ラオウ100USDTありがとう!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
上記の一覧にあなたの名前がない場合は、できるだけ早急に更新しますので再度ご支援をお願いします。
|
上記の一覧にあなたの名前がない場合は、できるだけ早急に更新しますので再度ご支援をお願いします。
|
||||||
|
|
||||||
## 貢献者
|
## 貢献者
|
||||||
|
|||||||
@@ -6,73 +6,95 @@
|
|||||||
|
|
||||||
**实验性! 使用风险自负!**
|
**实验性! 使用风险自负!**
|
||||||
|
|
||||||
|
|
||||||
>
|
|
||||||
> 这是非官方分支,保留所有权利 [@tiann](https://github.com/tiann)
|
> 这是非官方分支,保留所有权利 [@tiann](https://github.com/tiann)
|
||||||
> 但是,我们将会在未来成为一个单独维护的KSU分支
|
|
||||||
>
|
>
|
||||||
|
> 但是,我们将会在未来成为一个单独维护的 KSU 分支
|
||||||
|
|
||||||
## 如何添加
|
## 如何添加
|
||||||
|
|
||||||
在内核源码的根目录下执行以下命令:
|
在内核源码的根目录下执行以下命令:
|
||||||
|
|
||||||
使用 susfs-dev 分支(已集成susfs,带非GKI设备的支持)
|
使用 main 分支 (不支持非GKI设备构建)
|
||||||
|
|
||||||
```
|
|
||||||
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
使用 main 分支
|
|
||||||
```
|
```
|
||||||
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s main
|
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s main
|
||||||
```
|
```
|
||||||
|
|
||||||
|
使用支持非 GKI 设备的分支
|
||||||
|
```
|
||||||
|
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s nongki
|
||||||
|
```
|
||||||
|
|
||||||
## 如何集成 susfs
|
## 如何集成 susfs
|
||||||
|
|
||||||
1. 直接使用 susfs-stable 或者 susfs-dev 分支,不需要再集成 susfs
|
1. 直接使用 susfs-stable 或者 susfs-dev 分支,不需要再集成 susfs
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
|
||||||
|
```
|
||||||
|
|
||||||
## 钩子方法
|
## 钩子方法
|
||||||
|
|
||||||
- 此部分引用自 [rsuntk 的钩子方法](https://github.com/rsuntk/KernelSU)
|
- 此部分引用自 [rsuntk 的钩子方法](https://github.com/rsuntk/KernelSU)
|
||||||
|
|
||||||
1. **KPROBES 钩子:**
|
1. **KPROBES 钩子:**
|
||||||
- 此方法仅支持 GKI 2.0 (5.10 - 6.x) 内核, 所有非 GKI 2.0 内核都必须使用手动钩子
|
|
||||||
- 用于可加载内核模块 (LKM)
|
- 用于可加载内核模块 (LKM)
|
||||||
- GKI 2.0 内核的默认钩子方法
|
- GKI 2.0 内核的默认钩子方法
|
||||||
- 需要 `CONFIG_KPROBES=y`
|
- 需要 `CONFIG_KPROBES=y`
|
||||||
|
|
||||||
2. **手动钩子:**
|
2. **手动钩子:**
|
||||||
- 对于 GKI 2.0 (5.10 - 6.x) 内核,需要在对应设备的 defconfig 文件中添加 `CONFIG_KSU_MANUAL_HOOK=y` 并确保使用 `#ifdef CONFIG_KSU_MANUAL_HOOK` 而不是 `#ifdef CONFIG_KSU` 来保护 KernelSU 钩子
|
|
||||||
- 标准的 KernelSU 钩子:https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source
|
- 标准的 KernelSU 钩子:https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source
|
||||||
- backslashxx 的 syscall 手动钩子:https://github.com/backslashxx/KernelSU/issues/5
|
- backslashxx 的 syscall 手动钩子:https://github.com/backslashxx/KernelSU/issues/5
|
||||||
- 部分手动集成 KPROBES 的非 GKI 2.0 设备不需要手动 VFS 钩子 `new_hook.patch` 补丁
|
- 非 GKI 内核的默认挂钩方法
|
||||||
|
- 需要 `CONFIG_KSU_MANUAL_HOOK=y`
|
||||||
|
|
||||||
|
## KPM 支持
|
||||||
|
|
||||||
## KPM支持
|
- 我们基于 KernelPatch 去掉了和 KSU 重复的功能,仅保留了 KPM 支持
|
||||||
|
- 我们将会引入更多的兼容 APatch 的函数来确保 KPM 功能的完整性
|
||||||
- 我们基于KernelPatch去掉了和KSU重复的功能,保留了KPM支持
|
|
||||||
- 我们将会引入更多的兼容APatch的函数来确保KPM功能的完整性
|
|
||||||
|
|
||||||
|
|
||||||
开源地址: https://github.com/ShirkNeko/SukiSU_KernelPatch_patch
|
开源地址: https://github.com/ShirkNeko/SukiSU_KernelPatch_patch
|
||||||
|
|
||||||
|
KPM 模板地址: https://github.com/udochina/KPM-Build-Anywhere
|
||||||
|
|
||||||
KPM模板地址: https://github.com/udochina/KPM-Build-Anywhere
|
> [!Note]
|
||||||
|
> 1. 需要 `CONFIG_KPM=y`
|
||||||
|
> 2. 非GKI设备还需要 `CONFIG_KALLSYMS=y` 和 `CONFIG_KALLSYMS_ALL=y`
|
||||||
|
> 3. 部分内核 `4.19` 以下源码还需要从 `4.19` 向后移植头文件 `set_memory.h`
|
||||||
|
|
||||||
|
|
||||||
|
## 如何进行系统更新保留ROOT
|
||||||
|
- OTA后先不要重启,进入管理器刷写/修补内核界面,找到 `GKI/non_GKI安装` 选择需要刷写的Anykernel3内核压缩文件,选择与现在系统运行槽位相反的槽位进行刷写并重启即可保留GKI模式更新(暂不支持所有非GKI设备使用这种方法,请自行尝试。非GKI设备使用TWRP刷写是最稳妥的)
|
||||||
|
- 或者使用LKM模式的安装到未使用的槽位(OTA后)
|
||||||
|
|
||||||
|
## 兼容状态
|
||||||
|
- KernelSU(v1.0.0 之前版本)正式支持 Android GKI 2.0 设备(内核 5.10+)
|
||||||
|
|
||||||
|
- 旧内核(4.4+)也兼容,但必须手动构建内核
|
||||||
|
|
||||||
|
- 通过更多的反向移植,KernelSU 可以支持 3.x 内核(3.4-3.18)
|
||||||
|
|
||||||
|
- 目前支持 `arm64-v8a` ,`armeabi-v7a (bare)` 和部分 `X86_64`
|
||||||
|
|
||||||
## 更多链接
|
## 更多链接
|
||||||
|
|
||||||
基于 SukiSU 和 susfs 编译的项目
|
基于 SukiSU 和 susfs 编译的项目
|
||||||
- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
|
- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
|
||||||
- [一加](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS)
|
- [一加](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS)
|
||||||
|
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
### GKI
|
### 普适的 GKI
|
||||||
1. 适用于如小米红米三星等的 GKI 2.0 的设备 (不包含魔改内核的厂商如魅族、一加、真我和 oppo)
|
|
||||||
2. 找到更多链接里的 GKI 构建的项目找到设备内核版本直接下载用TWRP或者内核刷写工具刷入带 AnyKernel3 后缀的压缩包即可
|
请**全部**参考 https://kernelsu.org/zh_CN/guide/installation.html
|
||||||
3. 一般不带后缀的 .zip 压缩包是通用,gz 后缀的为天玑机型专用,lz4 后缀的为谷歌系机型专用,一般刷不带后缀的即可
|
|
||||||
|
> [!Note]
|
||||||
|
> 1. 适用于如小米、红米、三星等的 GKI 2.0 的设备 (不包含魔改内核的厂商如魅族、一加、真我和 oppo)
|
||||||
|
> 2. 找到[更多链接](#%E6%9B%B4%E5%A4%9A%E9%93%BE%E6%8E%A5)里的 GKI 构建的项目。找到设备内核版本。然后下载下来,用TWRP或者内核刷写工具刷入带 AnyKernel3 后缀的压缩包即可
|
||||||
|
> 3. 一般不带后缀的 .zip 压缩包是未压缩的,gz 后缀的为天玑机型所使用的压缩方式
|
||||||
|
|
||||||
|
|
||||||
### 一加
|
### 一加
|
||||||
|
|
||||||
1.找到更多链接里的一加项目进行自行填写,然后云编译构建,最后刷入带 AnyKernel3 后缀的压缩包即可
|
1.找到更多链接里的一加项目进行自行填写,然后云编译构建,最后刷入带 AnyKernel3 后缀的压缩包即可
|
||||||
|
|
||||||
> [!Note]
|
> [!Note]
|
||||||
@@ -80,7 +102,6 @@ KPM模板地址: https://github.com/udochina/KPM-Build-Anywhere
|
|||||||
> - 处理器代号请自行搜索,一般为全英文不带数字的代号
|
> - 处理器代号请自行搜索,一般为全英文不带数字的代号
|
||||||
> - 分支和配置文件请自行到一加内核开源地址进行填写
|
> - 分支和配置文件请自行到一加内核开源地址进行填写
|
||||||
|
|
||||||
|
|
||||||
## 特点
|
## 特点
|
||||||
|
|
||||||
1. 基于内核的 `su` 和 root 访问管理
|
1. 基于内核的 `su` 和 root 访问管理
|
||||||
@@ -88,8 +109,7 @@ KPM模板地址: https://github.com/udochina/KPM-Build-Anywhere
|
|||||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html):将 root 权限锁在笼子里
|
3. [App Profile](https://kernelsu.org/guide/app-profile.html):将 root 权限锁在笼子里
|
||||||
4. 恢复对非 GKI 2.0 内核的支持
|
4. 恢复对非 GKI 2.0 内核的支持
|
||||||
5. 更多自定义功能
|
5. 更多自定义功能
|
||||||
6. 对KPM内核模块的支持
|
6. 对 KPM 内核模块的支持
|
||||||
|
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
@@ -97,15 +117,13 @@ KPM模板地址: https://github.com/udochina/KPM-Build-Anywhere
|
|||||||
- 除 `kernel` 目录外,所有其他部分均为 [GPL-3.0 或更高版本](https://www.gnu.org/licenses/gpl-3.0.html)。
|
- 除 `kernel` 目录外,所有其他部分均为 [GPL-3.0 或更高版本](https://www.gnu.org/licenses/gpl-3.0.html)。
|
||||||
|
|
||||||
## 赞助名单
|
## 赞助名单
|
||||||
|
|
||||||
- [Ktouls](https://github.com/Ktouls) 非常感谢你给我带来的支持
|
- [Ktouls](https://github.com/Ktouls) 非常感谢你给我带来的支持
|
||||||
- [zaoqi123](https://github.com/zaoqi123) 请我喝奶茶也不错
|
- [zaoqi123](https://github.com/zaoqi123) 请我喝奶茶也不错
|
||||||
- [wswzgdg](https://github.com/wswzgdg) 非常感谢对此项目的支持
|
- [wswzgdg](https://github.com/wswzgdg) 非常感谢对此项目的支持
|
||||||
- [yspbwx2010](https://github.com/yspbwx2010) 非常感谢
|
- [yspbwx2010](https://github.com/yspbwx2010) 非常感谢
|
||||||
- [DARKWWEE](https://github.com/DARKWWEE) 感谢老哥的 100 USDT
|
- [DARKWWEE](https://github.com/DARKWWEE) 感谢老哥的 100 USDT
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
如果以上名单没有你的名称,我会及时更新,再次感谢大家的支持
|
如果以上名单没有你的名称,我会及时更新,再次感谢大家的支持
|
||||||
|
|
||||||
## 贡献
|
## 贡献
|
||||||
@@ -118,4 +136,4 @@ KPM模板地址: https://github.com/udochina/KPM-Build-Anywhere
|
|||||||
- [Magisk](https://github.com/topjohnwu/Magisk):强大的 root 工具
|
- [Magisk](https://github.com/topjohnwu/Magisk):强大的 root 工具
|
||||||
- [genuine](https://github.com/brevent/genuine/):APK v2 签名验证
|
- [genuine](https://github.com/brevent/genuine/):APK v2 签名验证
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技能
|
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技能
|
||||||
- [KernelPatch](https://github.com/bmax121/KernelPatch): KernelPatch是APatch实现内核模块的关键部分
|
- [KernelPatch](https://github.com/bmax121/KernelPatch): KernelPatch 是 APatch 实现内核模块的关键部分
|
||||||
@@ -63,6 +63,10 @@ u32 ksu_devpts_sid;
|
|||||||
// Detect whether it is on or not
|
// Detect whether it is on or not
|
||||||
static bool is_boot_phase = true;
|
static bool is_boot_phase = true;
|
||||||
|
|
||||||
|
#ifdef CONFIG_COMPAT
|
||||||
|
bool ksu_is_compat __read_mostly = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
void on_post_fs_data(void)
|
void on_post_fs_data(void)
|
||||||
{
|
{
|
||||||
static bool done = false;
|
static bool done = false;
|
||||||
@@ -107,6 +111,7 @@ static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr)
|
|||||||
if (get_user(compat, argv.ptr.compat + nr))
|
if (get_user(compat, argv.ptr.compat + nr))
|
||||||
return ERR_PTR(-EFAULT);
|
return ERR_PTR(-EFAULT);
|
||||||
|
|
||||||
|
ksu_is_compat = true;
|
||||||
return compat_ptr(compat);
|
return compat_ptr(compat);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -137,17 +137,45 @@ void apply_kernelsu_rules()
|
|||||||
#define CMD_TYPE_CHANGE 8
|
#define CMD_TYPE_CHANGE 8
|
||||||
#define CMD_GENFSCON 9
|
#define CMD_GENFSCON 9
|
||||||
|
|
||||||
|
#ifdef CONFIG_64BIT
|
||||||
struct sepol_data {
|
struct sepol_data {
|
||||||
u32 cmd;
|
u32 cmd;
|
||||||
u32 subcmd;
|
u32 subcmd;
|
||||||
char __user *sepol1;
|
u64 field_sepol1;
|
||||||
char __user *sepol2;
|
u64 field_sepol2;
|
||||||
char __user *sepol3;
|
u64 field_sepol3;
|
||||||
char __user *sepol4;
|
u64 field_sepol4;
|
||||||
char __user *sepol5;
|
u64 field_sepol5;
|
||||||
char __user *sepol6;
|
u64 field_sepol6;
|
||||||
char __user *sepol7;
|
u64 field_sepol7;
|
||||||
};
|
};
|
||||||
|
#ifdef CONFIG_COMPAT
|
||||||
|
extern bool ksu_is_compat __read_mostly;
|
||||||
|
struct sepol_compat_data {
|
||||||
|
u32 cmd;
|
||||||
|
u32 subcmd;
|
||||||
|
u32 field_sepol1;
|
||||||
|
u32 field_sepol2;
|
||||||
|
u32 field_sepol3;
|
||||||
|
u32 field_sepol4;
|
||||||
|
u32 field_sepol5;
|
||||||
|
u32 field_sepol6;
|
||||||
|
u32 field_sepol7;
|
||||||
|
};
|
||||||
|
#endif // CONFIG_COMPAT
|
||||||
|
#else
|
||||||
|
struct sepol_data {
|
||||||
|
u32 cmd;
|
||||||
|
u32 subcmd;
|
||||||
|
u32 field_sepol1;
|
||||||
|
u32 field_sepol2;
|
||||||
|
u32 field_sepol3;
|
||||||
|
u32 field_sepol4;
|
||||||
|
u32 field_sepol5;
|
||||||
|
u32 field_sepol6;
|
||||||
|
u32 field_sepol7;
|
||||||
|
};
|
||||||
|
#endif // CONFIG_64BIT
|
||||||
|
|
||||||
static int get_object(char *buf, char __user *user_object, size_t buf_sz,
|
static int get_object(char *buf, char __user *user_object, size_t buf_sz,
|
||||||
char **object)
|
char **object)
|
||||||
@@ -192,14 +220,58 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
pr_info("SELinux permissive or disabled when handle policy!\n");
|
pr_info("SELinux permissive or disabled when handle policy!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 cmd, subcmd;
|
||||||
|
char __user *sepol1, *sepol2, *sepol3, *sepol4, *sepol5, *sepol6, *sepol7;
|
||||||
|
|
||||||
|
#if defined(CONFIG_64BIT) && defined(CONFIG_COMPAT)
|
||||||
|
if (unlikely(ksu_is_compat)) {
|
||||||
|
struct sepol_compat_data compat_data;
|
||||||
|
if (copy_from_user(&compat_data, arg4, sizeof(struct sepol_compat_data))) {
|
||||||
|
pr_err("sepol: copy sepol_data failed.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
sepol1 = compat_ptr(compat_data.field_sepol1);
|
||||||
|
sepol2 = compat_ptr(compat_data.field_sepol2);
|
||||||
|
sepol3 = compat_ptr(compat_data.field_sepol3);
|
||||||
|
sepol4 = compat_ptr(compat_data.field_sepol4);
|
||||||
|
sepol5 = compat_ptr(compat_data.field_sepol5);
|
||||||
|
sepol6 = compat_ptr(compat_data.field_sepol6);
|
||||||
|
sepol7 = compat_ptr(compat_data.field_sepol7);
|
||||||
|
cmd = compat_data.cmd;
|
||||||
|
subcmd = compat_data.subcmd;
|
||||||
|
} else {
|
||||||
|
struct sepol_data data;
|
||||||
|
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||||
|
pr_err("sepol: copy sepol_data failed.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
sepol1 = data.field_sepol1;
|
||||||
|
sepol2 = data.field_sepol2;
|
||||||
|
sepol3 = data.field_sepol3;
|
||||||
|
sepol4 = data.field_sepol4;
|
||||||
|
sepol5 = data.field_sepol5;
|
||||||
|
sepol6 = data.field_sepol6;
|
||||||
|
sepol7 = data.field_sepol7;
|
||||||
|
cmd = data.cmd;
|
||||||
|
subcmd = data.subcmd;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// basically for full native, say (64BIT=y COMPAT=n) || (64BIT=n)
|
||||||
struct sepol_data data;
|
struct sepol_data data;
|
||||||
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||||
pr_err("sepol: copy sepol_data failed.\n");
|
pr_err("sepol: copy sepol_data failed.\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
sepol1 = data.field_sepol1;
|
||||||
u32 cmd = data.cmd;
|
sepol2 = data.field_sepol2;
|
||||||
u32 subcmd = data.subcmd;
|
sepol3 = data.field_sepol3;
|
||||||
|
sepol4 = data.field_sepol4;
|
||||||
|
sepol5 = data.field_sepol5;
|
||||||
|
sepol6 = data.field_sepol6;
|
||||||
|
sepol7 = data.field_sepol7;
|
||||||
|
cmd = data.cmd;
|
||||||
|
subcmd = data.subcmd;
|
||||||
|
#endif
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
|
|
||||||
@@ -213,22 +285,22 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
char perm_buf[MAX_SEPOL_LEN];
|
char perm_buf[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
char *s, *t, *c, *p;
|
char *s, *t, *c, *p;
|
||||||
if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) {
|
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
|
||||||
pr_err("sepol: copy src failed.\n");
|
pr_err("sepol: copy src failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) {
|
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||||
pr_err("sepol: copy tgt failed.\n");
|
pr_err("sepol: copy tgt failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) {
|
if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
|
||||||
pr_err("sepol: copy cls failed.\n");
|
pr_err("sepol: copy cls failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_object(perm_buf, data.sepol4, sizeof(perm_buf), &p) <
|
if (get_object(perm_buf, sepol4, sizeof(perm_buf), &p) <
|
||||||
0) {
|
0) {
|
||||||
pr_err("sepol: copy perm failed.\n");
|
pr_err("sepol: copy perm failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
@@ -258,24 +330,24 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
char perm_set[MAX_SEPOL_LEN];
|
char perm_set[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
char *s, *t, *c;
|
char *s, *t, *c;
|
||||||
if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) {
|
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
|
||||||
pr_err("sepol: copy src failed.\n");
|
pr_err("sepol: copy src failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) {
|
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||||
pr_err("sepol: copy tgt failed.\n");
|
pr_err("sepol: copy tgt failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) {
|
if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
|
||||||
pr_err("sepol: copy cls failed.\n");
|
pr_err("sepol: copy cls failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(operation, data.sepol4,
|
if (strncpy_from_user(operation, sepol4,
|
||||||
sizeof(operation)) < 0) {
|
sizeof(operation)) < 0) {
|
||||||
pr_err("sepol: copy operation failed.\n");
|
pr_err("sepol: copy operation failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(perm_set, data.sepol5, sizeof(perm_set)) <
|
if (strncpy_from_user(perm_set, sepol5, sizeof(perm_set)) <
|
||||||
0) {
|
0) {
|
||||||
pr_err("sepol: copy perm_set failed.\n");
|
pr_err("sepol: copy perm_set failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
@@ -295,7 +367,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
} else if (cmd == CMD_TYPE_STATE) {
|
} else if (cmd == CMD_TYPE_STATE) {
|
||||||
char src[MAX_SEPOL_LEN];
|
char src[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||||
pr_err("sepol: copy src failed.\n");
|
pr_err("sepol: copy src failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
@@ -315,11 +387,11 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
char type[MAX_SEPOL_LEN];
|
char type[MAX_SEPOL_LEN];
|
||||||
char attr[MAX_SEPOL_LEN];
|
char attr[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
if (strncpy_from_user(type, data.sepol1, sizeof(type)) < 0) {
|
if (strncpy_from_user(type, sepol1, sizeof(type)) < 0) {
|
||||||
pr_err("sepol: copy type failed.\n");
|
pr_err("sepol: copy type failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(attr, data.sepol2, sizeof(attr)) < 0) {
|
if (strncpy_from_user(attr, sepol2, sizeof(attr)) < 0) {
|
||||||
pr_err("sepol: copy attr failed.\n");
|
pr_err("sepol: copy attr failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
@@ -339,7 +411,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
} else if (cmd == CMD_ATTR) {
|
} else if (cmd == CMD_ATTR) {
|
||||||
char attr[MAX_SEPOL_LEN];
|
char attr[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
if (strncpy_from_user(attr, data.sepol1, sizeof(attr)) < 0) {
|
if (strncpy_from_user(attr, sepol1, sizeof(attr)) < 0) {
|
||||||
pr_err("sepol: copy attr failed.\n");
|
pr_err("sepol: copy attr failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
@@ -356,28 +428,28 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
char default_type[MAX_SEPOL_LEN];
|
char default_type[MAX_SEPOL_LEN];
|
||||||
char object[MAX_SEPOL_LEN];
|
char object[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||||
pr_err("sepol: copy src failed.\n");
|
pr_err("sepol: copy src failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) {
|
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
|
||||||
pr_err("sepol: copy tgt failed.\n");
|
pr_err("sepol: copy tgt failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) {
|
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
|
||||||
pr_err("sepol: copy cls failed.\n");
|
pr_err("sepol: copy cls failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(default_type, data.sepol4,
|
if (strncpy_from_user(default_type, sepol4,
|
||||||
sizeof(default_type)) < 0) {
|
sizeof(default_type)) < 0) {
|
||||||
pr_err("sepol: copy default_type failed.\n");
|
pr_err("sepol: copy default_type failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
char *real_object;
|
char *real_object;
|
||||||
if (data.sepol5 == NULL) {
|
if (sepol5 == NULL) {
|
||||||
real_object = NULL;
|
real_object = NULL;
|
||||||
} else {
|
} else {
|
||||||
if (strncpy_from_user(object, data.sepol5,
|
if (strncpy_from_user(object, sepol5,
|
||||||
sizeof(object)) < 0) {
|
sizeof(object)) < 0) {
|
||||||
pr_err("sepol: copy object failed.\n");
|
pr_err("sepol: copy object failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
@@ -396,19 +468,19 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
char cls[MAX_SEPOL_LEN];
|
char cls[MAX_SEPOL_LEN];
|
||||||
char default_type[MAX_SEPOL_LEN];
|
char default_type[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||||
pr_err("sepol: copy src failed.\n");
|
pr_err("sepol: copy src failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) {
|
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
|
||||||
pr_err("sepol: copy tgt failed.\n");
|
pr_err("sepol: copy tgt failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) {
|
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
|
||||||
pr_err("sepol: copy cls failed.\n");
|
pr_err("sepol: copy cls failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(default_type, data.sepol4,
|
if (strncpy_from_user(default_type, sepol4,
|
||||||
sizeof(default_type)) < 0) {
|
sizeof(default_type)) < 0) {
|
||||||
pr_err("sepol: copy default_type failed.\n");
|
pr_err("sepol: copy default_type failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
@@ -429,15 +501,15 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
|||||||
char name[MAX_SEPOL_LEN];
|
char name[MAX_SEPOL_LEN];
|
||||||
char path[MAX_SEPOL_LEN];
|
char path[MAX_SEPOL_LEN];
|
||||||
char context[MAX_SEPOL_LEN];
|
char context[MAX_SEPOL_LEN];
|
||||||
if (strncpy_from_user(name, data.sepol1, sizeof(name)) < 0) {
|
if (strncpy_from_user(name, sepol1, sizeof(name)) < 0) {
|
||||||
pr_err("sepol: copy name failed.\n");
|
pr_err("sepol: copy name failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(path, data.sepol2, sizeof(path)) < 0) {
|
if (strncpy_from_user(path, sepol2, sizeof(path)) < 0) {
|
||||||
pr_err("sepol: copy path failed.\n");
|
pr_err("sepol: copy path failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(context, data.sepol3, sizeof(context)) <
|
if (strncpy_from_user(context, sepol3, sizeof(context)) <
|
||||||
0) {
|
0) {
|
||||||
pr_err("sepol: copy context failed.\n");
|
pr_err("sepol: copy context failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|||||||
@@ -22,6 +22,10 @@
|
|||||||
|
|
||||||
extern void escape_to_root();
|
extern void escape_to_root();
|
||||||
|
|
||||||
|
#ifndef CONFIG_KPROBES
|
||||||
|
static bool ksu_sucompat_non_kp __read_mostly = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
static void __user *userspace_stack_buffer(const void *d, size_t len)
|
static void __user *userspace_stack_buffer(const void *d, size_t len)
|
||||||
{
|
{
|
||||||
/* To avoid having to mmap a page in userspace, just write below the stack
|
/* To avoid having to mmap a page in userspace, just write below the stack
|
||||||
@@ -50,6 +54,12 @@ int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
|
|||||||
{
|
{
|
||||||
const char su[] = SU_PATH;
|
const char su[] = SU_PATH;
|
||||||
|
|
||||||
|
#ifndef CONFIG_KPROBES
|
||||||
|
if (!ksu_sucompat_non_kp) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -71,6 +81,11 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
|||||||
// const char sh[] = SH_PATH;
|
// const char sh[] = SH_PATH;
|
||||||
const char su[] = SU_PATH;
|
const char su[] = SU_PATH;
|
||||||
|
|
||||||
|
#ifndef CONFIG_KPROBES
|
||||||
|
if (!ksu_sucompat_non_kp) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -115,6 +130,11 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
|
|||||||
const char sh[] = KSUD_PATH;
|
const char sh[] = KSUD_PATH;
|
||||||
const char su[] = SU_PATH;
|
const char su[] = SU_PATH;
|
||||||
|
|
||||||
|
#ifndef CONFIG_KPROBES
|
||||||
|
if (!ksu_sucompat_non_kp) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (unlikely(!filename_ptr))
|
if (unlikely(!filename_ptr))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
@@ -144,6 +164,11 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
|
|||||||
const char su[] = SU_PATH;
|
const char su[] = SU_PATH;
|
||||||
char path[sizeof(su) + 1];
|
char path[sizeof(su) + 1];
|
||||||
|
|
||||||
|
#ifndef CONFIG_KPROBES
|
||||||
|
if (!ksu_sucompat_non_kp){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (unlikely(!filename_user))
|
if (unlikely(!filename_user))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
@@ -237,6 +262,9 @@ void ksu_sucompat_init()
|
|||||||
su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre);
|
su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre);
|
||||||
su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre);
|
su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre);
|
||||||
su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre);
|
su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre);
|
||||||
|
#else
|
||||||
|
ksu_sucompat_non_kp = true;
|
||||||
|
pr_info("ksu_sucompat_init: hooks enabled: execve/execveat_su, faccessat, stat\n");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,5 +274,8 @@ void ksu_sucompat_exit()
|
|||||||
for (int i = 0; i < ARRAY_SIZE(su_kps); i++) {
|
for (int i = 0; i < ARRAY_SIZE(su_kps); i++) {
|
||||||
destroy_kprobe(&su_kps[i]);
|
destroy_kprobe(&su_kps[i]);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
ksu_sucompat_non_kp = false;
|
||||||
|
pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat\n");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -358,12 +358,14 @@ void track_throne()
|
|||||||
if (ksu_is_manager_uid_valid()) {
|
if (ksu_is_manager_uid_valid()) {
|
||||||
pr_info("manager is uninstalled, invalidate it!\n");
|
pr_info("manager is uninstalled, invalidate it!\n");
|
||||||
ksu_invalidate_manager_uid();
|
ksu_invalidate_manager_uid();
|
||||||
|
goto prune;
|
||||||
}
|
}
|
||||||
pr_info("Searching manager...\n");
|
pr_info("Searching manager...\n");
|
||||||
search_manager("/data/app", 2, &uid_list);
|
search_manager("/data/app", 2, &uid_list);
|
||||||
pr_info("Search manager finished\n");
|
pr_info("Search manager finished\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prune:
|
||||||
// then prune the allowlist
|
// then prune the allowlist
|
||||||
ksu_prune_allowlist(is_uid_exist, &uid_list);
|
ksu_prune_allowlist(is_uid_exist, &uid_list);
|
||||||
out:
|
out:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@file:Suppress("UnstableApiUsage")
|
@file:Suppress("UnstableApiUsage")
|
||||||
|
|
||||||
|
import com.android.build.api.dsl.ApkSigningConfig
|
||||||
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
||||||
import com.android.build.gradle.tasks.PackageAndroidArtifact
|
import com.android.build.gradle.tasks.PackageAndroidArtifact
|
||||||
|
|
||||||
@@ -24,7 +25,17 @@ apksign {
|
|||||||
keyPasswordProperty = "KEY_PASSWORD"
|
keyPasswordProperty = "KEY_PASSWORD"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
||||||
|
/**signingConfigs {
|
||||||
|
create("Debug") {
|
||||||
|
storeFile = file("D:\\other\\AndroidTool\\android_key\\keystore\\release-key.keystore")
|
||||||
|
storePassword = ""
|
||||||
|
keyAlias = ""
|
||||||
|
keyPassword = ""
|
||||||
|
}
|
||||||
|
}**/
|
||||||
namespace = "com.sukisu.ultra"
|
namespace = "com.sukisu.ultra"
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -33,6 +44,9 @@ android {
|
|||||||
isShrinkResources = true
|
isShrinkResources = true
|
||||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||||
}
|
}
|
||||||
|
/**debug {
|
||||||
|
signingConfig = signingConfigs.named("Debug").get() as ApkSigningConfig
|
||||||
|
}**/
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
@@ -140,4 +154,9 @@ dependencies {
|
|||||||
|
|
||||||
implementation(libs.com.github.topjohnwu.libsu.core)
|
implementation(libs.com.github.topjohnwu.libsu.core)
|
||||||
|
|
||||||
|
implementation(libs.mmrl.platform)
|
||||||
|
compileOnly(libs.mmrl.hidden.api)
|
||||||
|
implementation(libs.mmrl.webui)
|
||||||
|
implementation(libs.mmrl.ui)
|
||||||
|
|
||||||
}
|
}
|
||||||
47
manager/app/proguard-rules.pro
vendored
47
manager/app/proguard-rules.pro
vendored
@@ -0,0 +1,47 @@
|
|||||||
|
-verbose
|
||||||
|
-optimizationpasses 5
|
||||||
|
|
||||||
|
-dontwarn org.conscrypt.**
|
||||||
|
-dontwarn kotlinx.serialization.**
|
||||||
|
|
||||||
|
# Please add these rules to your existing keep rules in order to suppress warnings.
|
||||||
|
# This is generated automatically by the Android Gradle plugin.
|
||||||
|
-dontwarn com.google.auto.service.AutoService
|
||||||
|
-dontwarn com.google.j2objc.annotations.RetainedWith
|
||||||
|
-dontwarn javax.lang.model.SourceVersion
|
||||||
|
-dontwarn javax.lang.model.element.AnnotationMirror
|
||||||
|
-dontwarn javax.lang.model.element.AnnotationValue
|
||||||
|
-dontwarn javax.lang.model.element.Element
|
||||||
|
-dontwarn javax.lang.model.element.ElementKind
|
||||||
|
-dontwarn javax.lang.model.element.ElementVisitor
|
||||||
|
-dontwarn javax.lang.model.element.ExecutableElement
|
||||||
|
-dontwarn javax.lang.model.element.Modifier
|
||||||
|
-dontwarn javax.lang.model.element.Name
|
||||||
|
-dontwarn javax.lang.model.element.PackageElement
|
||||||
|
-dontwarn javax.lang.model.element.TypeElement
|
||||||
|
-dontwarn javax.lang.model.element.TypeParameterElement
|
||||||
|
-dontwarn javax.lang.model.element.VariableElement
|
||||||
|
-dontwarn javax.lang.model.type.ArrayType
|
||||||
|
-dontwarn javax.lang.model.type.DeclaredType
|
||||||
|
-dontwarn javax.lang.model.type.ExecutableType
|
||||||
|
-dontwarn javax.lang.model.type.TypeKind
|
||||||
|
-dontwarn javax.lang.model.type.TypeMirror
|
||||||
|
-dontwarn javax.lang.model.type.TypeVariable
|
||||||
|
-dontwarn javax.lang.model.type.TypeVisitor
|
||||||
|
-dontwarn javax.lang.model.util.AbstractAnnotationValueVisitor8
|
||||||
|
-dontwarn javax.lang.model.util.AbstractTypeVisitor8
|
||||||
|
-dontwarn javax.lang.model.util.ElementFilter
|
||||||
|
-dontwarn javax.lang.model.util.Elements
|
||||||
|
-dontwarn javax.lang.model.util.SimpleElementVisitor8
|
||||||
|
-dontwarn javax.lang.model.util.SimpleTypeVisitor7
|
||||||
|
-dontwarn javax.lang.model.util.SimpleTypeVisitor8
|
||||||
|
-dontwarn javax.lang.model.util.Types
|
||||||
|
-dontwarn javax.tools.Diagnostic$Kind
|
||||||
|
|
||||||
|
|
||||||
|
# MMRL:webui reflection
|
||||||
|
-keep class com.dergoogler.mmrl.webui.model.ModId { *; }
|
||||||
|
-keep class com.dergoogler.mmrl.webui.interfaces.** { *; }
|
||||||
|
-keep class com.sukisu.ultra.ui.webui.WebViewInterface { *; }
|
||||||
|
|
||||||
|
-keep,allowobfuscation class * extends com.dergoogler.mmrl.platform.content.IService { *; }
|
||||||
@@ -3,8 +3,10 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
tools:ignore="ScopedStorage" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
tools:ignore="ScopedStorage" />
|
||||||
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
@@ -37,6 +39,13 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/Theme.KernelSU.WebUI" />
|
android:theme="@style/Theme.KernelSU.WebUI" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.webui.WebUIXActivity"
|
||||||
|
android:autoRemoveFromRecents="true"
|
||||||
|
android:documentLaunchMode="intoExisting"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Theme.KernelSU.WebUI" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
|||||||
BIN
manager/app/src/main/assets/5_10-mkbootfs
Normal file
BIN
manager/app/src/main/assets/5_10-mkbootfs
Normal file
Binary file not shown.
@@ -1,20 +1,69 @@
|
|||||||
package com.sukisu.ultra
|
package com.sukisu.ultra
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.os.Build
|
||||||
import coil.Coil
|
import coil.Coil
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
||||||
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
lateinit var ksuApp: KernelSUApplication
|
lateinit var ksuApp: KernelSUApplication
|
||||||
|
|
||||||
class KernelSUApplication : Application() {
|
class KernelSUApplication : Application() {
|
||||||
|
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
val prefs = base.getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
val languageCode = prefs.getString("app_language", "") ?: ""
|
||||||
|
|
||||||
|
var context = base
|
||||||
|
if (languageCode.isNotEmpty()) {
|
||||||
|
val locale = Locale.forLanguageTag(languageCode)
|
||||||
|
Locale.setDefault(locale)
|
||||||
|
|
||||||
|
val config = Configuration(base.resources.configuration)
|
||||||
|
config.setLocale(locale)
|
||||||
|
|
||||||
|
context = base.createConfigurationContext(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.attachBaseContext(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ObsoleteSdkInt")
|
||||||
|
override fun getResources(): Resources {
|
||||||
|
val resources = super.getResources()
|
||||||
|
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
val languageCode = prefs.getString("app_language", "") ?: ""
|
||||||
|
|
||||||
|
if (languageCode.isNotEmpty()) {
|
||||||
|
val locale = Locale.forLanguageTag(languageCode)
|
||||||
|
val config = Configuration(resources.configuration)
|
||||||
|
config.setLocale(locale)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
return createConfigurationContext(config).resources
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
resources.updateConfiguration(config, resources.displayMetrics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
ksuApp = this
|
ksuApp = this
|
||||||
|
|
||||||
|
Platform.setHiddenApiExemptions()
|
||||||
|
|
||||||
val context = this
|
val context = this
|
||||||
val iconSize = resources.getDimensionPixelSize(android.R.dimen.app_icon_size)
|
val iconSize = resources.getDimensionPixelSize(android.R.dimen.app_icon_size)
|
||||||
Coil.setImageLoader(
|
Coil.setImageLoader(
|
||||||
@@ -32,5 +81,30 @@ class KernelSUApplication : Application() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
super.onConfigurationChanged(newConfig)
|
||||||
|
applyLanguageSetting()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ObsoleteSdkInt")
|
||||||
|
private fun applyLanguageSetting() {
|
||||||
|
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
val languageCode = prefs.getString("app_language", "") ?: ""
|
||||||
|
|
||||||
|
if (languageCode.isNotEmpty()) {
|
||||||
|
val locale = Locale.forLanguageTag(languageCode)
|
||||||
|
Locale.setDefault(locale)
|
||||||
|
|
||||||
|
val resources = resources
|
||||||
|
val config = Configuration(resources.configuration)
|
||||||
|
config.setLocale(locale)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
createConfigurationContext(config)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
resources.updateConfiguration(config, resources.displayMetrics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -16,14 +16,14 @@ object Natives {
|
|||||||
// 10946: add capabilities
|
// 10946: add capabilities
|
||||||
// 10977: change groups_count and groups to avoid overflow write
|
// 10977: change groups_count and groups to avoid overflow write
|
||||||
// 11071: Fix the issue of failing to set a custom SELinux type.
|
// 11071: Fix the issue of failing to set a custom SELinux type.
|
||||||
const val MINIMAL_SUPPORTED_KERNEL = 12800
|
const val MINIMAL_SUPPORTED_KERNEL = 11071
|
||||||
|
|
||||||
// 11640: Support query working mode, LKM or GKI
|
// 11640: Support query working mode, LKM or GKI
|
||||||
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
|
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
|
||||||
const val MINIMAL_SUPPORTED_KERNEL_LKM = 12800
|
const val MINIMAL_SUPPORTED_KERNEL_LKM = 11648
|
||||||
|
|
||||||
// 12040: Support disable sucompat mode
|
// 12040: Support disable sucompat mode
|
||||||
const val MINIMAL_SUPPORTED_SU_COMPAT = 12800
|
const val MINIMAL_SUPPORTED_SU_COMPAT = 12040
|
||||||
const val KERNEL_SU_DOMAIN = "u:r:su:s0"
|
const val KERNEL_SU_DOMAIN = "u:r:su:s0"
|
||||||
|
|
||||||
const val ROOT_UID = 0
|
const val ROOT_UID = 0
|
||||||
@@ -91,14 +91,6 @@ object Natives {
|
|||||||
return version < MINIMAL_SUPPORTED_KERNEL
|
return version < MINIMAL_SUPPORTED_KERNEL
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isKsuValid(pkgName: String?): Boolean {
|
|
||||||
if (becomeManager(pkgName)) {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@Keep
|
@Keep
|
||||||
|
|||||||
@@ -133,15 +133,13 @@ class HorizonKernelWorker(
|
|||||||
state.updateStep(context.getString(R.string.horizon_flashing))
|
state.updateStep(context.getString(R.string.horizon_flashing))
|
||||||
state.updateProgress(0.7f)
|
state.updateProgress(0.7f)
|
||||||
|
|
||||||
// 获取原始槽位信息
|
val isAbDevice = isAbDevice()
|
||||||
if (slot != null) {
|
|
||||||
|
if (isAbDevice && slot != null) {
|
||||||
state.updateStep(context.getString(R.string.horizon_getting_original_slot))
|
state.updateStep(context.getString(R.string.horizon_getting_original_slot))
|
||||||
state.updateProgress(0.72f)
|
state.updateProgress(0.72f)
|
||||||
originalSlot = runCommandGetOutput(true, "getprop ro.boot.slot_suffix")
|
originalSlot = runCommandGetOutput(true, "getprop ro.boot.slot_suffix")
|
||||||
}
|
|
||||||
|
|
||||||
// 设置目标槽位
|
|
||||||
if (!slot.isNullOrEmpty()) {
|
|
||||||
state.updateStep(context.getString(R.string.horizon_setting_target_slot))
|
state.updateStep(context.getString(R.string.horizon_setting_target_slot))
|
||||||
state.updateProgress(0.74f)
|
state.updateProgress(0.74f)
|
||||||
runCommand(true, "resetprop -n ro.boot.slot_suffix _$slot")
|
runCommand(true, "resetprop -n ro.boot.slot_suffix _$slot")
|
||||||
@@ -149,8 +147,7 @@ class HorizonKernelWorker(
|
|||||||
|
|
||||||
flash()
|
flash()
|
||||||
|
|
||||||
// 恢复原始槽位
|
if (isAbDevice && !originalSlot.isNullOrEmpty()) {
|
||||||
if (!originalSlot.isNullOrEmpty()) {
|
|
||||||
state.updateStep(context.getString(R.string.horizon_restoring_original_slot))
|
state.updateStep(context.getString(R.string.horizon_restoring_original_slot))
|
||||||
state.updateProgress(0.8f)
|
state.updateProgress(0.8f)
|
||||||
runCommand(true, "resetprop ro.boot.slot_suffix $originalSlot")
|
runCommand(true, "resetprop ro.boot.slot_suffix $originalSlot")
|
||||||
@@ -165,8 +162,7 @@ class HorizonKernelWorker(
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
state.setError(e.message ?: context.getString(R.string.horizon_unknown_error))
|
state.setError(e.message ?: context.getString(R.string.horizon_unknown_error))
|
||||||
|
|
||||||
// 恢复原始槽位
|
if (isAbDevice() && !originalSlot.isNullOrEmpty()) {
|
||||||
if (!originalSlot.isNullOrEmpty()) {
|
|
||||||
state.updateStep(context.getString(R.string.horizon_restoring_original_slot))
|
state.updateStep(context.getString(R.string.horizon_restoring_original_slot))
|
||||||
state.updateProgress(0.8f)
|
state.updateProgress(0.8f)
|
||||||
runCommand(true, "resetprop ro.boot.slot_suffix $originalSlot")
|
runCommand(true, "resetprop ro.boot.slot_suffix $originalSlot")
|
||||||
@@ -174,6 +170,17 @@ class HorizonKernelWorker(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查设备是否为AB分区设备
|
||||||
|
private fun isAbDevice(): Boolean {
|
||||||
|
val abUpdate = runCommandGetOutput(true, "getprop ro.build.ab_update")?.trim() ?: ""
|
||||||
|
if (abUpdate.equals("false", ignoreCase = true) || abUpdate.isEmpty()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val slotSuffix = runCommandGetOutput(true, "getprop ro.boot.slot_suffix")
|
||||||
|
return !slotSuffix.isNullOrEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
private fun cleanup() {
|
private fun cleanup() {
|
||||||
runCommand(false, "find ${context.filesDir.absolutePath} -type f ! -name '*.jpg' ! -name '*.png' -delete")
|
runCommand(false, "find ${context.filesDir.absolutePath} -type f ! -name '*.jpg' ! -name '*.png' -delete")
|
||||||
}
|
}
|
||||||
@@ -196,9 +203,25 @@ class HorizonKernelWorker(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun patch() {
|
private fun patch() {
|
||||||
val mkbootfsPath = "${context.filesDir.absolutePath}/mkbootfs"
|
val kernelVersion = runCommandGetOutput(true, "cat /proc/version")
|
||||||
AssetsUtil.exportFiles(context, "mkbootfs", mkbootfsPath)
|
val versionRegex = """\d+\.\d+\.\d+""".toRegex()
|
||||||
runCommand(false, "sed -i '/chmod -R 755 tools bin;/i cp -f $mkbootfsPath \$AKHOME/tools;' $binaryPath")
|
val version = kernelVersion?.let { versionRegex.find(it) }?.value ?: ""
|
||||||
|
val toolName = if (version.isNotEmpty()) {
|
||||||
|
val parts = version.split('.')
|
||||||
|
if (parts.size >= 2) {
|
||||||
|
val major = parts[0].toIntOrNull() ?: 0
|
||||||
|
val minor = parts[1].toIntOrNull() ?: 0
|
||||||
|
if (major < 5 || (major == 5 && minor <= 10)) "5_10" else "5_15+"
|
||||||
|
} else {
|
||||||
|
"5_15+"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"5_15+"
|
||||||
|
}
|
||||||
|
val toolPath = "${context.filesDir.absolutePath}/mkbootfs"
|
||||||
|
AssetsUtil.exportFiles(context, "$toolName-mkbootfs", toolPath)
|
||||||
|
state.addLog("${context.getString(R.string.kernel_version_log, version)} ${context.getString(R.string.tool_version_log, toolName)}")
|
||||||
|
runCommand(false, "sed -i '/chmod -R 755 tools bin;/i cp -f $toolPath \$AKHOME/tools;' $binaryPath")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun flash() {
|
private fun flash() {
|
||||||
@@ -275,7 +298,7 @@ class HorizonKernelWorker(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runCommandGetOutput(su: Boolean, cmd: String): String {
|
private fun runCommandGetOutput(su: Boolean, cmd: String): String? {
|
||||||
val process = ProcessBuilder(if (su) "su" else "sh")
|
val process = ProcessBuilder(if (su) "su" else "sh")
|
||||||
.redirectErrorStream(true)
|
.redirectErrorStream(true)
|
||||||
.start()
|
.start()
|
||||||
@@ -354,7 +377,7 @@ fun HorizonKernelFlashProgress(state: FlashState) {
|
|||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.heightIn(max = 150.dp)
|
.heightIn(max = 230.dp)
|
||||||
.padding(vertical = 4.dp),
|
.padding(vertical = 4.dp),
|
||||||
color = MaterialTheme.colorScheme.surface,
|
color = MaterialTheme.colorScheme.surface,
|
||||||
tonalElevation = 1.dp,
|
tonalElevation = 1.dp,
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
package com.sukisu.ultra.ui;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.UserHandle;
|
|
||||||
import android.os.UserManager;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.topjohnwu.superuser.ipc.RootService;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.sukisu.zako.IKsuInterface;
|
|
||||||
import rikka.parcelablelist.ParcelableListSlice;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author weishu
|
|
||||||
* @date 2023/4/18.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class KsuService extends RootService {
|
|
||||||
|
|
||||||
private static final String TAG = "KsuService";
|
|
||||||
|
|
||||||
class Stub extends IKsuInterface.Stub {
|
|
||||||
@Override
|
|
||||||
public ParcelableListSlice<PackageInfo> getPackages(int flags) {
|
|
||||||
List<PackageInfo> list = getInstalledPackagesAll(flags);
|
|
||||||
Log.i(TAG, "getPackages: " + list.size());
|
|
||||||
return new ParcelableListSlice<>(list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(@NonNull Intent intent) {
|
|
||||||
return new Stub();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Integer> getUserIds() {
|
|
||||||
List<Integer> result = new ArrayList<>();
|
|
||||||
UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
|
|
||||||
List<UserHandle> userProfiles = um.getUserProfiles();
|
|
||||||
for (UserHandle userProfile : userProfiles) {
|
|
||||||
int userId = userProfile.hashCode();
|
|
||||||
result.add(userProfile.hashCode());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<PackageInfo> getInstalledPackagesAll(int flags) {
|
|
||||||
ArrayList<PackageInfo> packages = new ArrayList<>();
|
|
||||||
for (Integer userId : getUserIds()) {
|
|
||||||
Log.i(TAG, "getInstalledPackagesAll: " + userId);
|
|
||||||
packages.addAll(getInstalledPackagesAsUser(flags, userId));
|
|
||||||
}
|
|
||||||
return packages;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
|
|
||||||
try {
|
|
||||||
PackageManager pm = getPackageManager();
|
|
||||||
Method getInstalledPackagesAsUser = pm.getClass().getDeclaredMethod("getInstalledPackagesAsUser", int.class, int.class);
|
|
||||||
return (List<PackageInfo>) getInstalledPackagesAsUser.invoke(pm, flags, userId);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Log.e(TAG, "err", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,22 @@
|
|||||||
package com.sukisu.ultra.ui
|
package com.sukisu.ultra.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.database.ContentObserver
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.navigation.NavBackStackEntry
|
import androidx.navigation.NavBackStackEntry
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
@@ -29,24 +33,114 @@ import com.sukisu.ultra.Natives
|
|||||||
import com.sukisu.ultra.ksuApp
|
import com.sukisu.ultra.ksuApp
|
||||||
import com.sukisu.ultra.ui.screen.BottomBarDestination
|
import com.sukisu.ultra.ui.screen.BottomBarDestination
|
||||||
import com.sukisu.ultra.ui.theme.*
|
import com.sukisu.ultra.ui.theme.*
|
||||||
|
import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha
|
||||||
import com.sukisu.ultra.ui.util.*
|
import com.sukisu.ultra.ui.util.*
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
||||||
|
import com.sukisu.ultra.ui.webui.initPlatform
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
private inner class ThemeChangeContentObserver(
|
||||||
|
handler: Handler,
|
||||||
|
private val onThemeChanged: () -> Unit
|
||||||
|
) : ContentObserver(handler) {
|
||||||
|
override fun onChange(selfChange: Boolean) {
|
||||||
|
super.onChange(selfChange)
|
||||||
|
onThemeChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用保存的语言设置
|
||||||
|
@SuppressLint("ObsoleteSdkInt")
|
||||||
|
private fun applyLanguageSetting() {
|
||||||
|
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
val languageCode = prefs.getString("app_language", "") ?: ""
|
||||||
|
|
||||||
|
if (languageCode.isNotEmpty()) {
|
||||||
|
val locale = Locale.forLanguageTag(languageCode)
|
||||||
|
Locale.setDefault(locale)
|
||||||
|
|
||||||
|
val resources = resources
|
||||||
|
val config = Configuration(resources.configuration)
|
||||||
|
config.setLocale(locale)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
createConfigurationContext(config)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
resources.updateConfiguration(config, resources.displayMetrics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun attachBaseContext(newBase: Context) {
|
||||||
|
val prefs = newBase.getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
val languageCode = prefs.getString("app_language", "") ?: ""
|
||||||
|
|
||||||
|
var context = newBase
|
||||||
|
if (languageCode.isNotEmpty()) {
|
||||||
|
val locale = Locale.forLanguageTag(languageCode)
|
||||||
|
Locale.setDefault(locale)
|
||||||
|
|
||||||
|
val config = Configuration(newBase.resources.configuration)
|
||||||
|
config.setLocale(locale)
|
||||||
|
context = newBase.createConfigurationContext(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.attachBaseContext(context)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
// 确保应用正确的语言设置
|
||||||
|
applyLanguageSetting()
|
||||||
|
|
||||||
|
applyCustomDpi()
|
||||||
|
|
||||||
// Enable edge to edge
|
// Enable edge to edge
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
window.isNavigationBarContrastEnforced = false
|
window.isNavigationBarContrastEnforced = false
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
val isFirstRun = prefs.getBoolean("is_first_run", true)
|
||||||
|
|
||||||
|
if (isFirstRun) {
|
||||||
|
ThemeConfig.preventBackgroundRefresh = false
|
||||||
|
getSharedPreferences("theme_prefs", MODE_PRIVATE).edit {
|
||||||
|
putBoolean("prevent_background_refresh", false)
|
||||||
|
}
|
||||||
|
prefs.edit { putBoolean("is_first_run", false) }
|
||||||
|
}
|
||||||
|
|
||||||
// 加载保存的背景设置
|
// 加载保存的背景设置
|
||||||
loadCustomBackground()
|
|
||||||
loadThemeMode()
|
loadThemeMode()
|
||||||
|
loadThemeColors()
|
||||||
|
loadDynamicColorState()
|
||||||
CardConfig.load(applicationContext)
|
CardConfig.load(applicationContext)
|
||||||
|
|
||||||
|
val contentObserver = ThemeChangeContentObserver(Handler(mainLooper)) {
|
||||||
|
runOnUiThread {
|
||||||
|
if (!ThemeConfig.preventBackgroundRefresh) {
|
||||||
|
ThemeConfig.backgroundImageLoaded = false
|
||||||
|
loadCustomBackground()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentResolver.registerContentObserver(
|
||||||
|
android.provider.Settings.System.getUriFor("ui_night_mode"),
|
||||||
|
false,
|
||||||
|
contentObserver
|
||||||
|
)
|
||||||
|
|
||||||
|
val destroyListeners = mutableListOf<() -> Unit>()
|
||||||
|
destroyListeners.add {
|
||||||
|
contentResolver.unregisterContentObserver(contentObserver)
|
||||||
|
}
|
||||||
|
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||||
if (isManager) {
|
if (isManager) {
|
||||||
@@ -58,12 +152,18 @@ class MainActivity : ComponentActivity() {
|
|||||||
KernelSUTheme {
|
KernelSUTheme {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val snackBarHostState = remember { SnackbarHostState() }
|
val snackBarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
// pre-init platform to faster start WebUI X activities
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
initPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
bottomBar = { BottomBar(navController) },
|
bottomBar = { BottomBar(navController) },
|
||||||
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalSnackbarHost provides snackBarHostState,
|
LocalSnackbarHost provides snackBarHostState
|
||||||
) {
|
) {
|
||||||
DestinationsNavHost(
|
DestinationsNavHost(
|
||||||
modifier = Modifier.padding(innerPadding),
|
modifier = Modifier.padding(innerPadding),
|
||||||
@@ -81,58 +181,116 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 应用自定义DPI设置
|
||||||
|
private fun applyCustomDpi() {
|
||||||
|
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
val customDpi = prefs.getInt("app_dpi", 0)
|
||||||
|
|
||||||
|
if (customDpi > 0) {
|
||||||
|
try {
|
||||||
|
val resources = resources
|
||||||
|
val metrics = resources.displayMetrics
|
||||||
|
metrics.density = customDpi / 160f
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
metrics.scaledDensity = customDpi / 160f
|
||||||
|
metrics.densityDpi = customDpi
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
CardConfig.save(applicationContext)
|
||||||
|
getSharedPreferences("theme_prefs", MODE_PRIVATE).edit {
|
||||||
|
putBoolean("prevent_background_refresh", true)
|
||||||
|
}
|
||||||
|
ThemeConfig.preventBackgroundRefresh = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
applyLanguageSetting()
|
||||||
|
|
||||||
|
if (!ThemeConfig.backgroundImageLoaded && !ThemeConfig.preventBackgroundRefresh) {
|
||||||
|
loadCustomBackground()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val destroyListeners = mutableListOf<() -> Unit>()
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
destroyListeners.forEach { it() }
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
super.onConfigurationChanged(newConfig)
|
||||||
|
applyLanguageSetting()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun BottomBar(navController: NavHostController) {
|
private fun BottomBar(navController: NavHostController) {
|
||||||
val navigator = navController.rememberDestinationsNavigator()
|
val navigator = navController.rememberDestinationsNavigator()
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||||
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
|
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
|
||||||
val kpmVersion = getKpmVersion()
|
val kpmVersion = getKpmVersion()
|
||||||
|
val containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
val cardColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
|
||||||
// 获取卡片颜色和透明度
|
// 检查是否显示KPM
|
||||||
val cardColor = MaterialTheme.colorScheme.secondaryContainer
|
val showKpmInfo = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
val cardAlpha = CardConfig.cardAlpha
|
.getBoolean("show_kpm_info", true)
|
||||||
val cardElevation = CardConfig.cardElevation
|
|
||||||
|
|
||||||
NavigationBar(
|
NavigationBar(
|
||||||
tonalElevation = cardElevation, // 动态设置阴影
|
modifier = Modifier.windowInsetsPadding(
|
||||||
containerColor = cardColor.copy(alpha = cardAlpha),
|
WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal)
|
||||||
windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(
|
),
|
||||||
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom
|
containerColor = TopAppBarDefaults.topAppBarColors(
|
||||||
)
|
containerColor = cardColor.copy(alpha = cardAlpha),
|
||||||
|
scrolledContainerColor = containerColor.copy(alpha = cardAlpha)
|
||||||
|
).containerColor,
|
||||||
|
tonalElevation = cardElevation
|
||||||
) {
|
) {
|
||||||
BottomBarDestination.entries.forEach { destination ->
|
BottomBarDestination.entries.forEach { destination ->
|
||||||
if (destination == BottomBarDestination.Kpm) {
|
if (destination == BottomBarDestination.Kpm) {
|
||||||
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) {
|
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error") && showKpmInfo) {
|
||||||
if (!fullFeatured && destination.rootRequired) return@forEach
|
if (!fullFeatured && destination.rootRequired) return@forEach
|
||||||
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
selected = isCurrentDestOnBackStack,
|
selected = isCurrentDestOnBackStack,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (isCurrentDestOnBackStack) {
|
if (!isCurrentDestOnBackStack) {
|
||||||
navigator.popBackStack(destination.direction, false)
|
navigator.navigate(destination.direction) {
|
||||||
}
|
popUpTo(NavGraphs.root as RouteOrDirection) {
|
||||||
navigator.navigate(destination.direction) {
|
saveState = true
|
||||||
popUpTo(NavGraphs.root as RouteOrDirection) {
|
}
|
||||||
saveState = true
|
launchSingleTop = true
|
||||||
|
restoreState = true
|
||||||
}
|
}
|
||||||
launchSingleTop = true
|
|
||||||
restoreState = true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon = {
|
icon = {
|
||||||
if (isCurrentDestOnBackStack) {
|
Icon(
|
||||||
Icon(destination.iconSelected, stringResource(destination.label))
|
imageVector = if (isCurrentDestOnBackStack) {
|
||||||
} else {
|
destination.iconSelected
|
||||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
} else {
|
||||||
}
|
destination.iconNotSelected
|
||||||
|
},
|
||||||
|
contentDescription = stringResource(destination.label),
|
||||||
|
tint = if (isCurrentDestOnBackStack) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
},
|
},
|
||||||
label = { Text(stringResource(destination.label)) },
|
label = {
|
||||||
alwaysShowLabel = false,
|
Text(
|
||||||
colors = NavigationBarItemDefaults.colors(
|
text = stringResource(destination.label),
|
||||||
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
|
style = MaterialTheme.typography.labelMedium
|
||||||
)
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -141,26 +299,33 @@ private fun BottomBar(navController: NavHostController) {
|
|||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
selected = isCurrentDestOnBackStack,
|
selected = isCurrentDestOnBackStack,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (isCurrentDestOnBackStack) {
|
if (!isCurrentDestOnBackStack) {
|
||||||
navigator.popBackStack(destination.direction, false)
|
navigator.navigate(destination.direction) {
|
||||||
}
|
popUpTo(NavGraphs.root as RouteOrDirection) {
|
||||||
navigator.navigate(destination.direction) {
|
saveState = true
|
||||||
popUpTo(NavGraphs.root as RouteOrDirection) {
|
}
|
||||||
saveState = true
|
launchSingleTop = true
|
||||||
|
restoreState = true
|
||||||
}
|
}
|
||||||
launchSingleTop = true
|
|
||||||
restoreState = true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon = {
|
icon = {
|
||||||
if (isCurrentDestOnBackStack) {
|
Icon(
|
||||||
Icon(destination.iconSelected, stringResource(destination.label))
|
imageVector = if (isCurrentDestOnBackStack) {
|
||||||
} else {
|
destination.iconSelected
|
||||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
} else {
|
||||||
}
|
destination.iconNotSelected
|
||||||
|
},
|
||||||
|
contentDescription = stringResource(destination.label),
|
||||||
|
tint = if (isCurrentDestOnBackStack) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
},
|
},
|
||||||
label = { Text(stringResource(destination.label)) },
|
label = {
|
||||||
alwaysShowLabel = false,
|
Text(
|
||||||
|
text = stringResource(destination.label),
|
||||||
|
style = MaterialTheme.typography.labelMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ fun ImageEditorDialog(
|
|||||||
var offsetY by remember { mutableFloatStateOf(0f) }
|
var offsetY by remember { mutableFloatStateOf(0f) }
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val density = LocalDensity.current
|
|
||||||
var lastScale by remember { mutableFloatStateOf(1f) }
|
var lastScale by remember { mutableFloatStateOf(1f) }
|
||||||
var lastOffsetX by remember { mutableFloatStateOf(0f) }
|
var lastOffsetX by remember { mutableFloatStateOf(0f) }
|
||||||
var lastOffsetY by remember { mutableFloatStateOf(0f) }
|
var lastOffsetY by remember { mutableFloatStateOf(0f) }
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.sukisu.ultra.ui.component
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import com.sukisu.ultra.Natives
|
||||||
|
import com.sukisu.ultra.ksuApp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun KsuIsValid(
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||||
|
val ksuVersion = if (isManager) Natives.version else null
|
||||||
|
|
||||||
|
if (ksuVersion != null) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,7 +63,7 @@ fun SearchAppBar(
|
|||||||
var onSearch by remember { mutableStateOf(false) }
|
var onSearch by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// 获取卡片颜色和透明度
|
// 获取卡片颜色和透明度
|
||||||
val cardColor = MaterialTheme.colorScheme.secondaryContainer
|
val cardColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
val cardAlpha = CardConfig.cardAlpha
|
val cardAlpha = CardConfig.cardAlpha
|
||||||
|
|
||||||
if (onSearch) {
|
if (onSearch) {
|
||||||
|
|||||||
@@ -5,14 +5,18 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||||||
import androidx.compose.foundation.selection.toggleable
|
import androidx.compose.foundation.selection.toggleable
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.ListItem
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import com.dergoogler.mmrl.ui.component.LabelItem
|
||||||
|
import com.dergoogler.mmrl.ui.component.text.TextRow
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SwitchItem(
|
fun SwitchItem(
|
||||||
@@ -21,9 +25,11 @@ fun SwitchItem(
|
|||||||
summary: String? = null,
|
summary: String? = null,
|
||||||
checked: Boolean,
|
checked: Boolean,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
onCheckedChange: (Boolean) -> Unit
|
beta: Boolean = false,
|
||||||
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val stateAlpha = remember(checked, enabled) { Modifier.alpha(if (enabled) 1f else 0.5f) }
|
||||||
|
|
||||||
ListItem(
|
ListItem(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -36,10 +42,31 @@ fun SwitchItem(
|
|||||||
onValueChange = onCheckedChange
|
onValueChange = onCheckedChange
|
||||||
),
|
),
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Text(title)
|
TextRow(
|
||||||
|
leadingContent = if (beta) {
|
||||||
|
{
|
||||||
|
LabelItem(
|
||||||
|
modifier = Modifier.then(stateAlpha),
|
||||||
|
text = "Beta"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.then(stateAlpha),
|
||||||
|
text = title,
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
leadingContent = icon?.let {
|
leadingContent = icon?.let {
|
||||||
{ Icon(icon, title) }
|
{
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.then(stateAlpha),
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = title,
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Switch(
|
Switch(
|
||||||
@@ -51,7 +78,10 @@ fun SwitchItem(
|
|||||||
},
|
},
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
if (summary != null) {
|
if (summary != null) {
|
||||||
Text(summary)
|
Text(
|
||||||
|
modifier = Modifier.then(stateAlpha),
|
||||||
|
text = summary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package com.sukisu.ultra.ui.component
|
package com.sukisu.ultra.ui.component
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -11,15 +14,16 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
import com.sukisu.ultra.ui.theme.ThemeConfig
|
import com.sukisu.ultra.ui.theme.ThemeConfig
|
||||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.foundation.shape.CornerSize
|
import androidx.compose.material.icons.filled.SdStorage
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 槽位选择对话框组件
|
* 槽位选择对话框组件
|
||||||
* 用于HorizonKernel刷写时选择目标槽位
|
* 用于Kernel刷写时选择目标槽位
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -43,38 +47,22 @@ fun SlotSelectionDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (show) {
|
if (show) {
|
||||||
val backgroundColor = if (!ThemeConfig.useDynamicColor) {
|
val cardColor = MaterialTheme.colorScheme.surfaceContainerHighest
|
||||||
ThemeConfig.currentTheme.ButtonContrast.copy(alpha = 1.0f)
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 1.0f)
|
|
||||||
}
|
|
||||||
|
|
||||||
Dialog(onDismissRequest = onDismiss) {
|
AlertDialog(
|
||||||
Card(
|
onDismissRequest = onDismiss,
|
||||||
shape = MaterialTheme.shapes.medium.copy(
|
title = {
|
||||||
topStart = CornerSize(16.dp),
|
Text(
|
||||||
topEnd = CornerSize(16.dp),
|
text = stringResource(id = R.string.select_slot_title),
|
||||||
bottomEnd = CornerSize(16.dp),
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
bottomStart = CornerSize(16.dp)
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
),
|
)
|
||||||
colors = CardDefaults.cardColors(
|
},
|
||||||
containerColor = backgroundColor
|
text = {
|
||||||
),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
|
|
||||||
) {
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
.padding(24.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
.fillMaxWidth(),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
) {
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.select_slot_title),
|
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (errorMessage != null) {
|
if (errorMessage != null) {
|
||||||
Text(
|
Text(
|
||||||
text = "Error: $errorMessage",
|
text = "Error: $errorMessage",
|
||||||
@@ -105,88 +93,134 @@ fun SlotSelectionDialog(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// Horizontal arrangement for slot options with highlighted current slot
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
.fillMaxWidth()
|
||||||
) {
|
.horizontalScroll(rememberScrollState()),
|
||||||
val isDefaultSlotA = currentSlot == "_a" || currentSlot == "a"
|
|
||||||
Button(
|
|
||||||
onClick = { onSlotSelected("a") },
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = if (isDefaultSlotA)
|
|
||||||
MaterialTheme.colorScheme.primary
|
|
||||||
else
|
|
||||||
MaterialTheme.colorScheme.primaryContainer,
|
|
||||||
contentColor = if (isDefaultSlotA)
|
|
||||||
MaterialTheme.colorScheme.onPrimary
|
|
||||||
else
|
|
||||||
MaterialTheme.colorScheme.onPrimaryContainer
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.slot_a),
|
|
||||||
style = MaterialTheme.typography.labelLarge
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val isDefaultSlotB = currentSlot == "_b" || currentSlot == "b"
|
|
||||||
Button(
|
|
||||||
onClick = { onSlotSelected("b") },
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = if (isDefaultSlotB)
|
|
||||||
MaterialTheme.colorScheme.secondary
|
|
||||||
else
|
|
||||||
MaterialTheme.colorScheme.secondaryContainer,
|
|
||||||
contentColor = if (isDefaultSlotB)
|
|
||||||
MaterialTheme.colorScheme.onSecondary
|
|
||||||
else
|
|
||||||
MaterialTheme.colorScheme.onSecondaryContainer
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.slot_b),
|
|
||||||
style = MaterialTheme.typography.labelLarge
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
|
|
||||||
HorizontalDivider(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
thickness = 1.dp,
|
|
||||||
color = MaterialTheme.colorScheme.outline
|
|
||||||
)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
TextButton(
|
val slotOptions = listOf(
|
||||||
onClick = onDismiss,
|
ListOption(
|
||||||
modifier = Modifier.weight(1f)
|
titleText = stringResource(id = R.string.slot_a),
|
||||||
) {
|
subtitleText = if (currentSlot == "a" || currentSlot == "_a") stringResource(id = R.string.currently_selected) else null,
|
||||||
Text(text = stringResource(id = android.R.string.cancel))
|
icon = Icons.Filled.SdStorage
|
||||||
}
|
),
|
||||||
TextButton(
|
ListOption(
|
||||||
onClick = {
|
titleText = stringResource(id = R.string.slot_b),
|
||||||
currentSlot?.let { onSlotSelected(it) }
|
subtitleText = if (currentSlot == "b" || currentSlot == "_b") stringResource(id = R.string.currently_selected) else null,
|
||||||
onDismiss()
|
icon = Icons.Filled.SdStorage
|
||||||
},
|
)
|
||||||
modifier = Modifier.weight(1f)
|
)
|
||||||
) {
|
|
||||||
Text(text = stringResource(id = android.R.string.ok))
|
slotOptions.forEachIndexed { index, option ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.background(
|
||||||
|
color = if (option.subtitleText != null) {
|
||||||
|
MaterialTheme.colorScheme.primary.copy(alpha = 0.9f)
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.clickable {
|
||||||
|
onSlotSelected(
|
||||||
|
when (index) {
|
||||||
|
0 -> "a"
|
||||||
|
else -> "b"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.padding(vertical = 12.dp, horizontal = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = option.icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = if (option.subtitleText != null) {
|
||||||
|
MaterialTheme.colorScheme.onPrimary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 16.dp)
|
||||||
|
.size(24.dp)
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = option.titleText,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = if (option.subtitleText != null) {
|
||||||
|
MaterialTheme.colorScheme.onPrimary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
}
|
||||||
|
)
|
||||||
|
option.subtitleText?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = if (true) {
|
||||||
|
MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.8f)
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
currentSlot?.let { onSlotSelected(it) }
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(android.R.string.ok),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = onDismiss
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(android.R.string.cancel),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
containerColor = cardColor,
|
||||||
|
shape = MaterialTheme.shapes.extraLarge,
|
||||||
|
tonalElevation = 4.dp
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前槽位信息
|
// Data class for list options
|
||||||
|
data class ListOption(
|
||||||
|
val titleText: String,
|
||||||
|
val subtitleText: String?,
|
||||||
|
val icon: ImageVector
|
||||||
|
)
|
||||||
|
|
||||||
|
// Utility function to get current slot
|
||||||
private fun getCurrentSlot(context: Context): String? {
|
private fun getCurrentSlot(context: Context): String? {
|
||||||
return runCommandGetOutput(true, "getprop ro.boot.slot_suffix")?.let {
|
return runCommandGetOutput(true, "getprop ro.boot.slot_suffix")?.let {
|
||||||
if (it.startsWith("_")) it.substring(1) else it
|
if (it.startsWith("_")) it.substring(1) else it
|
||||||
|
|||||||
@@ -1,32 +1,101 @@
|
|||||||
package com.sukisu.ultra.ui.component
|
package com.sukisu.ultra.ui.component
|
||||||
|
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.ListItem
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.SwitchDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SwitchItem(
|
fun SwitchItem(
|
||||||
icon: ImageVector,
|
icon: ImageVector,
|
||||||
title: String,
|
title: String,
|
||||||
summary: String,
|
summary: String? = null,
|
||||||
checked: Boolean,
|
checked: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
enabled: Boolean = true,
|
||||||
onCheckedChange: (Boolean) -> Unit
|
onCheckedChange: (Boolean) -> Unit
|
||||||
) {
|
) {
|
||||||
|
// 颜色动画
|
||||||
|
val iconTint by animateColorAsState(
|
||||||
|
targetValue = if (checked) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
|
||||||
|
animationSpec = tween(300),
|
||||||
|
label = "iconTint"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 开关颜色
|
||||||
|
val switchColors = SwitchDefaults.colors(
|
||||||
|
checkedThumbColor = MaterialTheme.colorScheme.primary,
|
||||||
|
checkedTrackColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
checkedBorderColor = MaterialTheme.colorScheme.primary,
|
||||||
|
checkedIconColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
uncheckedThumbColor = MaterialTheme.colorScheme.outline,
|
||||||
|
uncheckedTrackColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
uncheckedBorderColor = MaterialTheme.colorScheme.outline,
|
||||||
|
uncheckedIconColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
disabledCheckedThumbColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f),
|
||||||
|
disabledCheckedTrackColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
|
||||||
|
disabledCheckedBorderColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
|
||||||
|
disabledCheckedIconColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
disabledUncheckedThumbColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f),
|
||||||
|
disabledUncheckedTrackColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
|
||||||
|
disabledUncheckedBorderColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
|
||||||
|
disabledUncheckedIconColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
ListItem(
|
ListItem(
|
||||||
modifier = modifier,
|
headlineContent = {
|
||||||
leadingContent = { Icon(icon, contentDescription = null) },
|
Text(
|
||||||
headlineContent = { Text(title) },
|
text = title,
|
||||||
supportingContent = { Text(summary) },
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
maxLines = Int.MAX_VALUE,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
},
|
||||||
|
supportingContent = summary?.let {
|
||||||
|
{
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
maxLines = Int.MAX_VALUE,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = iconTint
|
||||||
|
)
|
||||||
|
},
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Switch(
|
Switch(
|
||||||
checked = checked,
|
checked = checked,
|
||||||
onCheckedChange = onCheckedChange
|
onCheckedChange = null,
|
||||||
|
enabled = enabled,
|
||||||
|
colors = switchColors
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(enabled = enabled) {
|
||||||
|
onCheckedChange(!checked)
|
||||||
|
}
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,13 @@
|
|||||||
package com.sukisu.ultra.ui.screen
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
@@ -25,16 +31,21 @@ import androidx.compose.material.icons.filled.Android
|
|||||||
import androidx.compose.material.icons.filled.Security
|
import androidx.compose.material.icons.filled.Security
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.ElevatedCard
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FilterChip
|
import androidx.compose.material3.FilterChip
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.IconButtonDefaults
|
||||||
import androidx.compose.material3.ListItem
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarColors
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -44,7 +55,10 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
@@ -63,13 +77,13 @@ import com.ramcosta.composedestinations.annotation.RootGraph
|
|||||||
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
|
||||||
import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import com.sukisu.ultra.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
import com.sukisu.ultra.ui.component.SwitchItem
|
import com.sukisu.ultra.ui.component.SwitchItem
|
||||||
import com.sukisu.ultra.ui.component.profile.AppProfileConfig
|
import com.sukisu.ultra.ui.component.profile.AppProfileConfig
|
||||||
import com.sukisu.ultra.ui.component.profile.RootProfileConfig
|
import com.sukisu.ultra.ui.component.profile.RootProfileConfig
|
||||||
import com.sukisu.ultra.ui.component.profile.TemplateConfig
|
import com.sukisu.ultra.ui.component.profile.TemplateConfig
|
||||||
|
import com.sukisu.ultra.ui.theme.CardConfig
|
||||||
import com.sukisu.ultra.ui.util.LocalSnackbarHost
|
import com.sukisu.ultra.ui.util.LocalSnackbarHost
|
||||||
import com.sukisu.ultra.ui.util.forceStopApp
|
import com.sukisu.ultra.ui.util.forceStopApp
|
||||||
import com.sukisu.ultra.ui.util.getSepolicy
|
import com.sukisu.ultra.ui.util.getSepolicy
|
||||||
@@ -78,6 +92,7 @@ import com.sukisu.ultra.ui.util.restartApp
|
|||||||
import com.sukisu.ultra.ui.util.setSepolicy
|
import com.sukisu.ultra.ui.util.setSepolicy
|
||||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
||||||
import com.sukisu.ultra.ui.viewmodel.getTemplateInfoById
|
import com.sukisu.ultra.ui.viewmodel.getTemplateInfoById
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author weishu
|
* @author weishu
|
||||||
@@ -107,9 +122,18 @@ fun AppProfileScreen(
|
|||||||
mutableStateOf(initialProfile)
|
mutableStateOf(initialProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val cardColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
val cardAlpha = CardConfig.cardAlpha
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopBar(
|
TopBar(
|
||||||
|
title = appInfo.label,
|
||||||
|
packageName = packageName,
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = cardColor.copy(alpha = cardAlpha),
|
||||||
|
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
||||||
|
),
|
||||||
onBack = dropUnlessResumed { navigator.popBackStack() },
|
onBack = dropUnlessResumed { navigator.popBackStack() },
|
||||||
scrollBehavior = scrollBehavior
|
scrollBehavior = scrollBehavior
|
||||||
)
|
)
|
||||||
@@ -181,22 +205,50 @@ private fun AppProfileInner(
|
|||||||
val isRootGranted = profile.allowSu
|
val isRootGranted = profile.allowSu
|
||||||
|
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
AppMenuBox(packageName) {
|
ElevatedCard(
|
||||||
ListItem(
|
modifier = Modifier
|
||||||
headlineContent = { Text(appLabel) },
|
.fillMaxWidth()
|
||||||
supportingContent = { Text(packageName) },
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
leadingContent = appIcon,
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
AppMenuBox(packageName) {
|
||||||
|
ListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Text(
|
||||||
|
text = appLabel,
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
Text(
|
||||||
|
text = packageName,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
},
|
||||||
|
leadingContent = appIcon,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ElevatedCard(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
SwitchItem(
|
||||||
|
icon = Icons.Filled.Security,
|
||||||
|
title = stringResource(id = R.string.superuser),
|
||||||
|
checked = isRootGranted,
|
||||||
|
onCheckedChange = { onProfileChange(profile.copy(allowSu = it)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SwitchItem(
|
Crossfade(
|
||||||
icon = Icons.Filled.Security,
|
targetState = isRootGranted,
|
||||||
title = stringResource(id = R.string.superuser),
|
label = "RootAccess"
|
||||||
checked = isRootGranted,
|
) { current ->
|
||||||
onCheckedChange = { onProfileChange(profile.copy(allowSu = it)) },
|
|
||||||
)
|
|
||||||
|
|
||||||
Crossfade(targetState = isRootGranted, label = "") { current ->
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(bottom = 6.dp + 48.dp + 6.dp /* SnackBar height */)
|
modifier = Modifier.padding(bottom = 6.dp + 48.dp + 6.dp /* SnackBar height */)
|
||||||
) {
|
) {
|
||||||
@@ -211,42 +263,91 @@ private fun AppProfileInner(
|
|||||||
var mode by rememberSaveable {
|
var mode by rememberSaveable {
|
||||||
mutableStateOf(initialMode)
|
mutableStateOf(initialMode)
|
||||||
}
|
}
|
||||||
ProfileBox(mode, true) {
|
|
||||||
// template mode shouldn't change profile here!
|
ElevatedCard(
|
||||||
if (it == Mode.Default || it == Mode.Custom) {
|
modifier = Modifier
|
||||||
onProfileChange(profile.copy(rootUseDefault = it == Mode.Default))
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
ProfileBox(mode, true) {
|
||||||
|
// template mode shouldn't change profile here!
|
||||||
|
if (it == Mode.Default || it == Mode.Custom) {
|
||||||
|
onProfileChange(profile.copy(rootUseDefault = it == Mode.Default))
|
||||||
|
}
|
||||||
|
mode = it
|
||||||
}
|
}
|
||||||
mode = it
|
|
||||||
}
|
}
|
||||||
Crossfade(targetState = mode, label = "") { currentMode ->
|
|
||||||
if (currentMode == Mode.Template) {
|
AnimatedVisibility(
|
||||||
TemplateConfig(
|
visible = mode != Mode.Default,
|
||||||
profile = profile,
|
enter = fadeIn() + expandVertically(),
|
||||||
onViewTemplate = onViewTemplate,
|
exit = fadeOut() + shrinkVertically()
|
||||||
onManageTemplate = onManageTemplate,
|
) {
|
||||||
onProfileChange = onProfileChange
|
ElevatedCard(
|
||||||
)
|
modifier = Modifier
|
||||||
} else if (mode == Mode.Custom) {
|
.fillMaxWidth()
|
||||||
RootProfileConfig(
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
fixedName = true,
|
shape = MaterialTheme.shapes.medium
|
||||||
profile = profile,
|
) {
|
||||||
onProfileChange = onProfileChange
|
Column(modifier = Modifier.padding(vertical = 8.dp)) {
|
||||||
)
|
Crossfade(targetState = mode, label = "ProfileMode") { currentMode ->
|
||||||
|
when (currentMode) {
|
||||||
|
Mode.Template -> {
|
||||||
|
TemplateConfig(
|
||||||
|
profile = profile,
|
||||||
|
onViewTemplate = onViewTemplate,
|
||||||
|
onManageTemplate = onManageTemplate,
|
||||||
|
onProfileChange = onProfileChange
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Mode.Custom -> {
|
||||||
|
RootProfileConfig(
|
||||||
|
fixedName = true,
|
||||||
|
profile = profile,
|
||||||
|
onProfileChange = onProfileChange
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val mode = if (profile.nonRootUseDefault) Mode.Default else Mode.Custom
|
val mode = if (profile.nonRootUseDefault) Mode.Default else Mode.Custom
|
||||||
ProfileBox(mode, false) {
|
|
||||||
onProfileChange(profile.copy(nonRootUseDefault = (it == Mode.Default)))
|
ElevatedCard(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
ProfileBox(mode, false) {
|
||||||
|
onProfileChange(profile.copy(nonRootUseDefault = (it == Mode.Default)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Crossfade(targetState = mode, label = "") { currentMode ->
|
|
||||||
val modifyEnabled = currentMode == Mode.Custom
|
AnimatedVisibility(
|
||||||
AppProfileConfig(
|
visible = mode == Mode.Custom,
|
||||||
fixedName = true,
|
enter = fadeIn() + expandVertically(),
|
||||||
profile = profile,
|
exit = fadeOut() + shrinkVertically()
|
||||||
enabled = modifyEnabled,
|
) {
|
||||||
onProfileChange = onProfileChange
|
ElevatedCard(
|
||||||
)
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(vertical = 8.dp)) {
|
||||||
|
AppProfileConfig(
|
||||||
|
fixedName = true,
|
||||||
|
profile = profile,
|
||||||
|
enabled = mode == Mode.Custom,
|
||||||
|
onProfileChange = onProfileChange
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,20 +365,51 @@ private enum class Mode(@StringRes private val res: Int) {
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun TopBar(
|
private fun TopBar(
|
||||||
|
title: String,
|
||||||
|
packageName: String,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
|
colors: TopAppBarColors,
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||||
) {
|
) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(stringResource(R.string.profile))
|
Column {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = packageName,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.alpha(0.8f)
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
colors = colors,
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onBack
|
onClick = onBack,
|
||||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
colors = IconButtonDefaults.iconButtonColors(
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
contentDescription = stringResource(R.string.back)
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
windowInsets = WindowInsets.safeDrawing.only(
|
||||||
scrollBehavior = scrollBehavior
|
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
|
||||||
|
),
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
modifier = Modifier.shadow(
|
||||||
|
elevation = if ((scrollBehavior?.state?.overlappedFraction ?: 0f) > 0.01f)
|
||||||
|
4.dp else 0.dp,
|
||||||
|
spotColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,40 +419,88 @@ private fun ProfileBox(
|
|||||||
hasTemplate: Boolean,
|
hasTemplate: Boolean,
|
||||||
onModeChange: (Mode) -> Unit,
|
onModeChange: (Mode) -> Unit,
|
||||||
) {
|
) {
|
||||||
ListItem(
|
Column(modifier = Modifier.padding(vertical = 8.dp)) {
|
||||||
headlineContent = { Text(stringResource(R.string.profile)) },
|
ListItem(
|
||||||
supportingContent = { Text(mode.text) },
|
headlineContent = {
|
||||||
leadingContent = { Icon(Icons.Filled.AccountCircle, null) },
|
Text(
|
||||||
)
|
text = stringResource(R.string.profile),
|
||||||
HorizontalDivider(thickness = Dp.Hairline)
|
style = MaterialTheme.typography.titleMedium
|
||||||
ListItem(headlineContent = {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly
|
|
||||||
) {
|
|
||||||
FilterChip(
|
|
||||||
selected = mode == Mode.Default,
|
|
||||||
label = { Text(stringResource(R.string.profile_default)) },
|
|
||||||
onClick = { onModeChange(Mode.Default) },
|
|
||||||
)
|
|
||||||
if (hasTemplate) {
|
|
||||||
FilterChip(
|
|
||||||
selected = mode == Mode.Template,
|
|
||||||
label = { Text(stringResource(R.string.profile_template)) },
|
|
||||||
onClick = { onModeChange(Mode.Template) },
|
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
Text(
|
||||||
|
text = mode.text,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.AccountCircle,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(
|
||||||
|
thickness = Dp.Hairline,
|
||||||
|
color = MaterialTheme.colorScheme.outlineVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
ListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)
|
||||||
|
) {
|
||||||
|
FilterChip(
|
||||||
|
selected = mode == Mode.Default,
|
||||||
|
onClick = { onModeChange(Mode.Default) },
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.profile_default),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
},
|
||||||
|
shape = MaterialTheme.shapes.small
|
||||||
|
)
|
||||||
|
|
||||||
|
if (hasTemplate) {
|
||||||
|
FilterChip(
|
||||||
|
selected = mode == Mode.Template,
|
||||||
|
onClick = { onModeChange(Mode.Template) },
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.profile_template),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
},
|
||||||
|
shape = MaterialTheme.shapes.small
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterChip(
|
||||||
|
selected = mode == Mode.Custom,
|
||||||
|
onClick = { onModeChange(Mode.Custom) },
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.profile_custom),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
},
|
||||||
|
shape = MaterialTheme.shapes.small
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
FilterChip(
|
)
|
||||||
selected = mode == Mode.Custom,
|
}
|
||||||
label = { Text(stringResource(R.string.profile_custom)) },
|
|
||||||
onClick = { onModeChange(Mode.Custom) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnusedBoxWithConstraintsScope")
|
||||||
@Composable
|
@Composable
|
||||||
private fun AppMenuBox(packageName: String, content: @Composable () -> Unit) {
|
private fun AppMenuBox(packageName: String, content: @Composable () -> Unit) {
|
||||||
|
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
var touchPoint: Offset by remember { mutableStateOf(Offset.Zero) }
|
var touchPoint: Offset by remember { mutableStateOf(Offset.Zero) }
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
@@ -329,13 +509,14 @@ private fun AppMenuBox(packageName: String, content: @Composable () -> Unit) {
|
|||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectTapGestures {
|
detectTapGestures(
|
||||||
touchPoint = it
|
onLongPress = {
|
||||||
expanded = true
|
touchPoint = it
|
||||||
}
|
expanded = true
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
content()
|
content()
|
||||||
|
|
||||||
val (offsetX, offsetY) = with(density) {
|
val (offsetX, offsetY) = with(density) {
|
||||||
@@ -349,45 +530,64 @@ private fun AppMenuBox(packageName: String, content: @Composable () -> Unit) {
|
|||||||
expanded = false
|
expanded = false
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
DropdownMenuItem(
|
AppMenuOption(
|
||||||
text = { Text(stringResource(id = R.string.launch_app)) },
|
text = stringResource(id = R.string.launch_app),
|
||||||
onClick = {
|
onClick = {
|
||||||
expanded = false
|
expanded = false
|
||||||
launchApp(packageName)
|
launchApp(packageName)
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(id = R.string.force_stop_app)) },
|
AppMenuOption(
|
||||||
|
text = stringResource(id = R.string.force_stop_app),
|
||||||
onClick = {
|
onClick = {
|
||||||
expanded = false
|
expanded = false
|
||||||
forceStopApp(packageName)
|
forceStopApp(packageName)
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(id = R.string.restart_app)) },
|
AppMenuOption(
|
||||||
|
text = stringResource(id = R.string.restart_app),
|
||||||
onClick = {
|
onClick = {
|
||||||
expanded = false
|
expanded = false
|
||||||
restartApp(packageName)
|
restartApp(packageName)
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AppMenuOption(text: String, onClick: () -> Unit) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = onClick
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun AppProfilePreview() {
|
private fun AppProfilePreview() {
|
||||||
var profile by remember { mutableStateOf(Natives.Profile("")) }
|
var profile by remember { mutableStateOf(Natives.Profile("")) }
|
||||||
AppProfileInner(
|
Surface {
|
||||||
packageName = "icu.nullptr.test",
|
AppProfileInner(
|
||||||
appLabel = "Test",
|
packageName = "icu.nullptr.test",
|
||||||
appIcon = { Icon(Icons.Filled.Android, null) },
|
appLabel = "Test",
|
||||||
profile = profile,
|
appIcon = {
|
||||||
onProfileChange = {
|
Icon(
|
||||||
profile = it
|
imageVector = Icons.Filled.Android,
|
||||||
},
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
profile = profile,
|
||||||
|
onProfileChange = {
|
||||||
|
profile = it
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -15,42 +15,86 @@ import androidx.compose.animation.shrinkVertically
|
|||||||
import androidx.compose.foundation.LocalIndication
|
import androidx.compose.foundation.LocalIndication
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.only
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.safeDrawing
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.selection.toggleable
|
import androidx.compose.foundation.selection.toggleable
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.AutoFixHigh
|
||||||
import androidx.compose.material.icons.filled.FileUpload
|
import androidx.compose.material.icons.filled.FileUpload
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.ElevatedCard
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.RadioButtonDefaults
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.maxkeppeker.sheets.core.models.base.Header
|
||||||
|
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
||||||
|
import com.maxkeppeler.sheets.list.ListDialog
|
||||||
import com.maxkeppeler.sheets.list.models.ListOption
|
import com.maxkeppeler.sheets.list.models.ListOption
|
||||||
|
import com.maxkeppeler.sheets.list.models.ListSelection
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
|
import com.sukisu.ultra.flash.HorizonKernelFlashProgress
|
||||||
|
import com.sukisu.ultra.flash.HorizonKernelState
|
||||||
|
import com.sukisu.ultra.flash.HorizonKernelWorker
|
||||||
import com.sukisu.ultra.ui.component.DialogHandle
|
import com.sukisu.ultra.ui.component.DialogHandle
|
||||||
import com.sukisu.ultra.ui.component.SlotSelectionDialog
|
import com.sukisu.ultra.ui.component.SlotSelectionDialog
|
||||||
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||||
import com.sukisu.ultra.ui.component.rememberCustomDialog
|
import com.sukisu.ultra.ui.component.rememberCustomDialog
|
||||||
import com.sukisu.ultra.flash.HorizonKernelFlashProgress
|
import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha
|
||||||
import com.sukisu.ultra.flash.HorizonKernelState
|
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
||||||
import com.sukisu.ultra.flash.HorizonKernelWorker
|
|
||||||
import com.sukisu.ultra.ui.theme.CardConfig
|
|
||||||
import com.sukisu.ultra.ui.theme.ThemeConfig
|
|
||||||
import com.sukisu.ultra.ui.theme.getCardColors
|
import com.sukisu.ultra.ui.theme.getCardColors
|
||||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
import com.sukisu.ultra.ui.util.LkmSelection
|
||||||
import com.sukisu.ultra.ui.util.*
|
import com.sukisu.ultra.ui.util.getCurrentKmi
|
||||||
|
import com.sukisu.ultra.ui.util.getSupportedKmis
|
||||||
|
import com.sukisu.ultra.ui.util.isAbDevice
|
||||||
|
import com.sukisu.ultra.ui.util.isInitBoot
|
||||||
|
import com.sukisu.ultra.ui.util.rootAvailable
|
||||||
|
import com.sukisu.ultra.getKernelVersion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author weishu
|
* @author weishu
|
||||||
@@ -69,6 +113,9 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
val horizonKernelState = remember { HorizonKernelState() }
|
val horizonKernelState = remember { HorizonKernelState() }
|
||||||
val flashState by horizonKernelState.state.collectAsState()
|
val flashState by horizonKernelState.state.collectAsState()
|
||||||
val summary = stringResource(R.string.horizon_kernel_summary)
|
val summary = stringResource(R.string.horizon_kernel_summary)
|
||||||
|
val kernelVersion = getKernelVersion()
|
||||||
|
val isGKI = kernelVersion.isGKI()
|
||||||
|
val isAbDevice = isAbDevice()
|
||||||
|
|
||||||
val onFlashComplete = {
|
val onFlashComplete = {
|
||||||
showRebootDialog = true
|
showRebootDialog = true
|
||||||
@@ -123,7 +170,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
|
|
||||||
// 槽位选择
|
// 槽位选择
|
||||||
SlotSelectionDialog(
|
SlotSelectionDialog(
|
||||||
show = showSlotSelectionDialog,
|
show = showSlotSelectionDialog && isAbDevice,
|
||||||
onDismiss = { showSlotSelectionDialog = false },
|
onDismiss = { showSlotSelectionDialog = false },
|
||||||
onSlotSelected = { slot ->
|
onSlotSelected = { slot ->
|
||||||
showSlotSelectionDialog = false
|
showSlotSelectionDialog = false
|
||||||
@@ -133,7 +180,6 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
summary = summary
|
summary = summary
|
||||||
)
|
)
|
||||||
installMethod = horizonMethod
|
installMethod = horizonMethod
|
||||||
onInstall()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -149,7 +195,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val onClickNext = {
|
val onClickNext = {
|
||||||
if (lkmSelection == LkmSelection.KmiNone && currentKmi.isBlank()) {
|
if (isGKI && lkmSelection == LkmSelection.KmiNone && currentKmi.isBlank()) {
|
||||||
selectKmiDialog.show()
|
selectKmiDialog.show()
|
||||||
} else {
|
} else {
|
||||||
onInstall()
|
onInstall()
|
||||||
@@ -191,12 +237,19 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(top = 12.dp)
|
||||||
) {
|
) {
|
||||||
SelectInstallMethod(
|
SelectInstallMethod(
|
||||||
|
isGKI = isGKI,
|
||||||
|
isAbDevice = isAbDevice,
|
||||||
onSelected = { method ->
|
onSelected = { method ->
|
||||||
if (method is InstallMethod.HorizonKernel && method.uri != null && method.slot == null) {
|
if (method is InstallMethod.HorizonKernel && method.uri != null) {
|
||||||
tempKernelUri = method.uri
|
if (isAbDevice) {
|
||||||
showSlotSelectionDialog = true
|
tempKernelUri = method.uri
|
||||||
|
showSlotSelectionDialog = true
|
||||||
|
} else {
|
||||||
|
installMethod = method
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
installMethod = method
|
installMethod = method
|
||||||
}
|
}
|
||||||
@@ -218,32 +271,73 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
(lkmSelection as? LkmSelection.LkmUri)?.let {
|
(lkmSelection as? LkmSelection.LkmUri)?.let {
|
||||||
Text(
|
ElevatedCard(
|
||||||
stringResource(
|
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
|
||||||
id = R.string.selected_lkm,
|
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation),
|
||||||
it.uri.lastPathSegment ?: "(file)"
|
modifier = Modifier
|
||||||
)
|
.fillMaxWidth()
|
||||||
)
|
.padding(bottom = 12.dp)
|
||||||
}
|
.clip(MaterialTheme.shapes.medium)
|
||||||
(installMethod as? InstallMethod.HorizonKernel)?.let { method ->
|
.shadow(
|
||||||
if (method.slot != null) {
|
elevation = cardElevation,
|
||||||
Text(
|
shape = MaterialTheme.shapes.medium,
|
||||||
stringResource(
|
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||||
id = R.string.selected_slot,
|
|
||||||
if (method.slot == "a") stringResource(id = R.string.slot_a)
|
|
||||||
else stringResource(id = R.string.slot_b)
|
|
||||||
)
|
)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
id = R.string.selected_lkm,
|
||||||
|
it.uri.lastPathSegment ?: "(file)"
|
||||||
|
),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(installMethod as? InstallMethod.HorizonKernel)?.let { method ->
|
||||||
|
if (method.slot != null) {
|
||||||
|
ElevatedCard(
|
||||||
|
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 12.dp)
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.shadow(
|
||||||
|
elevation = cardElevation,
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
id = R.string.selected_slot,
|
||||||
|
if (method.slot == "a") stringResource(id = R.string.slot_a)
|
||||||
|
else stringResource(id = R.string.slot_b)
|
||||||
|
),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = installMethod != null && !flashState.isFlashing,
|
enabled = installMethod != null && !flashState.isFlashing,
|
||||||
onClick = onClickNext
|
onClick = onClickNext,
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.6f),
|
||||||
|
disabledContentColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(id = R.string.install_next),
|
stringResource(id = R.string.install_next),
|
||||||
fontSize = MaterialTheme.typography.bodyMedium.fontSize
|
style = MaterialTheme.typography.bodyMedium
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -305,7 +399,11 @@ sealed class InstallMethod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
private fun SelectInstallMethod(
|
||||||
|
isGKI: Boolean = false,
|
||||||
|
isAbDevice: Boolean = false,
|
||||||
|
onSelected: (InstallMethod) -> Unit = {}
|
||||||
|
) {
|
||||||
val rootAvailable = rootAvailable()
|
val rootAvailable = rootAvailable()
|
||||||
val isAbDevice = isAbDevice()
|
val isAbDevice = isAbDevice()
|
||||||
val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary)
|
val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary)
|
||||||
@@ -335,8 +433,16 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
|||||||
if (it.resultCode == Activity.RESULT_OK) {
|
if (it.resultCode == Activity.RESULT_OK) {
|
||||||
it.data?.data?.let { uri ->
|
it.data?.data?.let { uri ->
|
||||||
val option = when (currentSelectingMethod) {
|
val option = when (currentSelectingMethod) {
|
||||||
is InstallMethod.SelectFile -> InstallMethod.SelectFile(uri, summary = selectFileTip)
|
is InstallMethod.SelectFile -> InstallMethod.SelectFile(
|
||||||
is InstallMethod.HorizonKernel -> InstallMethod.HorizonKernel(uri, summary = horizonKernelSummary)
|
uri,
|
||||||
|
summary = selectFileTip
|
||||||
|
)
|
||||||
|
|
||||||
|
is InstallMethod.HorizonKernel -> InstallMethod.HorizonKernel(
|
||||||
|
uri,
|
||||||
|
summary = horizonKernelSummary
|
||||||
|
)
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
option?.let {
|
option?.let {
|
||||||
@@ -364,55 +470,237 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
|||||||
is InstallMethod.SelectFile, is InstallMethod.HorizonKernel -> {
|
is InstallMethod.SelectFile, is InstallMethod.HorizonKernel -> {
|
||||||
selectImageLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply {
|
selectImageLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||||
type = "application/*"
|
type = "application/*"
|
||||||
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/octet-stream", "application/zip"))
|
putExtra(
|
||||||
|
Intent.EXTRA_MIME_TYPES,
|
||||||
|
arrayOf("application/octet-stream", "application/zip")
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
is InstallMethod.DirectInstall -> {
|
is InstallMethod.DirectInstall -> {
|
||||||
selectedOption = option
|
selectedOption = option
|
||||||
onSelected(option)
|
onSelected(option)
|
||||||
}
|
}
|
||||||
|
|
||||||
is InstallMethod.DirectInstallToInactiveSlot -> {
|
is InstallMethod.DirectInstallToInactiveSlot -> {
|
||||||
confirmDialog.showConfirm(dialogTitle, dialogContent)
|
confirmDialog.showConfirm(dialogTitle, dialogContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
var LKMExpanded by remember { mutableStateOf(false) }
|
||||||
radioOptions.forEach { option ->
|
var GKIExpanded by remember { mutableStateOf(false) }
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
|
||||||
Row(
|
Column(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
modifier = Modifier.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
// LKM 安装/修补
|
||||||
|
if (isGKI) {
|
||||||
|
ElevatedCard(
|
||||||
|
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.toggleable(
|
.padding(bottom = 12.dp)
|
||||||
value = option.javaClass == selectedOption?.javaClass,
|
.clip(MaterialTheme.shapes.large)
|
||||||
onValueChange = { onClick(option) },
|
.shadow(
|
||||||
role = Role.RadioButton,
|
elevation = cardElevation,
|
||||||
indication = LocalIndication.current,
|
shape = MaterialTheme.shapes.large,
|
||||||
interactionSource = interactionSource
|
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
RadioButton(
|
ListItem(
|
||||||
selected = option.javaClass == selectedOption?.javaClass,
|
leadingContent = {
|
||||||
onClick = { onClick(option) },
|
Icon(
|
||||||
interactionSource = interactionSource
|
Icons.Filled.AutoFixHigh,
|
||||||
)
|
contentDescription = null,
|
||||||
Column(
|
tint = MaterialTheme.colorScheme.primary
|
||||||
modifier = Modifier.padding(vertical = 12.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = option.label),
|
|
||||||
fontSize = MaterialTheme.typography.titleMedium.fontSize,
|
|
||||||
fontFamily = MaterialTheme.typography.titleMedium.fontFamily,
|
|
||||||
fontStyle = MaterialTheme.typography.titleMedium.fontStyle
|
|
||||||
)
|
|
||||||
option.summary?.let {
|
|
||||||
Text(
|
|
||||||
text = it,
|
|
||||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
|
||||||
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
|
|
||||||
fontStyle = MaterialTheme.typography.bodySmall.fontStyle
|
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
headlineContent = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.Lkm_install_methods),
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
LKMExpanded = !LKMExpanded
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = LKMExpanded,
|
||||||
|
enter = fadeIn() + expandVertically(),
|
||||||
|
exit = shrinkVertically() + fadeOut()
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
start = 16.dp,
|
||||||
|
end = 16.dp,
|
||||||
|
bottom = 16.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
radioOptions.take(3).forEach { option ->
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
Surface(
|
||||||
|
color = if (option.javaClass == selectedOption?.javaClass)
|
||||||
|
MaterialTheme.colorScheme.secondaryContainer.copy(alpha = cardAlpha)
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.surfaceContainerHighest.copy(alpha = cardAlpha),
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.toggleable(
|
||||||
|
value = option.javaClass == selectedOption?.javaClass,
|
||||||
|
onValueChange = { onClick(option) },
|
||||||
|
role = Role.RadioButton,
|
||||||
|
indication = LocalIndication.current,
|
||||||
|
interactionSource = interactionSource
|
||||||
|
)
|
||||||
|
.padding(vertical = 8.dp, horizontal = 12.dp)
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = option.javaClass == selectedOption?.javaClass,
|
||||||
|
onClick = null,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
colors = RadioButtonDefaults.colors(
|
||||||
|
selectedColor = MaterialTheme.colorScheme.primary,
|
||||||
|
unselectedColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 10.dp)
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = option.label),
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
option.summary?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// anykernel3 刷写
|
||||||
|
if (rootAvailable) {
|
||||||
|
ElevatedCard(
|
||||||
|
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 12.dp)
|
||||||
|
.clip(MaterialTheme.shapes.large)
|
||||||
|
.shadow(
|
||||||
|
elevation = cardElevation,
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ListItem(
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.FileUpload,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
},
|
||||||
|
headlineContent = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.GKI_install_methods),
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
GKIExpanded = !GKIExpanded
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = GKIExpanded,
|
||||||
|
enter = fadeIn() + expandVertically(),
|
||||||
|
exit = shrinkVertically() + fadeOut()
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
start = 16.dp,
|
||||||
|
end = 16.dp,
|
||||||
|
bottom = 16.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
radioOptions.filterIsInstance<InstallMethod.HorizonKernel>().forEach { option ->
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
Surface(
|
||||||
|
color = if (option.javaClass == selectedOption?.javaClass)
|
||||||
|
MaterialTheme.colorScheme.secondaryContainer.copy(alpha = cardAlpha)
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.surfaceContainerHighest.copy(alpha = cardAlpha),
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.toggleable(
|
||||||
|
value = option.javaClass == selectedOption?.javaClass,
|
||||||
|
onValueChange = { onClick(option) },
|
||||||
|
role = Role.RadioButton,
|
||||||
|
indication = LocalIndication.current,
|
||||||
|
interactionSource = interactionSource
|
||||||
|
)
|
||||||
|
.padding(vertical = 8.dp, horizontal = 12.dp)
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = option.javaClass == selectedOption?.javaClass,
|
||||||
|
onClick = null,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
colors = RadioButtonDefaults.colors(
|
||||||
|
selectedColor = MaterialTheme.colorScheme.primary,
|
||||||
|
unselectedColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 10.dp)
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = option.label),
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
option.summary?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -427,77 +715,33 @@ fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle {
|
|||||||
val supportedKmi by produceState(initialValue = emptyList<String>()) {
|
val supportedKmi by produceState(initialValue = emptyList<String>()) {
|
||||||
value = getSupportedKmis()
|
value = getSupportedKmis()
|
||||||
}
|
}
|
||||||
val listOptions = supportedKmi.map { value ->
|
val options = supportedKmi.map { value ->
|
||||||
ListOption(
|
ListOption(
|
||||||
titleText = value,
|
titleText = value
|
||||||
subtitleText = null,
|
|
||||||
icon = null
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var selection: String? = null
|
var selection by remember { mutableStateOf<String?>(null) }
|
||||||
val cardColor = if (!ThemeConfig.useDynamicColor) {
|
val backgroundColor = MaterialTheme.colorScheme.surfaceContainerHighest
|
||||||
ThemeConfig.currentTheme.ButtonContrast
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.secondaryContainer
|
|
||||||
}
|
|
||||||
|
|
||||||
AlertDialog(
|
MaterialTheme(
|
||||||
onDismissRequest = {
|
colorScheme = MaterialTheme.colorScheme.copy(
|
||||||
|
surface = backgroundColor
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ListDialog(state = rememberUseCaseState(visible = true, onFinishedRequest = {
|
||||||
|
onSelected(selection)
|
||||||
|
}, onCloseRequest = {
|
||||||
dismiss()
|
dismiss()
|
||||||
},
|
}), header = Header.Default(
|
||||||
title = {
|
title = stringResource(R.string.select_kmi),
|
||||||
Text(text = stringResource(R.string.select_kmi))
|
), selection = ListSelection.Single(
|
||||||
},
|
showRadioButtons = true,
|
||||||
text = {
|
options = options,
|
||||||
Column {
|
) { _, option ->
|
||||||
listOptions.forEachIndexed { index, option ->
|
selection = option.titleText
|
||||||
Row(
|
})
|
||||||
modifier = Modifier
|
}
|
||||||
.clickable {
|
|
||||||
selection = supportedKmi[index]
|
|
||||||
}
|
|
||||||
.padding(vertical = 8.dp)
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
Text(text = option.titleText)
|
|
||||||
option.subtitleText?.let {
|
|
||||||
Text(
|
|
||||||
text = it,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
if (selection != null) {
|
|
||||||
onSelected(selection)
|
|
||||||
}
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(android.R.string.ok))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
containerColor = getCardColors(cardColor.copy(alpha = 0.9f)).containerColor.copy(alpha = 0.9f),
|
|
||||||
shape = MaterialTheme.shapes.medium,
|
|
||||||
tonalElevation = getCardElevation()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,23 +752,26 @@ private fun TopBar(
|
|||||||
onLkmUpload: () -> Unit = {},
|
onLkmUpload: () -> Unit = {},
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||||
) {
|
) {
|
||||||
val cardColor = MaterialTheme.colorScheme.secondaryContainer
|
val cardColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
val cardAlpha = CardConfig.cardAlpha
|
val cardAlpha = cardAlpha
|
||||||
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.install)) },
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.install),
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = cardColor.copy(alpha = cardAlpha),
|
containerColor = cardColor.copy(alpha = cardAlpha),
|
||||||
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
||||||
),
|
),
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
Icon(
|
||||||
}
|
Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
},
|
contentDescription = stringResource(R.string.back)
|
||||||
actions = {
|
)
|
||||||
IconButton(onClick = onLkmUpload) {
|
|
||||||
Icon(Icons.Filled.FileUpload, contentDescription = null)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
windowInsets = WindowInsets.safeDrawing.only(
|
windowInsets = WindowInsets.safeDrawing.only(
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.sukisu.ultra.ui.screen
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
import android.app.Activity.RESULT_OK
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -10,11 +9,13 @@ import androidx.compose.foundation.layout.*
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@@ -36,6 +37,8 @@ import java.io.BufferedReader
|
|||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.net.*
|
import java.net.*
|
||||||
|
import android.app.Activity
|
||||||
|
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KPM 管理界面
|
* KPM 管理界面
|
||||||
@@ -53,11 +56,6 @@ fun KpmScreen(
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val snackBarHost = remember { SnackbarHostState() }
|
val snackBarHost = remember { SnackbarHostState() }
|
||||||
val confirmDialog = rememberConfirmDialog()
|
val confirmDialog = rememberConfirmDialog()
|
||||||
val cardColor = if (!ThemeConfig.useDynamicColor) {
|
|
||||||
ThemeConfig.currentTheme.ButtonContrast
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.secondaryContainer
|
|
||||||
}
|
|
||||||
|
|
||||||
val moduleConfirmContentMap = viewModel.moduleList.associate { module ->
|
val moduleConfirmContentMap = viewModel.moduleList.associate { module ->
|
||||||
val moduleFileName = module.id
|
val moduleFileName = module.id
|
||||||
@@ -103,77 +101,123 @@ fun KpmScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
dismiss()
|
dismiss()
|
||||||
tempFileForInstall?.delete()
|
tempFileForInstall?.delete()
|
||||||
tempFileForInstall = null
|
tempFileForInstall = null
|
||||||
},
|
},
|
||||||
title = { Text(kpmInstallMode) },
|
title = {
|
||||||
text = { moduleName?.let { Text(stringResource(R.string.kpm_install_mode_description, it)) } },
|
Text(
|
||||||
confirmButton = {
|
text = kpmInstallMode,
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
Column {
|
Column {
|
||||||
Button(
|
moduleName?.let {
|
||||||
onClick = {
|
Text(
|
||||||
scope.launch {
|
text = stringResource(R.string.kpm_install_mode_description, it),
|
||||||
dismiss()
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
tempFileForInstall?.let { tempFile ->
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
handleModuleInstall(
|
)
|
||||||
tempFile = tempFile,
|
|
||||||
isEmbed = false,
|
|
||||||
viewModel = viewModel,
|
|
||||||
snackBarHost = snackBarHost,
|
|
||||||
kpmInstallSuccess = kpmInstallSuccess,
|
|
||||||
kpmInstallFailed = kpmInstallFailed
|
|
||||||
)
|
|
||||||
}
|
|
||||||
tempFileForInstall = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(kpmInstallModeLoad)
|
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Button(
|
Column(
|
||||||
onClick = {
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
scope.launch {
|
|
||||||
dismiss()
|
|
||||||
tempFileForInstall?.let { tempFile ->
|
|
||||||
handleModuleInstall(
|
|
||||||
tempFile = tempFile,
|
|
||||||
isEmbed = true,
|
|
||||||
viewModel = viewModel,
|
|
||||||
snackBarHost = snackBarHost,
|
|
||||||
kpmInstallSuccess = kpmInstallSuccess,
|
|
||||||
kpmInstallFailed = kpmInstallFailed
|
|
||||||
)
|
|
||||||
}
|
|
||||||
tempFileForInstall = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Text(kpmInstallModeEmbed)
|
Button(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
dismiss()
|
||||||
|
tempFileForInstall?.let { tempFile ->
|
||||||
|
handleModuleInstall(
|
||||||
|
tempFile = tempFile,
|
||||||
|
isEmbed = false,
|
||||||
|
viewModel = viewModel,
|
||||||
|
snackBarHost = snackBarHost,
|
||||||
|
kpmInstallSuccess = kpmInstallSuccess,
|
||||||
|
kpmInstallFailed = kpmInstallFailed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
tempFileForInstall = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Download,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp).padding(end = 4.dp)
|
||||||
|
)
|
||||||
|
Text(kpmInstallModeLoad)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
dismiss()
|
||||||
|
tempFileForInstall?.let { tempFile ->
|
||||||
|
handleModuleInstall(
|
||||||
|
tempFile = tempFile,
|
||||||
|
isEmbed = true,
|
||||||
|
viewModel = viewModel,
|
||||||
|
snackBarHost = snackBarHost,
|
||||||
|
kpmInstallSuccess = kpmInstallSuccess,
|
||||||
|
kpmInstallFailed = kpmInstallFailed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
tempFileForInstall = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Inventory,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp).padding(end = 4.dp)
|
||||||
|
)
|
||||||
|
Text(kpmInstallModeEmbed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
confirmButton = {
|
||||||
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(
|
Column(
|
||||||
onClick = {
|
modifier = Modifier.fillMaxWidth(),
|
||||||
dismiss()
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
tempFileForInstall?.delete()
|
|
||||||
tempFileForInstall = null
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Text(cancel)
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
dismiss()
|
||||||
|
tempFileForInstall?.delete()
|
||||||
|
tempFileForInstall = null
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(cancel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
|
shape = MaterialTheme.shapes.extraLarge
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val selectPatchLauncher = rememberLauncherForActivityResult(
|
val selectPatchLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.StartActivityForResult()
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
) { result ->
|
) { result ->
|
||||||
if (result.resultCode != RESULT_OK) return@rememberLauncherForActivityResult
|
if (result.resultCode != Activity.RESULT_OK) return@rememberLauncherForActivityResult
|
||||||
|
|
||||||
val uri = result.data?.data ?: return@rememberLauncherForActivityResult
|
val uri = result.data?.data ?: return@rememberLauncherForActivityResult
|
||||||
|
|
||||||
@@ -236,10 +280,12 @@ fun KpmScreen(
|
|||||||
onClearClick = { viewModel.search = "" },
|
onClearClick = { viewModel.search = "" },
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
dropdownContent = {
|
dropdownContent = {
|
||||||
IconButton(onClick = { viewModel.fetchModuleList() }) {
|
IconButton(
|
||||||
|
onClick = { viewModel.fetchModuleList() }
|
||||||
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Refresh,
|
imageVector = Icons.Filled.Refresh,
|
||||||
contentDescription = stringResource(R.string.refresh)
|
contentDescription = stringResource(R.string.refresh),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,39 +302,70 @@ fun KpmScreen(
|
|||||||
},
|
},
|
||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Add,
|
imageVector = Icons.Filled.Add,
|
||||||
contentDescription = stringResource(R.string.kpm_install)
|
contentDescription = stringResource(R.string.kpm_install),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = { Text(stringResource(R.string.kpm_install)) },
|
text = {
|
||||||
containerColor = cardColor.copy(alpha = 1f),
|
Text(
|
||||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
text = stringResource(R.string.kpm_install),
|
||||||
|
color = MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
)
|
||||||
|
},
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
expanded = true,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(snackBarHost) }
|
snackbarHost = { SnackbarHost(snackBarHost) }
|
||||||
) { padding ->
|
) { padding ->
|
||||||
Column(modifier = Modifier.padding(padding)) {
|
Column(modifier = Modifier.padding(padding)) {
|
||||||
if (!isNoticeClosed) {
|
if (!isNoticeClosed) {
|
||||||
Row(
|
Card(
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||||
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(16.dp)
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
.clip(MaterialTheme.shapes.medium)
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Row(
|
||||||
text = stringResource(R.string.kernel_module_notice),
|
modifier = Modifier
|
||||||
modifier = Modifier.weight(1f),
|
.fillMaxWidth()
|
||||||
textAlign = TextAlign.Center
|
.padding(16.dp),
|
||||||
)
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
IconButton(onClick = {
|
verticalAlignment = Alignment.CenterVertically
|
||||||
isNoticeClosed = true
|
) {
|
||||||
sharedPreferences.edit { putBoolean("is_notice_closed", true) }
|
|
||||||
}) {
|
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Close,
|
imageVector = Icons.Filled.Info,
|
||||||
contentDescription = stringResource(R.string.close_notice)
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 16.dp)
|
||||||
|
.size(24.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.kernel_module_notice),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
)
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
isNoticeClosed = true
|
||||||
|
sharedPreferences.edit { putBoolean("is_notice_closed", true) }
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
colors = IconButtonDefaults.iconButtonColors(
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Close,
|
||||||
|
contentDescription = stringResource(R.string.close_notice)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,15 +375,30 @@ fun KpmScreen(
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Column(
|
||||||
stringResource(R.string.kpm_empty),
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
textAlign = TextAlign.Center
|
verticalArrangement = Arrangement.Center
|
||||||
)
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Code,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(96.dp)
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.kpm_empty),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(16.dp),
|
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
items(viewModel.moduleList) { module ->
|
items(viewModel.moduleList) { module ->
|
||||||
@@ -489,13 +581,34 @@ private fun KpmModuleItem(
|
|||||||
if (viewModel.showInputDialog && viewModel.selectedModuleId == module.id) {
|
if (viewModel.showInputDialog && viewModel.selectedModuleId == module.id) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { viewModel.hideInputDialog() },
|
onDismissRequest = { viewModel.hideInputDialog() },
|
||||||
title = { Text(stringResource(R.string.kpm_control)) },
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.kpm_control),
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
},
|
||||||
text = {
|
text = {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = viewModel.inputArgs,
|
value = viewModel.inputArgs,
|
||||||
onValueChange = { viewModel.updateInputArgs(it) },
|
onValueChange = { viewModel.updateInputArgs(it) },
|
||||||
label = { Text(stringResource(R.string.kpm_args)) },
|
label = {
|
||||||
placeholder = { Text(module.args) }
|
Text(
|
||||||
|
text = stringResource(R.string.kpm_args),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = module.args,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = OutlinedTextFieldDefaults.colors(
|
||||||
|
focusedBorderColor = MaterialTheme.colorScheme.primary,
|
||||||
|
unfocusedBorderColor = MaterialTheme.colorScheme.outline
|
||||||
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
@@ -512,23 +625,39 @@ private fun KpmModuleItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.confirm))
|
Text(
|
||||||
|
text = stringResource(R.string.confirm),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { viewModel.hideInputDialog() }) {
|
TextButton(onClick = { viewModel.hideInputDialog() }) {
|
||||||
Text(stringResource(R.string.cancel))
|
Text(
|
||||||
|
text = stringResource(R.string.cancel),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
|
shape = MaterialTheme.shapes.extraLarge
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ElevatedCard(
|
Card(
|
||||||
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
|
colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHigh),
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
|
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(MaterialTheme.shapes.large)
|
||||||
|
.shadow(
|
||||||
|
elevation = cardElevation,
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier.padding(20.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -538,54 +667,77 @@ private fun KpmModuleItem(
|
|||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
text = module.name,
|
text = module.name,
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "${stringResource(R.string.kpm_version)}: ${module.version}",
|
text = "${stringResource(R.string.kpm_version)}: ${module.version}",
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "${stringResource(R.string.kpm_author)}: ${module.author}",
|
text = "${stringResource(R.string.kpm_author)}: ${module.author}",
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "${stringResource(R.string.kpm_args)}: ${module.args}",
|
text = "${stringResource(R.string.kpm_args)}: ${module.args}",
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = module.description,
|
text = module.description,
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
FilledTonalButton(
|
Button(
|
||||||
onClick = { viewModel.showInputDialog(module.id) },
|
onClick = { viewModel.showInputDialog(module.id) },
|
||||||
enabled = module.hasAction
|
enabled = module.hasAction,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Settings,
|
imageVector = Icons.Filled.Settings,
|
||||||
contentDescription = null
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(stringResource(R.string.kpm_control))
|
Text(stringResource(R.string.kpm_control))
|
||||||
}
|
}
|
||||||
|
|
||||||
FilledTonalButton(
|
Button(
|
||||||
onClick = onUninstall
|
onClick = onUninstall,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Delete,
|
imageVector = Icons.Filled.Delete,
|
||||||
contentDescription = null
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(stringResource(R.string.kpm_uninstall))
|
Text(stringResource(R.string.kpm_uninstall))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,22 +9,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.only
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.selection.toggleable
|
import androidx.compose.foundation.selection.toggleable
|
||||||
@@ -32,41 +17,14 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.automirrored.outlined.*
|
import androidx.compose.material.icons.automirrored.outlined.*
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material.icons.outlined.*
|
import androidx.compose.material.icons.outlined.*
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.ButtonDefaults
|
|
||||||
import androidx.compose.material3.CardDefaults
|
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.DropdownMenu
|
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
|
||||||
import androidx.compose.material3.ElevatedCard
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
|
||||||
import androidx.compose.material3.FilledTonalButton
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.SnackbarDuration
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.material3.SnackbarResult
|
|
||||||
import androidx.compose.material3.Switch
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.produceState
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.*
|
import androidx.compose.ui.platform.*
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -105,15 +63,16 @@ import com.sukisu.ultra.ui.webui.WebUIActivity
|
|||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import com.sukisu.ultra.ui.util.ModuleModify
|
import com.sukisu.ultra.ui.util.ModuleModify
|
||||||
import com.sukisu.ultra.ui.theme.getCardColors
|
import com.sukisu.ultra.ui.theme.getCardColors
|
||||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
|
||||||
import com.sukisu.ultra.ui.viewmodel.ModuleViewModel
|
import com.sukisu.ultra.ui.viewmodel.ModuleViewModel
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.net.toUri
|
|
||||||
import com.sukisu.ultra.ui.theme.ThemeConfig
|
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
|
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
||||||
|
import com.sukisu.ultra.ui.webui.WebUIXActivity
|
||||||
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -239,7 +198,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
val backupLauncher = ModuleModify.rememberModuleBackupLauncher(context, snackBarHost)
|
val backupLauncher = ModuleModify.rememberModuleBackupLauncher(context, snackBarHost)
|
||||||
val restoreLauncher = ModuleModify.rememberModuleRestoreLauncher(context, snackBarHost)
|
val restoreLauncher = ModuleModify.rememberModuleRestoreLauncher(context, snackBarHost)
|
||||||
|
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val prefs = context.getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (viewModel.moduleList.isEmpty() || viewModel.isNeedRefresh) {
|
if (viewModel.moduleList.isEmpty() || viewModel.isNeedRefresh) {
|
||||||
@@ -275,7 +234,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.MoreVert,
|
imageVector = Icons.Filled.MoreVert,
|
||||||
contentDescription = stringResource(id = R.string.settings)
|
contentDescription = stringResource(id = R.string.settings),
|
||||||
)
|
)
|
||||||
|
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
@@ -284,7 +243,16 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
) {
|
) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(R.string.module_sort_action_first)) },
|
text = { Text(stringResource(R.string.module_sort_action_first)) },
|
||||||
trailingIcon = { Checkbox(viewModel.sortActionFirst, null) },
|
trailingIcon = {
|
||||||
|
Checkbox(
|
||||||
|
checked = viewModel.sortActionFirst,
|
||||||
|
onCheckedChange = null,
|
||||||
|
colors = CheckboxDefaults.colors(
|
||||||
|
checkedColor = MaterialTheme.colorScheme.primary,
|
||||||
|
uncheckedColor = MaterialTheme.colorScheme.outline
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.sortActionFirst = !viewModel.sortActionFirst
|
viewModel.sortActionFirst = !viewModel.sortActionFirst
|
||||||
prefs.edit {
|
prefs.edit {
|
||||||
@@ -300,23 +268,33 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(R.string.module_sort_enabled_first)) },
|
text = { Text(stringResource(R.string.module_sort_enabled_first)) },
|
||||||
trailingIcon = { Checkbox(viewModel.sortEnabledFirst, null) },
|
trailingIcon = {
|
||||||
|
Checkbox(
|
||||||
|
checked = viewModel.sortEnabledFirst,
|
||||||
|
onCheckedChange = null,
|
||||||
|
colors = CheckboxDefaults.colors(
|
||||||
|
checkedColor = MaterialTheme.colorScheme.primary,
|
||||||
|
uncheckedColor = MaterialTheme.colorScheme.outline
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.sortEnabledFirst = !viewModel.sortEnabledFirst
|
viewModel.sortEnabledFirst = !viewModel.sortEnabledFirst
|
||||||
prefs.edit {
|
prefs.edit {
|
||||||
putBoolean("module_sort_enabled_first", viewModel.sortEnabledFirst)
|
putBoolean("module_sort_enabled_first", viewModel.sortEnabledFirst)
|
||||||
}
|
}
|
||||||
scope.launch {
|
scope.launch {
|
||||||
viewModel.fetchModuleList()
|
viewModel.fetchModuleList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
HorizontalDivider(thickness = Dp.Hairline, modifier = Modifier.padding(vertical = 4.dp))
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(R.string.backup_modules)) },
|
text = { Text(stringResource(R.string.backup_modules)) },
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Download,
|
imageVector = Icons.Outlined.Download,
|
||||||
contentDescription = stringResource(R.string.backup)
|
contentDescription = stringResource(R.string.backup),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -329,7 +307,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Refresh,
|
imageVector = Icons.Outlined.Refresh,
|
||||||
contentDescription = stringResource(R.string.restore)
|
contentDescription = stringResource(R.string.restore),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -346,11 +324,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
if (!hideInstallButton) {
|
if (!hideInstallButton) {
|
||||||
val moduleInstall = stringResource(id = R.string.module_install)
|
val moduleInstall = stringResource(id = R.string.module_install)
|
||||||
val cardColor = if (!ThemeConfig.useDynamicColor) {
|
|
||||||
ThemeConfig.currentTheme.ButtonContrast
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.secondaryContainer
|
|
||||||
}
|
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
selectZipLauncher.launch(
|
selectZipLauncher.launch(
|
||||||
@@ -363,16 +336,17 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Add,
|
imageVector = Icons.Filled.Add,
|
||||||
contentDescription = moduleInstall
|
contentDescription = moduleInstall,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(
|
Text(
|
||||||
text = moduleInstall
|
text = moduleInstall,
|
||||||
|
color = MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
containerColor = cardColor.copy(alpha = 1f),
|
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
expanded = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -389,10 +363,25 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
.padding(24.dp),
|
.padding(24.dp),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Column(
|
||||||
stringResource(R.string.module_magisk_conflict),
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
textAlign = TextAlign.Center,
|
verticalArrangement = Arrangement.Center
|
||||||
)
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Warning,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(64.dp)
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.module_magisk_conflict),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
@@ -407,10 +396,17 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
onClickModule = { id, name, hasWebUi ->
|
onClickModule = { id, name, hasWebUi ->
|
||||||
if (hasWebUi) {
|
if (hasWebUi) {
|
||||||
webUILauncher.launch(
|
webUILauncher.launch(
|
||||||
Intent(context, WebUIActivity::class.java)
|
if (prefs.getBoolean("use_webuix", false) && Platform.isAlive) {
|
||||||
.setData("kernelsu://webui/$id".toUri())
|
Intent(context, WebUIXActivity::class.java)
|
||||||
.putExtra("id", id)
|
.setData("kernelsu://webuix/$id".toUri())
|
||||||
.putExtra("name", name)
|
.putExtra("id", id)
|
||||||
|
.putExtra("name", name)
|
||||||
|
} else {
|
||||||
|
Intent(context, WebUIActivity::class.java)
|
||||||
|
.setData("kernelsu://webui/$id".toUri())
|
||||||
|
.putExtra("id", id)
|
||||||
|
.putExtra("name", name)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -591,10 +587,25 @@ private fun ModuleList(
|
|||||||
modifier = Modifier.fillParentMaxSize(),
|
modifier = Modifier.fillParentMaxSize(),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Column(
|
||||||
stringResource(R.string.module_empty),
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
textAlign = TextAlign.Center
|
verticalArrangement = Arrangement.Center
|
||||||
)
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Extension,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(96.dp)
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.module_empty),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -677,8 +688,16 @@ fun ModuleItem(
|
|||||||
onClick: (ModuleViewModel.ModuleInfo) -> Unit
|
onClick: (ModuleViewModel.ModuleInfo) -> Unit
|
||||||
) {
|
) {
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
|
colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHigh),
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
|
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(MaterialTheme.shapes.large)
|
||||||
|
.shadow(
|
||||||
|
elevation = cardElevation,
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
val textDecoration = if (!module.remove) null else TextDecoration.LineThrough
|
val textDecoration = if (!module.remove) null else TextDecoration.LineThrough
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
@@ -706,6 +725,7 @@ fun ModuleItem(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
val moduleVersion = stringResource(id = R.string.module_version)
|
val moduleVersion = stringResource(id = R.string.module_version)
|
||||||
val moduleAuthor = stringResource(id = R.string.module_author)
|
val moduleAuthor = stringResource(id = R.string.module_author)
|
||||||
@@ -720,6 +740,7 @@ fun ModuleItem(
|
|||||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
||||||
fontFamily = MaterialTheme.typography.titleMedium.fontFamily,
|
fontFamily = MaterialTheme.typography.titleMedium.fontFamily,
|
||||||
textDecoration = textDecoration,
|
textDecoration = textDecoration,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
@@ -727,7 +748,8 @@ fun ModuleItem(
|
|||||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
||||||
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
|
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
|
||||||
textDecoration = textDecoration
|
textDecoration = textDecoration,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
@@ -735,7 +757,8 @@ fun ModuleItem(
|
|||||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
||||||
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
|
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
|
||||||
textDecoration = textDecoration
|
textDecoration = textDecoration,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -749,7 +772,15 @@ fun ModuleItem(
|
|||||||
enabled = !module.update,
|
enabled = !module.update,
|
||||||
checked = module.enabled,
|
checked = module.enabled,
|
||||||
onCheckedChange = onCheckChanged,
|
onCheckedChange = onCheckChanged,
|
||||||
interactionSource = if (!module.hasWebUi) interactionSource else null
|
interactionSource = if (!module.hasWebUi) interactionSource else null,
|
||||||
|
colors = SwitchDefaults.colors(
|
||||||
|
checkedThumbColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
checkedTrackColor = MaterialTheme.colorScheme.primary,
|
||||||
|
checkedIconColor = MaterialTheme.colorScheme.primary,
|
||||||
|
uncheckedThumbColor = MaterialTheme.colorScheme.outline,
|
||||||
|
uncheckedTrackColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
uncheckedIconColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -764,83 +795,70 @@ fun ModuleItem(
|
|||||||
fontWeight = MaterialTheme.typography.bodySmall.fontWeight,
|
fontWeight = MaterialTheme.typography.bodySmall.fontWeight,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 4,
|
maxLines = 4,
|
||||||
textDecoration = textDecoration
|
textDecoration = textDecoration,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
HorizontalDivider(thickness = Dp.Hairline)
|
HorizontalDivider(thickness = Dp.Hairline)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
|
||||||
if (module.hasActionScript) {
|
if (module.hasActionScript) {
|
||||||
FilledTonalButton(
|
FilledTonalButton(
|
||||||
modifier = Modifier.defaultMinSize(52.dp, 32.dp),
|
modifier = Modifier.defaultMinSize(minWidth = 52.dp, minHeight = 32.dp),
|
||||||
enabled = !module.remove && module.enabled,
|
enabled = !module.remove && module.enabled,
|
||||||
onClick = {
|
onClick = {
|
||||||
navigator.navigate(ExecuteModuleActionScreenDestination(module.dirId))
|
navigator.navigate(ExecuteModuleActionScreenDestination(module.dirId))
|
||||||
viewModel.markNeedRefresh()
|
viewModel.markNeedRefresh()
|
||||||
},
|
},
|
||||||
contentPadding = ButtonDefaults.TextButtonContentPadding,
|
contentPadding = ButtonDefaults.TextButtonContentPadding,
|
||||||
colors = if (!ThemeConfig.useDynamicColor) {
|
colors = ButtonDefaults.filledTonalButtonColors()
|
||||||
ButtonDefaults.filledTonalButtonColors(
|
|
||||||
containerColor = ThemeConfig.currentTheme.ButtonContrast
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ButtonDefaults.filledTonalButtonColors()
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.size(20.dp),
|
||||||
imageVector = Icons.Outlined.PlayArrow,
|
imageVector = Icons.Outlined.PlayArrow,
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
if (!module.hasWebUi && updateUrl.isEmpty()) {
|
//if (!module.hasWebUi && updateUrl.isEmpty()) {
|
||||||
Text(
|
//Text(
|
||||||
modifier = Modifier.padding(start = 7.dp),
|
// modifier = Modifier.padding(start = 7.dp),
|
||||||
text = stringResource(R.string.action),
|
// text = stringResource(R.string.action),
|
||||||
fontFamily = MaterialTheme.typography.labelMedium.fontFamily,
|
// fontFamily = MaterialTheme.typography.labelMedium.fontFamily,
|
||||||
fontSize = MaterialTheme.typography.labelMedium.fontSize
|
// fontSize = MaterialTheme.typography.labelMedium.fontSize
|
||||||
)
|
//)
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(0.1f, true))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (module.hasWebUi) {
|
if (module.hasWebUi) {
|
||||||
FilledTonalButton(
|
FilledTonalButton(
|
||||||
modifier = Modifier.defaultMinSize(52.dp, 32.dp),
|
modifier = Modifier.defaultMinSize(minWidth = 52.dp, minHeight = 32.dp),
|
||||||
enabled = !module.remove && module.enabled,
|
enabled = !module.remove && module.enabled,
|
||||||
onClick = { onClick(module) },
|
onClick = { onClick(module) },
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
contentPadding = ButtonDefaults.TextButtonContentPadding,
|
contentPadding = ButtonDefaults.TextButtonContentPadding,
|
||||||
colors = if (!ThemeConfig.useDynamicColor) {
|
colors = ButtonDefaults.filledTonalButtonColors()
|
||||||
ButtonDefaults.filledTonalButtonColors(
|
|
||||||
containerColor = ThemeConfig.currentTheme.ButtonContrast
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ButtonDefaults.filledTonalButtonColors()
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.size(20.dp),
|
||||||
imageVector = Icons.AutoMirrored.Outlined.Wysiwyg,
|
imageVector = Icons.AutoMirrored.Outlined.Wysiwyg,
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
if (!module.hasActionScript && updateUrl.isEmpty()) {
|
//if (!module.hasActionScript && updateUrl.isEmpty()) {
|
||||||
Text(
|
//Text(
|
||||||
modifier = Modifier.padding(start = 7.dp),
|
// modifier = Modifier.padding(start = 7.dp),
|
||||||
fontFamily = MaterialTheme.typography.labelMedium.fontFamily,
|
// fontFamily = MaterialTheme.typography.labelMedium.fontFamily,
|
||||||
fontSize = MaterialTheme.typography.labelMedium.fontSize,
|
// fontSize = MaterialTheme.typography.labelMedium.fontSize,
|
||||||
text = stringResource(R.string.open)
|
// text = stringResource(R.string.open)
|
||||||
)
|
//)
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -848,7 +866,7 @@ fun ModuleItem(
|
|||||||
|
|
||||||
if (updateUrl.isNotEmpty()) {
|
if (updateUrl.isNotEmpty()) {
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.defaultMinSize(52.dp, 32.dp),
|
modifier = Modifier.defaultMinSize(minWidth = 52.dp, minHeight = 32.dp),
|
||||||
enabled = !module.remove,
|
enabled = !module.remove,
|
||||||
onClick = { onUpdate(module) },
|
onClick = { onUpdate(module) },
|
||||||
shape = ButtonDefaults.textShape,
|
shape = ButtonDefaults.textShape,
|
||||||
@@ -859,30 +877,23 @@ fun ModuleItem(
|
|||||||
imageVector = Icons.Outlined.Download,
|
imageVector = Icons.Outlined.Download,
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
if (!module.hasActionScript || !module.hasWebUi) {
|
//if (!module.hasActionScript || !module.hasWebUi) {
|
||||||
Text(
|
//Text(
|
||||||
modifier = Modifier.padding(start = 7.dp),
|
// modifier = Modifier.padding(start = 7.dp),
|
||||||
fontFamily = MaterialTheme.typography.labelMedium.fontFamily,
|
// fontFamily = MaterialTheme.typography.labelMedium.fontFamily,
|
||||||
fontSize = MaterialTheme.typography.labelMedium.fontSize,
|
// fontSize = MaterialTheme.typography.labelMedium.fontSize,
|
||||||
text = stringResource(R.string.module_update)
|
// text = stringResource(R.string.module_update)
|
||||||
)
|
//)
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(0.1f, true))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FilledTonalButton(
|
FilledTonalButton(
|
||||||
modifier = Modifier.defaultMinSize(52.dp, 32.dp),
|
modifier = Modifier.defaultMinSize(minWidth = 52.dp, minHeight = 32.dp),
|
||||||
onClick = { onUninstallClicked(module) },
|
onClick = { onUninstallClicked(module) },
|
||||||
contentPadding = ButtonDefaults.TextButtonContentPadding,
|
contentPadding = ButtonDefaults.TextButtonContentPadding,
|
||||||
colors = if (!ThemeConfig.useDynamicColor) {
|
colors = ButtonDefaults.filledTonalButtonColors(
|
||||||
ButtonDefaults.filledTonalButtonColors(
|
containerColor = if (!module.remove) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.errorContainer)
|
||||||
containerColor = ThemeConfig.currentTheme.ButtonContrast
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ButtonDefaults.filledTonalButtonColors()
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
if (!module.remove) {
|
if (!module.remove) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -894,18 +905,18 @@ fun ModuleItem(
|
|||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(20.dp).rotate(180f),
|
modifier = Modifier.size(20.dp).rotate(180f),
|
||||||
imageVector = Icons.Outlined.Refresh,
|
imageVector = Icons.Outlined.Refresh,
|
||||||
contentDescription = null,
|
contentDescription = null
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (!module.hasActionScript && !module.hasWebUi && updateUrl.isEmpty()) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(start = 7.dp),
|
|
||||||
fontFamily = MaterialTheme.typography.labelMedium.fontFamily,
|
|
||||||
fontSize = MaterialTheme.typography.labelMedium.fontSize,
|
|
||||||
text = stringResource(if (!module.remove) R.string.uninstall else R.string.restore)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
//if (!module.hasActionScript && !module.hasWebUi && updateUrl.isEmpty()) {
|
||||||
|
//Text(
|
||||||
|
// modifier = Modifier.padding(start = 7.dp),
|
||||||
|
// fontFamily = MaterialTheme.typography.labelMedium.fontFamily,
|
||||||
|
// fontSize = MaterialTheme.typography.labelMedium.fontSize,
|
||||||
|
// text = stringResource(if (!module.remove) R.string.uninstall else R.string.restore),
|
||||||
|
// color = if (!module.remove) MaterialTheme.colorScheme.onErrorContainer else MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
//)
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -932,4 +943,3 @@ fun ModuleItemPreview() {
|
|||||||
)
|
)
|
||||||
ModuleItem(EmptyDestinationsNavigator, module, "", {}, {}, {}, {})
|
ModuleItem(EmptyDestinationsNavigator, module, "", {}, {}, {}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,14 +6,21 @@ import android.net.Uri
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.Undo
|
import androidx.compose.material.icons.automirrored.filled.Undo
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -24,13 +31,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.LineHeightStyle
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
@@ -42,7 +47,6 @@ import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplat
|
|||||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
||||||
import com.ramcosta.composedestinations.generated.destinations.MoreSettingsScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.MoreSettingsScreenDestination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -52,25 +56,22 @@ import com.sukisu.ultra.R
|
|||||||
import com.sukisu.ultra.*
|
import com.sukisu.ultra.*
|
||||||
import com.sukisu.ultra.ui.component.*
|
import com.sukisu.ultra.ui.component.*
|
||||||
import com.sukisu.ultra.ui.theme.*
|
import com.sukisu.ultra.ui.theme.*
|
||||||
|
import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha
|
||||||
|
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
||||||
import com.sukisu.ultra.ui.util.LocalSnackbarHost
|
import com.sukisu.ultra.ui.util.LocalSnackbarHost
|
||||||
import com.sukisu.ultra.ui.util.getBugreportFile
|
import com.sukisu.ultra.ui.util.getBugreportFile
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
import com.sukisu.ultra.ui.component.KsuIsValid
|
||||||
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author weishu
|
|
||||||
* @date 2023/1/1.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Destination<RootGraph>
|
@Destination<RootGraph>
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingScreen(navigator: DestinationsNavigator) {
|
fun SettingScreen(navigator: DestinationsNavigator) {
|
||||||
// region 界面基础设置
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
val snackBarHost = LocalSnackbarHost.current
|
val snackBarHost = LocalSnackbarHost.current
|
||||||
val ksuIsValid = Natives.isKsuValid(ksuApp.packageName)
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -85,7 +86,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
AboutDialog(it)
|
AboutDialog(it)
|
||||||
}
|
}
|
||||||
val loadingDialog = rememberLoadingDialog()
|
val loadingDialog = rememberLoadingDialog()
|
||||||
// endregion
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -93,12 +93,8 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
// region 上下文与协程
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region 日志导出功能
|
|
||||||
val exportBugreportLauncher = rememberLauncherForActivityResult(
|
val exportBugreportLauncher = rememberLauncherForActivityResult(
|
||||||
ActivityResultContracts.CreateDocument("application/gzip")
|
ActivityResultContracts.CreateDocument("application/gzip")
|
||||||
) { uri: Uri? ->
|
) { uri: Uri? ->
|
||||||
@@ -114,237 +110,459 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
snackBarHost.showSnackbar(context.getString(R.string.log_saved))
|
snackBarHost.showSnackbar(context.getString(R.string.log_saved))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// region 配置项列表
|
|
||||||
// 配置文件模板入口
|
|
||||||
val profileTemplate = stringResource(id = R.string.settings_profile_template)
|
|
||||||
if (ksuIsValid) {
|
|
||||||
ListItem(
|
|
||||||
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
|
|
||||||
headlineContent = { Text(profileTemplate) },
|
|
||||||
supportingContent = { Text(stringResource(id = R.string.settings_profile_template_summary)) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
navigator.navigate(AppProfileTemplateScreenDestination)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// 卸载模块开关
|
|
||||||
var umountChecked by rememberSaveable {
|
|
||||||
mutableStateOf(Natives.isDefaultUmountModules())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ksuIsValid) {
|
// 配置
|
||||||
SwitchItem(
|
Card(
|
||||||
icon = Icons.Filled.FolderDelete,
|
modifier = Modifier
|
||||||
title = stringResource(id = R.string.settings_umount_modules_default),
|
.fillMaxWidth()
|
||||||
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
checked = umountChecked
|
colors = CardDefaults.cardColors(
|
||||||
) {
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = cardAlpha)
|
||||||
if (Natives.setDefaultUmountModules(it)) {
|
),
|
||||||
umountChecked = it
|
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation)
|
||||||
}
|
) {
|
||||||
}
|
Column(modifier = Modifier.padding(vertical = 8.dp)) {
|
||||||
}
|
Text(
|
||||||
// SU 禁用开关(仅在兼容版本显示)
|
text = stringResource(R.string.configuration),
|
||||||
if (ksuIsValid) {
|
style = MaterialTheme.typography.titleMedium,
|
||||||
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
|
color = MaterialTheme.colorScheme.primary,
|
||||||
var isSuDisabled by rememberSaveable {
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
mutableStateOf(!Natives.isSuEnabled())
|
)
|
||||||
|
|
||||||
|
// 配置文件模板入口
|
||||||
|
val profileTemplate = stringResource(id = R.string.settings_profile_template)
|
||||||
|
KsuIsValid {
|
||||||
|
SettingItem(
|
||||||
|
icon = Icons.Filled.Fence,
|
||||||
|
title = profileTemplate,
|
||||||
|
summary = stringResource(id = R.string.settings_profile_template_summary),
|
||||||
|
onClick = {
|
||||||
|
navigator.navigate(AppProfileTemplateScreenDestination)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
SwitchItem(
|
|
||||||
icon = Icons.Filled.RemoveModerator,
|
// 卸载模块开关
|
||||||
title = stringResource(id = R.string.settings_disable_su),
|
var umountChecked by rememberSaveable {
|
||||||
summary = stringResource(id = R.string.settings_disable_su_summary),
|
mutableStateOf(Natives.isDefaultUmountModules())
|
||||||
checked = isSuDisabled,
|
}
|
||||||
) { checked ->
|
|
||||||
val shouldEnable = !checked
|
KsuIsValid {
|
||||||
if (Natives.setSuEnabled(shouldEnable)) {
|
SwitchSettingItem(
|
||||||
isSuDisabled = !shouldEnable
|
icon = Icons.Filled.FolderDelete,
|
||||||
|
title = stringResource(id = R.string.settings_umount_modules_default),
|
||||||
|
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
||||||
|
checked = umountChecked,
|
||||||
|
onCheckedChange = {
|
||||||
|
if (Natives.setDefaultUmountModules(it)) {
|
||||||
|
umountChecked = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SU 禁用开关(仅在兼容版本显示)
|
||||||
|
KsuIsValid {
|
||||||
|
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
|
||||||
|
var isSuDisabled by rememberSaveable {
|
||||||
|
mutableStateOf(!Natives.isSuEnabled())
|
||||||
|
}
|
||||||
|
SwitchSettingItem(
|
||||||
|
icon = Icons.Filled.RemoveModerator,
|
||||||
|
title = stringResource(id = R.string.settings_disable_su),
|
||||||
|
summary = stringResource(id = R.string.settings_disable_su_summary),
|
||||||
|
checked = isSuDisabled,
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
val shouldEnable = !checked
|
||||||
|
if (Natives.setSuEnabled(shouldEnable)) {
|
||||||
|
isSuDisabled = !shouldEnable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
// 应用设置
|
||||||
|
Card(
|
||||||
// 更新检查开关
|
modifier = Modifier
|
||||||
var checkUpdate by rememberSaveable {
|
.fillMaxWidth()
|
||||||
mutableStateOf(
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
prefs.getBoolean("check_update", true)
|
colors = CardDefaults.cardColors(
|
||||||
)
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = cardAlpha)
|
||||||
}
|
),
|
||||||
SwitchItem(
|
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation)
|
||||||
icon = Icons.Filled.Update,
|
|
||||||
title = stringResource(id = R.string.settings_check_update),
|
|
||||||
summary = stringResource(id = R.string.settings_check_update_summary),
|
|
||||||
checked = checkUpdate
|
|
||||||
) {
|
) {
|
||||||
prefs.edit {putBoolean("check_update", it) }
|
Column(modifier = Modifier.padding(vertical = 8.dp)) {
|
||||||
checkUpdate = it
|
Text(
|
||||||
}
|
text = stringResource(R.string.app_settings),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
// Web调试开关
|
color = MaterialTheme.colorScheme.primary,
|
||||||
var enableWebDebugging by rememberSaveable {
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
mutableStateOf(
|
|
||||||
prefs.getBoolean("enable_web_debugging", false)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (Natives.isKsuValid(ksuApp.packageName)) {
|
|
||||||
SwitchItem(
|
|
||||||
icon = Icons.Filled.DeveloperMode,
|
|
||||||
title = stringResource(id = R.string.enable_web_debugging),
|
|
||||||
summary = stringResource(id = R.string.enable_web_debugging_summary),
|
|
||||||
checked = enableWebDebugging
|
|
||||||
) {
|
|
||||||
prefs.edit { putBoolean("enable_web_debugging", it) }
|
|
||||||
enableWebDebugging = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 更多设置
|
|
||||||
val newButtonTitle = stringResource(id = R.string.more_settings)
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Settings,
|
|
||||||
contentDescription = newButtonTitle
|
|
||||||
)
|
)
|
||||||
},
|
|
||||||
headlineContent = { Text(newButtonTitle) },
|
|
||||||
supportingContent = { Text(stringResource(id = R.string.more_settings)) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
navigator.navigate(MoreSettingsScreenDestination)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var showBottomsheet by remember { mutableStateOf(false) }
|
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
ListItem(
|
// 更新检查开关
|
||||||
leadingContent = {
|
var checkUpdate by rememberSaveable {
|
||||||
Icon(
|
mutableStateOf(
|
||||||
Icons.Filled.BugReport,
|
prefs.getBoolean("check_update", true)
|
||||||
stringResource(id = R.string.send_log)
|
)
|
||||||
|
}
|
||||||
|
SwitchSettingItem(
|
||||||
|
icon = Icons.Filled.Update,
|
||||||
|
title = stringResource(id = R.string.settings_check_update),
|
||||||
|
summary = stringResource(id = R.string.settings_check_update_summary),
|
||||||
|
checked = checkUpdate,
|
||||||
|
onCheckedChange = {
|
||||||
|
prefs.edit {putBoolean("check_update", it) }
|
||||||
|
checkUpdate = it
|
||||||
|
}
|
||||||
)
|
)
|
||||||
},
|
|
||||||
headlineContent = { Text(stringResource(id = R.string.send_log)) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
showBottomsheet = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (showBottomsheet) {
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = { showBottomsheet = false },
|
|
||||||
content = {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(10.dp)
|
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
|
|
||||||
|
// Web调试开关
|
||||||
|
var enableWebDebugging by rememberSaveable {
|
||||||
|
mutableStateOf(
|
||||||
|
prefs.getBoolean("enable_web_debugging", false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
KsuIsValid {
|
||||||
|
SwitchSettingItem(
|
||||||
|
icon = Icons.Filled.DeveloperMode,
|
||||||
|
title = stringResource(id = R.string.enable_web_debugging),
|
||||||
|
summary = stringResource(id = R.string.enable_web_debugging_summary),
|
||||||
|
checked = enableWebDebugging,
|
||||||
|
onCheckedChange = {
|
||||||
|
prefs.edit { putBoolean("enable_web_debugging", it) }
|
||||||
|
enableWebDebugging = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Web X 开关
|
||||||
|
var useWebUIX by rememberSaveable {
|
||||||
|
mutableStateOf(
|
||||||
|
prefs.getBoolean("use_webuix", false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
KsuIsValid {
|
||||||
|
SwitchItem(
|
||||||
|
beta = true,
|
||||||
|
enabled = Platform.isAlive,
|
||||||
|
icon = Icons.Filled.WebAsset,
|
||||||
|
title = stringResource(id = R.string.use_webuix),
|
||||||
|
summary = stringResource(id = R.string.use_webuix_summary),
|
||||||
|
checked = useWebUIX
|
||||||
) {
|
) {
|
||||||
Box {
|
prefs.edit { putBoolean("use_webuix", it) }
|
||||||
Column(
|
useWebUIX = it
|
||||||
modifier = Modifier
|
}
|
||||||
.padding(16.dp)
|
}
|
||||||
.clickable {
|
|
||||||
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm")
|
// Web X Eruda 开关
|
||||||
val current = LocalDateTime.now().format(formatter)
|
var useWebUIXEruda by rememberSaveable {
|
||||||
exportBugreportLauncher.launch("KernelSU_bugreport_${current}.tar.gz")
|
mutableStateOf(
|
||||||
|
prefs.getBoolean("use_webuix_eruda", false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
KsuIsValid {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = useWebUIX && enableWebDebugging,
|
||||||
|
enter = fadeIn() + expandVertically(),
|
||||||
|
exit = fadeOut() + shrinkVertically()
|
||||||
|
) {
|
||||||
|
SwitchItem(
|
||||||
|
beta = true,
|
||||||
|
enabled = Platform.isAlive && useWebUIX && enableWebDebugging,
|
||||||
|
icon = Icons.Filled.FormatListNumbered,
|
||||||
|
title = stringResource(id = R.string.use_webuix_eruda),
|
||||||
|
summary = stringResource(id = R.string.use_webuix_eruda_summary),
|
||||||
|
checked = useWebUIXEruda
|
||||||
|
) {
|
||||||
|
prefs.edit { putBoolean("use_webuix_eruda", it) }
|
||||||
|
useWebUIXEruda = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更多设置
|
||||||
|
SettingItem(
|
||||||
|
icon = Icons.Filled.Settings,
|
||||||
|
title = stringResource(id = R.string.more_settings),
|
||||||
|
summary = stringResource(id = R.string.more_settings),
|
||||||
|
onClick = {
|
||||||
|
navigator.navigate(MoreSettingsScreenDestination)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工具
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = cardAlpha)
|
||||||
|
),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation)
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(vertical = 8.dp)) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.tools),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
var showBottomsheet by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
SettingItem(
|
||||||
|
icon = Icons.Filled.BugReport,
|
||||||
|
title = stringResource(id = R.string.send_log),
|
||||||
|
onClick = {
|
||||||
|
showBottomsheet = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (showBottomsheet) {
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = { showBottomsheet = false },
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
|
) {
|
||||||
|
LogActionButton(
|
||||||
|
icon = Icons.Filled.Save,
|
||||||
|
text = stringResource(R.string.save_log),
|
||||||
|
onClick = {
|
||||||
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm")
|
||||||
|
val current = LocalDateTime.now().format(formatter)
|
||||||
|
exportBugreportLauncher.launch("KernelSU_bugreport_${current}.tar.gz")
|
||||||
|
showBottomsheet = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
LogActionButton(
|
||||||
|
icon = Icons.Filled.Share,
|
||||||
|
text = stringResource(R.string.send_log),
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
val bugreport = loadingDialog.withLoading {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
getBugreportFile(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val uri: Uri =
|
||||||
|
FileProvider.getUriForFile(
|
||||||
|
context,
|
||||||
|
"${BuildConfig.APPLICATION_ID}.fileprovider",
|
||||||
|
bugreport
|
||||||
|
)
|
||||||
|
|
||||||
|
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
||||||
|
putExtra(Intent.EXTRA_STREAM, uri)
|
||||||
|
setDataAndType(uri, "application/gzip")
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
}
|
||||||
|
|
||||||
|
context.startActivity(
|
||||||
|
Intent.createChooser(
|
||||||
|
shareIntent,
|
||||||
|
context.getString(R.string.send_log)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
showBottomsheet = false
|
showBottomsheet = false
|
||||||
}
|
}
|
||||||
) {
|
}
|
||||||
Icon(
|
)
|
||||||
Icons.Filled.Save,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.save_log),
|
|
||||||
modifier = Modifier.padding(top = 16.dp),
|
|
||||||
textAlign = TextAlign.Center.also {
|
|
||||||
LineHeightStyle(
|
|
||||||
alignment = LineHeightStyle.Alignment.Center,
|
|
||||||
trim = LineHeightStyle.Trim.None
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Box {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
.clickable {
|
|
||||||
scope.launch {
|
|
||||||
val bugreport = loadingDialog.withLoading {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
getBugreportFile(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val uri: Uri =
|
|
||||||
FileProvider.getUriForFile(
|
|
||||||
context,
|
|
||||||
"${BuildConfig.APPLICATION_ID}.fileprovider",
|
|
||||||
bugreport
|
|
||||||
)
|
|
||||||
|
|
||||||
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
|
||||||
putExtra(Intent.EXTRA_STREAM, uri)
|
|
||||||
setDataAndType(uri, "application/gzip")
|
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
}
|
|
||||||
|
|
||||||
context.startActivity(
|
|
||||||
Intent.createChooser(
|
|
||||||
shareIntent,
|
|
||||||
context.getString(R.string.send_log)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Share,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.send_log),
|
|
||||||
modifier = Modifier.padding(top = 16.dp),
|
|
||||||
textAlign = TextAlign.Center.also {
|
|
||||||
LineHeightStyle(
|
|
||||||
alignment = LineHeightStyle.Alignment.Center,
|
|
||||||
trim = LineHeightStyle.Trim.None
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode
|
val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode
|
||||||
if (lkmMode) {
|
if (lkmMode) {
|
||||||
UninstallItem(navigator) {
|
UninstallItem(navigator) {
|
||||||
loadingDialog.withLoading(it)
|
loadingDialog.withLoading(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val about = stringResource(id = R.string.about)
|
// 设置分组卡片 - 关于
|
||||||
ListItem(
|
Card(
|
||||||
leadingContent = {
|
modifier = Modifier
|
||||||
Icon(
|
.fillMaxWidth()
|
||||||
Icons.Filled.ContactPage,
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
about
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = cardAlpha)
|
||||||
|
),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation)
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(vertical = 8.dp)) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.about),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingItem(
|
||||||
|
icon = Icons.Filled.Info,
|
||||||
|
title = stringResource(R.string.about),
|
||||||
|
onClick = {
|
||||||
|
aboutDialog.show()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
},
|
|
||||||
headlineContent = { Text(about) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
aboutDialog.show()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LogActionButton(
|
||||||
|
icon: ImageVector,
|
||||||
|
text: String,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(56.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(MaterialTheme.colorScheme.primaryContainer)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = text,
|
||||||
|
tint = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingItem(
|
||||||
|
icon: ImageVector,
|
||||||
|
title: String,
|
||||||
|
summary: String? = null,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 16.dp)
|
||||||
|
.size(24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
if (summary != null) {
|
||||||
|
Text(
|
||||||
|
text = summary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.ChevronRight,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SwitchSettingItem(
|
||||||
|
icon: ImageVector,
|
||||||
|
title: String,
|
||||||
|
summary: String? = null,
|
||||||
|
checked: Boolean,
|
||||||
|
onCheckedChange: (Boolean) -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onCheckedChange(!checked) }
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 16.dp)
|
||||||
|
.size(24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
if (summary != null) {
|
||||||
|
Text(
|
||||||
|
text = summary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch(
|
||||||
|
checked = checked,
|
||||||
|
onCheckedChange = onCheckedChange,
|
||||||
|
colors = SwitchDefaults.colors(
|
||||||
|
checkedThumbColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
checkedTrackColor = MaterialTheme.colorScheme.primary,
|
||||||
|
checkedIconColor = MaterialTheme.colorScheme.primary,
|
||||||
|
uncheckedThumbColor = MaterialTheme.colorScheme.outline,
|
||||||
|
uncheckedTrackColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
uncheckedIconColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,16 +599,11 @@ fun UninstallItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val uninstall = stringResource(id = R.string.settings_uninstall)
|
|
||||||
ListItem(
|
SettingItem(
|
||||||
leadingContent = {
|
icon = Icons.Filled.Delete,
|
||||||
Icon(
|
title = stringResource(id = R.string.settings_uninstall),
|
||||||
Icons.Filled.Delete,
|
onClick = {
|
||||||
uninstall
|
|
||||||
)
|
|
||||||
},
|
|
||||||
headlineContent = { Text(uninstall) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
uninstallDialog.show()
|
uninstallDialog.show()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -436,7 +649,7 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
|
|||||||
val cardColor = if (!ThemeConfig.useDynamicColor) {
|
val cardColor = if (!ThemeConfig.useDynamicColor) {
|
||||||
ThemeConfig.currentTheme.ButtonContrast
|
ThemeConfig.currentTheme.ButtonContrast
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colorScheme.secondaryContainer
|
MaterialTheme.colorScheme.surfaceContainerHigh
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
@@ -444,29 +657,46 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
|
|||||||
dismiss()
|
dismiss()
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.settings_uninstall))
|
Text(
|
||||||
|
text = stringResource(R.string.settings_uninstall),
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
listOptions.forEachIndexed { index, option ->
|
listOptions.forEachIndexed { index, option ->
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
.clickable {
|
.clickable {
|
||||||
selection = options[index]
|
selection = options[index]
|
||||||
}
|
}
|
||||||
.padding(vertical = 8.dp)
|
.padding(vertical = 12.dp, horizontal = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = options[index].icon,
|
imageVector = options[index].icon,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.padding(end = 8.dp)
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 16.dp)
|
||||||
|
.size(24.dp)
|
||||||
)
|
)
|
||||||
Column {
|
Column {
|
||||||
Text(text = option.titleText)
|
Text(
|
||||||
|
text = option.titleText,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
option.subtitleText?.let {
|
option.subtitleText?.let {
|
||||||
Text(
|
Text(
|
||||||
text = it,
|
text = it,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -476,7 +706,7 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
androidx.compose.material3.TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (selection != UninstallType.NONE) {
|
if (selection != UninstallType.NONE) {
|
||||||
onSelected(selection)
|
onSelected(selection)
|
||||||
@@ -484,21 +714,27 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
|
|||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(android.R.string.ok))
|
Text(
|
||||||
|
text = stringResource(android.R.string.ok),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
androidx.compose.material3.TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(
|
||||||
|
text = stringResource(android.R.string.cancel),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
containerColor = getCardColors(cardColor.copy(alpha = 0.9f)).containerColor.copy(alpha = 0.9f),
|
containerColor = cardColor,
|
||||||
shape = MaterialTheme.shapes.medium,
|
shape = MaterialTheme.shapes.extraLarge,
|
||||||
tonalElevation = getCardElevation()
|
tonalElevation = 4.dp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -508,24 +744,26 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
|
|||||||
private fun TopBar(
|
private fun TopBar(
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||||
) {
|
) {
|
||||||
val cardColor = MaterialTheme.colorScheme.secondaryContainer
|
val systemIsDark = isSystemInDarkTheme()
|
||||||
val cardAlpha = CardConfig.cardAlpha
|
val cardColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
val cardAlpha = if (ThemeConfig.customBackgroundUri != null) {
|
||||||
|
cardAlpha
|
||||||
|
} else {
|
||||||
|
if (systemIsDark) 0.8f else 1f
|
||||||
|
}
|
||||||
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.settings)) },
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.settings),
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = cardColor.copy(alpha = cardAlpha),
|
containerColor = cardColor.copy(alpha = cardAlpha),
|
||||||
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
||||||
),
|
),
|
||||||
|
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||||
scrollBehavior = scrollBehavior
|
scrollBehavior = scrollBehavior
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun SettingsPreview() {
|
|
||||||
SettingScreen(EmptyDestinationsNavigator)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package com.sukisu.ultra.ui.screen
|
import androidx.compose.animation.*
|
||||||
|
import androidx.compose.animation.core.*
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -13,7 +17,10 @@ import androidx.compose.material.icons.filled.*
|
|||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
@@ -21,6 +28,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
@@ -61,7 +69,8 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
|
|
||||||
LaunchedEffect(viewModel.search) {
|
LaunchedEffect(viewModel.search) {
|
||||||
if (viewModel.search.isEmpty()) {
|
if (viewModel.search.isEmpty()) {
|
||||||
listState.scrollToItem(0)
|
// 取消自动滚动到顶部的行为
|
||||||
|
// listState.scrollToItem(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,44 +89,76 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.MoreVert,
|
imageVector = Icons.Filled.MoreVert,
|
||||||
contentDescription = stringResource(id = R.string.settings)
|
contentDescription = stringResource(id = R.string.settings),
|
||||||
)
|
)
|
||||||
|
|
||||||
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
||||||
showDropdown = false
|
showDropdown = false
|
||||||
}) {
|
}) {
|
||||||
DropdownMenuItem(text = {
|
DropdownMenuItem(
|
||||||
Text(stringResource(R.string.refresh))
|
text = { Text(stringResource(R.string.refresh)) },
|
||||||
}, onClick = {
|
leadingIcon = {
|
||||||
scope.launch {
|
Icon(
|
||||||
viewModel.fetchAppList()
|
imageVector = Icons.Filled.Refresh,
|
||||||
}
|
contentDescription = null,
|
||||||
showDropdown = false
|
)
|
||||||
})
|
},
|
||||||
DropdownMenuItem(text = {
|
onClick = {
|
||||||
Text(
|
scope.launch {
|
||||||
if (viewModel.showSystemApps) {
|
viewModel.fetchAppList()
|
||||||
stringResource(R.string.hide_system_apps)
|
|
||||||
} else {
|
|
||||||
stringResource(R.string.show_system_apps)
|
|
||||||
}
|
}
|
||||||
)
|
showDropdown = false
|
||||||
}, onClick = {
|
}
|
||||||
viewModel.showSystemApps = !viewModel.showSystemApps
|
)
|
||||||
showDropdown = false
|
DropdownMenuItem(
|
||||||
})
|
text = {
|
||||||
DropdownMenuItem(text = {
|
Text(
|
||||||
Text(stringResource(R.string.backup_allowlist))
|
if (viewModel.showSystemApps) {
|
||||||
}, onClick = {
|
stringResource(R.string.hide_system_apps)
|
||||||
backupLauncher.launch(ModuleModify.createAllowlistBackupIntent())
|
} else {
|
||||||
showDropdown = false
|
stringResource(R.string.show_system_apps)
|
||||||
})
|
}
|
||||||
DropdownMenuItem(text = {
|
)
|
||||||
Text(stringResource(R.string.restore_allowlist))
|
},
|
||||||
}, onClick = {
|
leadingIcon = {
|
||||||
restoreLauncher.launch(ModuleModify.createAllowlistRestoreIntent())
|
Icon(
|
||||||
showDropdown = false
|
imageVector = if (viewModel.showSystemApps)
|
||||||
})
|
Icons.Filled.VisibilityOff else Icons.Filled.Visibility,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
viewModel.showSystemApps = !viewModel.showSystemApps
|
||||||
|
showDropdown = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
HorizontalDivider(thickness = 0.5.dp, modifier = Modifier.padding(vertical = 4.dp))
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.backup_allowlist)) },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Save,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
backupLauncher.launch(ModuleModify.createAllowlistBackupIntent())
|
||||||
|
showDropdown = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.restore_allowlist)) },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.RestoreFromTrash,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
restoreLauncher.launch(ModuleModify.createAllowlistRestoreIntent())
|
||||||
|
showDropdown = false
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -126,34 +167,310 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(snackBarHostState) },
|
snackbarHost = { SnackbarHost(snackBarHostState) },
|
||||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||||
bottomBar = {
|
floatingActionButton = {
|
||||||
// 批量操作按钮,直接放在底部栏
|
// 侧边悬浮按钮集合
|
||||||
if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) {
|
Column(
|
||||||
Row(
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
modifier = Modifier
|
horizontalAlignment = Alignment.End
|
||||||
.fillMaxWidth()
|
) {
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
// 批量操作相关按钮
|
||||||
.padding(16.dp),
|
// 只有在批量模式且有选中应用时才显示批量操作按钮
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) {
|
||||||
) {
|
// 取消按钮
|
||||||
Button(
|
val cancelInteractionSource = remember { MutableInteractionSource() }
|
||||||
|
val isCancelPressed by cancelInteractionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
|
FloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
viewModel.selectedApps = emptySet()
|
||||||
viewModel.updateBatchPermissions(true)
|
viewModel.showBatchActions = false
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(if (isCancelPressed) 56.dp else 40.dp),
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
shape = CircleShape,
|
||||||
|
interactionSource = cancelInteractionSource,
|
||||||
|
elevation = FloatingActionButtonDefaults.elevation(4.dp, 6.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Close,
|
||||||
|
contentDescription = stringResource(android.R.string.cancel),
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isCancelPressed,
|
||||||
|
enter = expandHorizontally() + fadeIn(),
|
||||||
|
exit = shrinkHorizontally() + fadeOut()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(android.R.string.cancel),
|
||||||
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
|
style = MaterialTheme.typography.labelMedium
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.batch_authorization))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(
|
// 取消授权按钮
|
||||||
|
val unauthorizeInteractionSource = remember { MutableInteractionSource() }
|
||||||
|
val isUnauthorizePressed by unauthorizeInteractionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
|
FloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
viewModel.updateBatchPermissions(false)
|
viewModel.updateBatchPermissions(false)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
modifier = Modifier.size(if (isUnauthorizePressed) 56.dp else 40.dp),
|
||||||
|
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onErrorContainer,
|
||||||
|
shape = CircleShape,
|
||||||
|
interactionSource = unauthorizeInteractionSource,
|
||||||
|
elevation = FloatingActionButtonDefaults.elevation(4.dp, 6.dp)
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.batch_cancel_authorization))
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Block,
|
||||||
|
contentDescription = stringResource(R.string.batch_cancel_authorization),
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isUnauthorizePressed,
|
||||||
|
enter = expandHorizontally() + fadeIn(),
|
||||||
|
exit = shrinkHorizontally() + fadeOut()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.batch_cancel_authorization),
|
||||||
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
|
style = MaterialTheme.typography.labelMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 授权按钮
|
||||||
|
val authorizeInteractionSource = remember { MutableInteractionSource() }
|
||||||
|
val isAuthorizePressed by authorizeInteractionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
viewModel.updateBatchPermissions(true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(if (isAuthorizePressed) 56.dp else 40.dp),
|
||||||
|
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
shape = CircleShape,
|
||||||
|
interactionSource = authorizeInteractionSource,
|
||||||
|
elevation = FloatingActionButtonDefaults.elevation(4.dp, 6.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Check,
|
||||||
|
contentDescription = stringResource(R.string.batch_authorization),
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isAuthorizePressed,
|
||||||
|
enter = expandHorizontally() + fadeIn(),
|
||||||
|
exit = shrinkHorizontally() + fadeOut()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.batch_authorization),
|
||||||
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
|
style = MaterialTheme.typography.labelMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加分隔
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) {
|
||||||
|
|
||||||
|
// 在批量操作按钮组中添加卸载模块的按钮
|
||||||
|
// 卸载模块启用按钮
|
||||||
|
val umountEnableInteractionSource = remember { MutableInteractionSource() }
|
||||||
|
val isUmountEnablePressed by umountEnableInteractionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
viewModel.updateBatchPermissions(
|
||||||
|
allowSu = false, // 不改变ROOT权限状态
|
||||||
|
umountModules = true // 启用卸载模块
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(if (isUmountEnablePressed) 56.dp else 40.dp),
|
||||||
|
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||||
|
shape = CircleShape,
|
||||||
|
interactionSource = umountEnableInteractionSource,
|
||||||
|
elevation = FloatingActionButtonDefaults.elevation(4.dp, 6.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.FolderOff,
|
||||||
|
contentDescription = stringResource(R.string.profile_umount_modules),
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isUmountEnablePressed,
|
||||||
|
enter = expandHorizontally() + fadeIn(),
|
||||||
|
exit = shrinkHorizontally() + fadeOut()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.profile_umount_modules),
|
||||||
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
|
style = MaterialTheme.typography.labelMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 卸载模块禁用按钮
|
||||||
|
val umountDisableInteractionSource = remember { MutableInteractionSource() }
|
||||||
|
val isUmountDisablePressed by umountDisableInteractionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
viewModel.updateBatchPermissions(
|
||||||
|
allowSu = false, // 不改变ROOT权限状态
|
||||||
|
umountModules = false // 禁用卸载模块
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(if (isUmountDisablePressed) 56.dp else 40.dp),
|
||||||
|
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||||
|
shape = CircleShape,
|
||||||
|
interactionSource = umountDisableInteractionSource,
|
||||||
|
elevation = FloatingActionButtonDefaults.elevation(4.dp, 6.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Folder,
|
||||||
|
contentDescription = stringResource(R.string.profile_umount_modules_disable),
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isUmountDisablePressed,
|
||||||
|
enter = expandHorizontally() + fadeIn(),
|
||||||
|
exit = shrinkHorizontally() + fadeOut()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.profile_umount_modules_disable),
|
||||||
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
|
style = MaterialTheme.typography.labelMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 添加分隔
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 向上导航按钮
|
||||||
|
val topBtnInteractionSource = remember { MutableInteractionSource() }
|
||||||
|
val isTopBtnPressed by topBtnInteractionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
listState.animateScrollToItem(0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(if (isTopBtnPressed) 56.dp else 40.dp),
|
||||||
|
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 1f),
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
shape = CircleShape,
|
||||||
|
interactionSource = topBtnInteractionSource,
|
||||||
|
elevation = FloatingActionButtonDefaults.elevation(4.dp, 6.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.KeyboardArrowUp,
|
||||||
|
contentDescription = stringResource(R.string.scroll_to_top_description),
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isTopBtnPressed,
|
||||||
|
enter = expandHorizontally() + fadeIn(),
|
||||||
|
exit = shrinkHorizontally() + fadeOut()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.scroll_to_top),
|
||||||
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
|
style = MaterialTheme.typography.labelMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 向下导航按钮
|
||||||
|
val bottomBtnInteractionSource = remember { MutableInteractionSource() }
|
||||||
|
val isBottomBtnPressed by bottomBtnInteractionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
val lastIndex = viewModel.appList.size - 1
|
||||||
|
if (lastIndex >= 0) {
|
||||||
|
listState.animateScrollToItem(lastIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(if (isBottomBtnPressed) 56.dp else 40.dp),
|
||||||
|
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 1f),
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
shape = CircleShape,
|
||||||
|
interactionSource = bottomBtnInteractionSource,
|
||||||
|
elevation = FloatingActionButtonDefaults.elevation(4.dp, 6.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.KeyboardArrowDown,
|
||||||
|
contentDescription = stringResource(R.string.scroll_to_bottom_description),
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isBottomBtnPressed,
|
||||||
|
enter = expandHorizontally() + fadeIn(),
|
||||||
|
exit = shrinkHorizontally() + fadeOut()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.scroll_to_bottom),
|
||||||
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
|
style = MaterialTheme.typography.labelMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,15 +487,23 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
state = listState,
|
state = listState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
contentPadding = PaddingValues(
|
||||||
|
top = 8.dp,
|
||||||
|
bottom = 16.dp
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
// 获取分组后的应用列表 - 修改分组逻辑,避免应用重复出现在多个分组中
|
// 获取分组后的应用列表
|
||||||
val rootApps = viewModel.appList.filter { it.allowSu }
|
val rootApps = viewModel.appList.filter { it.allowSu }
|
||||||
val customApps = viewModel.appList.filter { !it.allowSu && it.hasCustomProfile }
|
val customApps = viewModel.appList.filter { !it.allowSu && it.hasCustomProfile }
|
||||||
val otherApps = viewModel.appList.filter { !it.allowSu && !it.hasCustomProfile }
|
val otherApps = viewModel.appList.filter { !it.allowSu && !it.hasCustomProfile }
|
||||||
|
|
||||||
// 显示ROOT权限应用组
|
// 显示ROOT权限应用组
|
||||||
if (rootApps.isNotEmpty()) {
|
if (rootApps.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
GroupHeader(title = stringResource(R.string.apps_with_root))
|
||||||
|
}
|
||||||
|
|
||||||
items(rootApps, key = { "root_" + it.packageName + it.uid }) { app ->
|
items(rootApps, key = { "root_" + it.packageName + it.uid }) { app ->
|
||||||
AppItem(
|
AppItem(
|
||||||
app = app,
|
app = app,
|
||||||
@@ -189,7 +514,10 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
val profile = Natives.getAppProfile(app.packageName, app.uid)
|
val profile = Natives.getAppProfile(app.packageName, app.uid)
|
||||||
val updatedProfile = profile.copy(allowSu = allowSu)
|
val updatedProfile = profile.copy(allowSu = allowSu)
|
||||||
if (Natives.setAppProfile(updatedProfile)) {
|
if (Natives.setAppProfile(updatedProfile)) {
|
||||||
viewModel.fetchAppList()
|
// 不重新获取应用列表,避免滚动位置重置
|
||||||
|
// viewModel.fetchAppList()
|
||||||
|
// 仅更新当前应用的配置
|
||||||
|
viewModel.updateAppProfileLocally(app.packageName, updatedProfile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -214,6 +542,10 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
|
|
||||||
// 显示自定义配置应用组
|
// 显示自定义配置应用组
|
||||||
if (customApps.isNotEmpty()) {
|
if (customApps.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
GroupHeader(title = stringResource(R.string.apps_with_custom_profile))
|
||||||
|
}
|
||||||
|
|
||||||
items(customApps, key = { "custom_" + it.packageName + it.uid }) { app ->
|
items(customApps, key = { "custom_" + it.packageName + it.uid }) { app ->
|
||||||
AppItem(
|
AppItem(
|
||||||
app = app,
|
app = app,
|
||||||
@@ -224,7 +556,10 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
val profile = Natives.getAppProfile(app.packageName, app.uid)
|
val profile = Natives.getAppProfile(app.packageName, app.uid)
|
||||||
val updatedProfile = profile.copy(allowSu = allowSu)
|
val updatedProfile = profile.copy(allowSu = allowSu)
|
||||||
if (Natives.setAppProfile(updatedProfile)) {
|
if (Natives.setAppProfile(updatedProfile)) {
|
||||||
viewModel.fetchAppList()
|
// 不重新获取应用列表,避免滚动位置重置
|
||||||
|
// viewModel.fetchAppList()
|
||||||
|
// 仅更新当前应用的配置
|
||||||
|
viewModel.updateAppProfileLocally(app.packageName, updatedProfile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -249,6 +584,10 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
|
|
||||||
// 显示其他应用组
|
// 显示其他应用组
|
||||||
if (otherApps.isNotEmpty()) {
|
if (otherApps.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
GroupHeader(title = stringResource(R.string.other_apps))
|
||||||
|
}
|
||||||
|
|
||||||
items(otherApps, key = { "other_" + it.packageName + it.uid }) { app ->
|
items(otherApps, key = { "other_" + it.packageName + it.uid }) { app ->
|
||||||
AppItem(
|
AppItem(
|
||||||
app = app,
|
app = app,
|
||||||
@@ -259,7 +598,10 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
val profile = Natives.getAppProfile(app.packageName, app.uid)
|
val profile = Natives.getAppProfile(app.packageName, app.uid)
|
||||||
val updatedProfile = profile.copy(allowSu = allowSu)
|
val updatedProfile = profile.copy(allowSu = allowSu)
|
||||||
if (Natives.setAppProfile(updatedProfile)) {
|
if (Natives.setAppProfile(updatedProfile)) {
|
||||||
viewModel.fetchAppList()
|
// 不重新获取应用列表,避免滚动位置重置
|
||||||
|
// viewModel.fetchAppList()
|
||||||
|
// 仅更新当前应用的配置
|
||||||
|
viewModel.updateAppProfileLocally(app.packageName, updatedProfile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -281,6 +623,38 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 当没有应用显示时显示空状态
|
||||||
|
if (viewModel.appList.isEmpty()) {
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(400.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Apps,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(96.dp)
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.no_apps_found),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,7 +665,7 @@ fun GroupHeader(title: String) {
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
.background(MaterialTheme.colorScheme.surfaceContainerHighest.copy(alpha = 0.7f))
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -299,7 +673,7 @@ fun GroupHeader(title: String) {
|
|||||||
style = TextStyle(
|
style = TextStyle(
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -316,33 +690,48 @@ private fun AppItem(
|
|||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
viewModel: SuperUserViewModel
|
viewModel: SuperUserViewModel
|
||||||
) {
|
) {
|
||||||
ListItem(
|
val cardColor = if (app.allowSu)
|
||||||
|
MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f)
|
||||||
|
else if (app.hasCustomProfile)
|
||||||
|
MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.3f)
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.surfaceContainerLow
|
||||||
|
|
||||||
|
Card(
|
||||||
|
colors = CardDefaults.cardColors(containerColor = cardColor),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.shadow(
|
||||||
|
elevation = 0.dp,
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
if (isSelected)
|
||||||
|
Modifier.border(
|
||||||
|
width = 2.dp,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Modifier
|
||||||
|
)
|
||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectTapGestures(
|
detectTapGestures(
|
||||||
onLongPress = { onLongClick() },
|
onLongPress = { onLongClick() },
|
||||||
onTap = { onClick() }
|
onTap = { onClick() }
|
||||||
)
|
)
|
||||||
},
|
|
||||||
headlineContent = { Text(app.label) },
|
|
||||||
supportingContent = {
|
|
||||||
Column {
|
|
||||||
Text(app.packageName)
|
|
||||||
FlowRow {
|
|
||||||
if (app.allowSu) {
|
|
||||||
LabelText(label = "ROOT")
|
|
||||||
} else {
|
|
||||||
if (Natives.uidShouldUmount(app.uid)) {
|
|
||||||
LabelText(label = "UMOUNT")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (app.hasCustomProfile) {
|
|
||||||
LabelText(label = "CUSTOM")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
) {
|
||||||
leadingContent = {
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
.data(app.packageInfo)
|
.data(app.packageInfo)
|
||||||
@@ -350,43 +739,139 @@ private fun AppItem(
|
|||||||
.build(),
|
.build(),
|
||||||
contentDescription = app.label,
|
contentDescription = app.label,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(4.dp)
|
.padding(end = 16.dp)
|
||||||
.width(48.dp)
|
.size(48.dp)
|
||||||
.height(48.dp)
|
.clip(MaterialTheme.shapes.small)
|
||||||
)
|
)
|
||||||
},
|
|
||||||
trailingContent = {
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = app.label,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = app.packageName,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier.padding(top = 4.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
if (app.allowSu) {
|
||||||
|
LabelText(label = "ROOT", backgroundColor = MaterialTheme.colorScheme.primary)
|
||||||
|
}
|
||||||
|
if (Natives.uidShouldUmount(app.uid)) {
|
||||||
|
LabelText(label = "UMOUNT", backgroundColor = MaterialTheme.colorScheme.tertiary)
|
||||||
|
}
|
||||||
|
if (app.hasCustomProfile) {
|
||||||
|
LabelText(label = "CUSTOM", backgroundColor = MaterialTheme.colorScheme.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!viewModel.showBatchActions) {
|
if (!viewModel.showBatchActions) {
|
||||||
Switch(
|
// 开关交互源
|
||||||
checked = app.allowSu,
|
val switchInteractionSource = remember { MutableInteractionSource() }
|
||||||
onCheckedChange = onSwitchChange
|
val isSwitchPressed by switchInteractionSource.collectIsPressedAsState()
|
||||||
)
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isSwitchPressed,
|
||||||
|
enter = expandHorizontally() + fadeIn(),
|
||||||
|
exit = shrinkHorizontally() + fadeOut()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (app.allowSu) stringResource(R.string.authorized) else stringResource(R.string.unauthorized),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = if (app.allowSu) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline,
|
||||||
|
modifier = Modifier.padding(end = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch(
|
||||||
|
checked = app.allowSu,
|
||||||
|
onCheckedChange = onSwitchChange,
|
||||||
|
interactionSource = switchInteractionSource,
|
||||||
|
colors = SwitchDefaults.colors(
|
||||||
|
checkedThumbColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
checkedTrackColor = MaterialTheme.colorScheme.primary,
|
||||||
|
checkedIconColor = MaterialTheme.colorScheme.primary,
|
||||||
|
uncheckedThumbColor = MaterialTheme.colorScheme.outline,
|
||||||
|
uncheckedTrackColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
uncheckedIconColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Checkbox(
|
// 复选框交互源
|
||||||
checked = isSelected,
|
val checkboxInteractionSource = remember { MutableInteractionSource() }
|
||||||
onCheckedChange = { onToggleSelection() }
|
val isCheckboxPressed by checkboxInteractionSource.collectIsPressedAsState()
|
||||||
)
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isCheckboxPressed,
|
||||||
|
enter = expandHorizontally() + fadeIn(),
|
||||||
|
exit = shrinkHorizontally() + fadeOut()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (isSelected) stringResource(R.string.selected) else stringResource(R.string.select),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline,
|
||||||
|
modifier = Modifier.padding(end = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Checkbox(
|
||||||
|
checked = isSelected,
|
||||||
|
onCheckedChange = { onToggleSelection() },
|
||||||
|
interactionSource = checkboxInteractionSource,
|
||||||
|
colors = CheckboxDefaults.colors(
|
||||||
|
checkedColor = MaterialTheme.colorScheme.primary,
|
||||||
|
uncheckedColor = MaterialTheme.colorScheme.outline
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LabelText(label: String) {
|
fun LabelText(label: String, backgroundColor: Color) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(top = 4.dp, end = 4.dp)
|
.padding(top = 2.dp, end = 2.dp)
|
||||||
.background(
|
.background(
|
||||||
Color.Black,
|
backgroundColor,
|
||||||
shape = RoundedCornerShape(4.dp)
|
shape = RoundedCornerShape(4.dp)
|
||||||
)
|
)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
modifier = Modifier.padding(vertical = 2.dp, horizontal = 5.dp),
|
modifier = Modifier.padding(vertical = 2.dp, horizontal = 6.dp),
|
||||||
style = TextStyle(
|
style = TextStyle(
|
||||||
fontSize = 8.sp,
|
fontSize = 10.sp,
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.sukisu.ultra.ui.screen
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
|
import LabelText
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@@ -33,6 +34,7 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarColors
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
@@ -61,7 +63,7 @@ import com.ramcosta.composedestinations.result.getOr
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
import com.sukisu.ultra.ui.theme.ThemeConfig
|
import com.sukisu.ultra.ui.theme.CardConfig
|
||||||
import com.sukisu.ultra.ui.viewmodel.TemplateViewModel
|
import com.sukisu.ultra.ui.viewmodel.TemplateViewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,11 +81,6 @@ fun AppProfileTemplateScreen(
|
|||||||
val viewModel = viewModel<TemplateViewModel>()
|
val viewModel = viewModel<TemplateViewModel>()
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
val cardColor = if (!ThemeConfig.useDynamicColor) {
|
|
||||||
ThemeConfig.currentTheme.ButtonContrast
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.secondaryContainer
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (viewModel.templateList.isEmpty()) {
|
if (viewModel.templateList.isEmpty()) {
|
||||||
@@ -98,6 +95,9 @@ fun AppProfileTemplateScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val cardColorUse = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
val cardAlpha = CardConfig.cardAlpha
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -109,6 +109,10 @@ fun AppProfileTemplateScreen(
|
|||||||
}
|
}
|
||||||
TopBar(
|
TopBar(
|
||||||
onBack = dropUnlessResumed { navigator.popBackStack() },
|
onBack = dropUnlessResumed { navigator.popBackStack() },
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = cardColorUse.copy(alpha = cardAlpha),
|
||||||
|
scrolledContainerColor = cardColorUse.copy(alpha = cardAlpha)
|
||||||
|
),
|
||||||
onSync = {
|
onSync = {
|
||||||
scope.launch { viewModel.fetchTemplates(true) }
|
scope.launch { viewModel.fetchTemplates(true) }
|
||||||
},
|
},
|
||||||
@@ -155,7 +159,6 @@ fun AppProfileTemplateScreen(
|
|||||||
},
|
},
|
||||||
icon = { Icon(Icons.Filled.Add, null) },
|
icon = { Icon(Icons.Filled.Add, null) },
|
||||||
text = { Text(stringResource(id = R.string.app_profile_template_create)) },
|
text = { Text(stringResource(id = R.string.app_profile_template_create)) },
|
||||||
containerColor = cardColor.copy(alpha = 1f),
|
|
||||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -205,17 +208,17 @@ private fun TemplateItem(
|
|||||||
)
|
)
|
||||||
Text(template.description)
|
Text(template.description)
|
||||||
FlowRow {
|
FlowRow {
|
||||||
LabelText(label = "UID: ${template.uid}")
|
LabelText(label = "UID: ${template.uid}", backgroundColor = MaterialTheme.colorScheme.surface)
|
||||||
LabelText(label = "GID: ${template.gid}")
|
LabelText(label = "GID: ${template.gid}", backgroundColor = MaterialTheme.colorScheme.surface)
|
||||||
LabelText(label = template.context)
|
LabelText(label = template.context, backgroundColor = MaterialTheme.colorScheme.surface)
|
||||||
if (template.local) {
|
if (template.local) {
|
||||||
LabelText(label = "local")
|
LabelText(label = "local", backgroundColor = MaterialTheme.colorScheme.surface)
|
||||||
} else {
|
} else {
|
||||||
LabelText(label = "remote")
|
LabelText(label = "remote", backgroundColor = MaterialTheme.colorScheme.surface)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,12 +229,20 @@ private fun TopBar(
|
|||||||
onSync: () -> Unit = {},
|
onSync: () -> Unit = {},
|
||||||
onImport: () -> Unit = {},
|
onImport: () -> Unit = {},
|
||||||
onExport: () -> Unit = {},
|
onExport: () -> Unit = {},
|
||||||
|
colors: TopAppBarColors,
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||||
) {
|
) {
|
||||||
|
val cardColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
val cardAlpha = CardConfig.cardAlpha
|
||||||
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(stringResource(R.string.settings_profile_template))
|
Text(stringResource(R.string.settings_profile_template))
|
||||||
},
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = cardColor.copy(alpha = cardAlpha),
|
||||||
|
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
||||||
|
),
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onBack
|
onClick = onBack
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.compose.foundation.isSystemInDarkTheme
|
|||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -13,76 +14,120 @@ import androidx.compose.ui.unit.Dp
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
object CardConfig {
|
object CardConfig {
|
||||||
val defaultElevation: Dp = 0.dp
|
val settingElevation: Dp = 4.dp
|
||||||
|
val customBackgroundElevation: Dp = 0.dp
|
||||||
|
|
||||||
var cardAlpha by mutableStateOf(0.45f)
|
// 卡片透明度
|
||||||
var cardElevation by mutableStateOf(defaultElevation)
|
var cardAlpha by mutableFloatStateOf(1f)
|
||||||
|
// 卡片亮度
|
||||||
|
var cardDim by mutableFloatStateOf(0f)
|
||||||
|
// 卡片阴影
|
||||||
|
var cardElevation by mutableStateOf(settingElevation)
|
||||||
var isShadowEnabled by mutableStateOf(true)
|
var isShadowEnabled by mutableStateOf(true)
|
||||||
var isCustomAlphaSet by mutableStateOf(false)
|
var isCustomAlphaSet by mutableStateOf(false)
|
||||||
|
var isCustomDimSet by mutableStateOf(false)
|
||||||
var isUserDarkModeEnabled by mutableStateOf(false)
|
var isUserDarkModeEnabled by mutableStateOf(false)
|
||||||
var isUserLightModeEnabled by mutableStateOf(false)
|
var isUserLightModeEnabled by mutableStateOf(false)
|
||||||
var isCustomBackgroundEnabled by mutableStateOf(false)
|
var isCustomBackgroundEnabled by mutableStateOf(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存卡片配置到SharedPreferences
|
||||||
|
*/
|
||||||
fun save(context: Context) {
|
fun save(context: Context) {
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
prefs.edit().apply {
|
prefs.edit().apply {
|
||||||
putFloat("card_alpha", cardAlpha)
|
putFloat("card_alpha", cardAlpha)
|
||||||
putBoolean("custom_background_enabled", cardElevation == 0.dp)
|
putFloat("card_dim", cardDim)
|
||||||
|
putBoolean("custom_background_enabled", isCustomBackgroundEnabled)
|
||||||
|
putBoolean("is_shadow_enabled", isShadowEnabled)
|
||||||
putBoolean("is_custom_alpha_set", isCustomAlphaSet)
|
putBoolean("is_custom_alpha_set", isCustomAlphaSet)
|
||||||
|
putBoolean("is_custom_dim_set", isCustomDimSet)
|
||||||
putBoolean("is_user_dark_mode_enabled", isUserDarkModeEnabled)
|
putBoolean("is_user_dark_mode_enabled", isUserDarkModeEnabled)
|
||||||
putBoolean("is_user_light_mode_enabled", isUserLightModeEnabled)
|
putBoolean("is_user_light_mode_enabled", isUserLightModeEnabled)
|
||||||
putBoolean("is_custom_background_enabled", isCustomBackgroundEnabled)
|
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从SharedPreferences加载卡片配置
|
||||||
|
*/
|
||||||
fun load(context: Context) {
|
fun load(context: Context) {
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
cardAlpha = prefs.getFloat("card_alpha", 0.45f)
|
cardAlpha = prefs.getFloat("card_alpha", 1f)
|
||||||
cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else defaultElevation
|
cardDim = prefs.getFloat("card_dim", 0f)
|
||||||
|
isCustomBackgroundEnabled = prefs.getBoolean("custom_background_enabled", false)
|
||||||
|
isShadowEnabled = prefs.getBoolean("is_shadow_enabled", true)
|
||||||
isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false)
|
isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false)
|
||||||
|
isCustomDimSet = prefs.getBoolean("is_custom_dim_set", false)
|
||||||
isUserDarkModeEnabled = prefs.getBoolean("is_user_dark_mode_enabled", false)
|
isUserDarkModeEnabled = prefs.getBoolean("is_user_dark_mode_enabled", false)
|
||||||
isUserLightModeEnabled = prefs.getBoolean("is_user_light_mode_enabled", false)
|
isUserLightModeEnabled = prefs.getBoolean("is_user_light_mode_enabled", false)
|
||||||
isCustomBackgroundEnabled = prefs.getBoolean("is_custom_background_enabled", false)
|
updateShadowEnabled(isShadowEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新阴影启用状态
|
||||||
|
*/
|
||||||
fun updateShadowEnabled(enabled: Boolean) {
|
fun updateShadowEnabled(enabled: Boolean) {
|
||||||
isShadowEnabled = enabled
|
isShadowEnabled = enabled
|
||||||
cardElevation = if (enabled) defaultElevation else 0.dp
|
cardElevation = if (isCustomBackgroundEnabled && cardAlpha != 1f) {
|
||||||
|
customBackgroundElevation
|
||||||
|
} else if (enabled) {
|
||||||
|
settingElevation
|
||||||
|
} else {
|
||||||
|
customBackgroundElevation
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置深色模式默认值
|
||||||
|
*/
|
||||||
fun setDarkModeDefaults() {
|
fun setDarkModeDefaults() {
|
||||||
if (!isCustomAlphaSet) {
|
if (!isCustomAlphaSet) {
|
||||||
cardAlpha = 0.35f
|
cardAlpha = 0.70f
|
||||||
cardElevation = 0.dp
|
|
||||||
}
|
}
|
||||||
|
if (!isCustomDimSet) {
|
||||||
|
cardDim = 0.5f
|
||||||
|
}
|
||||||
|
updateShadowEnabled(isShadowEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置浅色模式默认值
|
||||||
|
*/
|
||||||
|
fun setLightModeDefaults() {
|
||||||
|
if (!isCustomAlphaSet) {
|
||||||
|
cardAlpha = 1f
|
||||||
|
}
|
||||||
|
if (!isCustomDimSet) {
|
||||||
|
cardDim = 0f
|
||||||
|
}
|
||||||
|
updateShadowEnabled(isShadowEnabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取卡片颜色配置
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun getCardColors(originalColor: Color) = CardDefaults.elevatedCardColors(
|
fun getCardColors(originalColor: Color) = CardDefaults.cardColors(
|
||||||
containerColor = originalColor.copy(alpha = CardConfig.cardAlpha),
|
containerColor = originalColor.copy(alpha = CardConfig.cardAlpha),
|
||||||
contentColor = when {
|
contentColor = determineContentColor(originalColor)
|
||||||
CardConfig.isUserLightModeEnabled -> {
|
|
||||||
Color.Black
|
|
||||||
}
|
|
||||||
CardConfig.isUserDarkModeEnabled -> {
|
|
||||||
Color.White
|
|
||||||
}
|
|
||||||
!isSystemInDarkTheme() && !CardConfig.isUserDarkModeEnabled -> {
|
|
||||||
Color.Black
|
|
||||||
}
|
|
||||||
!isSystemInDarkTheme() && !CardConfig.isCustomBackgroundEnabled && !CardConfig.isUserDarkModeEnabled && originalColor.luminance() > 0.3 -> {
|
|
||||||
Color.Black
|
|
||||||
}
|
|
||||||
isSystemInDarkTheme() && !CardConfig.isUserDarkModeEnabled && !CardConfig.isUserLightModeEnabled-> {
|
|
||||||
Color.White
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
Color.White
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getCardElevation() = CardConfig.cardElevation
|
/**
|
||||||
|
* 根据背景颜色、主题模式和用户设置确定内容颜色
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun determineContentColor(originalColor: Color): Color {
|
||||||
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
|
if (ThemeConfig.isThemeChanging) {
|
||||||
|
return if (isDarkTheme) Color.White else Color.Black
|
||||||
|
}
|
||||||
|
|
||||||
|
return when {
|
||||||
|
CardConfig.isUserLightModeEnabled -> Color.Black
|
||||||
|
!isDarkTheme && originalColor.luminance() > 0.5f -> Color.Black
|
||||||
|
isDarkTheme -> Color.White
|
||||||
|
else -> if (originalColor.luminance() > 0.5f) Color.Black else Color.White
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,153 +17,256 @@ sealed class ThemeColors {
|
|||||||
abstract val OnTertiaryContainer: Color
|
abstract val OnTertiaryContainer: Color
|
||||||
abstract val ButtonContrast: Color
|
abstract val ButtonContrast: Color
|
||||||
|
|
||||||
open fun getCustomSliderActiveColor(): Color = Primary
|
// 表面颜色
|
||||||
open fun getCustomSliderInactiveColor(): Color = PrimaryContainer
|
abstract val Surface: Color
|
||||||
|
abstract val SurfaceVariant: Color
|
||||||
|
abstract val OnSurface: Color
|
||||||
|
abstract val OnSurfaceVariant: Color
|
||||||
|
|
||||||
// Default Theme (white)
|
// 错误状态颜色
|
||||||
|
abstract val Error: Color
|
||||||
|
abstract val OnError: Color
|
||||||
|
abstract val ErrorContainer: Color
|
||||||
|
abstract val OnErrorContainer: Color
|
||||||
|
|
||||||
|
// 边框和背景色
|
||||||
|
abstract val Outline: Color
|
||||||
|
abstract val OutlineVariant: Color
|
||||||
|
abstract val Background: Color
|
||||||
|
abstract val OnBackground: Color
|
||||||
|
|
||||||
|
// 默认主题 (蓝色)
|
||||||
object Default : ThemeColors() {
|
object Default : ThemeColors() {
|
||||||
override val Primary = Color(0xFFFFFFFF)
|
|
||||||
override val Secondary = Color(0xFFF5F5F5)
|
|
||||||
override val Tertiary = Color(0xFFE0E0E0)
|
|
||||||
override val OnPrimary = Color(0xFF616161)
|
|
||||||
override val OnSecondary = Color(0xFF616161)
|
|
||||||
override val OnTertiary = Color(0xFF616161)
|
|
||||||
override val PrimaryContainer = Color(0xFFF5F5F5)
|
|
||||||
override val SecondaryContainer = Color(0xFFEEEEEE)
|
|
||||||
override val TertiaryContainer = Color(0xFFE0E0E0)
|
|
||||||
override val OnPrimaryContainer = Color(0xFF000000)
|
|
||||||
override val OnSecondaryContainer = Color(0xFF000000)
|
|
||||||
override val OnTertiaryContainer = Color(0xFF000000)
|
|
||||||
override val ButtonContrast = Color(0xFF00BFFF)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blue Theme
|
|
||||||
object Blue : ThemeColors() {
|
|
||||||
override val Primary = Color(0xFF2196F3)
|
override val Primary = Color(0xFF2196F3)
|
||||||
override val Secondary = Color(0xFF1E88E5)
|
override val Secondary = Color(0xFF64B5F6)
|
||||||
override val Tertiary = Color(0xFF0D47A1)
|
override val Tertiary = Color(0xFF0D47A1)
|
||||||
override val OnPrimary = Color(0xFFFFFFFF)
|
override val OnPrimary = Color(0xFFFFFFFF)
|
||||||
override val OnSecondary = Color(0xFFFFFFFF)
|
override val OnSecondary = Color(0xFFFFFFFF)
|
||||||
override val OnTertiary = Color(0xFFFFFFFF)
|
override val OnTertiary = Color(0xFFFFFFFF)
|
||||||
override val PrimaryContainer = Color(0xFFCBE6FC)
|
override val PrimaryContainer = Color(0xFFD6EAFF)
|
||||||
override val SecondaryContainer = Color(0xFFBBDEFB)
|
override val SecondaryContainer = Color(0xFFE3F2FD)
|
||||||
override val TertiaryContainer = Color(0xFF90CAF9)
|
override val TertiaryContainer = Color(0xFFCFD8DC)
|
||||||
override val OnPrimaryContainer = Color(0xFF0A1A2E)
|
override val OnPrimaryContainer = Color(0xFF0A3049)
|
||||||
override val OnSecondaryContainer = Color(0xFF0A192D)
|
override val OnSecondaryContainer = Color(0xFF0D3C61)
|
||||||
override val OnTertiaryContainer = Color(0xFF071B3D)
|
override val OnTertiaryContainer = Color(0xFF071D41)
|
||||||
override val ButtonContrast = Color(0xFF00BFFF)
|
override val ButtonContrast = Color(0xFF2196F3)
|
||||||
|
|
||||||
|
override val Surface = Color(0xFFF5F9FF)
|
||||||
|
override val SurfaceVariant = Color(0xFFEDF5FE)
|
||||||
|
override val OnSurface = Color(0xFF1A1C1E)
|
||||||
|
override val OnSurfaceVariant = Color(0xFF42474E)
|
||||||
|
|
||||||
|
override val Error = Color(0xFFB00020)
|
||||||
|
override val OnError = Color(0xFFFFFFFF)
|
||||||
|
override val ErrorContainer = Color(0xFFFDE7E9)
|
||||||
|
override val OnErrorContainer = Color(0xFF410008)
|
||||||
|
|
||||||
|
override val Outline = Color(0xFFBAC3CF)
|
||||||
|
override val OutlineVariant = Color(0xFFDFE3EB)
|
||||||
|
override val Background = Color(0xFFFAFCFF)
|
||||||
|
override val OnBackground = Color(0xFF1A1C1E)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Green Theme
|
// 绿色主题
|
||||||
object Green : ThemeColors() {
|
object Green : ThemeColors() {
|
||||||
override val Primary = Color(0xFF4CAF50)
|
override val Primary = Color(0xFF43A047)
|
||||||
override val Secondary = Color(0xFF43A047)
|
override val Secondary = Color(0xFF66BB6A)
|
||||||
override val Tertiary = Color(0xFF1B5E20)
|
override val Tertiary = Color(0xFF1B5E20)
|
||||||
override val OnPrimary = Color(0xFFFFFFFF)
|
override val OnPrimary = Color(0xFFFFFFFF)
|
||||||
override val OnSecondary = Color(0xFFFFFFFF)
|
override val OnSecondary = Color(0xFFFFFFFF)
|
||||||
override val OnTertiary = Color(0xFFFFFFFF)
|
override val OnTertiary = Color(0xFFFFFFFF)
|
||||||
override val PrimaryContainer = Color(0xFFC8E6C9)
|
override val PrimaryContainer = Color(0xFFD8EFDB)
|
||||||
override val SecondaryContainer = Color(0xFFA5D6A7)
|
override val SecondaryContainer = Color(0xFFE8F5E9)
|
||||||
override val TertiaryContainer = Color(0xFF81C784)
|
override val TertiaryContainer = Color(0xFFB9F6CA)
|
||||||
override val OnPrimaryContainer = Color(0xFF0A1F0B)
|
override val OnPrimaryContainer = Color(0xFF0A280D)
|
||||||
override val OnSecondaryContainer = Color(0xFF0A1D0B)
|
override val OnSecondaryContainer = Color(0xFF0E2912)
|
||||||
override val OnTertiaryContainer = Color(0xFF071F09)
|
override val OnTertiaryContainer = Color(0xFF051B07)
|
||||||
override val ButtonContrast = Color(0xFF32CD32)
|
override val ButtonContrast = Color(0xFF43A047)
|
||||||
|
|
||||||
|
override val Surface = Color(0xFFF6FBF6)
|
||||||
|
override val SurfaceVariant = Color(0xFFEDF7EE)
|
||||||
|
override val OnSurface = Color(0xFF191C19)
|
||||||
|
override val OnSurfaceVariant = Color(0xFF414941)
|
||||||
|
|
||||||
|
override val Error = Color(0xFFC62828)
|
||||||
|
override val OnError = Color(0xFFFFFFFF)
|
||||||
|
override val ErrorContainer = Color(0xFFF8D7DA)
|
||||||
|
override val OnErrorContainer = Color(0xFF4A0808)
|
||||||
|
|
||||||
|
override val Outline = Color(0xFFBDC9BF)
|
||||||
|
override val OutlineVariant = Color(0xFFDDE6DE)
|
||||||
|
override val Background = Color(0xFFFBFDFB)
|
||||||
|
override val OnBackground = Color(0xFF191C19)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Purple Theme
|
// 紫色主题
|
||||||
object Purple : ThemeColors() {
|
object Purple : ThemeColors() {
|
||||||
override val Primary = Color(0xFF9C27B0)
|
override val Primary = Color(0xFF9C27B0)
|
||||||
override val Secondary = Color(0xFF8E24AA)
|
override val Secondary = Color(0xFFBA68C8)
|
||||||
override val Tertiary = Color(0xFF4A148C)
|
override val Tertiary = Color(0xFF6A1B9A)
|
||||||
override val OnPrimary = Color(0xFFFFFFFF)
|
override val OnPrimary = Color(0xFFFFFFFF)
|
||||||
override val OnSecondary = Color(0xFFFFFFFF)
|
override val OnSecondary = Color(0xFFFFFFFF)
|
||||||
override val OnTertiary = Color(0xFFFFFFFF)
|
override val OnTertiary = Color(0xFFFFFFFF)
|
||||||
override val PrimaryContainer = Color(0xFFE1BEE7)
|
override val PrimaryContainer = Color(0xFFF3D8F8)
|
||||||
override val SecondaryContainer = Color(0xFFCE93D8)
|
override val SecondaryContainer = Color(0xFFF5E9F7)
|
||||||
override val TertiaryContainer = Color(0xFFB39DDB)
|
override val TertiaryContainer = Color(0xFFE1BEE7)
|
||||||
override val OnPrimaryContainer = Color(0xFF1F0A23)
|
override val OnPrimaryContainer = Color(0xFF2A0934)
|
||||||
override val OnSecondaryContainer = Color(0xFF1C0A21)
|
override val OnSecondaryContainer = Color(0xFF3C0F50)
|
||||||
override val OnTertiaryContainer = Color(0xFF12071C)
|
override val OnTertiaryContainer = Color(0xFF1D0830)
|
||||||
override val ButtonContrast = Color(0xFFDA70D6)
|
override val ButtonContrast = Color(0xFF9C27B0)
|
||||||
|
|
||||||
|
override val Surface = Color(0xFFFCF6FF)
|
||||||
|
override val SurfaceVariant = Color(0xFFF5EEFA)
|
||||||
|
override val OnSurface = Color(0xFF1D1B1E)
|
||||||
|
override val OnSurfaceVariant = Color(0xFF49454E)
|
||||||
|
|
||||||
|
override val Error = Color(0xFFD50000)
|
||||||
|
override val OnError = Color(0xFFFFFFFF)
|
||||||
|
override val ErrorContainer = Color(0xFFFFDCD5)
|
||||||
|
override val OnErrorContainer = Color(0xFF480000)
|
||||||
|
|
||||||
|
override val Outline = Color(0xFFC9B9D0)
|
||||||
|
override val OutlineVariant = Color(0xFFE8DAED)
|
||||||
|
override val Background = Color(0xFFFFFBFF)
|
||||||
|
override val OnBackground = Color(0xFF1D1B1E)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Orange Theme
|
// 橙色主题
|
||||||
object Orange : ThemeColors() {
|
object Orange : ThemeColors() {
|
||||||
override val Primary = Color(0xFFFF9800)
|
override val Primary = Color(0xFFFF9800)
|
||||||
override val Secondary = Color(0xFFFB8C00)
|
override val Secondary = Color(0xFFFFB74D)
|
||||||
override val Tertiary = Color(0xFFE65100)
|
override val Tertiary = Color(0xFFE65100)
|
||||||
override val OnPrimary = Color(0xFFFFFFFF)
|
override val OnPrimary = Color(0xFFFFFFFF)
|
||||||
override val OnSecondary = Color(0xFFFFFFFF)
|
override val OnSecondary = Color(0xFF000000)
|
||||||
override val OnTertiary = Color(0xFFFFFFFF)
|
override val OnTertiary = Color(0xFFFFFFFF)
|
||||||
override val PrimaryContainer = Color(0xFFFFE0B2)
|
override val PrimaryContainer = Color(0xFFFFECCC)
|
||||||
override val SecondaryContainer = Color(0xFFFFCC80)
|
override val SecondaryContainer = Color(0xFFFFF0D9)
|
||||||
override val TertiaryContainer = Color(0xFFFFB74D)
|
override val TertiaryContainer = Color(0xFFFFD180)
|
||||||
override val OnPrimaryContainer = Color(0xFF1A1100)
|
override val OnPrimaryContainer = Color(0xFF351F00)
|
||||||
override val OnSecondaryContainer = Color(0xFF1A1000)
|
override val OnSecondaryContainer = Color(0xFF3D2800)
|
||||||
override val OnTertiaryContainer = Color(0xFF1A0B00)
|
override val OnTertiaryContainer = Color(0xFF2E1500)
|
||||||
override val ButtonContrast = Color(0xFFFF6347)
|
override val ButtonContrast = Color(0xFFFF9800)
|
||||||
|
|
||||||
|
override val Surface = Color(0xFFFFF8F3)
|
||||||
|
override val SurfaceVariant = Color(0xFFFFF0E6)
|
||||||
|
override val OnSurface = Color(0xFF1F1B16)
|
||||||
|
override val OnSurfaceVariant = Color(0xFF4E4639)
|
||||||
|
|
||||||
|
override val Error = Color(0xFFD32F2F)
|
||||||
|
override val OnError = Color(0xFFFFFFFF)
|
||||||
|
override val ErrorContainer = Color(0xFFFFDBC8)
|
||||||
|
override val OnErrorContainer = Color(0xFF490700)
|
||||||
|
|
||||||
|
override val Outline = Color(0xFFD6C3AD)
|
||||||
|
override val OutlineVariant = Color(0xFFEFDFCC)
|
||||||
|
override val Background = Color(0xFFFFFBFF)
|
||||||
|
override val OnBackground = Color(0xFF1F1B16)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pink Theme
|
// 粉色主题
|
||||||
object Pink : ThemeColors() {
|
object Pink : ThemeColors() {
|
||||||
override val Primary = Color(0xFFE91E63)
|
override val Primary = Color(0xFFE91E63)
|
||||||
override val Secondary = Color(0xFFD81B60)
|
override val Secondary = Color(0xFFF06292)
|
||||||
override val Tertiary = Color(0xFF880E4F)
|
override val Tertiary = Color(0xFF880E4F)
|
||||||
override val OnPrimary = Color(0xFFFFFFFF)
|
override val OnPrimary = Color(0xFFFFFFFF)
|
||||||
override val OnSecondary = Color(0xFFFFFFFF)
|
override val OnSecondary = Color(0xFFFFFFFF)
|
||||||
override val OnTertiary = Color(0xFFFFFFFF)
|
override val OnTertiary = Color(0xFFFFFFFF)
|
||||||
override val PrimaryContainer = Color(0xFFF8BBD0)
|
override val PrimaryContainer = Color(0xFFFCE4EC)
|
||||||
override val SecondaryContainer = Color(0xFFF48FB1)
|
override val SecondaryContainer = Color(0xFFFCE4EC)
|
||||||
override val TertiaryContainer = Color(0xFFE91E63)
|
override val TertiaryContainer = Color(0xFFF8BBD0)
|
||||||
override val OnPrimaryContainer = Color(0xFF2E0A14)
|
override val OnPrimaryContainer = Color(0xFF3B0819)
|
||||||
override val OnSecondaryContainer = Color(0xFF2B0A13)
|
override val OnSecondaryContainer = Color(0xFF3B0819)
|
||||||
override val OnTertiaryContainer = Color(0xFF1C0311)
|
override val OnTertiaryContainer = Color(0xFF2B0516)
|
||||||
override val ButtonContrast = Color(0xFFFF1493)
|
override val ButtonContrast = Color(0xFFE91E63)
|
||||||
|
|
||||||
|
override val Surface = Color(0xFFFFF7F9)
|
||||||
|
override val SurfaceVariant = Color(0xFFFCEEF2)
|
||||||
|
override val OnSurface = Color(0xFF201A1C)
|
||||||
|
override val OnSurfaceVariant = Color(0xFF534347)
|
||||||
|
|
||||||
|
override val Error = Color(0xFFB71C1C)
|
||||||
|
override val OnError = Color(0xFFFFFFFF)
|
||||||
|
override val ErrorContainer = Color(0xFFFFDAD6)
|
||||||
|
override val OnErrorContainer = Color(0xFF410002)
|
||||||
|
|
||||||
|
override val Outline = Color(0xFFD6BABF)
|
||||||
|
override val OutlineVariant = Color(0xFFEFDDE0)
|
||||||
|
override val Background = Color(0xFFFFFBFF)
|
||||||
|
override val OnBackground = Color(0xFF201A1C)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gray Theme
|
// 灰色主题
|
||||||
object Gray : ThemeColors() {
|
object Gray : ThemeColors() {
|
||||||
override val Primary = Color(0xFF9E9E9E)
|
override val Primary = Color(0xFF607D8B)
|
||||||
override val Secondary = Color(0xFF757575)
|
override val Secondary = Color(0xFF90A4AE)
|
||||||
override val Tertiary = Color(0xFF616161)
|
override val Tertiary = Color(0xFF455A64)
|
||||||
override val OnPrimary = Color(0xFFFFFFFF)
|
override val OnPrimary = Color(0xFFFFFFFF)
|
||||||
override val OnSecondary = Color(0xFFFFFFFF)
|
override val OnSecondary = Color(0xFFFFFFFF)
|
||||||
override val OnTertiary = Color(0xFFFFFFFF)
|
override val OnTertiary = Color(0xFFFFFFFF)
|
||||||
override val PrimaryContainer = Color(0xFFEEEEEE)
|
override val PrimaryContainer = Color(0xFFECEFF1)
|
||||||
override val SecondaryContainer = Color(0xFFE0E0E0)
|
override val SecondaryContainer = Color(0xFFECEFF1)
|
||||||
override val TertiaryContainer = Color(0xFFBDBDBD)
|
override val TertiaryContainer = Color(0xFFCFD8DC)
|
||||||
override val OnPrimaryContainer = Color(0xFF1A1A1A)
|
override val OnPrimaryContainer = Color(0xFF1A2327)
|
||||||
override val OnSecondaryContainer = Color(0xFF171717)
|
override val OnSecondaryContainer = Color(0xFF1A2327)
|
||||||
override val OnTertiaryContainer = Color(0xFF141414)
|
override val OnTertiaryContainer = Color(0xFF121A1D)
|
||||||
override val ButtonContrast = Color(0xFF696969)
|
override val ButtonContrast = Color(0xFF607D8B)
|
||||||
|
|
||||||
|
override val Surface = Color(0xFFF6F9FB)
|
||||||
|
override val SurfaceVariant = Color(0xFFEEF2F4)
|
||||||
|
override val OnSurface = Color(0xFF191C1E)
|
||||||
|
override val OnSurfaceVariant = Color(0xFF41484D)
|
||||||
|
|
||||||
|
override val Error = Color(0xFFC62828)
|
||||||
|
override val OnError = Color(0xFFFFFFFF)
|
||||||
|
override val ErrorContainer = Color(0xFFFFDAD6)
|
||||||
|
override val OnErrorContainer = Color(0xFF410002)
|
||||||
|
|
||||||
|
override val Outline = Color(0xFFBDC1C4)
|
||||||
|
override val OutlineVariant = Color(0xFFDDE1E3)
|
||||||
|
override val Background = Color(0xFFFBFCFE)
|
||||||
|
override val OnBackground = Color(0xFF191C1E)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 黄色主题
|
||||||
object Yellow : ThemeColors() {
|
object Yellow : ThemeColors() {
|
||||||
override val Primary = Color(0xFFFFD700)
|
override val Primary = Color(0xFFFFC107)
|
||||||
override val Secondary = Color(0xFFFFBC52)
|
override val Secondary = Color(0xFFFFD54F)
|
||||||
override val Tertiary = Color(0xFF795548)
|
override val Tertiary = Color(0xFFFF8F00)
|
||||||
override val OnPrimary = Color(0xFFFFFFFF)
|
override val OnPrimary = Color(0xFF000000)
|
||||||
override val OnSecondary = Color(0xFFFFFFFF)
|
override val OnSecondary = Color(0xFF000000)
|
||||||
override val OnTertiary = Color(0xFFFFFFFF)
|
override val OnTertiary = Color(0xFFFFFFFF)
|
||||||
override val PrimaryContainer = Color(0xFFFFF7D6)
|
override val PrimaryContainer = Color(0xFFFFF8E1)
|
||||||
override val SecondaryContainer = Color(0xFFFFE6B3)
|
override val SecondaryContainer = Color(0xFFFFF8E1)
|
||||||
override val TertiaryContainer = Color(0xFFD7CCC8)
|
override val TertiaryContainer = Color(0xFFFFECB3)
|
||||||
override val OnPrimaryContainer = Color(0xFF1A1600)
|
override val OnPrimaryContainer = Color(0xFF332A00)
|
||||||
override val OnSecondaryContainer = Color(0xFF1A1100)
|
override val OnSecondaryContainer = Color(0xFF332A00)
|
||||||
override val OnTertiaryContainer = Color(0xFF1A1717)
|
override val OnTertiaryContainer = Color(0xFF221200)
|
||||||
override val ButtonContrast = Color(0xFFFFD700)
|
override val ButtonContrast = Color(0xFFFFC107)
|
||||||
|
|
||||||
|
override val Surface = Color(0xFFFFFAF3)
|
||||||
|
override val SurfaceVariant = Color(0xFFFFF7E6)
|
||||||
|
override val OnSurface = Color(0xFF1F1C17)
|
||||||
|
override val OnSurfaceVariant = Color(0xFF4E4A3C)
|
||||||
|
|
||||||
|
override val Error = Color(0xFFB71C1C)
|
||||||
|
override val OnError = Color(0xFFFFFFFF)
|
||||||
|
override val ErrorContainer = Color(0xFFFFDAD6)
|
||||||
|
override val OnErrorContainer = Color(0xFF410002)
|
||||||
|
|
||||||
|
override val Outline = Color(0xFFD1C8AF)
|
||||||
|
override val OutlineVariant = Color(0xFFEEE8D7)
|
||||||
|
override val Background = Color(0xFFFFFCF8)
|
||||||
|
override val OnBackground = Color(0xFF1F1C17)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromName(name: String): ThemeColors = when (name.lowercase()) {
|
fun fromName(name: String): ThemeColors = when (name.lowercase()) {
|
||||||
"blue" -> Blue
|
|
||||||
"green" -> Green
|
"green" -> Green
|
||||||
"purple" -> Purple
|
"purple" -> Purple
|
||||||
"orange" -> Orange
|
"orange" -> Orange
|
||||||
"pink" -> Pink
|
"pink" -> Pink
|
||||||
"gray" -> Gray
|
"gray" -> Gray
|
||||||
"white" -> Yellow
|
"yellow" -> Yellow
|
||||||
else -> Default
|
else -> Default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import android.content.Context
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.compose.animation.core.animateFloat
|
||||||
|
import androidx.compose.animation.core.spring
|
||||||
|
import androidx.compose.animation.core.updateTransition
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@@ -14,19 +18,24 @@ import androidx.compose.material3.dynamicDarkColorScheme
|
|||||||
import androidx.compose.material3.dynamicLightColorScheme
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.paint
|
import androidx.compose.ui.draw.paint
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
|
import coil.compose.AsyncImagePainter
|
||||||
import coil.compose.rememberAsyncImagePainter
|
import coil.compose.rememberAsyncImagePainter
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.ui.graphics.luminance
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
@@ -35,40 +44,304 @@ import androidx.core.content.edit
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.sukisu.ultra.ui.util.BackgroundTransformation
|
import com.sukisu.ultra.ui.util.BackgroundTransformation
|
||||||
import com.sukisu.ultra.ui.util.saveTransformedBackground
|
import com.sukisu.ultra.ui.util.saveTransformedBackground
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.runtime.SideEffect
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题配置对象,管理应用的主题相关状态
|
||||||
|
*/
|
||||||
object ThemeConfig {
|
object ThemeConfig {
|
||||||
var customBackgroundUri by mutableStateOf<Uri?>(null)
|
var customBackgroundUri by mutableStateOf<Uri?>(null)
|
||||||
var forceDarkMode by mutableStateOf<Boolean?>(null)
|
var forceDarkMode by mutableStateOf<Boolean?>(null)
|
||||||
var currentTheme by mutableStateOf<ThemeColors>(ThemeColors.Default)
|
var currentTheme by mutableStateOf<ThemeColors>(ThemeColors.Default)
|
||||||
var useDynamicColor by mutableStateOf(false)
|
var useDynamicColor by mutableStateOf(false)
|
||||||
|
var backgroundImageLoaded by mutableStateOf(false)
|
||||||
|
var needsResetOnThemeChange by mutableStateOf(false)
|
||||||
|
var isThemeChanging by mutableStateOf(false)
|
||||||
|
var preventBackgroundRefresh by mutableStateOf(false)
|
||||||
|
|
||||||
|
private var lastDarkModeState: Boolean? = null
|
||||||
|
fun detectThemeChange(currentDarkMode: Boolean): Boolean {
|
||||||
|
val isChanged = lastDarkModeState != null && lastDarkModeState != currentDarkMode
|
||||||
|
lastDarkModeState = currentDarkMode
|
||||||
|
return isChanged
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetBackgroundState() {
|
||||||
|
if (!preventBackgroundRefresh) {
|
||||||
|
backgroundImageLoaded = false
|
||||||
|
}
|
||||||
|
isThemeChanging = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用主题
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun getDarkColorScheme() = darkColorScheme(
|
fun KernelSUTheme(
|
||||||
|
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
|
||||||
|
true -> true
|
||||||
|
false -> false
|
||||||
|
null -> isSystemInDarkTheme()
|
||||||
|
},
|
||||||
|
dynamicColor: Boolean = ThemeConfig.useDynamicColor,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val systemIsDark = isSystemInDarkTheme()
|
||||||
|
|
||||||
|
// 检测系统主题变化并保存状态
|
||||||
|
val themeChanged = ThemeConfig.detectThemeChange(systemIsDark)
|
||||||
|
LaunchedEffect(systemIsDark, themeChanged) {
|
||||||
|
if (ThemeConfig.forceDarkMode == null && themeChanged) {
|
||||||
|
Log.d("ThemeSystem", "系统主题变化检测: 从 ${!systemIsDark} 变为 $systemIsDark")
|
||||||
|
ThemeConfig.resetBackgroundState()
|
||||||
|
|
||||||
|
if (!ThemeConfig.preventBackgroundRefresh) {
|
||||||
|
context.loadCustomBackground()
|
||||||
|
}
|
||||||
|
|
||||||
|
CardConfig.apply {
|
||||||
|
load(context)
|
||||||
|
if (!isCustomAlphaSet) {
|
||||||
|
cardAlpha = if (systemIsDark) 0.50f else 1f
|
||||||
|
}
|
||||||
|
if (!isCustomDimSet) {
|
||||||
|
cardDim = if (systemIsDark) 0.5f else 0f
|
||||||
|
}
|
||||||
|
save(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemBarStyle(
|
||||||
|
darkMode = darkTheme
|
||||||
|
)
|
||||||
|
|
||||||
|
// 初始加载配置
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
context.loadThemeMode()
|
||||||
|
context.loadThemeColors()
|
||||||
|
context.loadDynamicColorState()
|
||||||
|
CardConfig.load(context)
|
||||||
|
|
||||||
|
if (!ThemeConfig.backgroundImageLoaded && !ThemeConfig.preventBackgroundRefresh) {
|
||||||
|
context.loadCustomBackground()
|
||||||
|
ThemeConfig.backgroundImageLoaded = false
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeConfig.preventBackgroundRefresh = context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
|
.getBoolean("prevent_background_refresh", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建颜色方案
|
||||||
|
val colorScheme = when {
|
||||||
|
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||||
|
if (darkTheme) createDynamicDarkColorScheme(context) else createDynamicLightColorScheme(context)
|
||||||
|
}
|
||||||
|
darkTheme -> createDarkColorScheme()
|
||||||
|
else -> createLightColorScheme()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据暗色模式和自定义背景调整卡片配置
|
||||||
|
val isDarkModeWithCustomBackground = darkTheme && ThemeConfig.customBackgroundUri != null
|
||||||
|
if (darkTheme && !dynamicColor) {
|
||||||
|
CardConfig.setDarkModeDefaults()
|
||||||
|
} else if (!darkTheme && !dynamicColor) {
|
||||||
|
CardConfig.setLightModeDefaults()
|
||||||
|
}
|
||||||
|
CardConfig.updateShadowEnabled(!isDarkModeWithCustomBackground)
|
||||||
|
|
||||||
|
val backgroundUri = rememberSaveable { mutableStateOf(ThemeConfig.customBackgroundUri) }
|
||||||
|
|
||||||
|
LaunchedEffect(ThemeConfig.customBackgroundUri) {
|
||||||
|
backgroundUri.value = ThemeConfig.customBackgroundUri
|
||||||
|
}
|
||||||
|
|
||||||
|
val bgImagePainter = backgroundUri.value?.let {
|
||||||
|
rememberAsyncImagePainter(
|
||||||
|
model = it,
|
||||||
|
onError = {
|
||||||
|
Log.e("ThemeSystem", "背景图加载失败: ${it.result.throwable.message}")
|
||||||
|
ThemeConfig.customBackgroundUri = null
|
||||||
|
context.saveCustomBackground(null)
|
||||||
|
},
|
||||||
|
onSuccess = {
|
||||||
|
Log.d("ThemeSystem", "背景图加载成功")
|
||||||
|
ThemeConfig.backgroundImageLoaded = true
|
||||||
|
ThemeConfig.isThemeChanging = false
|
||||||
|
|
||||||
|
ThemeConfig.preventBackgroundRefresh = true
|
||||||
|
context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
|
.edit { putBoolean("prevent_background_refresh", true) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val transition = updateTransition(
|
||||||
|
targetState = ThemeConfig.backgroundImageLoaded,
|
||||||
|
label = "bgTransition"
|
||||||
|
)
|
||||||
|
val bgAlpha by transition.animateFloat(
|
||||||
|
label = "bgAlpha",
|
||||||
|
transitionSpec = {
|
||||||
|
spring(
|
||||||
|
dampingRatio = 0.8f,
|
||||||
|
stiffness = 300f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { loaded -> if (loaded) 1f else 0f }
|
||||||
|
|
||||||
|
DisposableEffect(systemIsDark) {
|
||||||
|
onDispose {
|
||||||
|
if (ThemeConfig.isThemeChanging) {
|
||||||
|
ThemeConfig.isThemeChanging = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算适用的暗化值
|
||||||
|
val dimFactor = CardConfig.cardDim
|
||||||
|
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = colorScheme,
|
||||||
|
typography = Typography
|
||||||
|
) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.zIndex(-2f)
|
||||||
|
.background(if (darkTheme) Color.Black else Color.White)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 自定义背景层
|
||||||
|
backgroundUri.value?.let { uri ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.zIndex(-1f)
|
||||||
|
.alpha(bgAlpha)
|
||||||
|
) {
|
||||||
|
// 背景图片
|
||||||
|
bgImagePainter?.let { painter ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.paint(
|
||||||
|
painter = painter,
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
.graphicsLayer {
|
||||||
|
alpha = (painter.state as? AsyncImagePainter.State.Success)?.let { 1f } ?: 0f
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 亮度调节层 (根据cardDim调整)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(
|
||||||
|
if (darkTheme) Color.Black.copy(alpha = 0.6f + dimFactor * 0.3f)
|
||||||
|
else Color.White.copy(alpha = 0.1f + dimFactor * 0.2f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 边缘渐变遮罩
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(
|
||||||
|
Brush.radialGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color.Transparent,
|
||||||
|
if (darkTheme) Color.Black.copy(alpha = 0.5f + dimFactor * 0.2f)
|
||||||
|
else Color.Black.copy(alpha = 0.2f + dimFactor * 0.1f)
|
||||||
|
),
|
||||||
|
radius = 1200f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内容层
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.zIndex(1f)
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建动态深色颜色方案
|
||||||
|
*/
|
||||||
|
@RequiresApi(Build.VERSION_CODES.S)
|
||||||
|
@Composable
|
||||||
|
private fun createDynamicDarkColorScheme(context: Context) =
|
||||||
|
dynamicDarkColorScheme(context).copy(
|
||||||
|
background = Color.Transparent,
|
||||||
|
surface = Color.Transparent,
|
||||||
|
onBackground = Color.White,
|
||||||
|
onSurface = Color.White
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建动态浅色颜色方案
|
||||||
|
*/
|
||||||
|
@RequiresApi(Build.VERSION_CODES.S)
|
||||||
|
@Composable
|
||||||
|
private fun createDynamicLightColorScheme(context: Context) =
|
||||||
|
dynamicLightColorScheme(context).copy(
|
||||||
|
background = Color.Transparent,
|
||||||
|
surface = Color.Transparent
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建深色颜色方案
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun createDarkColorScheme() = darkColorScheme(
|
||||||
primary = ThemeConfig.currentTheme.Primary.copy(alpha = 0.8f),
|
primary = ThemeConfig.currentTheme.Primary.copy(alpha = 0.8f),
|
||||||
onPrimary = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f),
|
onPrimary = Color.White,
|
||||||
primaryContainer = ThemeConfig.currentTheme.PrimaryContainer.copy(alpha = 0.15f),
|
primaryContainer = ThemeConfig.currentTheme.PrimaryContainer.copy(alpha = 0.15f),
|
||||||
onPrimaryContainer = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f),
|
onPrimaryContainer = Color.White,
|
||||||
secondary = ThemeConfig.currentTheme.Secondary.copy(alpha = 0.8f),
|
secondary = ThemeConfig.currentTheme.Secondary.copy(alpha = 0.8f),
|
||||||
onSecondary = mixColors(ThemeConfig.currentTheme.Secondary, Color.White, 0.2f),
|
onSecondary = Color.White,
|
||||||
secondaryContainer = ThemeConfig.currentTheme.SecondaryContainer.copy(alpha = 0.15f),
|
secondaryContainer = ThemeConfig.currentTheme.SecondaryContainer.copy(alpha = 0.15f),
|
||||||
onSecondaryContainer = mixColors(ThemeConfig.currentTheme.Secondary, Color.White, 0.2f),
|
onSecondaryContainer = Color.White,
|
||||||
tertiary = ThemeConfig.currentTheme.Tertiary.copy(alpha = 0.8f),
|
tertiary = ThemeConfig.currentTheme.Tertiary.copy(alpha = 0.8f),
|
||||||
onTertiary = mixColors(ThemeConfig.currentTheme.Tertiary, Color.White, 0.2f),
|
onTertiary = Color.White,
|
||||||
tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer.copy(alpha = 0.15f),
|
tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer.copy(alpha = 0.15f),
|
||||||
onTertiaryContainer = mixColors(ThemeConfig.currentTheme.Tertiary, Color.White, 0.2f),
|
onTertiaryContainer = Color.White,
|
||||||
background = Color.Transparent,
|
background = Color.Transparent,
|
||||||
surface = Color.Transparent,
|
surface = Color.Transparent,
|
||||||
onBackground = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.1f),
|
onBackground = Color.White,
|
||||||
onSurface = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.1f),
|
onSurface = Color.White,
|
||||||
surfaceVariant = Color(0xFF2F2F2F),
|
surfaceVariant = Color(0xFF2F2F2F),
|
||||||
onSurfaceVariant = mixColors(ThemeConfig.currentTheme.Primary, Color.White, 0.2f),
|
onSurfaceVariant = Color.White.copy(alpha = 0.7f),
|
||||||
outline = Color.White.copy(alpha = 0.12f),
|
outline = Color.White.copy(alpha = 0.12f),
|
||||||
outlineVariant = Color.White.copy(alpha = 0.12f)
|
outlineVariant = Color.White.copy(alpha = 0.12f),
|
||||||
|
error = ThemeConfig.currentTheme.Error,
|
||||||
|
onError = ThemeConfig.currentTheme.OnError,
|
||||||
|
errorContainer = ThemeConfig.currentTheme.ErrorContainer.copy(alpha = 0.15f),
|
||||||
|
onErrorContainer = Color.White
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建浅色颜色方案
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun getLightColorScheme() = lightColorScheme(
|
private fun createLightColorScheme() = lightColorScheme(
|
||||||
primary = ThemeConfig.currentTheme.Primary,
|
primary = ThemeConfig.currentTheme.Primary,
|
||||||
onPrimary = ThemeConfig.currentTheme.OnPrimary,
|
onPrimary = ThemeConfig.currentTheme.OnPrimary,
|
||||||
primaryContainer = ThemeConfig.currentTheme.PrimaryContainer,
|
primaryContainer = ThemeConfig.currentTheme.PrimaryContainer,
|
||||||
@@ -88,163 +361,52 @@ private fun getLightColorScheme() = lightColorScheme(
|
|||||||
surfaceVariant = Color(0xFFF5F5F5),
|
surfaceVariant = Color(0xFFF5F5F5),
|
||||||
onSurfaceVariant = Color.Black.copy(alpha = 0.78f),
|
onSurfaceVariant = Color.Black.copy(alpha = 0.78f),
|
||||||
outline = Color.Black.copy(alpha = 0.12f),
|
outline = Color.Black.copy(alpha = 0.12f),
|
||||||
outlineVariant = Color.Black.copy(alpha = 0.12f)
|
outlineVariant = Color.Black.copy(alpha = 0.12f),
|
||||||
|
error = ThemeConfig.currentTheme.Error,
|
||||||
|
onError = ThemeConfig.currentTheme.OnError,
|
||||||
|
errorContainer = ThemeConfig.currentTheme.ErrorContainer,
|
||||||
|
onErrorContainer = ThemeConfig.currentTheme.OnErrorContainer
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
/**
|
||||||
fun KernelSUTheme(
|
* 复制图片到应用内部存储并提升持久性
|
||||||
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
|
*/
|
||||||
true -> true
|
|
||||||
false -> false
|
|
||||||
null -> isSystemInDarkTheme()
|
|
||||||
},
|
|
||||||
dynamicColor: Boolean = ThemeConfig.useDynamicColor,
|
|
||||||
content: @Composable () -> Unit
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
context.loadCustomBackground()
|
|
||||||
context.loadThemeColors()
|
|
||||||
context.loadDynamicColorState()
|
|
||||||
|
|
||||||
val colorScheme = when {
|
|
||||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
|
||||||
if (darkTheme) {
|
|
||||||
val originalScheme = dynamicDarkColorScheme(context)
|
|
||||||
originalScheme.copy(
|
|
||||||
primary = adjustColor(originalScheme.primary),
|
|
||||||
onPrimary = adjustColor(originalScheme.onPrimary),
|
|
||||||
primaryContainer = adjustColor(originalScheme.primaryContainer),
|
|
||||||
onPrimaryContainer = adjustColor(originalScheme.onPrimaryContainer),
|
|
||||||
background = Color.Transparent,
|
|
||||||
surface = Color.Transparent,
|
|
||||||
onBackground = Color.White,
|
|
||||||
onSurface = Color.White,
|
|
||||||
onSecondary = Color.White,
|
|
||||||
onTertiary = Color.White,
|
|
||||||
onSecondaryContainer = Color.White,
|
|
||||||
onTertiaryContainer = Color.White
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
val originalScheme = dynamicLightColorScheme(context)
|
|
||||||
originalScheme.copy(
|
|
||||||
primary = adjustColor(originalScheme.primary),
|
|
||||||
onPrimary = adjustColor(originalScheme.onPrimary),
|
|
||||||
primaryContainer = adjustColor(originalScheme.primaryContainer),
|
|
||||||
onPrimaryContainer = adjustColor(originalScheme.onPrimaryContainer),
|
|
||||||
background = Color.Transparent,
|
|
||||||
surface = Color.Transparent
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
darkTheme -> getDarkColorScheme()
|
|
||||||
else -> getLightColorScheme()
|
|
||||||
}
|
|
||||||
|
|
||||||
val isDarkModeWithCustomBackground = darkTheme && ThemeConfig.customBackgroundUri != null
|
|
||||||
|
|
||||||
if (darkTheme && !dynamicColor) {
|
|
||||||
CardConfig.setDarkModeDefaults()
|
|
||||||
}
|
|
||||||
|
|
||||||
CardConfig.updateShadowEnabled(!isDarkModeWithCustomBackground)
|
|
||||||
|
|
||||||
MaterialTheme(
|
|
||||||
colorScheme = colorScheme,
|
|
||||||
typography = Typography
|
|
||||||
) {
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
// 背景图层
|
|
||||||
ThemeConfig.customBackgroundUri?.let { uri ->
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.zIndex(-1f)
|
|
||||||
) {
|
|
||||||
// 背景图片
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.paint(
|
|
||||||
painter = rememberAsyncImagePainter(
|
|
||||||
model = uri,
|
|
||||||
onError = {
|
|
||||||
ThemeConfig.customBackgroundUri = null
|
|
||||||
context.saveCustomBackground(null)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 亮度调节层
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(
|
|
||||||
if (darkTheme) {
|
|
||||||
Color.Black.copy(alpha = 0.4f)
|
|
||||||
} else {
|
|
||||||
Color.White.copy(alpha = 0.1f)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 边缘渐变遮罩层
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(
|
|
||||||
Brush.radialGradient(
|
|
||||||
colors = listOf(
|
|
||||||
Color.Transparent,
|
|
||||||
if (darkTheme) {
|
|
||||||
Color.Black.copy(alpha = 0.5f)
|
|
||||||
} else {
|
|
||||||
Color.Black.copy(alpha = 0.2f)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
radius = 1200f
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 内容图层
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.zIndex(1f)
|
|
||||||
) {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 复制图片到应用内部存储
|
|
||||||
private fun Context.copyImageToInternalStorage(uri: Uri): Uri? {
|
private fun Context.copyImageToInternalStorage(uri: Uri): Uri? {
|
||||||
try {
|
return try {
|
||||||
val contentResolver: ContentResolver = contentResolver
|
val contentResolver: ContentResolver = contentResolver
|
||||||
val inputStream: InputStream = contentResolver.openInputStream(uri)!!
|
val inputStream: InputStream = contentResolver.openInputStream(uri) ?: return null
|
||||||
|
|
||||||
val fileName = "custom_background.jpg"
|
val fileName = "custom_background.jpg"
|
||||||
val file = File(filesDir, fileName)
|
val file = File(filesDir, fileName)
|
||||||
val outputStream = FileOutputStream(file)
|
|
||||||
|
val backupFile = File(filesDir, "${fileName}.backup")
|
||||||
|
val outputStream = FileOutputStream(backupFile)
|
||||||
val buffer = ByteArray(4 * 1024)
|
val buffer = ByteArray(4 * 1024)
|
||||||
var read: Int
|
var read: Int
|
||||||
|
|
||||||
while (inputStream.read(buffer).also { read = it } != -1) {
|
while (inputStream.read(buffer).also { read = it } != -1) {
|
||||||
outputStream.write(buffer, 0, read)
|
outputStream.write(buffer, 0, read)
|
||||||
}
|
}
|
||||||
|
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
outputStream.close()
|
outputStream.close()
|
||||||
inputStream.close()
|
inputStream.close()
|
||||||
return Uri.fromFile(file)
|
|
||||||
|
if (file.exists()) {
|
||||||
|
file.delete()
|
||||||
|
}
|
||||||
|
backupFile.renameTo(file)
|
||||||
|
|
||||||
|
Uri.fromFile(file)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("ImageCopy", "Failed to copy image: ${e.message}")
|
Log.e("ImageCopy", "复制图片失败: ${e.message}")
|
||||||
return null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存变换后的背景图片到应用内部存储并更新配置
|
/**
|
||||||
|
* 保存并应用自定义背景
|
||||||
|
*/
|
||||||
fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTransformation? = null) {
|
fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTransformation? = null) {
|
||||||
val finalUri = if (transformation != null) {
|
val finalUri = if (transformation != null) {
|
||||||
saveTransformedBackground(uri, transformation)
|
saveTransformedBackground(uri, transformation)
|
||||||
@@ -252,36 +414,73 @@ fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTra
|
|||||||
copyImageToInternalStorage(uri)
|
copyImageToInternalStorage(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保存到配置文件
|
||||||
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
.edit {
|
.edit {
|
||||||
putString("custom_background", finalUri?.toString())
|
putString("custom_background", finalUri?.toString())
|
||||||
|
// 设置阻止刷新标志为false,允许新设置的背景加载一次
|
||||||
|
putBoolean("prevent_background_refresh", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeConfig.customBackgroundUri = finalUri
|
ThemeConfig.customBackgroundUri = finalUri
|
||||||
|
ThemeConfig.backgroundImageLoaded = false
|
||||||
|
ThemeConfig.preventBackgroundRefresh = false
|
||||||
CardConfig.cardElevation = 0.dp
|
CardConfig.cardElevation = 0.dp
|
||||||
CardConfig.isCustomBackgroundEnabled = true
|
CardConfig.isCustomBackgroundEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存背景图片到应用内部存储并更新配置
|
/**
|
||||||
|
* 保存自定义背景
|
||||||
|
*/
|
||||||
fun Context.saveCustomBackground(uri: Uri?) {
|
fun Context.saveCustomBackground(uri: Uri?) {
|
||||||
val newUri = uri?.let { copyImageToInternalStorage(it) }
|
val newUri = uri?.let { copyImageToInternalStorage(it) }
|
||||||
|
|
||||||
|
// 保存到配置文件
|
||||||
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
.edit {
|
.edit {
|
||||||
putString("custom_background", newUri?.toString())
|
putString("custom_background", newUri?.toString())
|
||||||
|
if (uri == null) {
|
||||||
|
// 如果清除背景,也重置阻止刷新标志
|
||||||
|
putBoolean("prevent_background_refresh", false)
|
||||||
|
} else {
|
||||||
|
// 设置阻止刷新标志为false,允许新设置的背景加载一次
|
||||||
|
putBoolean("prevent_background_refresh", false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeConfig.customBackgroundUri = newUri
|
ThemeConfig.customBackgroundUri = newUri
|
||||||
|
ThemeConfig.backgroundImageLoaded = false
|
||||||
|
ThemeConfig.preventBackgroundRefresh = false
|
||||||
|
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
CardConfig.cardElevation = 0.dp
|
CardConfig.cardElevation = 0.dp
|
||||||
CardConfig.isCustomBackgroundEnabled = true
|
CardConfig.isCustomBackgroundEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载自定义背景
|
||||||
|
*/
|
||||||
fun Context.loadCustomBackground() {
|
fun Context.loadCustomBackground() {
|
||||||
val uriString = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
val uriString = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
.getString("custom_background", null)
|
.getString("custom_background", null)
|
||||||
ThemeConfig.customBackgroundUri = uriString?.toUri()
|
|
||||||
|
val newUri = uriString?.toUri()
|
||||||
|
val preventRefresh = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
|
.getBoolean("prevent_background_refresh", false)
|
||||||
|
|
||||||
|
ThemeConfig.preventBackgroundRefresh = preventRefresh
|
||||||
|
|
||||||
|
if (!preventRefresh || ThemeConfig.customBackgroundUri?.toString() != newUri?.toString()) {
|
||||||
|
Log.d("ThemeSystem", "加载自定义背景: $uriString, 阻止刷新: $preventRefresh")
|
||||||
|
ThemeConfig.customBackgroundUri = newUri
|
||||||
|
ThemeConfig.backgroundImageLoaded = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存主题模式
|
||||||
|
*/
|
||||||
fun Context.saveThemeMode(forceDark: Boolean?) {
|
fun Context.saveThemeMode(forceDark: Boolean?) {
|
||||||
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
.edit {
|
.edit {
|
||||||
@@ -294,52 +493,49 @@ fun Context.saveThemeMode(forceDark: Boolean?) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
ThemeConfig.forceDarkMode = forceDark
|
ThemeConfig.forceDarkMode = forceDark
|
||||||
|
ThemeConfig.needsResetOnThemeChange = forceDark == null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载主题模式
|
||||||
|
*/
|
||||||
fun Context.loadThemeMode() {
|
fun Context.loadThemeMode() {
|
||||||
val mode = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
val mode = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
.getString("theme_mode", "system")
|
.getString("theme_mode", "system")
|
||||||
|
|
||||||
ThemeConfig.forceDarkMode = when(mode) {
|
ThemeConfig.forceDarkMode = when(mode) {
|
||||||
"dark" -> true
|
"dark" -> true
|
||||||
"light" -> false
|
"light" -> false
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
ThemeConfig.needsResetOnThemeChange = ThemeConfig.forceDarkMode == null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存主题颜色
|
||||||
|
*/
|
||||||
fun Context.saveThemeColors(themeName: String) {
|
fun Context.saveThemeColors(themeName: String) {
|
||||||
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
.edit {
|
.edit {
|
||||||
putString("theme_colors", themeName)
|
putString("theme_colors", themeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeConfig.currentTheme = when(themeName) {
|
ThemeConfig.currentTheme = ThemeColors.fromName(themeName)
|
||||||
"blue" -> ThemeColors.Blue
|
|
||||||
"green" -> ThemeColors.Green
|
|
||||||
"purple" -> ThemeColors.Purple
|
|
||||||
"orange" -> ThemeColors.Orange
|
|
||||||
"pink" -> ThemeColors.Pink
|
|
||||||
"gray" -> ThemeColors.Gray
|
|
||||||
"yellow" -> ThemeColors.Yellow
|
|
||||||
else -> ThemeColors.Default
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载主题颜色
|
||||||
|
*/
|
||||||
fun Context.loadThemeColors() {
|
fun Context.loadThemeColors() {
|
||||||
val themeName = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
val themeName = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
.getString("theme_colors", "default")
|
.getString("theme_colors", "default")
|
||||||
|
|
||||||
ThemeConfig.currentTheme = when(themeName) {
|
ThemeConfig.currentTheme = ThemeColors.fromName(themeName ?: "default")
|
||||||
"blue" -> ThemeColors.Blue
|
|
||||||
"green" -> ThemeColors.Green
|
|
||||||
"purple" -> ThemeColors.Purple
|
|
||||||
"orange" -> ThemeColors.Orange
|
|
||||||
"pink" -> ThemeColors.Pink
|
|
||||||
"gray" -> ThemeColors.Gray
|
|
||||||
"yellow" -> ThemeColors.Yellow
|
|
||||||
else -> ThemeColors.Default
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存动态颜色状态
|
||||||
|
*/
|
||||||
fun Context.saveDynamicColorState(enabled: Boolean) {
|
fun Context.saveDynamicColorState(enabled: Boolean) {
|
||||||
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
.edit {
|
.edit {
|
||||||
@@ -348,28 +544,41 @@ fun Context.saveDynamicColorState(enabled: Boolean) {
|
|||||||
ThemeConfig.useDynamicColor = enabled
|
ThemeConfig.useDynamicColor = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载动态颜色状态
|
||||||
|
*/
|
||||||
fun Context.loadDynamicColorState() {
|
fun Context.loadDynamicColorState() {
|
||||||
val enabled = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
val enabled = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
.getBoolean("use_dynamic_color", true)
|
.getBoolean("use_dynamic_color", true)
|
||||||
|
|
||||||
ThemeConfig.useDynamicColor = enabled
|
ThemeConfig.useDynamicColor = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun adjustColor(color: Color): Color {
|
@Composable
|
||||||
val minLuminance = 0.75f
|
private fun SystemBarStyle(
|
||||||
val maxLuminance = 1f
|
darkMode: Boolean,
|
||||||
var luminance = color.luminance()
|
statusBarScrim: Color = Color.Transparent,
|
||||||
if (luminance < minLuminance) {
|
navigationBarScrim: Color = Color.Transparent,
|
||||||
luminance = minLuminance
|
) {
|
||||||
} else if (luminance > maxLuminance) {
|
val context = LocalContext.current
|
||||||
luminance = maxLuminance
|
val activity = context as ComponentActivity
|
||||||
}
|
|
||||||
return color.copy(luminance)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun mixColors(color1: Color, color2: Color, ratio: Float): Color {
|
SideEffect {
|
||||||
val r = (color1.red * ratio + color2.red * (1 - ratio))
|
activity.enableEdgeToEdge(
|
||||||
val g = (color1.green * ratio + color2.green * (1 - ratio))
|
statusBarStyle = SystemBarStyle.auto(
|
||||||
val b = (color1.blue * ratio + color2.blue * (1 - ratio))
|
statusBarScrim.toArgb(),
|
||||||
val a = (color1.alpha * ratio + color2.alpha * (1 - ratio))
|
statusBarScrim.toArgb(),
|
||||||
return Color(r, g, b, a)
|
) { darkMode },
|
||||||
|
navigationBarStyle = when {
|
||||||
|
darkMode -> SystemBarStyle.dark(
|
||||||
|
navigationBarScrim.toArgb()
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> SystemBarStyle.light(
|
||||||
|
navigationBarScrim.toArgb(),
|
||||||
|
navigationBarScrim.toArgb(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,33 +1,108 @@
|
|||||||
package com.sukisu.ultra.ui.theme
|
package com.sukisu.ultra.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.material3.Typography
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
// Set of Material typography styles to start with
|
val Typography = Typography(
|
||||||
val Typography = androidx.compose.material3.Typography(
|
// 大标题
|
||||||
bodyLarge = TextStyle(
|
displayLarge = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 16.sp,
|
fontSize = 57.sp,
|
||||||
lineHeight = 24.sp,
|
lineHeight = 64.sp,
|
||||||
letterSpacing = 0.5.sp
|
letterSpacing = (-0.25).sp
|
||||||
)
|
),
|
||||||
/* Other default text styles to override
|
displayMedium = TextStyle(
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 45.sp,
|
||||||
|
lineHeight = 52.sp,
|
||||||
|
letterSpacing = 0.sp
|
||||||
|
),
|
||||||
|
displaySmall = TextStyle(
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 36.sp,
|
||||||
|
lineHeight = 44.sp,
|
||||||
|
letterSpacing = 0.sp
|
||||||
|
),
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
headlineLarge = TextStyle(
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
fontSize = 32.sp,
|
||||||
|
lineHeight = 40.sp,
|
||||||
|
letterSpacing = 0.sp
|
||||||
|
),
|
||||||
|
headlineMedium = TextStyle(
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
fontSize = 28.sp,
|
||||||
|
lineHeight = 36.sp,
|
||||||
|
letterSpacing = 0.sp
|
||||||
|
),
|
||||||
|
headlineSmall = TextStyle(
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
fontSize = 24.sp,
|
||||||
|
lineHeight = 32.sp,
|
||||||
|
letterSpacing = 0.sp
|
||||||
|
),
|
||||||
|
|
||||||
|
// 标题栏
|
||||||
titleLarge = TextStyle(
|
titleLarge = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
fontSize = 22.sp,
|
fontSize = 22.sp,
|
||||||
lineHeight = 28.sp,
|
lineHeight = 28.sp,
|
||||||
letterSpacing = 0.sp
|
letterSpacing = 0.sp
|
||||||
),
|
),
|
||||||
|
titleMedium = TextStyle(
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
lineHeight = 24.sp,
|
||||||
|
letterSpacing = 0.15.sp
|
||||||
|
),
|
||||||
|
titleSmall = TextStyle(
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
lineHeight = 20.sp,
|
||||||
|
letterSpacing = 0.1.sp
|
||||||
|
),
|
||||||
|
|
||||||
|
// 主体文字
|
||||||
|
bodyLarge = TextStyle(
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
lineHeight = 24.sp,
|
||||||
|
letterSpacing = 0.5.sp
|
||||||
|
),
|
||||||
|
bodyMedium = TextStyle(
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
lineHeight = 20.sp,
|
||||||
|
letterSpacing = 0.25.sp
|
||||||
|
),
|
||||||
|
bodySmall = TextStyle(
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
lineHeight = 16.sp,
|
||||||
|
letterSpacing = 0.4.sp
|
||||||
|
),
|
||||||
|
|
||||||
|
// 标签
|
||||||
|
labelLarge = TextStyle(
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
lineHeight = 20.sp,
|
||||||
|
letterSpacing = 0.1.sp
|
||||||
|
),
|
||||||
|
labelMedium = TextStyle(
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
lineHeight = 16.sp,
|
||||||
|
letterSpacing = 0.5.sp
|
||||||
|
),
|
||||||
labelSmall = TextStyle(
|
labelSmall = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
fontSize = 11.sp,
|
fontSize = 11.sp,
|
||||||
lineHeight = 16.sp,
|
lineHeight = 16.sp,
|
||||||
letterSpacing = 0.5.sp
|
letterSpacing = 0.5.sp
|
||||||
)
|
)
|
||||||
*/
|
|
||||||
)
|
)
|
||||||
@@ -63,10 +63,6 @@ fun Context.applyTransformationToBitmap(bitmap: Bitmap, transformation: Backgrou
|
|||||||
val safeScale = maxOf(0.1f, transformation.scale)
|
val safeScale = maxOf(0.1f, transformation.scale)
|
||||||
matrix.postScale(safeScale, safeScale)
|
matrix.postScale(safeScale, safeScale)
|
||||||
|
|
||||||
// 计算中心点
|
|
||||||
val centerX = targetWidth / 2f
|
|
||||||
val centerY = targetHeight / 2f
|
|
||||||
|
|
||||||
// 计算偏移量,确保不会出现负最大值的问题
|
// 计算偏移量,确保不会出现负最大值的问题
|
||||||
val widthDiff = (bitmap.width * safeScale - targetWidth)
|
val widthDiff = (bitmap.width * safeScale - targetWidth)
|
||||||
val heightDiff = (bitmap.height * safeScale - targetHeight)
|
val heightDiff = (bitmap.height * safeScale - targetHeight)
|
||||||
|
|||||||
@@ -537,7 +537,7 @@ fun getKpmModuleInfo(name: String): String {
|
|||||||
|
|
||||||
fun controlKpmModule(name: String, args: String? = null): Int {
|
fun controlKpmModule(name: String, args: String? = null): Int {
|
||||||
val shell = getRootShell()
|
val shell = getRootShell()
|
||||||
val cmd = "${getKpmmgrPath()} control $name ${args ?: ""}"
|
val cmd = """${getKpmmgrPath()} control $name "${args ?: ""}""""
|
||||||
val result = runCmd(shell, cmd)
|
val result = runCmd(shell, cmd)
|
||||||
return result.trim().toIntOrNull() ?: -1
|
return result.trim().toIntOrNull() ?: -1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
package com.sukisu.ultra.ui.viewmodel
|
package com.sukisu.ultra.ui.viewmodel
|
||||||
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.ServiceConnection
|
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.os.IBinder
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -14,22 +10,23 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import com.sukisu.zako.IKsuInterface
|
|
||||||
import com.sukisu.ultra.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import com.sukisu.ultra.ksuApp
|
import com.sukisu.ultra.ksuApp
|
||||||
import com.sukisu.ultra.ui.KsuService
|
|
||||||
import com.sukisu.ultra.ui.util.HanziToPinyin
|
import com.sukisu.ultra.ui.util.HanziToPinyin
|
||||||
import com.sukisu.ultra.ui.util.KsuCli
|
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.resume
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import com.dergoogler.mmrl.platform.TIMEOUT_MILLIS
|
||||||
|
import com.sukisu.ultra.ui.webui.packageManager
|
||||||
|
import com.sukisu.ultra.ui.webui.userManager
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
|
||||||
class SuperUserViewModel : ViewModel() {
|
class SuperUserViewModel : ViewModel() {
|
||||||
|
val isPlatformAlive get() = Platform.isAlive
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "SuperUserViewModel"
|
private const val TAG = "SuperUserViewModel"
|
||||||
private var apps by mutableStateOf<List<AppInfo>>(emptyList())
|
private var apps by mutableStateOf<List<AppInfo>>(emptyList())
|
||||||
@@ -68,9 +65,9 @@ class SuperUserViewModel : ViewModel() {
|
|||||||
|
|
||||||
// 批量操作相关状态
|
// 批量操作相关状态
|
||||||
var showBatchActions by mutableStateOf(false)
|
var showBatchActions by mutableStateOf(false)
|
||||||
private set
|
internal set
|
||||||
var selectedApps by mutableStateOf<Set<String>>(emptySet())
|
var selectedApps by mutableStateOf<Set<String>>(emptySet())
|
||||||
private set
|
internal set
|
||||||
|
|
||||||
private val sortedList by derivedStateOf {
|
private val sortedList by derivedStateOf {
|
||||||
val comparator = compareBy<AppInfo> {
|
val comparator = compareBy<AppInfo> {
|
||||||
@@ -142,55 +139,65 @@ class SuperUserViewModel : ViewModel() {
|
|||||||
fetchAppList() // 刷新列表以显示最新状态
|
fetchAppList() // 刷新列表以显示最新状态
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun connectKsuService(
|
// 批量更新权限和umount模块设置
|
||||||
onDisconnect: () -> Unit = {}
|
suspend fun updateBatchPermissions(allowSu: Boolean, umountModules: Boolean? = null) {
|
||||||
): Pair<IBinder, ServiceConnection> = suspendCoroutine { continuation ->
|
selectedApps.forEach { packageName ->
|
||||||
val connection = object : ServiceConnection {
|
val app = apps.find { it.packageName == packageName }
|
||||||
override fun onServiceDisconnected(name: ComponentName?) {
|
app?.let {
|
||||||
onDisconnect()
|
val profile = Natives.getAppProfile(packageName, it.uid)
|
||||||
}
|
val updatedProfile = profile.copy(
|
||||||
|
allowSu = allowSu,
|
||||||
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
|
umountModules = umountModules ?: profile.umountModules,
|
||||||
continuation.resume(binder as IBinder to this)
|
nonRootUseDefault = false
|
||||||
|
)
|
||||||
|
if (Natives.setAppProfile(updatedProfile)) {
|
||||||
|
apps = apps.map { app ->
|
||||||
|
if (app.packageName == packageName) {
|
||||||
|
app.copy(profile = updatedProfile)
|
||||||
|
} else {
|
||||||
|
app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
clearSelection()
|
||||||
val intent = Intent(ksuApp, KsuService::class.java)
|
showBatchActions = false // 批量操作完成后退出批量模式
|
||||||
|
fetchAppList() // 刷新列表以显示最新状态
|
||||||
val task = KsuService.bindOrTask(
|
|
||||||
intent,
|
|
||||||
Shell.EXECUTOR,
|
|
||||||
connection,
|
|
||||||
)
|
|
||||||
val shell = KsuCli.SHELL
|
|
||||||
task?.let { it1 -> shell.execTask(it1) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopKsuService() {
|
// 仅更新本地应用配置,避免重新获取整个列表导致滚动位置重置
|
||||||
val intent = Intent(ksuApp, KsuService::class.java)
|
fun updateAppProfileLocally(packageName: String, updatedProfile: Natives.Profile) {
|
||||||
KsuService.stop(intent)
|
apps = apps.map { app ->
|
||||||
|
if (app.packageName == packageName) {
|
||||||
|
app.copy(profile = updatedProfile)
|
||||||
|
} else {
|
||||||
|
app
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetchAppList() {
|
suspend fun fetchAppList() {
|
||||||
isRefreshing = true
|
isRefreshing = true
|
||||||
|
|
||||||
val result = connectKsuService {
|
|
||||||
Log.w(TAG, "KsuService disconnected")
|
|
||||||
}
|
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
withTimeoutOrNull(TIMEOUT_MILLIS) {
|
||||||
|
while (!isPlatformAlive) {
|
||||||
|
delay(500)
|
||||||
|
}
|
||||||
|
} ?: return@withContext // Exit early if timeout
|
||||||
val pm = ksuApp.packageManager
|
val pm = ksuApp.packageManager
|
||||||
val start = SystemClock.elapsedRealtime()
|
val start = SystemClock.elapsedRealtime()
|
||||||
|
|
||||||
val binder = result.first
|
val userInfos = Platform.userManager.getUsers()
|
||||||
val allPackages = IKsuInterface.Stub.asInterface(binder).getPackages(0)
|
val packages = mutableListOf<PackageInfo>()
|
||||||
|
val packageManager = Platform.packageManager
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
for (userInfo in userInfos) {
|
||||||
stopKsuService()
|
Log.i(TAG, "fetchAppList: ${userInfo.id}")
|
||||||
|
packages.addAll(packageManager.getInstalledPackages(0, userInfo.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
val packages = allPackages.list
|
|
||||||
|
|
||||||
apps = packages.map {
|
apps = packages.map {
|
||||||
val appInfo = it.applicationInfo
|
val appInfo = it.applicationInfo
|
||||||
val uid = appInfo!!.uid
|
val uid = appInfo!!.uid
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.sukisu.ultra.ui.webui
|
||||||
|
|
||||||
|
import android.content.ServiceConnection
|
||||||
|
import android.util.Log
|
||||||
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
|
import com.dergoogler.mmrl.platform.hiddenApi.HiddenPackageManager
|
||||||
|
import com.dergoogler.mmrl.platform.hiddenApi.HiddenUserManager
|
||||||
|
import com.dergoogler.mmrl.platform.model.IProvider
|
||||||
|
import com.dergoogler.mmrl.platform.model.PlatformIntent
|
||||||
|
import com.sukisu.ultra.ksuApp
|
||||||
|
import com.sukisu.ultra.Natives
|
||||||
|
import com.topjohnwu.superuser.ipc.RootService
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class KsuLibSuProvider : IProvider {
|
||||||
|
override val name = "KsuLibSu"
|
||||||
|
|
||||||
|
override fun isAvailable() = true
|
||||||
|
|
||||||
|
override suspend fun isAuthorized() = Natives.becomeManager(ksuApp.packageName)
|
||||||
|
|
||||||
|
private val serviceIntent
|
||||||
|
get() = PlatformIntent(
|
||||||
|
ksuApp,
|
||||||
|
Platform.KsuNext,
|
||||||
|
SuService::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun bind(connection: ServiceConnection) {
|
||||||
|
RootService.bind(serviceIntent.intent, connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unbind(connection: ServiceConnection) {
|
||||||
|
RootService.stop(serviceIntent.intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// webui x
|
||||||
|
suspend fun initPlatform() = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val active = Platform.init {
|
||||||
|
this.context = ksuApp
|
||||||
|
this.platform = Platform.KsuNext
|
||||||
|
this.provider = from(KsuLibSuProvider())
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!active) {
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return@withContext active
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("KsuLibSu", "Failed to initialize platform", e)
|
||||||
|
return@withContext false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val Platform.Companion.packageManager get(): HiddenPackageManager = HiddenPackageManager(this.mService)
|
||||||
|
val Platform.Companion.userManager get(): HiddenUserManager = HiddenUserManager(this.mService)
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.sukisu.ultra.ui.webui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.IBinder
|
||||||
|
import com.dergoogler.mmrl.platform.model.PlatformIntent.Companion.getPlatform
|
||||||
|
import com.dergoogler.mmrl.platform.service.ServiceManager
|
||||||
|
import com.topjohnwu.superuser.ipc.RootService
|
||||||
|
|
||||||
|
class SuService : RootService() {
|
||||||
|
override fun onBind(intent: Intent): IBinder {
|
||||||
|
val mode = intent.getPlatform()
|
||||||
|
return ServiceManager(mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,9 +15,11 @@ import androidx.core.view.ViewCompat
|
|||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.webkit.WebViewAssetLoader
|
import androidx.webkit.WebViewAssetLoader
|
||||||
|
import com.dergoogler.mmrl.platform.model.ModId
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.sukisu.ultra.ui.util.createRootShell
|
import com.sukisu.ultra.ui.util.createRootShell
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import com.dergoogler.mmrl.webui.interfaces.WXOptions
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
class WebUIActivity : ComponentActivity() {
|
class WebUIActivity : ComponentActivity() {
|
||||||
@@ -41,7 +43,8 @@ class WebUIActivity : ComponentActivity() {
|
|||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
setTaskDescription(ActivityManager.TaskDescription("KernelSU - $name"))
|
setTaskDescription(ActivityManager.TaskDescription("KernelSU - $name"))
|
||||||
} else {
|
} else {
|
||||||
val taskDescription = ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build()
|
val taskDescription =
|
||||||
|
ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build()
|
||||||
setTaskDescription(taskDescription)
|
setTaskDescription(taskDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +85,7 @@ class WebUIActivity : ComponentActivity() {
|
|||||||
settings.javaScriptEnabled = true
|
settings.javaScriptEnabled = true
|
||||||
settings.domStorageEnabled = true
|
settings.domStorageEnabled = true
|
||||||
settings.allowFileAccess = false
|
settings.allowFileAccess = false
|
||||||
webviewInterface = WebViewInterface(this@WebUIActivity, this, moduleDir)
|
webviewInterface = WebViewInterface(WXOptions(this@WebUIActivity, this, ModId(moduleId)))
|
||||||
addJavascriptInterface(webviewInterface, "ksu")
|
addJavascriptInterface(webviewInterface, "ksu")
|
||||||
setWebViewClient(webViewClient)
|
setWebViewClient(webViewClient)
|
||||||
loadUrl("https://mui.kernelsu.org/index.html")
|
loadUrl("https://mui.kernelsu.org/index.html")
|
||||||
|
|||||||
@@ -0,0 +1,275 @@
|
|||||||
|
package com.sukisu.ultra.ui.webui
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.SideEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.draw.paint
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.animation.core.animateFloat
|
||||||
|
import androidx.compose.animation.core.spring
|
||||||
|
import androidx.compose.animation.core.updateTransition
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import coil.compose.AsyncImagePainter
|
||||||
|
import coil.compose.rememberAsyncImagePainter
|
||||||
|
import com.sukisu.ultra.ui.theme.ThemeConfig
|
||||||
|
import com.sukisu.ultra.ui.theme.Typography
|
||||||
|
import com.sukisu.ultra.ui.theme.loadCustomBackground
|
||||||
|
|
||||||
|
// 提供界面类型的本地组合
|
||||||
|
val LocalIsSecondaryScreen = staticCompositionLocalOf { false }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebUI专用主题配置
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun WebUIXTheme(
|
||||||
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
|
dynamicColor: Boolean = true,
|
||||||
|
isSecondaryScreen: Boolean = false,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
if (!ThemeConfig.backgroundImageLoaded && !ThemeConfig.preventBackgroundRefresh) {
|
||||||
|
context.loadCustomBackground()
|
||||||
|
ThemeConfig.backgroundImageLoaded = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val colorScheme = when {
|
||||||
|
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||||
|
if (darkTheme) {
|
||||||
|
dynamicDarkColorScheme(context).let { scheme ->
|
||||||
|
if (isSecondaryScreen) {
|
||||||
|
scheme.copy(
|
||||||
|
background = scheme.surfaceContainerHighest,
|
||||||
|
surface = scheme.surfaceContainerHighest
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
scheme.copy(
|
||||||
|
background = Color.Transparent,
|
||||||
|
surface = Color.Transparent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dynamicLightColorScheme(context).let { scheme ->
|
||||||
|
if (isSecondaryScreen) {
|
||||||
|
scheme.copy(
|
||||||
|
background = scheme.surfaceContainerHighest,
|
||||||
|
surface = scheme.surfaceContainerHighest
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
scheme.copy(
|
||||||
|
background = Color.Transparent,
|
||||||
|
surface = Color.Transparent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
darkTheme -> {
|
||||||
|
if (isSecondaryScreen) {
|
||||||
|
darkColorScheme().copy(
|
||||||
|
background = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
|
surface = MaterialTheme.colorScheme.surfaceContainerHighest
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
darkColorScheme().copy(
|
||||||
|
background = Color.Transparent,
|
||||||
|
surface = Color.Transparent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
if (isSecondaryScreen) {
|
||||||
|
lightColorScheme().copy(
|
||||||
|
background = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
|
surface = MaterialTheme.colorScheme.surfaceContainerHighest
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
lightColorScheme().copy(
|
||||||
|
background = Color.Transparent,
|
||||||
|
surface = Color.Transparent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigureSystemBars(darkTheme)
|
||||||
|
|
||||||
|
val backgroundUri = remember { mutableStateOf(ThemeConfig.customBackgroundUri) }
|
||||||
|
|
||||||
|
LaunchedEffect(ThemeConfig.customBackgroundUri) {
|
||||||
|
backgroundUri.value = ThemeConfig.customBackgroundUri
|
||||||
|
}
|
||||||
|
val bgImagePainter = backgroundUri.value?.let {
|
||||||
|
rememberAsyncImagePainter(
|
||||||
|
model = it,
|
||||||
|
onError = {
|
||||||
|
ThemeConfig.backgroundImageLoaded = false
|
||||||
|
},
|
||||||
|
onSuccess = {
|
||||||
|
ThemeConfig.backgroundImageLoaded = true
|
||||||
|
ThemeConfig.isThemeChanging = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 背景透明度动画
|
||||||
|
val transition = updateTransition(
|
||||||
|
targetState = ThemeConfig.backgroundImageLoaded,
|
||||||
|
label = "bgTransition"
|
||||||
|
)
|
||||||
|
|
||||||
|
val bgAlpha by transition.animateFloat(
|
||||||
|
label = "bgAlpha",
|
||||||
|
transitionSpec = {
|
||||||
|
spring(
|
||||||
|
dampingRatio = 0.8f,
|
||||||
|
stiffness = 300f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { loaded -> if (loaded) 1f else 0f }
|
||||||
|
CompositionLocalProvider(LocalIsSecondaryScreen provides isSecondaryScreen) {
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = colorScheme,
|
||||||
|
typography = Typography,
|
||||||
|
) {
|
||||||
|
if (isSecondaryScreen) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceContainerHighest)
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.zIndex(-2f)
|
||||||
|
.background(if (darkTheme) Color.Black else Color.White)
|
||||||
|
)
|
||||||
|
|
||||||
|
backgroundUri.value?.let { uri ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.zIndex(-1f)
|
||||||
|
.alpha(bgAlpha)
|
||||||
|
) {
|
||||||
|
bgImagePainter?.let { painter ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.paint(
|
||||||
|
painter = painter,
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
.graphicsLayer {
|
||||||
|
alpha = (painter.state as? AsyncImagePainter.State.Success)?.let { 1f } ?: 0f
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(
|
||||||
|
if (darkTheme) Color.Black.copy(alpha = 0.6f)
|
||||||
|
else Color.White.copy(alpha = 0.1f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(
|
||||||
|
Brush.radialGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color.Transparent,
|
||||||
|
if (darkTheme) Color.Black.copy(alpha = 0.5f)
|
||||||
|
else Color.Black.copy(alpha = 0.2f)
|
||||||
|
),
|
||||||
|
radius = 1200f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.zIndex(1f)
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前界面是否为二级界面
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun isSecondaryScreen(): Boolean {
|
||||||
|
return LocalIsSecondaryScreen.current
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置WebUI的系统栏样式
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun ConfigureSystemBars(
|
||||||
|
darkMode: Boolean,
|
||||||
|
statusBarScrim: Color = Color.Transparent,
|
||||||
|
navigationBarScrim: Color = Color.Transparent
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val activity = context as ComponentActivity
|
||||||
|
|
||||||
|
SideEffect {
|
||||||
|
activity.enableEdgeToEdge(
|
||||||
|
statusBarStyle = SystemBarStyle.auto(
|
||||||
|
statusBarScrim.toArgb(),
|
||||||
|
statusBarScrim.toArgb()
|
||||||
|
) { darkMode },
|
||||||
|
navigationBarStyle = when {
|
||||||
|
darkMode -> SystemBarStyle.dark(
|
||||||
|
navigationBarScrim.toArgb()
|
||||||
|
)
|
||||||
|
else -> SystemBarStyle.light(
|
||||||
|
navigationBarScrim.toArgb(),
|
||||||
|
navigationBarScrim.toArgb()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package com.sukisu.ultra.ui.webui
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.webkit.WebView
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
|
import com.dergoogler.mmrl.platform.model.ModId
|
||||||
|
import com.dergoogler.mmrl.ui.component.Loading
|
||||||
|
import com.dergoogler.mmrl.webui.screen.WebUIScreen
|
||||||
|
import com.dergoogler.mmrl.webui.util.rememberWebUIOptions
|
||||||
|
import com.sukisu.ultra.BuildConfig
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class WebUIXActivity : ComponentActivity() {
|
||||||
|
private lateinit var webView: WebView
|
||||||
|
|
||||||
|
private val userAgent
|
||||||
|
get(): String {
|
||||||
|
val ksuVersion = BuildConfig.VERSION_CODE
|
||||||
|
|
||||||
|
val platform = Platform.get("Unknown") {
|
||||||
|
platform.name
|
||||||
|
}
|
||||||
|
|
||||||
|
val platformVersion = Platform.get(-1) {
|
||||||
|
moduleManager.versionCode
|
||||||
|
}
|
||||||
|
|
||||||
|
val osVersion = Build.VERSION.RELEASE
|
||||||
|
val deviceModel = Build.MODEL
|
||||||
|
|
||||||
|
return "SukiSU /$ksuVersion (Linux; Android $osVersion; $deviceModel; $platform/$platformVersion)"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
|
||||||
|
webView = WebView(this)
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
initPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
val moduleId = intent.getStringExtra("id")!!
|
||||||
|
val name = intent.getStringExtra("name")!!
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
setTaskDescription(ActivityManager.TaskDescription("KernelSU - $name"))
|
||||||
|
} else {
|
||||||
|
val taskDescription =
|
||||||
|
ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build()
|
||||||
|
setTaskDescription(taskDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
WebUIXTheme {
|
||||||
|
var isLoading by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
LaunchedEffect(Platform.isAlive) {
|
||||||
|
while (!Platform.isAlive) {
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
Loading()
|
||||||
|
return@WebUIXTheme
|
||||||
|
}
|
||||||
|
|
||||||
|
val webDebugging = prefs.getBoolean("enable_web_debugging", false)
|
||||||
|
val erudaInject = prefs.getBoolean("use_webuix_eruda", false)
|
||||||
|
val dark = isSystemInDarkTheme()
|
||||||
|
|
||||||
|
val options = rememberWebUIOptions(
|
||||||
|
modId = ModId(moduleId),
|
||||||
|
debug = webDebugging,
|
||||||
|
appVersionCode = BuildConfig.VERSION_CODE,
|
||||||
|
isDarkMode = dark,
|
||||||
|
enableEruda = erudaInject,
|
||||||
|
cls = WebUIXActivity::class.java,
|
||||||
|
userAgentString = userAgent
|
||||||
|
)
|
||||||
|
|
||||||
|
WebUIScreen(
|
||||||
|
webView = webView,
|
||||||
|
options = options,
|
||||||
|
interfaces = listOf(
|
||||||
|
WebViewInterface.factory()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
package com.sukisu.ultra.ui.webui
|
package com.sukisu.ultra.ui.webui
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import android.webkit.JavascriptInterface
|
import android.webkit.JavascriptInterface
|
||||||
import android.webkit.WebView
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
|
import com.dergoogler.mmrl.webui.interfaces.WXOptions
|
||||||
|
import com.dergoogler.mmrl.webui.interfaces.WebUIInterface
|
||||||
|
import com.dergoogler.mmrl.webui.model.JavaScriptInterface
|
||||||
import com.topjohnwu.superuser.CallbackList
|
import com.topjohnwu.superuser.CallbackList
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
@@ -23,12 +24,24 @@ import com.sukisu.ultra.ui.util.controlKpmModule
|
|||||||
import com.sukisu.ultra.ui.util.listKpmModules
|
import com.sukisu.ultra.ui.util.listKpmModules
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
class WebViewInterface(
|
class WebViewInterface(
|
||||||
val context: Context,
|
wxOptions: WXOptions,
|
||||||
private val webView: WebView,
|
) : WebUIInterface(wxOptions) {
|
||||||
private val modDir: String
|
override var name: String = "ksu"
|
||||||
) {
|
|
||||||
|
companion object {
|
||||||
|
fun factory() = JavaScriptInterface(WebViewInterface::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val modDir get() = "/data/adb/modules/${modId.id}"
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@JavascriptInterface
|
||||||
|
fun isSecondaryPage(): Boolean {
|
||||||
|
return isSecondaryScreen()
|
||||||
|
}
|
||||||
|
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
fun exec(cmd: String): String {
|
fun exec(cmd: String): String {
|
||||||
@@ -170,9 +183,9 @@ class WebViewInterface(
|
|||||||
if (context is Activity) {
|
if (context is Activity) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
if (enable) {
|
if (enable) {
|
||||||
hideSystemUI(context.window)
|
hideSystemUI(activity.window)
|
||||||
} else {
|
} else {
|
||||||
showSystemUI(context.window)
|
showSystemUI(activity.window)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,8 +73,7 @@
|
|||||||
<string name="require_kernel_version" formatted="false">現在の KernelSU のバージョン %d は低すぎるため、マネージャーは正常に動作しません。バージョン %d 以上に更新してください!</string>
|
<string name="require_kernel_version" formatted="false">現在の KernelSU のバージョン %d は低すぎるため、マネージャーは正常に動作しません。バージョン %d 以上に更新してください!</string>
|
||||||
<string name="settings_umount_modules_default">デフォルトでモジュールのマウントを解除する</string>
|
<string name="settings_umount_modules_default">デフォルトでモジュールのマウントを解除する</string>
|
||||||
<string name="settings_umount_modules_default_summary">アプリプロファイルの「モジュールのアンマウント」の共通となるデフォルト値です。 有効にすると、プロファイルセットを持たないアプリのシステムに対するすべてのモジュールの変更が削除されます。</string>
|
<string name="settings_umount_modules_default_summary">アプリプロファイルの「モジュールのアンマウント」の共通となるデフォルト値です。 有効にすると、プロファイルセットを持たないアプリのシステムに対するすべてのモジュールの変更が削除されます。</string>
|
||||||
<string name="settings_susfs_toggle">Kprobe フックを非表示にする</string>
|
<string name="settings_susfs_toggle">kprobe フックを無効化</string>
|
||||||
<string name="settings_susfs_toggle_summary">KSU によって生成された Kprobe フックを無効化して、代替となる組み込みの非 Kprobe を有効化します。Kprobe をサポートしない 非 GKI カーネルに適用される同等の機能を実装します。</string>
|
|
||||||
<string name="profile_umount_modules_summary">このオプションを有効にすると、KernelSU はこのアプリのモジュールによって変更されたファイルを復元できるようになります。</string>
|
<string name="profile_umount_modules_summary">このオプションを有効にすると、KernelSU はこのアプリのモジュールによって変更されたファイルを復元できるようになります。</string>
|
||||||
<string name="profile_selinux_domain">ドメイン</string>
|
<string name="profile_selinux_domain">ドメイン</string>
|
||||||
<string name="profile_selinux_rules">ルール</string>
|
<string name="profile_selinux_rules">ルール</string>
|
||||||
@@ -110,14 +109,14 @@
|
|||||||
<string name="app_profile_template_import_empty">クリップボードが空です!</string>
|
<string name="app_profile_template_import_empty">クリップボードが空です!</string>
|
||||||
<string name="module_changelog_failed">変更ログの取得に失敗しました: %s</string>
|
<string name="module_changelog_failed">変更ログの取得に失敗しました: %s</string>
|
||||||
<string name="settings_check_update">更新を確認する</string>
|
<string name="settings_check_update">更新を確認する</string>
|
||||||
<string name="settings_check_update_summary">アプリを開いたときに更新を自動的に確認します</string>
|
<string name="settings_check_update_summary">アプリの起動時に更新を自動で確認します</string>
|
||||||
<string name="grant_root_failed">root の付与に失敗しました!</string>
|
<string name="grant_root_failed">root の付与に失敗しました!</string>
|
||||||
<string name="action">アクション</string>
|
<string name="action">アクション</string>
|
||||||
<string name="open">開く</string>
|
<string name="open">開く</string>
|
||||||
<string name="enable_web_debugging">WebView デバッグを有効化する</string>
|
<string name="enable_web_debugging">WebView デバッグを有効化する</string>
|
||||||
<string name="enable_web_debugging_summary">WebUI のデバッグに使用できます。必要な場合でのみ有効化してください</string>
|
<string name="enable_web_debugging_summary">WebUI のデバッグに使用できます。必要な場合でのみ有効化してください</string>
|
||||||
<string name="direct_install">直接インストール (推奨)</string>
|
<string name="direct_install">直接インストール (推奨)</string>
|
||||||
<string name="select_file">ファイルを選択</string>
|
<string name="select_file">パッチを行うイメージを選択してください</string>
|
||||||
<string name="install_inactive_slot">非アクティブなスロットにインストール (OTA 後)</string>
|
<string name="install_inactive_slot">非アクティブなスロットにインストール (OTA 後)</string>
|
||||||
<string name="install_inactive_slot_warning">再起動後、デバイスは**強制的に**、現在非アクティブなスロットから起動します。
|
<string name="install_inactive_slot_warning">再起動後、デバイスは**強制的に**、現在非アクティブなスロットから起動します。
|
||||||
\nこのオプションは、OTA が完了した後にのみ使用してください。
|
\nこのオプションは、OTA が完了した後にのみ使用してください。
|
||||||
@@ -171,8 +170,8 @@
|
|||||||
<string name="allowlist_restore_failed">許可リストの復元に失敗: %1$s</string>
|
<string name="allowlist_restore_failed">許可リストの復元に失敗: %1$s</string>
|
||||||
<string name="backup_allowlist">許可リストをバックアップ</string>
|
<string name="backup_allowlist">許可リストをバックアップ</string>
|
||||||
<string name="restore_allowlist">許可リストを復元</string>
|
<string name="restore_allowlist">許可リストを復元</string>
|
||||||
<string name="settings_custom_background">カスタム背景を設定</string>
|
<string name="settings_custom_background">アプリの背景を変更</string>
|
||||||
<string name="settings_custom_background_summary">カスタム背景を設定します</string>
|
<string name="settings_custom_background_summary">背景にする画像を選択してください</string>
|
||||||
<string name="settings_card_alpha">ナビゲーションバーの透過</string>
|
<string name="settings_card_alpha">ナビゲーションバーの透過</string>
|
||||||
<string name="settings_restore_default">デフォルトに復元</string>
|
<string name="settings_restore_default">デフォルトに復元</string>
|
||||||
<string name="home_android_version">Android のバージョン</string>
|
<string name="home_android_version">Android のバージョン</string>
|
||||||
@@ -189,22 +188,23 @@
|
|||||||
<string name="selinux_disabled">無効</string>
|
<string name="selinux_disabled">無効</string>
|
||||||
<string name="simple_mode">シンプルモード</string>
|
<string name="simple_mode">シンプルモード</string>
|
||||||
<string name="simple_mode_summary">ON にすると不要なカードを非表示にします</string>
|
<string name="simple_mode_summary">ON にすると不要なカードを非表示にします</string>
|
||||||
<string name="hide_kernel_kernelsu_version">カーネルのバージョンを非表示にする</string>
|
<string name="hide_kernel_kernelsu_version">カーネルのバージョンを非表示</string>
|
||||||
<string name="hide_kernel_kernelsu_version_summary">カーネルのバージョンを非表示にします</string>
|
<string name="hide_kernel_kernelsu_version_summary">カーネルのバージョンを非表示にします</string>
|
||||||
<string name="hide_other_info">その他の情報を非表示にする</string>
|
<string name="hide_other_info">その他の情報を非表示</string>
|
||||||
<string name="hide_other_info_summary">ホームページ上のスーパーユーザー、モジュール、KPM モジュールの数に関する情報を非表示にします</string>
|
<string name="hide_other_info_summary">ホームページ上のスーパーユーザー、モジュール、KPM モジュールの数に関する情報を非表示にします</string>
|
||||||
<string name="hide_susfs_status">SuSFS ステータスを非表示にする</string>
|
<string name="hide_susfs_status">SuSFS ステータスを非表示</string>
|
||||||
<string name="hide_susfs_status_summary">ホームページ上の SuSFS ステータス情報を非表示にします</string>
|
<string name="hide_susfs_status_summary">ホームページ上の SuSFS ステータス情報を非表示にします</string>
|
||||||
<string name="theme_mode">テーマモード</string>
|
<string name="hide_link_card">リンクカードのステータスを非表示</string>
|
||||||
|
<string name="hide_link_card_summary">ホームページ上のリンクカード情報を非表示にします</string>
|
||||||
|
<string name="theme_mode">テーマ</string>
|
||||||
<string name="theme_follow_system">システムに従う</string>
|
<string name="theme_follow_system">システムに従う</string>
|
||||||
<string name="theme_light">ライトカラー</string>
|
<string name="theme_light">ライト</string>
|
||||||
<string name="theme_dark">ダークカラー</string>
|
<string name="theme_dark">ダーク</string>
|
||||||
<string name="manual_hook">手動でフック</string>
|
<string name="manual_hook">手動でフック</string>
|
||||||
<string name="dynamic_color_title">ダイナミックカラー</string>
|
<string name="dynamic_color_title">ダイナミックカラー</string>
|
||||||
<string name="dynamic_color_summary">システムテーマのダイナミックカラーを使用します</string>
|
<string name="dynamic_color_summary">システムテーマのダイナミックカラーを使用します</string>
|
||||||
<string name="choose_theme_color">テーマカラーを選択</string>
|
<string name="choose_theme_color">テーマカラーを選択</string>
|
||||||
<string name="color_default">ホワイト</string>
|
<string name="color_default">ブルー</string>
|
||||||
<string name="color_blue">ブルー</string>
|
|
||||||
<string name="color_green">グリーン</string>
|
<string name="color_green">グリーン</string>
|
||||||
<string name="color_purple">パープル</string>
|
<string name="color_purple">パープル</string>
|
||||||
<string name="color_orange">オレンジ</string>
|
<string name="color_orange">オレンジ</string>
|
||||||
@@ -213,8 +213,8 @@
|
|||||||
<string name="color_ivory">アイボリー</string>
|
<string name="color_ivory">アイボリー</string>
|
||||||
<string name="flash_option">ブラシの設定</string>
|
<string name="flash_option">ブラシの設定</string>
|
||||||
<string name="flash_option_tip">フラッシュするファイルを選択</string>
|
<string name="flash_option_tip">フラッシュするファイルを選択</string>
|
||||||
<string name="horizon_kernel">AnyKernel3 をフラッシュ</string>
|
<string name="horizon_kernel">AnyKernel3 をインストール</string>
|
||||||
<string name="horizon_kernel_summary">AnyKernel3 をフラッシュします</string>
|
<string name="horizon_kernel_summary">AnyKernel3 カーネルファイルをフラッシュします</string>
|
||||||
<string name="root_required">root 権限が必要です</string>
|
<string name="root_required">root 権限が必要です</string>
|
||||||
<string name="copy_failed">ファイルのコピーに失敗しました</string>
|
<string name="copy_failed">ファイルのコピーに失敗しました</string>
|
||||||
<string name="reboot_complete_title">スクラブが完了しました</string>
|
<string name="reboot_complete_title">スクラブが完了しました</string>
|
||||||
@@ -222,48 +222,133 @@
|
|||||||
<string name="yes">はい</string>
|
<string name="yes">はい</string>
|
||||||
<string name="no">いいえ</string>
|
<string name="no">いいえ</string>
|
||||||
<string name="failed_reboot">再起動に失敗しました</string>
|
<string name="failed_reboot">再起動に失敗しました</string>
|
||||||
<string name="batch_authorization">bulk ライセンス</string>
|
<string name="batch_authorization">権限を付与</string>
|
||||||
<string name="batch_cancel_authorization">認証を一括でキャンセル</string>
|
<string name="batch_cancel_authorization">撤回する</string>
|
||||||
<string name="backup">バックアップ</string>
|
<string name="backup">バックアップ</string>
|
||||||
<string name="color_yellow">イエロー</string>
|
<string name="color_yellow">イエロー</string>
|
||||||
<string name="kpm">KPM モジュール</string>
|
<string name="kpm_title">KPM</string>
|
||||||
<string name="kpm_title">カーネルモジュール</string>
|
|
||||||
<string name="kpm_empty">カーネルモジュールは現在インストールされていません</string>
|
<string name="kpm_empty">カーネルモジュールは現在インストールされていません</string>
|
||||||
<string name="kpm_version">リリース</string>
|
<string name="kpm_version">バージョン</string>
|
||||||
<string name="kpm_author">作者</string>
|
<string name="kpm_author">作者</string>
|
||||||
<string name="kpm_uninstall">アンインストール</string>
|
<string name="kpm_uninstall">アンインストール</string>
|
||||||
<string name="kpm_uninstall_success">アンインストールに失敗しました</string>
|
<string name="kpm_uninstall_success">アンインストールに失敗しました</string>
|
||||||
<string name="kpm_uninstall_failed">アンインストールに失敗しました</string>
|
<string name="kpm_uninstall_failed">アンインストールに失敗しました</string>
|
||||||
<string name="kpm_install">インストールを選択</string>
|
<string name="kpm_install">インストール</string>
|
||||||
<string name="kpm_install_success">KPM モジュールの読み込みに成功しました</string>
|
<string name="kpm_install_success">KPM モジュールの読み込みに成功しました</string>
|
||||||
<string name="kpm_install_failed">KPM モジュールの読み込みに失敗しました</string>
|
<string name="kpm_install_failed">KPM モジュールの読み込みに失敗しました</string>
|
||||||
<string name="kpm_args">KPM パラメーター</string>
|
<string name="kpm_args">パラメータ</string>
|
||||||
<string name="kpm_control">完全</string>
|
<string name="kpm_control">実行</string>
|
||||||
<string name="home_kpm_version">KPM のバージョン</string>
|
<string name="home_kpm_version">KPM のバージョン</string>
|
||||||
<string name="close_notice">閉じる</string>
|
<string name="close_notice">閉じる</string>
|
||||||
<string name="kernel_module_notice">以下のカーネルモジュール関数は KernelPatch によって開発され、SukiSU Ultra のカーネルモジュール関数を含むように変更されました</string>
|
<string name="kernel_module_notice">以下のカーネルモジュール関数は KernelPatch によって開発され、SukiSU Ultra のカーネルモジュール関数を含むように変更されました</string>
|
||||||
<string name="home_ContributionCard_kernelsu">SukiSU Ultra の今後にご期待ください</string>
|
<string name="home_ContributionCard_kernelsu">SukiSU Ultra の今後にご期待ください</string>
|
||||||
<string name="kpm_control_success">成功</string>
|
<string name="kpm_control_success">成功</string>
|
||||||
<string name="kpm_control_failed">失敗</string>
|
<string name="kpm_control_failed">失敗</string>
|
||||||
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra は将来的に KSU から比較的に独立したブランチになりますが、公式の KernelSU や MKSU などの貢献に繋がるでしょう!</string>
|
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra は将来的に KSU から比較的に独立したブランチになりますが、公式の KernelSU や MKSU などの貢献に感謝しています!</string>
|
||||||
<string name="not_supported">非対応</string>
|
<string name="not_supported">非対応</string>
|
||||||
<string name="supported">対応</string>
|
<string name="supported">対応</string>
|
||||||
<string name="home_kpm_module">KPM モジュール数: %d </string>
|
<string name="home_kpm_module">"KPM モジュールの数: %d "</string>
|
||||||
<string name="kpm_invalid_file">無効な KPM ファイル</string>
|
<string name="kpm_invalid_file">無効な KPM ファイル</string>
|
||||||
<string name="kernel_patched">カーネルはパッチされていません</string>
|
<string name="kernel_patched">カーネルはパッチされていません</string>
|
||||||
<string name="kernel_not_enabled">カーネルは未設定です</string>
|
<string name="kernel_not_enabled">カーネルは未設定です</string>
|
||||||
<string name="custom_settings">カスタム設定</string>
|
<string name="custom_settings">カスタム設定</string>
|
||||||
<string name="kpm_install_mode">インストール</string>
|
<string name="kpm_install_mode">KPM をインストール</string>
|
||||||
<string name="kpm_install_mode_load">読み込む</string>
|
<string name="kpm_install_mode_load">読み込む</string>
|
||||||
<string name="kpm_install_mode_embed">埋め込む</string>
|
<string name="kpm_install_mode_embed">埋め込む</string>
|
||||||
<string name="kpm_install_mode_description">モジュール:%1\$s のインストールモードを選択してください \n\n読み込む: モジュールを一時的に読み込みます\n埋め込む: システムに恒久的にインストールします</string>
|
<string name="kpm_install_mode_description">選択してください: %1\$s モジュールのインストールモード \n\n読み込む: モジュールを一時的に読み込みます\n埋め込む: システムで恒久的にインストールします</string>
|
||||||
<string name="log_failed_to_check_module_file">モジュールファイルの存在を確認できませんでした</string>
|
<string name="log_failed_to_check_module_file">モジュールファイルの存在を確認できませんでした</string>
|
||||||
<string name="snackbar_failed_to_check_module_file">モジュールファイルが存在するか確認できません</string>
|
<string name="snackbar_failed_to_check_module_file">モジュールファイルが存在するか確認できません</string>
|
||||||
<string name="confirm_uninstall_title">アンインストールを確認</string>
|
<string name="confirm_uninstall_title">アンインストールを確認</string>
|
||||||
<string name="confirm_uninstall_confirm">アンインストール中</string>
|
<string name="confirm_uninstall_confirm">アンインストール</string>
|
||||||
<string name="confirm_uninstall_dismiss">中止</string>
|
<string name="confirm_uninstall_dismiss">キャンセル</string>
|
||||||
<string name="theme_color">テーマカラー</string>
|
<string name="theme_color">テーマカラー</string>
|
||||||
<string name="invalid_file_type">ファイルの種類が正しくありません。.kpm ファイルを選択してください。</string>
|
<string name="invalid_file_type">ファイルの種類が間違っています!.kpm ファイルを選択してください。</string>
|
||||||
<string name="confirm_uninstall_title_with_filename">アンインストール</string>
|
<string name="confirm_uninstall_title_with_filename">アンインストール</string>
|
||||||
<string name="confirm_uninstall_content">次の KPM モジュールがアンインストールされます:\n%s</string>
|
<string name="confirm_uninstall_content">次の KPM がアンインストールされます: %s</string>
|
||||||
|
<string name="settings_susfs_toggle_summary">KernelSU によって作成された kprobe フックを無効化して、代替となるインラインフックを使用します。これは、非 GKI カーネルのフック方式に似た物になります。</string>
|
||||||
|
<string name="image_editor_title">背景画像を調整</string>
|
||||||
|
<string name="image_editor_hint">2 本の指で画像を拡大、1 本の指でドラッグで位置を調整します</string>
|
||||||
|
<string name="background_image_error">イメージを読み込めません</string>
|
||||||
|
<string name="reprovision">再プロビジョニング</string>
|
||||||
|
<!-- Kernel Flash Progress Related -->
|
||||||
|
<string name="horizon_flash_title">カーネルをフラッシュ</string>
|
||||||
|
<string name="horizon_logs_label">ログ:</string>
|
||||||
|
<string name="horizon_flash_complete">フラッシュが完了しました</string>
|
||||||
|
<!-- Flash Status Related -->
|
||||||
|
<string name="horizon_preparing">準備中…</string>
|
||||||
|
<string name="horizon_cleaning_files">ファイルを削除中…</string>
|
||||||
|
<string name="horizon_copying_files">ファイルをコピー中…</string>
|
||||||
|
<string name="horizon_extracting_tool">フラッシュツールを展開中…</string>
|
||||||
|
<string name="horizon_patching_script">フラッシュスクリプトをパッチ中…</string>
|
||||||
|
<string name="horizon_flashing">カーネルをフラッシュ中…</string>
|
||||||
|
<string name="horizon_flash_complete_status">フラッシュが完了しました</string>
|
||||||
|
<!-- Slot selection related strings -->
|
||||||
|
<string name="select_slot_title">フラッシュ先のスロットを選択</string>
|
||||||
|
<string name="select_slot_description">フラッシュする boot のターゲットスロットを選択</string>
|
||||||
|
<string name="slot_a">スロット A</string>
|
||||||
|
<string name="slot_b">スロット B</string>
|
||||||
|
<string name="selected_slot">選択したスロット: %1$s</string>
|
||||||
|
<string name="horizon_getting_original_slot">オリジナルのスロットを取得</string>
|
||||||
|
<string name="horizon_setting_target_slot">指定するスロットを設定</string>
|
||||||
|
<string name="horizon_restoring_original_slot">デフォルトのスロットに復元</string>
|
||||||
|
<string name="current_slot">現在のスロット: %1$s </string>
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="horizon_copy_failed">コピーに失敗しました</string>
|
||||||
|
<string name="horizon_unknown_error">不明なエラー</string>
|
||||||
|
<string name="flash_failed_message">フラッシュに失敗しました</string>
|
||||||
|
<!-- lkm/gki install -->
|
||||||
|
<string name="Lkm_install_methods">LKM の修復またはインストール</string>
|
||||||
|
<string name="GKI_install_methods">GKI または 非 GKI のインストール</string>
|
||||||
|
<string name="kernel_version_log">カーネルのバージョン: %1$s</string>
|
||||||
|
<string name="tool_version_log">パッチ適用ツールの使用: %1$s</string>
|
||||||
|
<string name="configuration">設定</string>
|
||||||
|
<string name="app_settings">アプリの設定</string>
|
||||||
|
<string name="tools">ツール</string>
|
||||||
|
<string name="currently_selected">現在</string>
|
||||||
|
<!-- String resources used in SuperUser -->
|
||||||
|
<string name="clear">削除</string>
|
||||||
|
<string name="apps_with_root">root アプリの権限</string>
|
||||||
|
<string name="apps_with_custom_profile">カスタマイズされたアプリ構成</string>
|
||||||
|
<string name="other_apps">その他のアプリ</string>
|
||||||
|
<string name="no_apps_found">アプリがありません</string>
|
||||||
|
<string name="selinux_enabled_toast">SELinux 有効</string>
|
||||||
|
<string name="selinux_disabled_toast">SELinux 無効</string>
|
||||||
|
<string name="selinux_change_failed">SELinux ステータスの変更に失敗しました</string>
|
||||||
|
<string name="advanced_settings">高度な設定</string>
|
||||||
|
<string name="appearance_settings">ツールバーをカスタマイズ</string>
|
||||||
|
<string name="back">戻る</string>
|
||||||
|
<string name="expand">展開する</string>
|
||||||
|
<string name="collapse">折りたたむ</string>
|
||||||
|
<string name="susfs_enabled">SuSFS 有効</string>
|
||||||
|
<string name="susfs_disabled">SuSFS 無効</string>
|
||||||
|
<string name="background_set_success">背景の設定が成功しました</string>
|
||||||
|
<string name="background_removed">カスタム背景を削除しました</string>
|
||||||
|
<string name="root_require_for_install">root 権限が必要です</string>
|
||||||
|
<!-- KPM display settings -->
|
||||||
|
<string name="show_kpm_info">KPM 機能を表示</string>
|
||||||
|
<string name="show_kpm_info_summary">KPM の情報と機能をホームとボトムバーに表示します (アプリを開き直す必要があります)</string>
|
||||||
|
<!-- Webui X settings -->
|
||||||
|
<string name="use_webuix">WebUI X を使用する</string>
|
||||||
|
<string name="use_webuix_summary">より多くの API をサポートする WebUI の代わりに WebUI X を使用します</string>
|
||||||
|
<string name="use_webuix_eruda">WebUI に Eruda をインジェクトする</string>
|
||||||
|
<string name="use_webuix_eruda_summary">デバッグを容易にするために WebUI X にデバッグコンソールを挿入します。Web デバッグが ON になっている必要があります。</string>
|
||||||
|
<!-- DPI setting related strings -->
|
||||||
|
<string name="dpi_settings">DPI の設定</string>
|
||||||
|
<string name="app_dpi_title">DPI の変更を適用</string>
|
||||||
|
<string name="app_dpi_summary">このアプリのみで画面表示密度を調整します</string>
|
||||||
|
<string name="dpi_size_small">小 </string>
|
||||||
|
<string name="dpi_size_medium">中 </string>
|
||||||
|
<string name="dpi_size_large">大</string>
|
||||||
|
<string name="dpi_size_extra_large">特大</string>
|
||||||
|
<string name="dpi_size_custom">カスタマイズ</string>
|
||||||
|
<string name="dpi_apply_settings">DPI の設定を適用する</string>
|
||||||
|
<string name="dpi_confirm_title">DPI の変更を確認</string>
|
||||||
|
<string name="dpi_confirm_message">アプリの DPI を %1$d から %2$d に変更してもよろしいですか?</string>
|
||||||
|
<string name="dpi_confirm_summary">変更した DPI 設定を適用するにはアプリを再起動する必要がありますが、システムステータスバーや他のアプリには影響しません</string>
|
||||||
|
<string name="dpi_applied_success">DPI は %1$d に変更されました。アプリの再起動後に適用されます。</string>
|
||||||
|
<!-- Language settings related strings -->
|
||||||
|
<string name="language_setting">アプリの言語</string>
|
||||||
|
<string name="language_follow_system">システムに従う</string>
|
||||||
|
<string name="language_changed">言語の変更を適用するために再起動しています</string>
|
||||||
|
<string name="settings_card_dim">カードの暗さを調整</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,32 +1,33 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="home">Home</string>
|
<string name="app_name" translatable="false">SukiSU Ultra</string>
|
||||||
<string name="home_not_installed">Chưa cài đặt</string>
|
<string name="home">Trang chủ</string>
|
||||||
<string name="home_click_to_install">Nhấn vào đây để cài đặt</string>
|
<string name="home_not_installed">Hỗ trợ nhưng chưa cài đặt</string>
|
||||||
|
<string name="home_click_to_install">Bấm để cài đặt</string>
|
||||||
<string name="home_working">Đang hoạt động</string>
|
<string name="home_working">Đang hoạt động</string>
|
||||||
<string name="home_working_version">Phiên bản: %d</string>
|
<string name="home_working_version">Phiên bản: %d</string>
|
||||||
<string name="home_superuser_count">Superusers: %d</string>
|
<string name="home_superuser_count">Ứng dụng đã cấp SU: %d</string>
|
||||||
<string name="home_module_count">Mô-đun: %d</string>
|
<string name="home_module_count">Modules: %d</string>
|
||||||
<string name="home_unsupported">Không được hỗ trợ</string>
|
<string name="home_unsupported">Không được hỗ trợ</string>
|
||||||
<string name="home_unsupported_reason">Không phát hiện được trình điều khiển KernelSU trên kernel của bạn.</string>
|
<string name="home_unsupported_reason">Không phát hiện được trình điều khiển SukiSU Ultra trên Kernel của bạn, Kernel sai?</string>
|
||||||
<string name="home_kernel">Phiên bản Kernel</string>
|
<string name="home_kernel">Phiên bản Kernel</string>
|
||||||
<string name="home_susfs">SuSFS: %s</string>
|
<string name="home_susfs">SuSFS: %s</string>
|
||||||
<string name="home_susfs_version">Phiên bản SuSFS</string>
|
<string name="home_susfs_version">Phiên bản SuSFS</string>
|
||||||
<string name="home_susfs_sus_su">SuS SU</string>
|
<string name="home_susfs_sus_su">SuS SU</string>
|
||||||
<string name="home_manager_version">Phiên bản quản lý</string>
|
<string name="home_manager_version">Phiên bản trình quản lý</string>
|
||||||
<string name="home_fingerprint">Dấu vân tay</string>
|
<string name="home_fingerprint">Dấu vân tay</string>
|
||||||
<string name="home_selinux_status">Trạng thái SELinux</string>
|
<string name="home_selinux_status">Trạng thái SELinux</string>
|
||||||
<string name="selinux_status_disabled">Disabled</string>
|
<string name="selinux_status_disabled">Vô hiệu hoá</string>
|
||||||
<string name="selinux_status_enforcing">Enforcing</string>
|
<string name="selinux_status_enforcing">Enforcing</string>
|
||||||
<string name="selinux_status_permissive">Permissive</string>
|
<string name="selinux_status_permissive">Permissive</string>
|
||||||
<string name="selinux_status_unknown">Không rõ</string>
|
<string name="selinux_status_unknown">Không xác định</string>
|
||||||
<string name="superuser">Superuser</string>
|
<string name="superuser">Superuser</string>
|
||||||
<string name="module_failed_to_enable">Không thể bật mô-đun: %s</string>
|
<string name="module_failed_to_enable">Không thể kích hoạt module: %s</string>
|
||||||
<string name="module_failed_to_disable">Không thể vô hiệu hóa mô-đun: %s</string>
|
<string name="module_failed_to_disable">Không thể vô hiệu hoá module: %s</string>
|
||||||
<string name="module_empty">Không có mô-đun nào được cài đặt</string>
|
<string name="module_empty">Chưa cài module nào</string>
|
||||||
<string name="module">Mô-đun</string>
|
<string name="module">Modules</string>
|
||||||
<string name="module_sort_action_first">Sắp xếp (theo hành động)</string>
|
<string name="module_sort_action_first">Sắp xếp (Theo hành động)</string>
|
||||||
<string name="module_sort_enabled_first">Sắp xếp (theo trạng thái)</string>
|
<string name="module_sort_enabled_first">Sắp xếp (Theo trạng thái)</string>
|
||||||
<string name="uninstall">Gỡ cài đặt</string>
|
<string name="uninstall">Gỡ cài đặt</string>
|
||||||
<string name="restore">Khôi phục</string>
|
<string name="restore">Khôi phục</string>
|
||||||
<string name="module_install">Cài đặt</string>
|
<string name="module_install">Cài đặt</string>
|
||||||
@@ -36,63 +37,63 @@
|
|||||||
<string name="reboot_userspace">Khởi động lại không gian người dùng</string>
|
<string name="reboot_userspace">Khởi động lại không gian người dùng</string>
|
||||||
<string name="reboot_recovery">Khởi động lại vào Recovery</string>
|
<string name="reboot_recovery">Khởi động lại vào Recovery</string>
|
||||||
<string name="reboot_bootloader">Khởi động lại vào Bootloader</string>
|
<string name="reboot_bootloader">Khởi động lại vào Bootloader</string>
|
||||||
<string name="reboot_download">Khởi động lại vào Download mode</string>
|
<string name="reboot_download">Khởi động lại vào Download Mode</string>
|
||||||
<string name="reboot_edl">Khởi động lại vào EDL</string>
|
<string name="reboot_edl">Khởi động lại vào EDL</string>
|
||||||
<string name="about">Thông tin thêm</string>
|
<string name="about">Thông tin</string>
|
||||||
<string name="module_uninstall_confirm">Bạn có chắc chắn muốn gỡ cài đặt mô-đun %s không?</string>
|
<string name="module_uninstall_confirm">Bạn có THẬT SỰ muốn gỡ cài đặt module %s không?</string>
|
||||||
<string name="module_uninstall_success">%s đã gỡ cài đặt</string>
|
<string name="module_uninstall_success">%s đã được gỡ cài đặt</string>
|
||||||
<string name="module_uninstall_failed">Không thể gỡ cài đặt: %s</string>
|
<string name="module_uninstall_failed">Gỡ cài đặt thất bại: %s</string>
|
||||||
<string name="module_version">Phiên bản</string>
|
<string name="module_version">Phiên bản</string>
|
||||||
<string name="module_author">Tác giả</string>
|
<string name="module_author">Tác giả</string>
|
||||||
<string name="refresh">Làm mới</string>
|
<string name="refresh">Làm mới</string>
|
||||||
<string name="show_system_apps">Hiển thị ứng dụng hệ thống</string>
|
<string name="show_system_apps">Hiển thị ứng dụng hệ thống</string>
|
||||||
<string name="hide_system_apps">Ẩn ứng dụng hệ thống</string>
|
<string name="hide_system_apps">Ẩn ứng dụng hệ thống</string>
|
||||||
<string name="send_log">Gửi nhật ký</string>
|
<string name="send_log">Gửi logs</string>
|
||||||
<string name="safe_mode">Chế độ an toàn</string>
|
<string name="safe_mode">CHẾ ĐỘ AN TOÀN</string>
|
||||||
<string name="reboot_to_apply">Khởi động lại để có hiệu lực</string>
|
<string name="reboot_to_apply">Khởi động lại để có hiệu lực</string>
|
||||||
<string name="module_magisk_conflict">Các mô-đun không khả dụng do xung đột với Magisk!</string>
|
<string name="module_magisk_conflict">Các module không khả dụng do xung đột với Magisk!</string>
|
||||||
<string name="home_learn_kernelsu">Tìm hiểu KernelSU</string>
|
<string name="home_learn_kernelsu">Tìm hiểu về KernelSU</string>
|
||||||
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
|
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
|
||||||
<string name="home_click_to_learn_kernelsu">Tìm hiểu cách cài đặt KernelSU và sử dụng các mô-đun</string>
|
<string name="home_click_to_learn_kernelsu">Tìm hiểu cách cài đặt KernelSU và sử dụng các Module!</string>
|
||||||
<string name="home_support_title">Hỗ trợ chúng tôi</string>
|
<string name="home_support_title">Hỗ trợ chúng tôi</string>
|
||||||
<string name="home_support_content">KernelSU sẽ luôn là miễn phí và mã nguồn mở. Tuy nhiên, bạn có thể cho chúng tôi thấy rằng bạn quan tâm bằng cách quyên góp.</string>
|
<string name="home_support_content">KernelSU sẽ luôn là miễn phí và mã nguồn mở. Tuy nhiên, bạn có thể cho chúng tôi thấy rằng bạn quan tâm bằng cách quyên góp!</string>
|
||||||
<string name="about_source_code"><![CDATA[View source code at %1$s<br/>Tham gia kênh %2$s của chúng tôi]]></string>
|
<string name="about_source_code"><![CDATA[Xem mã nguồn tại %1$s<br/>Tham gia kênh %2$s của chúng tôi]]></string>
|
||||||
|
<string name="profile" translatable="false">Hồ sơ ứng dụng</string>
|
||||||
<string name="profile_default">Mặc định</string>
|
<string name="profile_default">Mặc định</string>
|
||||||
<string name="profile_template">Bản mẫu</string>
|
<string name="profile_template">Bản mẫu</string>
|
||||||
<string name="profile_custom">Tùy chỉnh</string>
|
<string name="profile_custom">Tuỳ chỉnh</string>
|
||||||
<string name="profile_name">Tên hồ sơ</string>
|
<string name="profile_name">Tên hồ sơ</string>
|
||||||
<string name="profile_namespace">Tên không gian gắn kết</string>
|
<string name="profile_namespace">Tên không gian gắn kết</string>
|
||||||
<string name="profile_namespace_inherited">Được thừa hưởng</string>
|
<string name="profile_namespace_inherited">Thừa hưởng</string>
|
||||||
<string name="profile_namespace_global">Toàn cầu</string>
|
<string name="profile_namespace_global">Chung</string>
|
||||||
<string name="profile_namespace_individual">Cá nhân</string>
|
<string name="profile_namespace_individual">Riêng biệt</string>
|
||||||
<string name="profile_groups">Nhóm</string>
|
<string name="profile_groups">Nhóm</string>
|
||||||
<string name="profile_capabilities">Khả năng</string>
|
<string name="profile_capabilities">Tính tương thích</string>
|
||||||
<string name="profile_selinux_context">Bối cảnh SELinux</string>
|
<string name="profile_selinux_context">Bối cảnh SELinux</string>
|
||||||
<string name="profile_umount_modules">Bỏ gắn kết mô-đun</string>
|
<string name="profile_umount_modules">Bỏ gắn kết Modules</string>
|
||||||
<string name="failed_to_update_app_profile">Không cập nhật được Hồ sơ ứng dụng cho %s</string>
|
<string name="failed_to_update_app_profile">Cập nhật hồ sơ ứng dụng thất bại cho %s</string>
|
||||||
<string name="require_kernel_version" formatted="false">Phiên bản KernelSU hiện tại %d quá thấp để trình quản lý hoạt động bình thường. Vui lòng nâng cấp lên phiên bản %d hoặc cao hơn!</string>
|
<string name="require_kernel_version" formatted="false">Phiên bản SukiSU Ultra hiện tại %d quá thấp để trình quản lý hoạt động bình thường. Vui lòng cập nhật lên phiên bản %d hoặc cao hơn!</string>
|
||||||
<string name="settings_umount_modules_default">Bỏ gắn kết các mô-đun theo mặc định</string>
|
<string name="settings_umount_modules_default">Bỏ gắn kết các Module cho toàn hệ thống</string>
|
||||||
<string name="settings_umount_modules_default_summary">Giá trị mặc định cho \"Bỏ gắn kết các mô-đun\" trong Hồ sơ ứng dụng. Nếu được bật, nó sẽ xóa tất cả các sửa đổi của mô-đun đối với hệ thống đối và các ứng dụng không có hồ sơ được đặt.</string>
|
<string name="settings_umount_modules_default_summary">Giá trị mặc định chung cho \"Bỏ gắn kết các Module\" trong Hồ sơ ứng dụng. Nếu được bật, mọi thay đổi hệ thống do các Module gây ra sẽ bị gỡ bỏ khỏi hệ thống và các ứng dụng chưa thiết lập hồ sơ</string>
|
||||||
<string name="settings_susfs_toggle">Ẩn móc kprobe</string>
|
<string name="settings_susfs_toggle">Ẩn hooks kprobe</string>
|
||||||
<string name="settings_susfs_toggle_summary">Vô hiệu hóa các móc kprobe do KSU tạo ra và kích hoạt các móc không phải kprobe tích hợp sẵn, triển khai sẽ được áp dụng cho hạt nhân không phải GKI, không hỗ trợ krp</string>
|
<string name="profile_umount_modules_summary">Bật tùy chọn này sẽ cho phép SukiSU Ultra khôi phục mọi tệp đã được các Module sửa đổi cho ứng dụng này</string>
|
||||||
<string name="profile_umount_modules_summary">Bật tùy chọn này sẽ cho phép KernelSU khôi phục mọi tệp đã được các mô-đun sửa đổi cho ứng dụng này.</string>
|
<string name="profile_selinux_domain">Tên miền</string>
|
||||||
<string name="profile_selinux_domain">Domain</string>
|
|
||||||
<string name="profile_selinux_rules">Quy tắc</string>
|
<string name="profile_selinux_rules">Quy tắc</string>
|
||||||
<string name="module_update">Cập nhật</string>
|
<string name="module_update">Cập nhật</string>
|
||||||
<string name="module_downloading">Đang tải xuống mô-đun: %s</string>
|
<string name="module_downloading">Tải xuống module: %s</string>
|
||||||
<string name="module_start_downloading">Bắt đầu tải xuống: %s</string>
|
<string name="module_start_downloading">Đang tải xuống module: %s</string>
|
||||||
<string name="new_version_available">Phiên bản mới %s đã có sẵn, hãy nhấp để cập nhật.</string>
|
<string name="new_version_available">Phiên bản mới %s đã có sẵn, hãy nhấp để cập nhật</string>
|
||||||
<string name="launch_app">Khởi chạy</string>
|
<string name="launch_app">Mở</string>
|
||||||
<string name="force_stop_app" formatted="false">Buộc dừng</string>
|
<string name="force_stop_app" formatted="false">Buộc dừng</string>
|
||||||
<string name="restart_app">Khởi động lại</string>
|
<string name="restart_app">Khởi động lại</string>
|
||||||
<string name="failed_to_update_sepolicy">Không cập nhật được quy tắc SELinux cho %s</string>
|
<string name="failed_to_update_sepolicy">Không cập nhật được quy tắc SELinux cho %s</string>
|
||||||
<string name="module_changelog">Nhật ký thay đổi</string>
|
<string name="module_changelog">Changelog</string>
|
||||||
<string name="settings_profile_template">Mẫu hồ sơ ứng dụng</string>
|
<string name="settings_profile_template">Mẫu hồ sơ ứng dụng</string>
|
||||||
<string name="settings_profile_template_summary">Quản lý mẫu cục bộ và trực tuyến của Hồ sơ ứng dụng</string>
|
<string name="settings_profile_template_summary">Quản lý mẫu cục bộ và trực tuyến của Hồ sơ ứng dụng</string>
|
||||||
<string name="app_profile_template_create">Tạo mẫu</string>
|
<string name="app_profile_template_create">Tạo mẫu</string>
|
||||||
<string name="app_profile_template_edit">Chỉnh sửa mẫu</string>
|
<string name="app_profile_template_edit">Chỉnh sửa mẫu</string>
|
||||||
<string name="app_profile_template_id">ID</string>
|
<string name="app_profile_template_id">ID</string>
|
||||||
<string name="app_profile_template_id_invalid">ID mẫu không hợp lệ</string>
|
<string name="app_profile_template_id_invalid">ID mẫu không hợp lệ/tồn tại</string>
|
||||||
<string name="app_profile_template_name">Tên</string>
|
<string name="app_profile_template_name">Tên</string>
|
||||||
<string name="app_profile_template_description">Miêu tả</string>
|
<string name="app_profile_template_description">Miêu tả</string>
|
||||||
<string name="app_profile_template_save">Lưu</string>
|
<string name="app_profile_template_save">Lưu</string>
|
||||||
@@ -103,97 +104,99 @@
|
|||||||
<string name="app_profile_import_export">Nhập/Xuất</string>
|
<string name="app_profile_import_export">Nhập/Xuất</string>
|
||||||
<string name="app_profile_import_from_clipboard">Nhập từ bộ nhớ tạm clipboard</string>
|
<string name="app_profile_import_from_clipboard">Nhập từ bộ nhớ tạm clipboard</string>
|
||||||
<string name="app_profile_export_to_clipboard">Xuất vào bộ nhớ tạm clipboard</string>
|
<string name="app_profile_export_to_clipboard">Xuất vào bộ nhớ tạm clipboard</string>
|
||||||
<string name="app_profile_template_export_empty">Cannot find local template to export!</string>
|
<string name="app_profile_template_export_empty">Không tìm thấy hồ sơ nội bộ để xuất!</string>
|
||||||
<string name="app_profile_template_import_success">Đã nhập thành công</string>
|
<string name="app_profile_template_import_success">Nhập thành công</string>
|
||||||
<string name="app_profile_template_sync">Đồng bộ mẫu trực tuyến</string>
|
<string name="app_profile_template_sync">Đồng bộ với hồ sơ ứng dụng trực tuyến</string>
|
||||||
<string name="app_profile_template_save_failed">Không lưu được mẫu</string>
|
<string name="app_profile_template_save_failed">Lưu hồ sơ ứng dụng thất bại</string>
|
||||||
<string name="app_profile_template_import_empty">Bộ nhớ tạm đang trống!</string>
|
<string name="app_profile_template_import_empty">Bộ nhớ tạm đang trống!</string>
|
||||||
<string name="module_changelog_failed">Lấy nhật ký thay đổi không thành công: %s</string>
|
<string name="module_changelog_failed">Lấy changelog thất bại: %s</string>
|
||||||
<string name="settings_check_update">Kiểm tra cập nhật</string>
|
<string name="settings_check_update">Kiểm tra cập nhật</string>
|
||||||
<string name="settings_check_update_summary">Tự động kiểm tra cập nhật khi mở ứng dụng</string>
|
<string name="settings_check_update_summary">Tự động kiểm tra cập nhật khi mở ứng dụng</string>
|
||||||
<string name="grant_root_failed">Không cấp được quyền root!</string>
|
<string name="grant_root_failed">Cấp quyền root thất bại!</string>
|
||||||
<string name="action">Khởi chạy</string>
|
<string name="action">Khởi chạy</string>
|
||||||
<string name="open">Mở</string>
|
<string name="open">Mở</string>
|
||||||
<string name="enable_web_debugging">Bật gỡ lỗi WebView</string>
|
<string name="enable_web_debugging">Bật gỡ lỗi WebView</string>
|
||||||
<string name="enable_web_debugging_summary">Có thể sử dụng để gỡ lỗi WebUI. Vui lòng chỉ bật khi cần thiết.</string>
|
<string name="enable_web_debugging_summary">Có thể sử dụng để gỡ lỗi WebUI. Vui lòng chỉ bật khi cần thiết</string>
|
||||||
<string name="direct_install">Cài đặt trực tiếp (Khuyến nghị)</string>
|
<string name="direct_install">Cài đặt trực tiếp (Khuyến nghị)</string>
|
||||||
<string name="select_file">Chọn một tập tin</string>
|
<string name="select_file">Chọn tệp .img cần vá</string>
|
||||||
<string name="install_inactive_slot">Cài đặt vào khe không hoạt động (Sau OTA)</string>
|
<string name="install_inactive_slot">Cài đặt vào phân vùng chưa được sử dụng (Sau OTA)</string>
|
||||||
<string name="install_inactive_slot_warning">Thiết bị của bạn sẽ **BUỘC** phải khởi động vào khe cắm hiện tại không hoạt động sau khi khởi động lại!\nChỉ sử dụng tùy chọn này sau khi OTA hoàn tất.\nTiếp tục?</string>
|
<string name="install_inactive_slot_warning">Thiết bị của bạn sẽ **BUỘC** phải khởi động vào phân vùng chưa được sử dụng!\nChỉ sử dụng tùy chọn này sau khi OTA hoàn tất.\nTiếp tục?</string>
|
||||||
<string name="install_next">Kế tiếp</string>
|
<string name="install_next">Kế tiếp</string>
|
||||||
<string name="select_file_tip">Phân vùng %1$s được khuyến nghị</string>
|
<string name="select_file_tip">Phân vùng %1$s được khuyến nghị</string>
|
||||||
<string name="select_kmi">Chọn KMI</string>
|
<string name="select_kmi">Chọn KMI</string>
|
||||||
<string name="settings_uninstall">Uninstall</string>
|
<string name="settings_uninstall">Gỡ cài đặt</string>
|
||||||
<string name="settings_uninstall_temporary">Gỡ cài đặt tạm thời</string>
|
<string name="settings_uninstall_temporary">Gỡ cài đặt tạm thời</string>
|
||||||
<string name="settings_uninstall_permanent">Gỡ cài đặt vĩnh viễn</string>
|
<string name="settings_uninstall_permanent">Gỡ cài đặt sạch</string>
|
||||||
<string name="settings_restore_stock_image">Khôi phục img ban đầu</string>
|
<string name="settings_restore_stock_image">Khôi phục phân vùng khởi động về mặc định</string>
|
||||||
<string name="settings_uninstall_temporary_message">Gỡ cài đặt tạm thời KernelSU, khôi phục lại trạng thái ban đầu sau lần khởi động lại tiếp theo.</string>
|
<string name="settings_uninstall_temporary_message">Gỡ cài đặt tạm thời SukiSU Ultra, khôi phục lại trạng thái ban đầu sau lần khởi động lại tiếp theo</string>
|
||||||
<string name="settings_uninstall_permanent_message">Gỡ cài đặt KernelSU (Root và tất cả các mô-đun) hoàn toàn và vĩnh viễn.</string>
|
<string name="settings_uninstall_permanent_message">Gỡ cài đặt SukiSU Ultra (Root và tất cả các module) sạch hoàn toàn, trả về trạng thái ban đầu</string>
|
||||||
<string name="settings_restore_stock_image_message">Khôi phục ảnh gốc (nếu có bản sao lưu), thường được sử dụng trước OTA; nếu bạn cần gỡ cài đặt KernelSU, vui lòng sử dụng \"Gỡ cài đặt vĩnh viễn\".</string>
|
<string name="settings_restore_stock_image_message">Khôi phục lại boot lúc đầu (Nếu có bản sao lưu), thường được sử dụng trước OTA; nếu bạn cần gỡ hẳn SukiSU Ultra, sử dụng\"Gỡ cài đặt sạch\"</string>
|
||||||
<string name="flashing">Đang flash...</string>
|
<string name="flashing">Đang flash...</string>
|
||||||
<string name="flash_success">Flash thành công</string>
|
<string name="flash_success">Flash thành công</string>
|
||||||
<string name="flash_failed">Lỗi flash</string>
|
<string name="flash_failed">Flash thất bại</string>
|
||||||
<string name="selected_lkm">LKM đã chọn: %s</string>
|
<string name="selected_lkm">Chọn tệp LKM: %s</string>
|
||||||
<string name="save_log">Lưu nhật ký</string>
|
<string name="save_log">Lưu logs</string>
|
||||||
<string name="log_saved">Nhật ký đã lưu</string>
|
<string name="log_saved">Logs đã được lưu</string>
|
||||||
<string name="status_supported">Được hỗ trợ</string>
|
<string name="status_supported">Được hỗ trợ</string>
|
||||||
<string name="status_not_supported">Không được hỗ trợ</string>
|
<string name="status_not_supported">Không được hỗ trợ</string>
|
||||||
<string name="status_unknown">Không rõ</string>
|
<string name="status_unknown">Không rõ</string>
|
||||||
<string name="sus_su_mode">Chế độ SuS: SU</string>
|
<string name="sus_su_mode">Chế độ SU của SuS:</string>
|
||||||
<!-- Module related -->
|
<!-- Module related -->
|
||||||
<string name="module_install_confirm">Xác nhận cài đặt mô-đun %1$s ?</string>
|
<string name="module_install_confirm">Xác nhận cài đặt Module %1$s?</string>
|
||||||
<string name="unknown_module">Mô-đun không xác định</string>
|
<string name="unknown_module">Module không xác định</string>
|
||||||
<!-- Restore related -->
|
<!-- Restore related -->
|
||||||
<string name="restore_confirm_title">Xác nhận khôi phục mô-đun</string>
|
<string name="restore_confirm_title">Xác nhận khôi phục Modules</string>
|
||||||
<string name="restore_confirm_message">Hành động này sẽ ghi đè lên tất cả các mô-đun hiện có. Tiếp tục?</string>
|
<string name="restore_confirm_message">Hành động này sẽ ghi đè lên tất cả các Module hiện có. Tiếp tục?</string>
|
||||||
<string name="confirm">Xác nhận</string>
|
<string name="confirm">Xác nhận</string>
|
||||||
<string name="cancel">Hủy bỏ</string>
|
<string name="cancel">Huỷ bỏ</string>
|
||||||
<!-- Backup related -->
|
<!-- Backup related -->
|
||||||
<string name="backup_success">Sao lưu thành công (tar.gz)</string>
|
<string name="backup_success">Sao lưu thành công (tar.gz)</string>
|
||||||
<string name="backup_failed">Sao lưu không thành công: %1$s</string>
|
<string name="backup_failed">Sao lưu thất bại: %1$s</string>
|
||||||
<string name="backup_modules">Sao lưu mô-đun</string>
|
<string name="backup_modules">Sao lưu Modules</string>
|
||||||
<string name="restore_modules">Khôi phục các mô-đun</string>
|
<string name="restore_modules">Khôi phục Modules</string>
|
||||||
<!-- Restore related messages -->
|
<!-- Restore related messages -->
|
||||||
<string name="restore_success">Các mô-đun đã được khôi phục thành công, cần khởi động lại</string>
|
<string name="restore_success">Các Module đã được khôi phục thành công, cần khởi động lại</string>
|
||||||
<string name="restore_failed">Khôi phục không thành công: %1$s</string>
|
<string name="restore_failed">Khôi phục thất bại: %1$s</string>
|
||||||
<string name="restart_now">Khởi động lại ngay</string>
|
<string name="restart_now">Khởi động lại ngay</string>
|
||||||
<string name="unknown_error">Lỗi không xác định</string>
|
<string name="unknown_error">Lỗi không xác định</string>
|
||||||
<!-- Command related -->
|
<!-- Command related -->
|
||||||
<string name="command_execution_failed">Thực hiện lệnh không thành công: %1$s</string>
|
<string name="command_execution_failed">Thực hiện lệnh thất bại: %1$s</string>
|
||||||
<!-- Allowlist related -->
|
<!-- Allowlist related -->
|
||||||
<string name="allowlist_backup_success">Sao lưu danh sách cho phép thành công</string>
|
<string name="allowlist_backup_success">Sao lưu danh sách cho phép thành công</string>
|
||||||
<string name="allowlist_backup_failed">Sao lưu danh sách cho phép không thành công: %1$s</string>
|
<string name="allowlist_backup_failed">Sao lưu danh sách cho phép thất bại: %1$s</string>
|
||||||
<string name="allowlist_restore_confirm_title">Xác nhận khôi phục danh sách cho phép</string>
|
<string name="allowlist_restore_confirm_title">Xác nhận khôi phục danh sách cho phép</string>
|
||||||
<string name="allowlist_restore_confirm_message">Hành động này sẽ ghi đè lên danh sách cho phép hiện tại. Tiếp tục?</string>
|
<string name="allowlist_restore_confirm_message">Hành động này sẽ ghi đè lên danh sách cho phép hiện tại. Tiếp tục?</string>
|
||||||
<string name="allowlist_restore_success">Đã khôi phục danh sách cho phép thành công</string>
|
<string name="allowlist_restore_success">Khôi phục danh sách cho phép thành công</string>
|
||||||
<string name="allowlist_restore_failed">Khôi phục danh sách cho phép không thành công: %1$s</string>
|
<string name="allowlist_restore_failed">Khôi phục danh sách cho phép thất bại: %1$s</string>
|
||||||
<string name="backup_allowlist">Sao lưu danh sách cho phép</string>
|
<string name="backup_allowlist">Sao lưu danh sách cho phép</string>
|
||||||
<string name="restore_allowlist">Khôi phục danh sách cho phép</string>
|
<string name="restore_allowlist">Khôi phục danh sách cho phép</string>
|
||||||
<string name="settings_custom_background">Cài đặt nền tùy chỉnh</string>
|
<string name="settings_custom_background">Tuỳ chỉnh nền ứng dụng</string>
|
||||||
<string name="settings_custom_background_summary">Cài đặt tùy chỉnh nền</string>
|
<string name="settings_custom_background_summary">Chọn một hình ảnh làm hình nền</string>
|
||||||
<string name="settings_card_alpha">Thẻ alpha</string>
|
<string name="settings_card_alpha">Độ trong suốt của thanh điều hướng</string>
|
||||||
<string name="settings_restore_default">Khôi phục mặc định</string>
|
<string name="settings_restore_default">Khôi phục mặc định</string>
|
||||||
<string name="home_android_version">Phiên bản Android</string>
|
<string name="home_android_version">Phiên bản Android</string>
|
||||||
<string name="home_device_model">Model thiết bị</string>
|
<string name="home_device_model">Model thiết bị</string>
|
||||||
<string name="su_not_allowed">Không được phép cấp siêu người dùng cho %s</string>
|
<string name="su_not_allowed">Cấp quyền SU không được phép cho: %s</string>
|
||||||
<string name="settings_disable_su">Vô hiệu hóa khả năng tương thích su</string>
|
<string name="settings_disable_su">Vô hiệu hoá khả năng của lệnh SU</string>
|
||||||
<string name="settings_disable_su_summary">Tạm thời vô hiệu hóa mọi ứng dụng khỏi quyền root thông qua lệnh su (các tiến trình root hiện có sẽ không bị ảnh hưởng).</string>
|
<string name="settings_disable_su_summary">Vô hiệu hoá khả năng thực thi lệnh SU để lấy quyền root (Những app đã cấp trước đó không bị ảnh hưởng)</string>
|
||||||
<string name="using_mksu_manager">Bạn đang sử dụng trình quản lý SukiSU thử nghiệm</string>
|
<string name="using_mksu_manager">Bạn đang sử dụng trình quản lý SukiSU Beta</string>
|
||||||
<string name="module_install_multiple_confirm">Bạn có chắc chắn muốn cài đặt các mô-đun %d đã chọn không?</string>
|
<string name="module_install_multiple_confirm">Bạn có chắc muốn cài đặt các Module %d đã chọn không?</string>
|
||||||
<string name="module_install_multiple_confirm_with_names">Bạn có chắc chắn muốn cài đặt các mô-đun %1$d sau không? \n\n%2$s</string>
|
<string name="module_install_multiple_confirm_with_names">Bạn có chắc muốn cài đặt các module %1$d sau không? \n\n%2$s</string>
|
||||||
<string name="more_settings">Nhiều thiết lập hơn</string>
|
<string name="more_settings">Nhiều thiết lập hơn</string>
|
||||||
<string name="selinux">SELinux</string>
|
<string name="selinux">SELinux</string>
|
||||||
<string name="selinux_enabled">Đã bật</string>
|
<string name="selinux_enabled">Đã bật</string>
|
||||||
<string name="selinux_disabled">Đã tắt</string>
|
<string name="selinux_disabled">Đã tắt</string>
|
||||||
<string name="simple_mode">Chế độ đơn giản</string>
|
<string name="simple_mode">Chế độ đơn giản</string>
|
||||||
<string name="simple_mode_summary">Ẩn các thẻ không cần thiết khi bật</string>
|
<string name="simple_mode_summary">Ẩn các thẻ không cần thiết ở Trang chủ</string>
|
||||||
<string name="hide_kernel_kernelsu_version">Ẩn phiên bản kernel</string>
|
<string name="hide_kernel_kernelsu_version">Ẩn phiên bản kernel</string>
|
||||||
<string name="hide_kernel_kernelsu_version_summary">Ẩn phiên bản kernel hiện tại</string>
|
<string name="hide_kernel_kernelsu_version_summary">Ẩn thông tin phiên bản kernel ở Trang chủ</string>
|
||||||
<string name="hide_other_info">Ẩn thông tin thêm</string>
|
<string name="hide_other_info">Ẩn thông tin khác</string>
|
||||||
<string name="hide_other_info_summary">Ẩn thông tin về số lượng app đã được cấp root, mô-đun và mô-đun KPM trên trang chủ</string>
|
<string name="hide_other_info_summary">Ẩn thông tin về số lượng app đã được cấp root, Modules và KPM Modules ở Trang chủ</string>
|
||||||
<string name="hide_susfs_status">Ẩn trạng thái SuSFS</string>
|
<string name="hide_susfs_status">Ẩn trạng thái SuSFS</string>
|
||||||
<string name="hide_susfs_status_summary">Ẩn thông tin trạng thái SuSFS trên trang chủ</string>
|
<string name="hide_susfs_status_summary">Ẩn thông tin trạng thái SuSFS ở Trang chủ</string>
|
||||||
<string name="theme_mode">Chế độ chủ đề</string>
|
<string name="hide_link_card">Ẩn trạng thái thẻ liên kết</string>
|
||||||
|
<string name="hide_link_card_summary">Ẩn thông tin thẻ liên kết ở Trang chủ</string>
|
||||||
|
<string name="theme_mode">Chủ đề</string>
|
||||||
<string name="theme_follow_system">Theo hệ thống</string>
|
<string name="theme_follow_system">Theo hệ thống</string>
|
||||||
<string name="theme_light">Sáng</string>
|
<string name="theme_light">Sáng</string>
|
||||||
<string name="theme_dark">Tối</string>
|
<string name="theme_dark">Tối</string>
|
||||||
@@ -201,63 +204,161 @@
|
|||||||
<string name="dynamic_color_title">Màu sắc động</string>
|
<string name="dynamic_color_title">Màu sắc động</string>
|
||||||
<string name="dynamic_color_summary">Màu sắc động sử dụng chủ đề hệ thống</string>
|
<string name="dynamic_color_summary">Màu sắc động sử dụng chủ đề hệ thống</string>
|
||||||
<string name="choose_theme_color">Chọn màu chủ đề</string>
|
<string name="choose_theme_color">Chọn màu chủ đề</string>
|
||||||
<string name="color_default">Trắng</string>
|
<string name="color_default">Xanh dương</string>
|
||||||
<string name="color_blue">Xanh dương</string>
|
|
||||||
<string name="color_green">Xanh lá</string>
|
<string name="color_green">Xanh lá</string>
|
||||||
<string name="color_purple">Tím</string>
|
<string name="color_purple">Tím</string>
|
||||||
<string name="color_orange">Cam</string>
|
<string name="color_orange">Cam</string>
|
||||||
<string name="color_pink">Hồng</string>
|
<string name="color_pink">Hồng</string>
|
||||||
<string name="color_gray">Xám</string>
|
<string name="color_gray">Xám</string>
|
||||||
<string name="color_ivory">Ngà voi</string>
|
<string name="color_ivory">Trắng ngà</string>
|
||||||
<string name="flash_option">Tùy chọn cọ</string>
|
<string name="flash_option">Tuỳ chọn Flash</string>
|
||||||
<string name="flash_option_tip">Chọn tập tin cần flash</string>
|
<string name="flash_option_tip">Chọn tập tin cần Flash</string>
|
||||||
<string name="horizon_kernel">File Anykernel3</string>
|
<string name="horizon_kernel">Tệp Anykernel3</string>
|
||||||
|
<string name="horizon_kernel_summary">Flash tệp Kernel AnyKernel3</string>
|
||||||
<string name="root_required">Yêu cầu quyền root</string>
|
<string name="root_required">Yêu cầu quyền root</string>
|
||||||
<string name="copy_failed">Sao chép tập tin không thành công</string>
|
<string name="copy_failed">Sao chép tập tin thất bại</string>
|
||||||
<string name="reboot_complete_title">Hoàn tất</string>
|
<string name="reboot_complete_title">Khởi động lại để hoàn tất</string>
|
||||||
<string name="reboot_complete_msg">khởi động lại ngay lập tức?</string>
|
<string name="reboot_complete_msg">Khởi động lại ngay lập tức?</string>
|
||||||
<string name="yes">Có</string>
|
<string name="yes">Có</string>
|
||||||
<string name="no">Không</string>
|
<string name="no">Không</string>
|
||||||
<string name="failed_reboot">Khởi động lại không thành công</string>
|
<string name="failed_reboot">Khởi động lại thất bại</string>
|
||||||
<string name="batch_authorization">Giấy phép số lượng lớn</string>
|
<string name="batch_authorization">Uỷ quyền</string>
|
||||||
<string name="batch_cancel_authorization">Hủy ủy quyền hàng loạt</string>
|
<string name="batch_cancel_authorization">Hủy ủy quyền hàng loạt</string>
|
||||||
<string name="backup">Sao lưu</string>
|
<string name="backup">Sao lưu</string>
|
||||||
<string name="color_yellow">Vàng</string>
|
<string name="color_yellow">Vàng</string>
|
||||||
<string name="kpm">Mô-đun kpm</string>
|
<string name="kpm_title">KPM</string>
|
||||||
<string name="kpm_title">Mô-đun KPM</string>
|
<string name="kpm_empty">Không có Kernel Modules nào được cài đặt tại thời điểm này</string>
|
||||||
<string name="kpm_empty">Không có mô-đun nào được cài đặt.</string>
|
<string name="kpm_version">Phiên bản</string>
|
||||||
<string name="kpm_version">Phát hành</string>
|
|
||||||
<string name="kpm_author">Tác giả</string>
|
<string name="kpm_author">Tác giả</string>
|
||||||
<string name="kpm_uninstall">Gỡ cài đặt</string>
|
<string name="kpm_uninstall">Gỡ cài đặt</string>
|
||||||
<string name="kpm_uninstall_success">Đã gỡ cài đặt thành công</string>
|
<string name="kpm_uninstall_success">Gỡ cài đặt thành công</string>
|
||||||
<string name="kpm_uninstall_failed">Không thể gỡ cài đặt</string>
|
<string name="kpm_uninstall_failed">Gỡ cài đặt thất bại</string>
|
||||||
<string name="kpm_install">Cài đặt</string>
|
<string name="kpm_install">Cài đặt</string>
|
||||||
<string name="kpm_install_success">Tải module kpm thành công</string>
|
<string name="kpm_install_success">Tải Module KPM thành công</string>
|
||||||
<string name="kpm_install_failed">Tải module kpm không thành công</string>
|
<string name="kpm_install_failed">Tải Module KPM thất bại</string>
|
||||||
<string name="kpm_args">thông số kpm</string>
|
<string name="kpm_args">Thông số KPM</string>
|
||||||
<string name="kpm_control">Tùy chỉnh</string>
|
<string name="kpm_control">Tuỳ chỉnh</string>
|
||||||
<string name="home_kpm_version">Phiên bản KPM</string>
|
<string name="home_kpm_version">Phiên bản KPM</string>
|
||||||
<string name="close_notice">Đóng</string>
|
<string name="close_notice">Đóng</string>
|
||||||
<string name="kernel_module_notice">Các chức năng mô-đun kernel sau đây được KernelPatch phát triển và sửa đổi để bao gồm các chức năng mô-đun hạt nhân của SukiSU Ultra</string>
|
<string name="kernel_module_notice">Các chức năng Module Kernel sau đây được KernelPatch phát triển và sửa đổi để tương thích với các chức năng Module Kernel của SukiSU Ultra</string>
|
||||||
<string name="home_ContributionCard_kernelsu">SukiSU Ultra mong đợi:</string>
|
<string name="home_ContributionCard_kernelsu">SukiSU Ultra mong đợi</string>
|
||||||
<string name="kpm_control_success">Thành công</string>
|
<string name="kpm_control_success">Thành công</string>
|
||||||
<string name="kpm_control_failed">Thất bại</string>
|
<string name="kpm_control_failed">Thất bại</string>
|
||||||
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra sẽ là một nhánh tương đối độc lập của KSU trong tương lai, nhưng xin cảm ơn KernelSU và MKSU chính thức vì những đóng góp của họ!</string>
|
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra sẽ là một nhánh tương đối độc lập của KSU trong tương lai, nhưng chúng tôi xin cảm ơn KernelSU và MKSU,... vì những đóng góp của họ!</string>
|
||||||
<string name="not_supported">Không được hỗ trợ</string>
|
<string name="not_supported">Không được hỗ trợ</string>
|
||||||
<string name="supported">Được hỗ trợ</string>
|
<string name="supported">Được hỗ trợ</string>
|
||||||
<string name="home_kpm_module">Mô-đun KPM:%d </string>
|
<string name="home_kpm_module">Module KPM: %d</string>
|
||||||
<string name="kpm_invalid_file">Tệp KPM không hợp lệ</string>
|
<string name="kpm_invalid_file">Tệp KPM không hợp lệ</string>
|
||||||
<string name="kernel_patched">chưa được vá</string>
|
<string name="kernel_patched">Kernel chưa được vá</string>
|
||||||
<string name="kernel_not_enabled">chưa được vá</string>
|
<string name="kernel_not_enabled">Kernel chưa được cấu hình</string>
|
||||||
<string name="custom_settings">Cài đặt tùy chỉnh</string>
|
<string name="custom_settings">Cài đặt tùy chỉnh</string>
|
||||||
<string name="kpm_install_mode">Cài đặt</string>
|
<string name="kpm_install_mode">Cài đặt KPM</string>
|
||||||
<string name="kpm_install_mode_load">Tải</string>
|
<string name="kpm_install_mode_load">Tải</string>
|
||||||
<string name="kpm_install_mode_embed">Nhúng</string>
|
<string name="kpm_install_mode_embed">Nhúng</string>
|
||||||
<string name="kpm_install_mode_description">Vui lòng:%1\$s chọn chế độ cài đặt mô-đun \n\nTải: Tải tạm thời mô-đun \nNhúng: Cài đặt vĩnh viễn vào hệ thống</string>
|
<string name="kpm_install_mode_description">Vui lòng:%1\$s chọn chế độ cài đặt Module \n\nTải: Tải tạm thời Module \nNhúng: Cài đặt vĩnh viễn vào hệ thống</string>
|
||||||
<string name="log_failed_to_check_module_file">Không kiểm tra được sự tồn tại của tệp mô-đun</string>
|
<string name="log_failed_to_check_module_file">Kiểm tra tệp Module thất bại</string>
|
||||||
<string name="snackbar_failed_to_check_module_file">Không thể kiểm tra xem tệp mô-đun có tồn tại không</string>
|
<string name="snackbar_failed_to_check_module_file">Không thể kiểm tra tệp Module</string>
|
||||||
<string name="confirm_uninstall_title">Xác nhận gỡ cài đặt</string>
|
<string name="confirm_uninstall_title">Xác nhận gỡ cài đặt</string>
|
||||||
<string name="confirm_uninstall_confirm">loại bỏ</string>
|
<string name="confirm_uninstall_confirm">Gỡ cài đặt</string>
|
||||||
<string name="confirm_uninstall_dismiss">bãi bỏ</string>
|
<string name="confirm_uninstall_dismiss">Hủy bỏ</string>
|
||||||
<string name="theme_color">Màu chủ đề</string>
|
<string name="theme_color">Màu chủ đề</string>
|
||||||
|
<string name="invalid_file_type">Loại tệp không đúng! Vui lòng chọn tệp .kpm</string>
|
||||||
|
<string name="confirm_uninstall_title_with_filename">Gỡ cài đặt</string>
|
||||||
|
<string name="confirm_uninstall_content">KPM sau đây sẽ được gỡ cài đặt: %s</string>
|
||||||
|
<string name="settings_susfs_toggle_summary">Vô hiệu hóa các hook kprobe được tạo bởi SukiSU Ultra, thay vào đó sử dụng các hook nội tuyến, tương tự như phương pháp hook kernel không phải GKI</string>
|
||||||
|
<string name="image_editor_title">Điều chỉnh hình nền</string>
|
||||||
|
<string name="image_editor_hint">Sử dụng hai ngón tay để phóng to hình ảnh và một ngón tay để kéo nó để điều chỉnh vị trí</string>
|
||||||
|
<string name="background_image_error">Không thể tải hình ảnh</string>
|
||||||
|
<string name="reprovision">Chọn lại</string>
|
||||||
|
<!-- Kernel Flash Progress Related -->
|
||||||
|
<string name="horizon_flash_title">Kernel Flashing</string>
|
||||||
|
<string name="horizon_logs_label">Logs:</string>
|
||||||
|
<string name="horizon_flash_complete">Flash Complete</string>
|
||||||
|
<!-- Flash Status Related -->
|
||||||
|
<string name="horizon_preparing">Preparing…</string>
|
||||||
|
<string name="horizon_cleaning_files">Cleaning files…</string>
|
||||||
|
<string name="horizon_copying_files">Copying files…</string>
|
||||||
|
<string name="horizon_extracting_tool">Extracting flash tool…</string>
|
||||||
|
<string name="horizon_patching_script">Patching flash script…</string>
|
||||||
|
<string name="horizon_flashing">Flashing kernel…</string>
|
||||||
|
<string name="horizon_flash_complete_status">Flash completed</string>
|
||||||
|
<!-- Slot selection related strings -->
|
||||||
|
<string name="select_slot_title">Chọn Slot Flash</string>
|
||||||
|
<string name="select_slot_description">Vui lòng chọn Slot để Flash Boot</string>
|
||||||
|
<string name="slot_a">Slot A</string>
|
||||||
|
<string name="slot_b">Slot B</string>
|
||||||
|
<string name="selected_slot">Chọn Slot: %1$s</string>
|
||||||
|
<string name="horizon_getting_original_slot">Lấy Slot ban đầu</string>
|
||||||
|
<string name="horizon_setting_target_slot">Cài đặt Slot được chỉ định</string>
|
||||||
|
<string name="horizon_restoring_original_slot">Khôi phục Slot mặc định</string>
|
||||||
|
<string name="current_slot">Slot hiện tại:%1$s </string>
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="horizon_copy_failed">Copy thất bại</string>
|
||||||
|
<string name="horizon_unknown_error">Lỗi không xác định</string>
|
||||||
|
<string name="flash_failed_message">Flash thất bại</string>
|
||||||
|
<!-- lkm/gki install -->
|
||||||
|
<string name="Lkm_install_methods">LKM cài đặt</string>
|
||||||
|
<string name="GKI_install_methods">GKI/non-GKI cài đặt</string>
|
||||||
|
<string name="kernel_version_log">Phiên bản Kernel:%1$s</string>
|
||||||
|
<string name="tool_version_log">Sử dụng công cụ vá lỗi:%1$s</string>
|
||||||
|
<string name="configuration">Cấu hình</string>
|
||||||
|
<string name="app_settings">Cài đặt ứng dụng</string>
|
||||||
|
<string name="tools">Công cụ</string>
|
||||||
|
<string name="currently_selected">Hiện tại</string>
|
||||||
|
<!-- String resources used in SuperUser -->
|
||||||
|
<string name="clear">Loại bỏ</string>
|
||||||
|
<string name="apps_with_root">Quyền Root của ứng dụng</string>
|
||||||
|
<string name="apps_with_custom_profile">Cấu hình tuỳ chỉnh của ứng dụng</string>
|
||||||
|
<string name="other_apps">Các ứng dụng khác</string>
|
||||||
|
<string name="no_apps_found">Không tìm thấy ứng dụng</string>
|
||||||
|
<string name="selinux_enabled_toast">SELinux đã bật</string>
|
||||||
|
<string name="selinux_disabled_toast">SELinux đã tắt</string>
|
||||||
|
<string name="selinux_change_failed">Thay đổi trạng thái SELinux thất bại</string>
|
||||||
|
<string name="advanced_settings">Cài đặt nâng cao</string>
|
||||||
|
<string name="appearance_settings">Tùy chỉnh thanh công cụ</string>
|
||||||
|
<string name="back">Trở lại</string>
|
||||||
|
<string name="expand">Be in full swing</string>
|
||||||
|
<string name="collapse">put away</string>
|
||||||
|
<string name="susfs_enabled">SuSFS đã bật</string>
|
||||||
|
<string name="susfs_disabled">SuSFS đã tắt</string>
|
||||||
|
<string name="background_set_success">Đã cài đặt hình nền thành công</string>
|
||||||
|
<string name="background_removed">Đã xóa hình nền tùy chỉnh</string>
|
||||||
|
<string name="root_require_for_install">Yêu cầu quyền root</string>
|
||||||
|
<!-- KPM display settings -->
|
||||||
|
<string name="show_kpm_info">Hiển thị chức năng KPM</string>
|
||||||
|
<string name="show_kpm_info_summary">Hiển thị thông tin KPM, chức năng ở trang chủ và thanh dưới cùng (Cần khởi động lại ứng dụng)</string>
|
||||||
|
<!-- Webui X settings -->
|
||||||
|
<string name="use_webuix">Sử dụng WebUI X</string>
|
||||||
|
<string name="use_webuix_summary">Sử dụng WebUI X thay cho WebUI vì hỗ trợ nhiều API hơn</string>
|
||||||
|
<string name="use_webuix_eruda">Tiêm Eruda vào WebUI X</string>
|
||||||
|
<string name="use_webuix_eruda_summary">Chèn bảng điều khiển gỡ lỗi vào WebUI X để gỡ lỗi dễ dàng hơn. Yêu cầu bật gỡ lỗi web</string>
|
||||||
|
<!-- DPI setting related strings -->
|
||||||
|
<string name="dpi_settings">Cài đặt DPI</string>
|
||||||
|
<string name="app_dpi_title">Tuỳ chỉnh DPI</string>
|
||||||
|
<string name="app_dpi_summary">Điều chỉnh DPI hiển thị cho ứng dụng</string>
|
||||||
|
<string name="dpi_size_small">Nhỏ</string>
|
||||||
|
<string name="dpi_size_medium">Vừa</string>
|
||||||
|
<string name="dpi_size_large">Lớn</string>
|
||||||
|
<string name="dpi_size_extra_large">Cực lớn</string>
|
||||||
|
<string name="dpi_size_custom">DPI đang hiển thị</string>
|
||||||
|
<string name="dpi_apply_settings">Áp dụng</string>
|
||||||
|
<string name="dpi_confirm_title">Xác nhận thay đổi DPI</string>
|
||||||
|
<string name="dpi_confirm_message">Bạn có chắc chắn muốn thay đổi DPI của ứng dụng từ %1$d thành %2$d?</string>
|
||||||
|
<string name="dpi_confirm_summary">Ứng dụng cần được khởi động lại để áp dụng cài đặt DPI mới, không ảnh hưởng đến thanh trạng thái hệ thống hoặc các ứng dụng khác</string>
|
||||||
|
<string name="dpi_applied_success">DPI đã được đặt thành %1$d, có hiệu lực sau khi khởi động lại ứng dụng</string>
|
||||||
|
<!-- Language settings related strings -->
|
||||||
|
<string name="language_setting">Ngôn ngữ ứng dụng</string>
|
||||||
|
<string name="language_follow_system">Theo dõi hệ thống</string>
|
||||||
|
<string name="language_changed">Ngôn ngữ đã thay đổi, khởi động lại để áp dụng thay đổi</string>
|
||||||
|
<string name="settings_card_dim">Điều chỉnh độ tối của thẻ</string>
|
||||||
|
<!-- Super User Related -->
|
||||||
|
<string name="scroll_to_top">Lên trên</string>
|
||||||
|
<string name="scroll_to_bottom">Xuống dưới</string>
|
||||||
|
<string name="scroll_to_top_description">Cuộn lên trên</string>
|
||||||
|
<string name="scroll_to_bottom_description">Cuộn xuống dưới</string>
|
||||||
|
<string name="authorized">Uỷ quyền</string>
|
||||||
|
<string name="unauthorized">Huỷ uỷ quyền</string>
|
||||||
|
<string name="selected">Đã chọn</string>
|
||||||
|
<string name="select">Chọn</string>
|
||||||
|
<string name="profile_umount_modules_disable">Vô hiệu hóa tuỳ chỉnh của các module</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -117,7 +117,7 @@
|
|||||||
<string name="enable_web_debugging">启用 WebView 调试</string>
|
<string name="enable_web_debugging">启用 WebView 调试</string>
|
||||||
<string name="enable_web_debugging_summary">可用于调试 WebUI 。请仅在需要时启用。</string>
|
<string name="enable_web_debugging_summary">可用于调试 WebUI 。请仅在需要时启用。</string>
|
||||||
<string name="direct_install">直接安装(推荐)</string>
|
<string name="direct_install">直接安装(推荐)</string>
|
||||||
<string name="select_file">选择一个文件</string>
|
<string name="select_file">选择一个需要修补的镜像</string>
|
||||||
<string name="install_inactive_slot">安装到未使用的槽位(OTA 后)</string>
|
<string name="install_inactive_slot">安装到未使用的槽位(OTA 后)</string>
|
||||||
<string name="install_inactive_slot_warning">将在重启后强制切换到另一个槽位!\n注意只能在 OTA 更新完成后的重启之前使用。\n确认?</string>
|
<string name="install_inactive_slot_warning">将在重启后强制切换到另一个槽位!\n注意只能在 OTA 更新完成后的重启之前使用。\n确认?</string>
|
||||||
<string name="install_next">下一步</string>
|
<string name="install_next">下一步</string>
|
||||||
@@ -172,7 +172,7 @@
|
|||||||
<string name="restore_allowlist">还原应用列表</string>
|
<string name="restore_allowlist">还原应用列表</string>
|
||||||
<string name="settings_custom_background">自定义背景</string>
|
<string name="settings_custom_background">自定义背景</string>
|
||||||
<string name="settings_custom_background_summary">选择一张图片作为应用背景</string>
|
<string name="settings_custom_background_summary">选择一张图片作为应用背景</string>
|
||||||
<string name="settings_card_alpha">卡片透明度</string>
|
<string name="settings_card_alpha">卡片不透明度</string>
|
||||||
<string name="settings_restore_default">恢复默认</string>
|
<string name="settings_restore_default">恢复默认</string>
|
||||||
<string name="home_android_version">Android 版本</string>
|
<string name="home_android_version">Android 版本</string>
|
||||||
<string name="home_device_model">设备</string>
|
<string name="home_device_model">设备</string>
|
||||||
@@ -194,6 +194,8 @@
|
|||||||
<string name="hide_other_info_summary">隐藏主页上的超级用户数、模块数和 KPM 模块数信息</string>
|
<string name="hide_other_info_summary">隐藏主页上的超级用户数、模块数和 KPM 模块数信息</string>
|
||||||
<string name="hide_susfs_status">隐藏 SuSFS 状态信息</string>
|
<string name="hide_susfs_status">隐藏 SuSFS 状态信息</string>
|
||||||
<string name="hide_susfs_status_summary">隐藏主页上的 SuSFS 状态信息</string>
|
<string name="hide_susfs_status_summary">隐藏主页上的 SuSFS 状态信息</string>
|
||||||
|
<string name="hide_link_card">隐藏链接卡片</string>
|
||||||
|
<string name="hide_link_card_summary">隐藏主页上的链接卡片信息</string>
|
||||||
<string name="theme_mode">主题模式</string>
|
<string name="theme_mode">主题模式</string>
|
||||||
<string name="theme_follow_system">跟随系统</string>
|
<string name="theme_follow_system">跟随系统</string>
|
||||||
<string name="theme_light">浅色</string>
|
<string name="theme_light">浅色</string>
|
||||||
@@ -202,8 +204,7 @@
|
|||||||
<string name="dynamic_color_title">动态颜色</string>
|
<string name="dynamic_color_title">动态颜色</string>
|
||||||
<string name="dynamic_color_summary">使用系统主题的动态颜色</string>
|
<string name="dynamic_color_summary">使用系统主题的动态颜色</string>
|
||||||
<string name="choose_theme_color">选择主题色</string>
|
<string name="choose_theme_color">选择主题色</string>
|
||||||
<string name="color_default">白色</string>
|
<string name="color_default">蓝色</string>
|
||||||
<string name="color_blue">蓝色</string>
|
|
||||||
<string name="color_green">绿色</string>
|
<string name="color_green">绿色</string>
|
||||||
<string name="color_purple">紫色</string>
|
<string name="color_purple">紫色</string>
|
||||||
<string name="color_orange">橙色</string>
|
<string name="color_orange">橙色</string>
|
||||||
@@ -221,10 +222,9 @@
|
|||||||
<string name="yes">是</string>
|
<string name="yes">是</string>
|
||||||
<string name="no">否</string>
|
<string name="no">否</string>
|
||||||
<string name="failed_reboot">重启失败</string>
|
<string name="failed_reboot">重启失败</string>
|
||||||
<string name="batch_authorization">批量授权</string>
|
<string name="batch_authorization">授权</string>
|
||||||
<string name="batch_cancel_authorization">批量取消授权</string>
|
<string name="batch_cancel_authorization">撤销</string>
|
||||||
<string name="color_yellow">黄色</string>
|
<string name="color_yellow">黄色</string>
|
||||||
<string name="kpm">内核模块</string>
|
|
||||||
<string name="kpm_title">内核模块</string>
|
<string name="kpm_title">内核模块</string>
|
||||||
<string name="kpm_empty">暂无已安装的内核模块</string>
|
<string name="kpm_empty">暂无已安装的内核模块</string>
|
||||||
<string name="kpm_version">版本</string>
|
<string name="kpm_version">版本</string>
|
||||||
@@ -284,12 +284,77 @@
|
|||||||
<string name="slot_a">A槽位</string>
|
<string name="slot_a">A槽位</string>
|
||||||
<string name="slot_b">B槽位</string>
|
<string name="slot_b">B槽位</string>
|
||||||
<string name="selected_slot">已选择槽位: %1$s</string>
|
<string name="selected_slot">已选择槽位: %1$s</string>
|
||||||
<!-- 错误信息 -->
|
|
||||||
<string name="horizon_copy_failed">复制失败</string>
|
|
||||||
<string name="horizon_unknown_error">未知错误</string>
|
|
||||||
<string name="horizon_getting_original_slot">获取原有槽位</string>
|
<string name="horizon_getting_original_slot">获取原有槽位</string>
|
||||||
<string name="horizon_setting_target_slot">设置指定槽位</string>
|
<string name="horizon_setting_target_slot">设置指定槽位</string>
|
||||||
<string name="horizon_restoring_original_slot">恢复默认槽位</string>
|
<string name="horizon_restoring_original_slot">恢复默认槽位</string>
|
||||||
<string name="current_slot">当前槽位:%1$s </string>
|
<string name="current_slot">当前槽位:%1$s </string>
|
||||||
|
<!-- 错误信息 -->
|
||||||
|
<string name="horizon_copy_failed">复制失败</string>
|
||||||
|
<string name="horizon_unknown_error">未知错误</string>
|
||||||
<string name="flash_failed_message">刷写失败</string>
|
<string name="flash_failed_message">刷写失败</string>
|
||||||
|
<!-- lkm/gki install -->
|
||||||
|
<string name="Lkm_install_methods">LKM修补/安装</string>
|
||||||
|
<string name="GKI_install_methods">GKI/non-GKI安装</string>
|
||||||
|
<string name="kernel_version_log">内核版本:%1$s</string>
|
||||||
|
<string name="tool_version_log">使用修补工具:%1$s</string>
|
||||||
|
<string name="configuration">配置</string>
|
||||||
|
<string name="app_settings">应用设置</string>
|
||||||
|
<string name="tools">工具</string>
|
||||||
|
<string name="currently_selected">当前</string>
|
||||||
|
<!-- SuperUser 里用到的字符串资源 -->
|
||||||
|
<string name="clear">清除</string>
|
||||||
|
<string name="apps_with_root">Root 权限应用</string>
|
||||||
|
<string name="apps_with_custom_profile">自定义配置应用</string>
|
||||||
|
<string name="other_apps">其他应用</string>
|
||||||
|
<string name="no_apps_found">未找到应用</string>
|
||||||
|
<string name="selinux_enabled_toast">SELinux 已设置为启用状态</string>
|
||||||
|
<string name="selinux_disabled_toast">SELinux 已设置为禁用状态</string>
|
||||||
|
<string name="selinux_change_failed">SELinux 状态更改失败</string>
|
||||||
|
<string name="advanced_settings">高级设置</string>
|
||||||
|
<string name="appearance_settings">外观设置</string>
|
||||||
|
<string name="back">返回</string>
|
||||||
|
<string name="expand">展开</string>
|
||||||
|
<string name="collapse">收起</string>
|
||||||
|
<string name="susfs_enabled">SuSFS 已启用</string>
|
||||||
|
<string name="susfs_disabled">SuSFS 已禁用</string>
|
||||||
|
<string name="background_set_success">背景设置成功</string>
|
||||||
|
<string name="background_removed">已移除自定义背景</string>
|
||||||
|
<string name="root_require_for_install">需要 root 权限</string>
|
||||||
|
<!-- KPM 显示设置相关 -->
|
||||||
|
<string name="show_kpm_info">显示 KPM 功能</string>
|
||||||
|
<string name="show_kpm_info_summary">在主页和底栏显示 KPM 相关功能和信息 (需要重新打开应用)</string>
|
||||||
|
<!-- Webui X 设置相关 -->
|
||||||
|
<string name="use_webuix">使用 WebUI X</string>
|
||||||
|
<string name="use_webuix_summary">使用支持更多 API 的 WebUI X 而不是 WebUI</string>
|
||||||
|
<string name="use_webuix_eruda">将 Eruda 注入 WebUI X</string>
|
||||||
|
<string name="use_webuix_eruda_summary">在 WebUI X 中注入调试控制台,使调试更容易,需要启用 WebView 调试</string>
|
||||||
|
<!-- DPI设置相关字符串 -->
|
||||||
|
<string name="dpi_settings">DPI 设置</string>
|
||||||
|
<string name="app_dpi_title">应用DPI</string>
|
||||||
|
<string name="app_dpi_summary">仅调整当前应用的屏幕显示密度</string>
|
||||||
|
<string name="dpi_size_small">小</string>
|
||||||
|
<string name="dpi_size_medium">中</string>
|
||||||
|
<string name="dpi_size_large">大</string>
|
||||||
|
<string name="dpi_size_extra_large">超大</string>
|
||||||
|
<string name="dpi_size_custom">自定义</string>
|
||||||
|
<string name="dpi_apply_settings">应用DPI设置</string>
|
||||||
|
<string name="dpi_confirm_title">确认更改DPI</string>
|
||||||
|
<string name="dpi_confirm_message">你确定要将应用DPI从 %1$d 更改为 %2$d 吗?</string>
|
||||||
|
<string name="dpi_confirm_summary">应用需要重启以应用新的DPI设置,不会影响系统状态栏或其他应用</string>
|
||||||
|
<string name="dpi_applied_success">DPI 已设置为 %1$d,重启应用后生效</string>
|
||||||
|
<!-- 语言设置相关字符串 -->
|
||||||
|
<string name="language_setting">应用语言</string>
|
||||||
|
<string name="language_follow_system">跟随系统</string>
|
||||||
|
<string name="language_changed">语言已更改,重启应用以应用更改</string>
|
||||||
|
<string name="settings_card_dim">卡片暗度调节</string>
|
||||||
|
<!-- 超级用户相关 -->
|
||||||
|
<string name="scroll_to_top">顶部</string>
|
||||||
|
<string name="scroll_to_bottom">底部</string>
|
||||||
|
<string name="scroll_to_top_description">滚动到顶部</string>
|
||||||
|
<string name="scroll_to_bottom_description">滚动到底部</string>
|
||||||
|
<string name="authorized">已授权</string>
|
||||||
|
<string name="unauthorized">未授权</string>
|
||||||
|
<string name="selected">已选择</string>
|
||||||
|
<string name="select">选择</string>
|
||||||
|
<string name="profile_umount_modules_disable">禁用自定义卸载模块</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -118,7 +118,7 @@
|
|||||||
<string name="enable_web_debugging">Enable WebView debugging</string>
|
<string name="enable_web_debugging">Enable WebView debugging</string>
|
||||||
<string name="enable_web_debugging_summary">Can be used to debug WebUI. Please enable only when needed.</string>
|
<string name="enable_web_debugging_summary">Can be used to debug WebUI. Please enable only when needed.</string>
|
||||||
<string name="direct_install">Direct install (Recommended)</string>
|
<string name="direct_install">Direct install (Recommended)</string>
|
||||||
<string name="select_file">Select a file</string>
|
<string name="select_file">Select a image that needs to be patched</string>
|
||||||
<string name="install_inactive_slot">Install to inactive slot (After OTA)</string>
|
<string name="install_inactive_slot">Install to inactive slot (After OTA)</string>
|
||||||
<string name="install_inactive_slot_warning">Your device will be **FORCED** to boot to the current inactive slot after a reboot!\nOnly use this option after OTA is done.\nContinue?</string>
|
<string name="install_inactive_slot_warning">Your device will be **FORCED** to boot to the current inactive slot after a reboot!\nOnly use this option after OTA is done.\nContinue?</string>
|
||||||
<string name="install_next">Next</string>
|
<string name="install_next">Next</string>
|
||||||
@@ -194,6 +194,8 @@
|
|||||||
<string name="hide_other_info_summary">Hides information about the number of super users, modules and KPM modules on the home page</string>
|
<string name="hide_other_info_summary">Hides information about the number of super users, modules and KPM modules on the home page</string>
|
||||||
<string name="hide_susfs_status">Hide SuSFS status</string>
|
<string name="hide_susfs_status">Hide SuSFS status</string>
|
||||||
<string name="hide_susfs_status_summary">Hide SuSFS status information on the home page</string>
|
<string name="hide_susfs_status_summary">Hide SuSFS status information on the home page</string>
|
||||||
|
<string name="hide_link_card">Hide Link Card Status</string>
|
||||||
|
<string name="hide_link_card_summary">Hide link card information on the home page</string>
|
||||||
<string name="theme_mode">Theme</string>
|
<string name="theme_mode">Theme</string>
|
||||||
<string name="theme_follow_system">Follow system</string>
|
<string name="theme_follow_system">Follow system</string>
|
||||||
<string name="theme_light">Light</string>
|
<string name="theme_light">Light</string>
|
||||||
@@ -202,8 +204,7 @@
|
|||||||
<string name="dynamic_color_title">Dynamic colours</string>
|
<string name="dynamic_color_title">Dynamic colours</string>
|
||||||
<string name="dynamic_color_summary">Dynamic colours using system themes</string>
|
<string name="dynamic_color_summary">Dynamic colours using system themes</string>
|
||||||
<string name="choose_theme_color">Choose a theme colour</string>
|
<string name="choose_theme_color">Choose a theme colour</string>
|
||||||
<string name="color_default">White</string>
|
<string name="color_default">Blue</string>
|
||||||
<string name="color_blue">Blue</string>
|
|
||||||
<string name="color_green">Green</string>
|
<string name="color_green">Green</string>
|
||||||
<string name="color_purple">Purple</string>
|
<string name="color_purple">Purple</string>
|
||||||
<string name="color_orange">Orange</string>
|
<string name="color_orange">Orange</string>
|
||||||
@@ -221,12 +222,11 @@
|
|||||||
<string name="yes">Yes</string>
|
<string name="yes">Yes</string>
|
||||||
<string name="no">No</string>
|
<string name="no">No</string>
|
||||||
<string name="failed_reboot">Reboot Failed</string>
|
<string name="failed_reboot">Reboot Failed</string>
|
||||||
<string name="batch_authorization">Bulk license</string>
|
<string name="batch_authorization">empower</string>
|
||||||
<string name="batch_cancel_authorization">Batch cancel authorization</string>
|
<string name="batch_cancel_authorization">withdraw</string>
|
||||||
<string name="backup">Backup</string>
|
<string name="backup">Backup</string>
|
||||||
<string name="color_yellow">Yellow</string>
|
<string name="color_yellow">Yellow</string>
|
||||||
<string name="kpm">Kernel Module</string>
|
<string name="kpm_title">KPM</string>
|
||||||
<string name="kpm_title">Kernel Module</string>
|
|
||||||
<string name="kpm_empty">No installed kernel modules at this time</string>
|
<string name="kpm_empty">No installed kernel modules at this time</string>
|
||||||
<string name="kpm_version">Version</string>
|
<string name="kpm_version">Version</string>
|
||||||
<string name="kpm_author">Author</string>
|
<string name="kpm_author">Author</string>
|
||||||
@@ -288,12 +288,77 @@
|
|||||||
<string name="slot_a">Slot A</string>
|
<string name="slot_a">Slot A</string>
|
||||||
<string name="slot_b">Slot B</string>
|
<string name="slot_b">Slot B</string>
|
||||||
<string name="selected_slot">Selected slot: %1$s</string>
|
<string name="selected_slot">Selected slot: %1$s</string>
|
||||||
<!-- Error Messages -->
|
|
||||||
<string name="horizon_copy_failed">Copy failed</string>
|
|
||||||
<string name="horizon_unknown_error">Unknown error</string>
|
|
||||||
<string name="horizon_getting_original_slot">Getting the original slot</string>
|
<string name="horizon_getting_original_slot">Getting the original slot</string>
|
||||||
<string name="horizon_setting_target_slot">Setting the specified slot</string>
|
<string name="horizon_setting_target_slot">Setting the specified slot</string>
|
||||||
<string name="horizon_restoring_original_slot">Restore Default Slot</string>
|
<string name="horizon_restoring_original_slot">Restore Default Slot</string>
|
||||||
<string name="current_slot">Current Slot:%1$s </string>
|
<string name="current_slot">Current Slot:%1$s </string>
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="horizon_copy_failed">Copy failed</string>
|
||||||
|
<string name="horizon_unknown_error">Unknown error</string>
|
||||||
<string name="flash_failed_message">Flash failed</string>
|
<string name="flash_failed_message">Flash failed</string>
|
||||||
|
<!-- lkm/gki install -->
|
||||||
|
<string name="Lkm_install_methods">LKM repair/installation</string>
|
||||||
|
<string name="GKI_install_methods">GKI/non-GKI installation</string>
|
||||||
|
<string name="kernel_version_log">Kernel version:%1$s</string>
|
||||||
|
<string name="tool_version_log">Using the patching tool:%1$s</string>
|
||||||
|
<string name="configuration">Configure</string>
|
||||||
|
<string name="app_settings">Application Settings</string>
|
||||||
|
<string name="tools">Tools</string>
|
||||||
|
<string name="currently_selected">Currently</string>
|
||||||
|
<!-- String resources used in SuperUser -->
|
||||||
|
<string name="clear">Removals</string>
|
||||||
|
<string name="apps_with_root">Root Application of permissions</string>
|
||||||
|
<string name="apps_with_custom_profile">Customized Configuration Application</string>
|
||||||
|
<string name="other_apps">Other Applications</string>
|
||||||
|
<string name="no_apps_found">Application not found</string>
|
||||||
|
<string name="selinux_enabled_toast">SELinux Enabled</string>
|
||||||
|
<string name="selinux_disabled_toast">SELinux Disabled</string>
|
||||||
|
<string name="selinux_change_failed">SELinux Status change failed</string>
|
||||||
|
<string name="advanced_settings">Advanced Settings</string>
|
||||||
|
<string name="appearance_settings">Customize the toolbar</string>
|
||||||
|
<string name="back">Comeback</string>
|
||||||
|
<string name="expand">Be in full swing</string>
|
||||||
|
<string name="collapse">put away</string>
|
||||||
|
<string name="susfs_enabled">SuSFS enabled</string>
|
||||||
|
<string name="susfs_disabled">SuSFS disabled</string>
|
||||||
|
<string name="background_set_success">Background set successfully</string>
|
||||||
|
<string name="background_removed">Removed custom backgrounds</string>
|
||||||
|
<string name="root_require_for_install">Requires root privileges</string>
|
||||||
|
<!-- KPM display settings -->
|
||||||
|
<string name="show_kpm_info">Display KPM Function</string>
|
||||||
|
<string name="show_kpm_info_summary">Display KPM information and Function in home and bottom bar (Need to reopen the app)</string>
|
||||||
|
<!-- Webui X settings -->
|
||||||
|
<string name="use_webuix">Use WebUI X</string>
|
||||||
|
<string name="use_webuix_summary">Use WebUI X instead of WebUI which supports more API\'s</string>
|
||||||
|
<string name="use_webuix_eruda">Inject Eruda into WebUI X</string>
|
||||||
|
<string name="use_webuix_eruda_summary">Inject a debug console into WebUI X to make debugging easier. Requires web debugging to be on.</string>
|
||||||
|
<!-- DPI setting related strings -->
|
||||||
|
<string name="dpi_settings">DPI setting</string>
|
||||||
|
<string name="app_dpi_title">Applied DPI</string>
|
||||||
|
<string name="app_dpi_summary">Adjust the screen display density for the current application only</string>
|
||||||
|
<string name="dpi_size_small">Small </string>
|
||||||
|
<string name="dpi_size_medium">Medium </string>
|
||||||
|
<string name="dpi_size_large">Big</string>
|
||||||
|
<string name="dpi_size_extra_large">oversize</string>
|
||||||
|
<string name="dpi_size_custom">customizable</string>
|
||||||
|
<string name="dpi_apply_settings">Applying DPI settings</string>
|
||||||
|
<string name="dpi_confirm_title">Confirm DPI change</string>
|
||||||
|
<string name="dpi_confirm_message">Are you sure you want to change the application DPI from %1$d to %2$d?</string>
|
||||||
|
<string name="dpi_confirm_summary">Application needs to be restarted to apply the new DPI settings, does not affect the system status bar or other applications</string>
|
||||||
|
<string name="dpi_applied_success">DPI has been set to %1$d, effective after restarting the application</string>
|
||||||
|
<!-- Language settings related strings -->
|
||||||
|
<string name="language_setting">App Language</string>
|
||||||
|
<string name="language_follow_system">Follow System</string>
|
||||||
|
<string name="language_changed">Language changed, restarting to apply changes</string>
|
||||||
|
<string name="settings_card_dim">Card Darkness Adjustment</string>
|
||||||
|
<!-- Super User Related -->
|
||||||
|
<string name="scroll_to_top">Top</string>
|
||||||
|
<string name="scroll_to_bottom">Bottom</string>
|
||||||
|
<string name="scroll_to_top_description">Scroll to top</string>
|
||||||
|
<string name="scroll_to_bottom_description">Scroll to the bottom</string>
|
||||||
|
<string name="authorized">authorized</string>
|
||||||
|
<string name="unauthorized">unauthorized</string>
|
||||||
|
<string name="selected">Selected</string>
|
||||||
|
<string name="select">option</string>
|
||||||
|
<string name="profile_umount_modules_disable">Disable custom uninstallation module</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import com.android.build.api.dsl.ApplicationDefaultConfig
|
import com.android.build.api.dsl.ApplicationDefaultConfig
|
||||||
import com.android.build.api.dsl.CommonExtension
|
import com.android.build.api.dsl.CommonExtension
|
||||||
import com.android.build.gradle.api.AndroidBasePlugin
|
import com.android.build.gradle.api.AndroidBasePlugin
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.agp.app) apply false
|
alias(libs.plugins.agp.app) apply false
|
||||||
@@ -18,7 +17,7 @@ cmaker {
|
|||||||
"-DANDROID_STL=none",
|
"-DANDROID_STL=none",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
abiFilters("arm64-v8a", "x86_64", "riscv64")
|
abiFilters("arm64-v8a", "x86_64", "armeabi-v7a")
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
if (it.name == "release") {
|
if (it.name == "release") {
|
||||||
@@ -48,18 +47,6 @@ fun getGitDescribe(): String {
|
|||||||
}.standardOutput.asText.get().trim()
|
}.standardOutput.asText.get().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun getVersionCode(): Int {
|
|
||||||
val commitCount = getGitCommitCount()
|
|
||||||
val major = 1
|
|
||||||
return major * 10000 + commitCount + 606
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getVersionName(): String {
|
|
||||||
return getGitDescribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
plugins.withType(AndroidBasePlugin::class.java) {
|
plugins.withType(AndroidBasePlugin::class.java) {
|
||||||
extensions.configure(CommonExtension::class.java) {
|
extensions.configure(CommonExtension::class.java) {
|
||||||
@@ -74,7 +61,7 @@ subprojects {
|
|||||||
versionName = managerVersionName
|
versionName = managerVersionName
|
||||||
}
|
}
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters += listOf("arm64-v8a", "x86_64", "riscv64")
|
abiFilters += listOf("arm64-v8a", "x86_64", "armeabi-v7a")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.9.2"
|
agp = "8.10.0"
|
||||||
kotlin = "2.1.10"
|
kotlin = "2.1.10"
|
||||||
ksp = "2.1.10-1.0.30"
|
ksp = "2.1.10-1.0.30"
|
||||||
compose-bom = "2025.04.01"
|
compose-bom = "2025.04.01"
|
||||||
@@ -22,6 +22,7 @@ compose-material3 = "1.3.2"
|
|||||||
compose-ui = "1.8.0"
|
compose-ui = "1.8.0"
|
||||||
compose-foundation = "1.7.8"
|
compose-foundation = "1.7.8"
|
||||||
documentfile = "1.0.1"
|
documentfile = "1.0.1"
|
||||||
|
mmrl = "v33560"
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
agp-app = { id = "com.android.application", version.ref = "agp" }
|
agp-app = { id = "com.android.application", version.ref = "agp" }
|
||||||
@@ -82,3 +83,9 @@ markdown = { group = "io.noties.markwon", name = "core", version.ref = "markdown
|
|||||||
|
|
||||||
lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" }
|
lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" }
|
||||||
androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
|
androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
|
||||||
|
|
||||||
|
|
||||||
|
mmrl-webui = { group = "com.github.MMRLApp.MMRL", name = "webui", version.ref = "mmrl" }
|
||||||
|
mmrl-platform = { group = "com.github.MMRLApp.MMRL", name = "platform", version.ref = "mmrl" }
|
||||||
|
mmrl-ui = { group = "com.github.MMRLApp.MMRL", name = "ui", version.ref = "mmrl" }
|
||||||
|
mmrl-hidden-api = { group = "com.github.MMRLApp.MMRL", name = "hidden-api", version.ref = "mmrl" }
|
||||||
416
userspace/ksud/Cargo.lock
generated
416
userspace/ksud/Cargo.lock
generated
@@ -25,9 +25,9 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.8.11"
|
version = "0.8.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -55,9 +55,9 @@ checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android_log-sys"
|
name = "android_log-sys"
|
||||||
version = "0.3.1"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937"
|
checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android_logger"
|
name = "android_logger"
|
||||||
@@ -131,9 +131,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.96"
|
version = "1.0.98"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
|
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arbitrary"
|
name = "arbitrary"
|
||||||
@@ -146,9 +146,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.86"
|
version = "0.1.88"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d"
|
checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -163,9 +163,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.74"
|
version = "0.3.75"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
|
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"addr2line",
|
"addr2line",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
@@ -184,9 +184,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.8.0"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
|
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
@@ -211,15 +211,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.10.0"
|
version = "1.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
|
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.15"
|
version = "1.2.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af"
|
checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
@@ -232,23 +232,23 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.39"
|
version = "0.4.41"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
"android-tzdata",
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-targets",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.30"
|
version = "4.5.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d"
|
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -256,9 +256,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.30"
|
version = "4.5.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c"
|
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -268,9 +268,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.28"
|
version = "4.5.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
|
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -336,9 +336,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc"
|
name = "crc"
|
||||||
version = "3.2.1"
|
version = "3.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
|
checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc-catalog",
|
"crc-catalog",
|
||||||
]
|
]
|
||||||
@@ -373,9 +373,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.14"
|
version = "0.5.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
|
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
@@ -438,9 +438,9 @@ checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.3.11"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
]
|
]
|
||||||
@@ -477,22 +477,11 @@ dependencies = [
|
|||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "displaydoc"
|
|
||||||
version = "0.2.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.14.0"
|
version = "1.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d"
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
@@ -520,9 +509,9 @@ checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.11.6"
|
version = "0.11.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
|
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"env_filter",
|
"env_filter",
|
||||||
"log",
|
"log",
|
||||||
@@ -547,9 +536,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.10"
|
version = "0.3.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
@@ -584,24 +573,14 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.35"
|
version = "1.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
|
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fs4"
|
|
||||||
version = "0.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "be058769cf1633370c3d0dac6bb9b223b8f18900cf808abadf7843192e706238"
|
|
||||||
dependencies = [
|
|
||||||
"rustix 0.38.44",
|
|
||||||
"windows-sys 0.59.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.7"
|
version = "0.14.7"
|
||||||
@@ -623,14 +602,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.3.1"
|
version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
|
"r-efi",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-targets",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -651,9 +630,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.2"
|
version = "0.15.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
@@ -678,14 +657,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.61"
|
version = "0.1.63"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"iana-time-zone-haiku",
|
"iana-time-zone-haiku",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
"log",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-core",
|
"windows-core",
|
||||||
]
|
]
|
||||||
@@ -724,12 +704,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.7.1"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
|
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.15.2",
|
"hashbrown 0.15.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -749,9 +729,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.14"
|
version = "1.0.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "java-properties"
|
name = "java-properties"
|
||||||
@@ -784,7 +764,7 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zakozako"
|
name = "ksud"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-properties",
|
"android-properties",
|
||||||
@@ -797,7 +777,6 @@ dependencies = [
|
|||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"extattr",
|
"extattr",
|
||||||
"fs4",
|
|
||||||
"getopts",
|
"getopts",
|
||||||
"humansize",
|
"humansize",
|
||||||
"is_executable",
|
"is_executable",
|
||||||
@@ -805,7 +784,6 @@ dependencies = [
|
|||||||
"jwalk",
|
"jwalk",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"loopdev",
|
|
||||||
"nom",
|
"nom",
|
||||||
"procfs",
|
"procfs",
|
||||||
"regex-lite",
|
"regex-lite",
|
||||||
@@ -828,9 +806,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.170"
|
version = "0.2.172"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
|
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libflate"
|
name = "libflate"
|
||||||
@@ -858,9 +836,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
version = "0.2.11"
|
version = "0.2.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
|
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
@@ -869,25 +847,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lockfree-object-pool"
|
name = "linux-raw-sys"
|
||||||
version = "0.1.6"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
|
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.26"
|
version = "0.4.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "loopdev"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "git+https://github.com/Kernel-SU/loopdev#7a921f8d966477a645b1188732fac486c71a68ef"
|
|
||||||
dependencies = [
|
|
||||||
"errno 0.2.8",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lzma-rs"
|
name = "lzma-rs"
|
||||||
@@ -899,6 +868,17 @@ dependencies = [
|
|||||||
"crc",
|
"crc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lzma-sys"
|
||||||
|
version = "0.1.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.4"
|
||||||
@@ -907,9 +887,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.5"
|
version = "0.8.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
|
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler2",
|
"adler2",
|
||||||
]
|
]
|
||||||
@@ -949,9 +929,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.20.3"
|
version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
@@ -959,6 +939,12 @@ version = "0.2.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -967,9 +953,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.93"
|
version = "1.0.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -980,7 +966,7 @@ version = "0.17.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f"
|
checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.8.0",
|
"bitflags 2.9.0",
|
||||||
"chrono",
|
"chrono",
|
||||||
"flate2",
|
"flate2",
|
||||||
"hex",
|
"hex",
|
||||||
@@ -994,20 +980,26 @@ version = "0.17.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec"
|
checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.8.0",
|
"bitflags 2.9.0",
|
||||||
"chrono",
|
"chrono",
|
||||||
"hex",
|
"hex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.38"
|
version = "1.0.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "5.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayon"
|
name = "rayon"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
@@ -1042,9 +1034,9 @@ checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-embed"
|
name = "rust-embed"
|
||||||
version = "8.5.0"
|
version = "8.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0"
|
checksum = "60e425e204264b144d4c929d126d0de524b40a961686414bab5040f7465c71be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"include-flate",
|
"include-flate",
|
||||||
"rust-embed-impl",
|
"rust-embed-impl",
|
||||||
@@ -1054,9 +1046,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-embed-impl"
|
name = "rust-embed-impl"
|
||||||
version = "8.5.0"
|
version = "8.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478"
|
checksum = "6bf418c9a2e3f6663ca38b8a7134cc2c2167c9d69688860e8961e3faa731702e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1067,9 +1059,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-embed-utils"
|
name = "rust-embed-utils"
|
||||||
version = "8.5.0"
|
version = "8.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d"
|
checksum = "08d55b95147fe01265d06b3955db798bdaed52e60e2211c41137701b3aba8e21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sha2",
|
"sha2",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
@@ -1086,11 +1078,11 @@ name = "rustix"
|
|||||||
version = "0.38.34"
|
version = "0.38.34"
|
||||||
source = "git+https://github.com/Kernel-SU/rustix.git?branch=main#4a53fbc7cb7a07cabe87125cc21dbc27db316259"
|
source = "git+https://github.com/Kernel-SU/rustix.git?branch=main#4a53fbc7cb7a07cabe87125cc21dbc27db316259"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.8.0",
|
"bitflags 2.9.0",
|
||||||
"errno 0.3.10",
|
"errno 0.3.11",
|
||||||
"itoa",
|
"itoa",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys 0.4.15",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
@@ -1101,24 +1093,37 @@ version = "0.38.44"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.8.0",
|
"bitflags 2.9.0",
|
||||||
"errno 0.3.10",
|
"errno 0.3.11",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys 0.4.15",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.0",
|
||||||
|
"errno 0.3.11",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys 0.9.4",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.19"
|
version = "1.0.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
|
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.19"
|
version = "1.0.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
|
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
@@ -1131,18 +1136,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.218"
|
version = "1.0.219"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.218"
|
version = "1.0.219"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1151,9 +1156,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.139"
|
version = "1.0.140"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
|
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -1174,9 +1179,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.8"
|
version = "0.10.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
@@ -1185,9 +1190,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha256"
|
name = "sha256"
|
||||||
version = "1.5.0"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0"
|
checksum = "f880fc8562bdeb709793f00eb42a2ad0e672c4f883bbe59122b926eca935c8f6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -1216,9 +1221,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.98"
|
version = "2.0.101"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1227,43 +1232,22 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.17.1"
|
version = "3.19.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
|
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 0.38.44",
|
"rustix 1.0.7",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror"
|
|
||||||
version = "2.0.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror-impl",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror-impl"
|
|
||||||
version = "2.0.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.37"
|
version = "0.3.41"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"num-conv",
|
"num-conv",
|
||||||
@@ -1274,15 +1258,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-core"
|
name = "time-core"
|
||||||
version = "0.1.2"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.43.0"
|
version = "1.45.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
|
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -1297,9 +1281,9 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.17"
|
version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
|
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
@@ -1337,9 +1321,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.13.3+wasi-0.2.2"
|
version = "0.14.2+wasi-0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wit-bindgen-rt",
|
"wit-bindgen-rt",
|
||||||
]
|
]
|
||||||
@@ -1404,13 +1388,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "which"
|
name = "which"
|
||||||
version = "7.0.2"
|
version = "7.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2774c861e1f072b3aadc02f8ba886c26ad6321567ecc294c935434cad06f1283"
|
checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
"env_home",
|
"env_home",
|
||||||
"rustix 0.38.44",
|
"rustix 1.0.7",
|
||||||
"winsafe",
|
"winsafe",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1447,11 +1431,61 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.52.0"
|
version = "0.61.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-targets",
|
"windows-implement",
|
||||||
|
"windows-interface",
|
||||||
|
"windows-link",
|
||||||
|
"windows-result",
|
||||||
|
"windows-strings",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.60.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.59.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-result"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-strings"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1544,27 +1578,36 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen-rt"
|
name = "wit-bindgen-rt"
|
||||||
version = "0.33.0"
|
version = "0.39.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
|
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.8.0",
|
"bitflags 2.9.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xz2"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
|
||||||
|
dependencies = [
|
||||||
|
"lzma-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.7.35"
|
version = "0.8.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.7.35"
|
version = "0.8.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1573,43 +1616,40 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zip"
|
name = "zip"
|
||||||
version = "2.2.2"
|
version = "2.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45"
|
checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arbitrary",
|
"arbitrary",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
"deflate64",
|
"deflate64",
|
||||||
"displaydoc",
|
|
||||||
"flate2",
|
"flate2",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"lzma-rs",
|
"lzma-rs",
|
||||||
"memchr",
|
"memchr",
|
||||||
"thiserror",
|
|
||||||
"time",
|
"time",
|
||||||
|
"xz2",
|
||||||
"zopfli",
|
"zopfli",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zip-extensions"
|
name = "zip-extensions"
|
||||||
version = "0.8.1"
|
version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "386508a00aae1d8218b9252a41f59bba739ccee3f8e420bb90bcb1c30d960d4a"
|
checksum = "79cdbf826e5a6eec81fc5a0d33cd7c09c31fd8f9918f15434f74c42d39ef337a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zip",
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zopfli"
|
name = "zopfli"
|
||||||
version = "0.8.1"
|
version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946"
|
checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"lockfree-object-pool",
|
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
|
||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ rustix = { git = "https://github.com/Kernel-SU/rustix.git", branch = "main", fea
|
|||||||
# some android specific dependencies which compiles under unix are also listed here for convenience of coding
|
# some android specific dependencies which compiles under unix are also listed here for convenience of coding
|
||||||
android-properties = { version = "0.2", features = ["bionic-deprecated"] }
|
android-properties = { version = "0.2", features = ["bionic-deprecated"] }
|
||||||
procfs = "0.17"
|
procfs = "0.17"
|
||||||
loopdev = { git = "https://github.com/Kernel-SU/loopdev" }
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
android_logger = { version = "0.14", default-features = false }
|
android_logger = { version = "0.14", default-features = false }
|
||||||
|
|||||||
BIN
userspace/ksud/bin/arm/busybox
Normal file
BIN
userspace/ksud/bin/arm/busybox
Normal file
Binary file not shown.
BIN
userspace/ksud/bin/arm/resetprop
Normal file
BIN
userspace/ksud/bin/arm/resetprop
Normal file
Binary file not shown.
@@ -15,11 +15,16 @@ pub const BOOTCTL_PATH: &str = concatcp!(BINARY_DIR, "bootctl");
|
|||||||
struct Asset;
|
struct Asset;
|
||||||
|
|
||||||
// IF NOT x86_64 ANDROID, ie. macos, linux, windows, always use aarch64
|
// IF NOT x86_64 ANDROID, ie. macos, linux, windows, always use aarch64
|
||||||
#[cfg(not(all(target_arch = "x86_64", target_os = "android")))]
|
#[cfg(all(target_arch = "aarch64", target_os = "android"))]
|
||||||
#[derive(RustEmbed)]
|
#[derive(RustEmbed)]
|
||||||
#[folder = "bin/aarch64"]
|
#[folder = "bin/aarch64"]
|
||||||
struct Asset;
|
struct Asset;
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "arm", target_os = "android"))]
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "bin/arm"]
|
||||||
|
struct Asset;
|
||||||
|
|
||||||
pub fn ensure_binaries(ignore_if_exist: bool) -> Result<()> {
|
pub fn ensure_binaries(ignore_if_exist: bool) -> Result<()> {
|
||||||
for file in Asset::iter() {
|
for file in Asset::iter() {
|
||||||
if file == "ksuinit" || file.ends_with(".ko") {
|
if file == "ksuinit" || file.ends_with(".ko") {
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ pub fn root_shell() -> Result<()> {
|
|||||||
if free_idx < matches.free.len() {
|
if free_idx < matches.free.len() {
|
||||||
let name = &matches.free[free_idx];
|
let name = &matches.free[free_idx];
|
||||||
uid = unsafe {
|
uid = unsafe {
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||||
let pw = libc::getpwnam(name.as_ptr()).as_ref();
|
let pw = libc::getpwnam(name.as_ptr()).as_ref();
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
let pw = libc::getpwnam(name.as_ptr() as *const i8).as_ref();
|
let pw = libc::getpwnam(name.as_ptr() as *const i8).as_ref();
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
APP_ABI := arm64-v8a x86_64
|
APP_ABI := arm64-v8a x86_64 armeabi-v7a
|
||||||
APP_PLATFORM := android-24
|
APP_PLATFORM := android-24
|
||||||
APP_STL := none
|
APP_STL := none
|
||||||
|
|||||||
Reference in New Issue
Block a user