mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2025-07-08 03:20:44 +08:00
Compare commits
406 Commits
feat/toolt
...
docs/readm
Author | SHA1 | Date | |
---|---|---|---|
a5d09b2bf5 | |||
8731e7267a | |||
c2a942b46c | |||
b9401b5f89 | |||
0eee869b99 | |||
15cfeb6ba7 | |||
d47819cae7 | |||
351e00c380 | |||
046179db1b | |||
5993dc34cb | |||
91f6672711 | |||
d3748cf305 | |||
db304a70c8 | |||
8f9ea54790 | |||
a53a0bd2d4 | |||
1c6a9dbbbd | |||
802330c062 | |||
d1ef14a25a | |||
617481f26f | |||
6598db5078 | |||
5b790a86e2 | |||
bec0d823dc | |||
2616576b3d | |||
44fc4551aa | |||
6dfb2a2de0 | |||
03c91687ae | |||
91e06cbce6 | |||
b0624a3544 | |||
04a2a78914 | |||
4364f2e4c2 | |||
177b716fd0 | |||
818dc09aa4 | |||
a762969966 | |||
74338931b8 | |||
0ab424bfdb | |||
fff1a41fee | |||
7644a74648 | |||
9db3bd5b3f | |||
b81bd17fbc | |||
cf3866f892 | |||
5d3a81f4b9 | |||
f9831d4da5 | |||
8a20d8cf9b | |||
49f75f9edd | |||
9916e4da4d | |||
2ec1c0238d | |||
9dc716b1c8 | |||
31fb8b1404 | |||
d987845bba | |||
50c46dc20d | |||
5125084279 | |||
0685479d53 | |||
20c13ee71c | |||
cf322147d5 | |||
b4c37e6ddc | |||
911201ad9f | |||
8442bf2e14 | |||
697386c36c | |||
f6f72387b9 | |||
d201bdc422 | |||
2055400565 | |||
05bf940cdf | |||
3bbad156c4 | |||
db4ce6bb77 | |||
a290369d8d | |||
adf5f9f6e8 | |||
ace6701aaf | |||
10e7e4b39f | |||
edb4e421e2 | |||
747017a5f9 | |||
e992a99783 | |||
e869db0555 | |||
936a9efd0b | |||
edc11b6e1d | |||
32f2710763 | |||
4257c32bf5 | |||
c18901c35b | |||
f126fe9fa8 | |||
4cbd480e84 | |||
afc72ffd85 | |||
cdc69ea8ff | |||
52a89b1638 | |||
641d518b6e | |||
2eca45d397 | |||
de6fddf405 | |||
45f32040f8 | |||
42ef2ca99d | |||
563f2f37f7 | |||
881b13740a | |||
d39804f7ed | |||
b8a85c4891 | |||
37e9630b9c | |||
40492b67d1 | |||
f655a6e03a | |||
36f864efbb | |||
a995f43b7b | |||
1ee1330e47 | |||
3f4a234915 | |||
032ca39cf6 | |||
6f9a984541 | |||
e6e043f168 | |||
7c7fb7b343 | |||
b26fe30861 | |||
f99cdfe926 | |||
ec0a077539 | |||
a22158d070 | |||
48fe3a707e | |||
8d3d500b7b | |||
d8248cc915 | |||
397a1f8f9c | |||
a0f187354f | |||
1bf004ddee | |||
495100dea9 | |||
e3bd8a8b22 | |||
d987ac6c7a | |||
9883dcd0a7 | |||
d63133189d | |||
39fbb87010 | |||
f197760efd | |||
fa13d4a538 | |||
71b5f539c1 | |||
603a827e45 | |||
47bdc69a43 | |||
1ce56af3b1 | |||
a12c5c583b | |||
5455cf20ab | |||
75fcdb139c | |||
269fa79136 | |||
16fea59605 | |||
c7d183ee8d | |||
413083c58d | |||
1f671aba33 | |||
6bfd9098d6 | |||
4e7d96e91d | |||
ac0a036035 | |||
2a1445d61f | |||
0df39a1136 | |||
634d793839 | |||
afd6c5d6b7 | |||
ab0682cc5c | |||
60ca901ac7 | |||
91ca5be57a | |||
ac47a7eaa4 | |||
ce134224a8 | |||
ca49d3a465 | |||
5d7f9d1387 | |||
8d5d86fea8 | |||
6709505e9e | |||
088de60c91 | |||
ef041869e5 | |||
16cdc7aca4 | |||
39536c0e18 | |||
607d8b67c9 | |||
0b9889ea44 | |||
4acef776b2 | |||
e186dfdaa9 | |||
c0f3d02e6f | |||
36c8f59d6f | |||
f38b31a591 | |||
3232bb10e6 | |||
b7cb6b94f5 | |||
aa6e612fba | |||
d9d7b98409 | |||
4fdd6bbe5f | |||
439f6250f3 | |||
d55abf5dda | |||
94de170490 | |||
3c56db4121 | |||
a8b9d9316f | |||
55c7800f39 | |||
d9eb1c42bc | |||
8cd617e32d | |||
a17a05995a | |||
de4e616dcc | |||
12b00e5c8d | |||
9cab91959e | |||
bd9778a3d1 | |||
62a5fce66c | |||
2bd84636d6 | |||
62bb0d34ce | |||
1521d21e4e | |||
35996b6c69 | |||
d32a213457 | |||
2a3395ce15 | |||
944b57c336 | |||
ac561e7aca | |||
59daceef99 | |||
1dc41badd9 | |||
1a83315424 | |||
3c5776214f | |||
5fff0a2923 | |||
8df7f2992d | |||
7741394c9c | |||
25bd91debc | |||
7fe4724e10 | |||
123ae37524 | |||
172604fcdb | |||
7d887c73e8 | |||
6abaac25d9 | |||
cc897840e2 | |||
757840b76f | |||
bc09af9d0b | |||
0419b2f86b | |||
2f31fc7d6e | |||
dcf51c1777 | |||
94eb893a01 | |||
af49457bfc | |||
a8682d671d | |||
10815c8f73 | |||
891fb57531 | |||
07ee005613 | |||
a3c48d14ab | |||
f14b697769 | |||
50e8d1f8f4 | |||
d06fb08239 | |||
cf9a14c762 | |||
7b49af21d0 | |||
18774084c9 | |||
a70ad3d666 | |||
0d2d879603 | |||
8acdc17cc4 | |||
1f5331dbfe | |||
98747f4afb | |||
2f782b4963 | |||
3c083edfda | |||
7bf1a5af65 | |||
8a3d163266 | |||
d58fd96bb0 | |||
259f76379e | |||
b8378fbb02 | |||
2f0cdfff59 | |||
cd5787a4f7 | |||
a280fc2446 | |||
a503f4830b | |||
f732df1b9a | |||
9b9525ece5 | |||
e0dfbaf4a3 | |||
949b1dad0e | |||
830a666a58 | |||
26b9652135 | |||
85c6e7283d | |||
67603f64cd | |||
65f8d38c59 | |||
e70c10adbd | |||
64ec73d821 | |||
32e8a37f33 | |||
5290713504 | |||
18cfb56b45 | |||
4b12ae1531 | |||
9df98edca5 | |||
c3af6acb2c | |||
7ba00cafd9 | |||
5aefb3bc59 | |||
212e55ffd8 | |||
bf54d38c91 | |||
cee2240cdc | |||
4c1ad868a9 | |||
f5b3b29d6d | |||
8f5449527d | |||
8f6d720454 | |||
56a4a7043d | |||
5762859906 | |||
676eb1e9e2 | |||
e7b94868d6 | |||
608bac6854 | |||
723f9cd98c | |||
abf4d91703 | |||
d8392ad3eb | |||
39caad18a5 | |||
6437f7bb65 | |||
e232044157 | |||
f78b56ef0a | |||
ca3c9af3b8 | |||
b8b2e74151 | |||
1f8341ac42 | |||
0964f15475 | |||
63fd7957c6 | |||
65377ffd9e | |||
f79320c013 | |||
cf71ea26ec | |||
ee96c37c20 | |||
a86923aee1 | |||
e0f8d06152 | |||
4cb4ce298a | |||
36de61a57f | |||
6f2ca5bb89 | |||
940885768d | |||
fc577b4c3e | |||
bf10af2ae2 | |||
b4dfcf1bb4 | |||
0b0ba21852 | |||
42e0346e25 | |||
212db84d0b | |||
e6eb8accf2 | |||
8bd73c3afa | |||
5369a25fa2 | |||
eeae46a415 | |||
c0badbe96b | |||
2bb51c136a | |||
3cfa4ea6d6 | |||
f01adf5eb0 | |||
a0b92554e9 | |||
ac4c7e06e7 | |||
0f9a6f4340 | |||
9586a9c0dd | |||
f6563b265b | |||
7aea9473de | |||
3f059d7748 | |||
7e3c31c4b2 | |||
1707a9690a | |||
379ce917a9 | |||
299aaa2b68 | |||
5cf5e87fa8 | |||
55f22562eb | |||
272d911464 | |||
6beb34baa8 | |||
61de0b67fa | |||
aec8cec9b8 | |||
83b9573b52 | |||
21d99a1f24 | |||
1331479072 | |||
b472a36a9a | |||
3238fcdae7 | |||
cd2587b1fd | |||
879884a9fa | |||
5d3b963682 | |||
955e7a4f1c | |||
d2dcd4209d | |||
6299ff5b48 | |||
94a4dbaba1 | |||
c36deea045 | |||
7030d43aa5 | |||
aa02e9f8cf | |||
37e177b56e | |||
453f4da8ec | |||
400163b820 | |||
4ae9904c8a | |||
fe5e191cb5 | |||
d9d83df9de | |||
8dd8f88d2b | |||
01fd4c8ffa | |||
7ac3bb74e0 | |||
3b65cd0edc | |||
a9606728bf | |||
4d4f1a242c | |||
6b7143dd8f | |||
7e4ee00cb2 | |||
4868c45b43 | |||
81f485da6b | |||
18cbe51e6b | |||
149c8cc8b2 | |||
0dccb8c27b | |||
4302ea8832 | |||
1eac42dab8 | |||
9dd74f1f22 | |||
923ce74735 | |||
2d9f9adfee | |||
9a55e51a3a | |||
5681c917c5 | |||
6309e8bdf5 | |||
535efa3d73 | |||
b8a51d32f5 | |||
919b6b7014 | |||
971277ed39 | |||
7ce4de7a8b | |||
9591f4e14f | |||
27426b1390 | |||
fcb75dd780 | |||
1be9c9c1bd | |||
e088d053ab | |||
ffa8d9c063 | |||
7a5596a281 | |||
9f46f74357 | |||
36c4e2dfe0 | |||
5cb31dbe9d | |||
399fc98dec | |||
c22371e0c5 | |||
a4842c078b | |||
c332760786 | |||
ea4247c688 | |||
fec8c0cc14 | |||
9b585c73fb | |||
c695fa525f | |||
93f3e27d48 | |||
52ab7937bd | |||
762bfa8514 | |||
ca20996b62 | |||
ad14818de8 | |||
32839656f8 | |||
a48faad17a | |||
40487923f9 | |||
f1656c6d1e | |||
4c3dbbd8d5 | |||
4088ed747e | |||
bca8df8efd | |||
54f0a69596 | |||
9065c0d260 | |||
cb0150a0f9 | |||
ec0f7e3f7a | |||
e5d898f025 | |||
52bdb1cd6a | |||
49f9dfcf95 | |||
9536cdcae1 | |||
57e2632f38 | |||
b372f7ee84 | |||
70e8253b63 |
61
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
Normal file
61
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
name: 🐞 Bug report
|
||||
description: Create a new bug report.
|
||||
title: 'bug: <title>'
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# ReVanced Manager bug report
|
||||
|
||||
Please check for existing issues [here](https://github.com/revanced/revanced-manager/labels/bug) before creating a new one.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Bug description
|
||||
description: |
|
||||
- Describe your bug in detail
|
||||
- Add steps to reproduce the bug if possible (Step 1. Download some files. Step 2. ...)
|
||||
- Add images and videos if possible
|
||||
- List selected patches if applicable
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Version of ReVanced Manager and version & name of application you tried to patch
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Installation type
|
||||
options:
|
||||
- Non-root
|
||||
- Root
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Device logs
|
||||
description: Export logs in ReVanced Manager settings.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Patcher logs
|
||||
description: Export logs in "Patcher" screen.
|
||||
render: shell
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your issue will be closed if you don't follow the checklist below!
|
||||
options:
|
||||
- label: This request is not a duplicate of an existing issue.
|
||||
required: true
|
||||
- label: I have chosen an appropriate title.
|
||||
required: true
|
||||
- label: All requested information has been provided properly.
|
||||
required: true
|
||||
- label: The issue is solely related to the ReVanced Manager
|
||||
required: true
|
109
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
109
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,109 +0,0 @@
|
||||
name: 🐞 Bug report
|
||||
description: Report a bug or an issue.
|
||||
title: 'bug: '
|
||||
labels: ['Bug report']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="https://raw.githubusercontent.com/revanced/revanced-manager/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="https://raw.githubusercontent.com/revanced/revanced-manager/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-manager/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-manager/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="http://revanced.app/discord">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/revancedapp">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://t.me/app_revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://x.com/revancedapp">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
|
||||
# ReVanced Manager bug report
|
||||
|
||||
Before creating a new bug report, please keep the following in mind:
|
||||
|
||||
- **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-manager/issues?q=label%3A%22Bug+report%22).
|
||||
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-manager/blob/main/CONTRIBUTING.md).
|
||||
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Bug description
|
||||
description: |
|
||||
- Describe your bug in detail
|
||||
- Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...)
|
||||
- Add images and videos if possible
|
||||
- List used patches, downloader and settings if applicable
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Patch logs
|
||||
description: Patch logs can be exported by clicking on the "Logs" button in the "Patcher" screen, when patching finishes.
|
||||
render: shell
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Debug logs
|
||||
description: Debug logs can be exported by clicking on "Export debug logs" in "Settings" > "Advanced".
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your bug report will be closed if you don't follow the checklist below.
|
||||
options:
|
||||
- label: I have checked all open and closed bug reports and this is not a duplicate.
|
||||
required: true
|
||||
- label: I have chosen an appropriate title.
|
||||
required: true
|
||||
- label: All requested information has been provided properly.
|
||||
required: true
|
||||
- label: The bug is only related to ReVanced Manager.
|
||||
required: true
|
6
.github/ISSUE_TEMPLATE/config.yml
vendored
6
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,5 +1 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🗨 Discussions
|
||||
url: https://github.com/revanced/revanced-suggestions/discussions
|
||||
about: Have something unspecific to ReVanced Manager in mind? Search for or start a new discussion!
|
||||
blank_issues_enabled: false
|
42
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
Normal file
42
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
name: ⭐ Feature request
|
||||
description: Create a new feature request.
|
||||
title: 'feat: <title>'
|
||||
labels: [feature request]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# ReVanced Manager feature request
|
||||
|
||||
Please check for existing feature requests [here](https://github.com/revanced/revanced-manager/labels/bug) before creating a new one.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Feature description
|
||||
description: Describe your feature in detail.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Motivation
|
||||
description: Explain why the lack of it is a problem.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: In case there is something else you want to add.
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your issue will be closed if you don't follow the checklist below!
|
||||
options:
|
||||
- label: This request is not a duplicate of an existing issue.
|
||||
required: true
|
||||
- label: I have chosen an appropriate title.
|
||||
required: true
|
||||
- label: All requested information has been provided properly.
|
||||
required: true
|
||||
- label: The issue is solely related to the ReVanced Manager
|
||||
required: true
|
103
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
103
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,103 +0,0 @@
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="https://raw.githubusercontent.com/revanced/revanced-manager/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="https://raw.githubusercontent.com/revanced/revanced-manager/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-manager/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-manager/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="http://revanced.app/discord">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/revancedapp">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://t.me/app_revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://x.com/revancedapp">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
|
||||
# ReVanced Manager feature request
|
||||
|
||||
Before creating a new feature request, please keep the following in mind:
|
||||
|
||||
- **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-manager/issues?q=label%3A%22Feature+request%22).
|
||||
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-manager/blob/main/CONTRIBUTING.md).
|
||||
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Feature description
|
||||
description: |
|
||||
- Describe your feature in detail
|
||||
- Add images, videos, links, examples, references, etc. if possible
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Motivation
|
||||
description: |
|
||||
A strong motivation is necessary for a feature request to be considered.
|
||||
|
||||
- Why should this feature be implemented?
|
||||
- What is the explicit use case?
|
||||
- What are the benefits?
|
||||
- What makes this feature important?
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your feature request will be closed if you don't follow the checklist below.
|
||||
options:
|
||||
- label: I have checked all open and closed feature requests and this is not a duplicate
|
||||
required: true
|
||||
- label: I have chosen an appropriate title.
|
||||
required: true
|
||||
- label: All requested information has been provided properly.
|
||||
required: true
|
||||
- label: The feature request is only related to ReVanced Manager.
|
||||
required: true
|
2
.github/config.yaml
vendored
2
.github/config.yaml
vendored
@ -1,2 +1,2 @@
|
||||
firstPRMergeComment: >
|
||||
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) to receive a role for your contribution.
|
||||
❤️ Thank you for contributing to ReVanced Manager. Join us on [Discord](https://revanced.app/discord) if you want to receive a contributor role.
|
||||
|
33
.github/workflows/build_pull_request.yml
vendored
33
.github/workflows/build_pull_request.yml
vendored
@ -1,33 +0,0 @@
|
||||
name: Build pull request
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: burrunan/gradle-cache-action@v1
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew assembleRelease --no-daemon
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: revanced-manager
|
||||
path: |
|
||||
app/build/outputs/apk/release/revanced-manager*.apk
|
||||
app/build/outputs/apk/release/revanced-manager*.apk.asc
|
26
.github/workflows/open_pull_request.yml
vendored
26
.github/workflows/open_pull_request.yml
vendored
@ -1,26 +0,0 @@
|
||||
name: Open a PR to main
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
MESSAGE: Merge branch `${{ github.head_ref || github.ref_name }}` to `main`
|
||||
|
||||
jobs:
|
||||
pull-request:
|
||||
name: Open pull request
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Open pull request
|
||||
uses: repo-sync/pull-request@v2
|
||||
with:
|
||||
destination_branch: 'main'
|
||||
pr_title: 'chore: ${{ env.MESSAGE }}'
|
||||
pr_body: 'This pull request will ${{ env.MESSAGE }}.'
|
||||
pr_draft: true
|
44
.github/workflows/pr-build.yml
vendored
Normal file
44
.github/workflows/pr-build.yml
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
name: Build pull request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/pr-build.yml"
|
||||
- "app/**"
|
||||
- "gradle/**"
|
||||
- "*.properties"
|
||||
- ".kts"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Build with Gradle
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew assembleRelease --no-daemon -PnoProguard -PsignAsDebug
|
||||
|
||||
- name: Set env
|
||||
run: echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
|
||||
- name: Add hash to APK
|
||||
run: mv app/build/outputs/apk/release/app-release.apk revanced-manager-${{ env.COMMIT_HASH }}.apk
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: revanced-manager
|
||||
path: revanced-manager-${{ env.COMMIT_HASH }}.apk
|
49
.github/workflows/release-build.yml
vendored
Normal file
49
.github/workflows/release-build.yml
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
name: Release Build
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set env
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Build with Gradle
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew assembleRelease --no-daemon
|
||||
|
||||
- name: Sign APK
|
||||
id: sign_apk
|
||||
uses: ilharp/sign-android-release@v1
|
||||
with:
|
||||
releaseDir: ./app/build/outputs/apk/release/
|
||||
signingKey: ${{ secrets.SIGNING_KEYSTORE }}
|
||||
keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
||||
keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
|
||||
- name: Add version to APK
|
||||
run: mv ${{ steps.sign_apk.outputs.signedFile }} revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||
|
||||
- name: Publish release APK
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
prerelease: false
|
||||
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
72
.github/workflows/release.yml
vendored
72
.github/workflows/release.yml
vendored
@ -1,72 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: burrunan/gradle-cache-action@v1
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew assembleRelease
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
fingerprint: ${{ vars.GPG_FINGERPRINT }}
|
||||
|
||||
- name: Setup keystore
|
||||
run: |
|
||||
echo "${{ secrets.KEYSTORE }}" | base64 --decode > "app/keystore.jks"
|
||||
|
||||
- name: Semantic Release
|
||||
uses: cycjimmy/semantic-release-action@v4
|
||||
id: semantic
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
KEYSTORE_ENTRY_ALIAS: ${{ secrets.KEYSTORE_ENTRY_ALIAS }}
|
||||
KEYSTORE_ENTRY_PASSWORD: ${{ secrets.KEYSTORE_ENTRY_PASSWORD }}
|
||||
|
||||
- name: Attest
|
||||
if: steps.semantic.outputs.new_release_published == 'true'
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-name: 'ReVanced Manager ${{ steps.release.outputs.new_release_git_tag }}'
|
||||
subject-path: app/build/outputs/apk/release/revanced-manager*.apk
|
@ -16,4 +16,4 @@ jobs:
|
||||
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
|
||||
repository: revanced/revanced-documentation
|
||||
event-type: update-documentation
|
||||
client-payload: '{"repo": "${{ github.event.repository.name }}", "ref": "${{ github.ref }}"}'
|
||||
client-payload: '{"repo": "${{ github.event.repository.name }}", "ref": "${{ github.ref }}"}'
|
136
.gitignore
vendored
136
.gitignore
vendored
@ -1,132 +1,12 @@
|
||||
### Java template
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
.idea/artifacts
|
||||
.idea/compiler.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/modules.xml
|
||||
.idea/*.iml
|
||||
.idea/modules
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Gradle template
|
||||
.gradle
|
||||
**/build/
|
||||
!src/**/build/
|
||||
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
|
||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||
!gradle-wrapper.jar
|
||||
|
||||
# Cache of project
|
||||
.gradletasknamecache
|
||||
|
||||
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
|
||||
# gradle/wrapper/gradle-wrapper.properties
|
||||
|
||||
# Potentially copyrighted test APK
|
||||
*.apk
|
||||
|
||||
# Ignore vscode config
|
||||
.vscode/
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# Ignore IDEA files
|
||||
.idea/
|
||||
|
||||
.kotlin/
|
||||
|
||||
/local.properties
|
||||
/.idea
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
|
||||
.cxx
|
||||
.kotlin/
|
||||
|
49
.releaserc
49
.releaserc
@ -1,49 +0,0 @@
|
||||
{
|
||||
"branches": [
|
||||
"main",
|
||||
{
|
||||
"name": "dev",
|
||||
"prerelease": true
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"@semantic-release/commit-analyzer", {
|
||||
"releaseRules": [
|
||||
{ "type": "build", "scope": "Needs bump", "release": "patch" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/changelog",
|
||||
"gradle-semantic-release-plugin",
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": [
|
||||
"CHANGELOG.md",
|
||||
"gradle.properties",
|
||||
],
|
||||
"message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/github",
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
"path": "app/build/outputs/apk/release/revanced-manager*.apk?(.asc)"
|
||||
},
|
||||
],
|
||||
successComment: false
|
||||
}
|
||||
],
|
||||
[
|
||||
"@saithodev/semantic-release-backmerge",
|
||||
{
|
||||
backmergeBranches: [{"from": "main", "to": "dev"}],
|
||||
clearWorkspace: true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
@ -60,44 +60,52 @@
|
||||
|
||||
# 👋 Contribution guidelines
|
||||
|
||||
This document describes how to contribute to ReVanced Manager.
|
||||
Welcome to contribution guidelines, this document contains
|
||||
everything you'll need to contribute to ReVanced Manager (and might even possibly apply to our other project like ReVanced Patches!)
|
||||
|
||||
There are many ways to contribute like writing docs and code, opening issues,
|
||||
helping people out in our community, translating or sponsoring our project, etc.
|
||||
|
||||
## 📖 Resources to help you get started
|
||||
|
||||
* The [documentation](/docs/README.md) provides steps to build ReVanced Manager from source
|
||||
* The [documentation](/docs/developer/README.md) provides steps to build ReVanced Manager from source
|
||||
* Our [backlog](https://github.com/orgs/ReVanced/projects/12) is where we keep track of what we're working on
|
||||
* [Issues](https://github.com/ReVanced/revanced-manager/issues) are where we keep track of bugs and feature requests
|
||||
|
||||
## 🙏 Submitting a feature request
|
||||
|
||||
Features can be requested by opening an issue using the
|
||||
[Feature request issue template](https://github.com/ReVanced/revanced-manager/issues/new?assignees=&labels=Feature+request&projects=&template=feature_request.yml&title=feat%3A+).
|
||||
[feature request issue template](https://github.com/ReVanced/revanced-manager/issues/new?assignees=&labels=feature-request&projects=&template=feature-issue.yml&title=feat%3A+%3Ctitle%3E).
|
||||
|
||||
> **Note**
|
||||
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Manager.
|
||||
> [!NOTE]
|
||||
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Manager.
|
||||
> Good motivation has to be provided for a request to be accepted.
|
||||
|
||||
## 🐞 Submitting a bug report
|
||||
|
||||
If you encounter a bug while using ReVanced Manager, open an issue using the
|
||||
[Bug report issue template](https://github.com/ReVanced/revanced-manager/issues/new?assignees=&labels=Bug+report&projects=&template=bug_report.yml&title=bug%3A+).
|
||||
If you encounter a bug while using the ReVanced Manager app, open an issue using the
|
||||
[bug report issue template](https://github.com/ReVanced/revanced-manager/issues/new?assignees=&labels=bug&projects=&template=bug-issue.yml&title=bug%3A+%3Ctitle%3E).
|
||||
|
||||
## 📝 How to contribute
|
||||
## 🌐 Submitting translations
|
||||
|
||||
1. Before contributing, it is recommended to open an issue to discuss your change
|
||||
with the maintainers of ReVanced Manager. This will help you determine whether your change is acceptable
|
||||
and whether it is worth your time to implement it
|
||||
2. Development happens on the `dev` branch. Fork the repository and create your branch from `dev`
|
||||
3. Commit your changes
|
||||
4. Submit a pull request to the `dev` branch of the repository and reference issues
|
||||
that your pull request closes in the description of your pull request
|
||||
5. Our team will review your pull request and provide feedback. Once your pull request is approved,
|
||||
it will be merged into the `dev` branch and will be included in the next release of ReVanced Manager
|
||||
You can contribute translations at translate.revanced.app
|
||||
|
||||
## 🤚 I want to contribute but don't know how to code
|
||||
> [!TIP]
|
||||
> * Try to keep the translated text roughly the same length as the original.
|
||||
> * Consider opting for gender-neutral words for language with variations of words based on gender.
|
||||
|
||||
Even if you don't know how to code, you can still contribute by
|
||||
translating ReVanced Manager on [Crowdin](https://translate.revanced.app/).
|
||||
## 🧑💻 Developing for ReVanced Manager
|
||||
|
||||
❤️ Thank you for considering contributing to ReVanced Manager,
|
||||
See the guidelines for developing for ReVanced Manager [here](/docs/developer/README.md)
|
||||
|
||||
## 🔒 Submitting a vulnerability
|
||||
|
||||
See the guideline for reporting security vulnerability [here](/SECURITY.md)
|
||||
|
||||
## 🤚 I don't want to do any of that but I want to contribute either way
|
||||
|
||||
You can still contribute by spreading positive word about us or if you'd like to sponsor us, checkout https://revanced.app/donate
|
||||
to learn more about how you can sponsor via GitHub, Open Collective, and cryptocurrencies.
|
||||
|
||||
❤️ Thank you for considering contributing to ReVanced Manager,
|
||||
ReVanced
|
||||
|
20
README.md
20
README.md
@ -60,8 +60,8 @@
|
||||
|
||||
# 💊 ReVanced Manager
|
||||
|
||||

|
||||

|
||||
[](https://github.com/ReVanced/revanced-manager/actions/workflows/release.yml)
|
||||
[](#️-license)
|
||||
|
||||
Application to use ReVanced on Android
|
||||
|
||||
@ -73,13 +73,16 @@ ReVanced Manager is an application that uses [ReVanced Patcher](https://github.c
|
||||
|
||||
Some of the features ReVanced Manager provides are:
|
||||
|
||||
- ⬇️ **Download**: Automatically download apps using the ReVanced Manager downloader plugin system
|
||||
- 💉 **Patch**: Select and apply patches to any Android app
|
||||
- 🛠️ **Customize**: Manage patches, apps, signing, themes, updates, and many more settings
|
||||
- 💉 **Patch apps**: Apply any patch of your choice to Android apps
|
||||
- 📱 **Portable**: ReVanced Patcher that fits in your pocket
|
||||
- 🤗 **Simple UI**: Quickly understand the ins and outs of ReVanced Manager
|
||||
- 🛠️ **Customization**: Configurable API, custom sources, language, signing keystore, theme and more
|
||||
|
||||
## 🔽 Download
|
||||
|
||||
You can download the most recent version of ReVanced Manager at [revanced.app/download](https://revanced.app/download) or from [GitHub releases](https://github.com/ReVanced/revanced-manager/releases/latest).
|
||||
You can get the most recent version of ReVanced Manager by downloading from
|
||||
the [ReVanced site](https://revanced.app/download).
|
||||
|
||||
Learn how to use ReVanced Manager by following the [documentation](/docs).
|
||||
|
||||
## 📚 Everything else
|
||||
@ -87,11 +90,8 @@ Learn how to use ReVanced Manager by following the [documentation](/docs).
|
||||
### 📙 Contributing
|
||||
|
||||
Thank you for considering contributing to ReVanced Manager.
|
||||
You can find the contribution guidelines [here](CONTRIBUTING.md).
|
||||
|
||||
### 🛠️ Building
|
||||
|
||||
To build a ReVanced Manager, you can follow the [documentation](/docs).
|
||||
The [contribution guidelines](CONTRIBUTING.md) provides information you'll need to open an issue, develop for ReVanced Manager and translations.
|
||||
|
||||
### 📄 Documentation
|
||||
|
||||
|
83
SECURITY.md
Normal file
83
SECURITY.md
Normal file
@ -0,0 +1,83 @@
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="assets/revanced-logo/revanced-logo.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="http://revanced.app/discord">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/revancedapp">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://t.me/app_revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://x.com/revancedapp">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
|
||||
# 🔒 Security Policy
|
||||
|
||||
This document describes how to report security vulnerabilities for ReVanced Manager.
|
||||
|
||||
## 🚨 Reporting a Vulnerability
|
||||
|
||||
Please open an issue in our [advisory tracker](https://github.com/ReVanced/revanced-manager/security/advisories/new)
|
||||
or reach out privately to us on [Discord](https://discord.gg/revanced).
|
||||
|
||||
If a vulnerability is confirmed and accepted, they will be published and
|
||||
you can join our [Discord](https://discord.gg/revanced) server to receive a
|
||||
special contributor role.
|
||||
|
||||
### ⏳ Supported Versions
|
||||
|
||||
| Version | Branch | Supported |
|
||||
|------------------------------------------|---------------|--------------------|
|
||||
| ![Latest stable release][LatestRelBadge] | `main` | :white_check_mark: |
|
||||
| ![Latest version][LatestVerBadge] | `dev` | :white_check_mark: |
|
||||
| ![Latest version][LatestVerBadge] | `compose-dev` | :white_check_mark: |
|
||||
|
||||
[LatestRelBadge]: https://img.shields.io/github/v/release/ReVanced/revanced-manager?style=for-the-badge "Latest stable release"
|
||||
[LatestVerBadge]: https://img.shields.io/badge/version-latest-brightgreen?style=for-the-badge "Latest version"
|
@ -1,150 +0,0 @@
|
||||
import java.io.IOException
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.parcelize)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
alias(libs.plugins.binary.compatibility.validator)
|
||||
`maven-publish`
|
||||
signing
|
||||
}
|
||||
|
||||
group = "app.revanced"
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.ktx)
|
||||
implementation(libs.runtime.ktx)
|
||||
implementation(libs.activity.compose)
|
||||
implementation(libs.appcompat)
|
||||
}
|
||||
|
||||
fun String.runCommand(): String {
|
||||
val process = ProcessBuilder(split("\\s".toRegex()))
|
||||
.redirectErrorStream(true)
|
||||
.directory(rootDir)
|
||||
.start()
|
||||
|
||||
val output = StringBuilder()
|
||||
val reader = process.inputStream.bufferedReader()
|
||||
|
||||
val thread = Thread {
|
||||
reader.forEachLine {
|
||||
output.appendLine(it)
|
||||
}
|
||||
}
|
||||
thread.start()
|
||||
|
||||
if (!process.waitFor(10, TimeUnit.SECONDS)) {
|
||||
process.destroy()
|
||||
throw IOException("Command timed out: $this")
|
||||
}
|
||||
|
||||
thread.join()
|
||||
return output.toString().trim()
|
||||
}
|
||||
|
||||
val projectPath: String = projectDir.relativeTo(rootDir).path
|
||||
val lastTag = "git describe --tags --abbrev=0".runCommand()
|
||||
val hasChangesInThisModule = "git diff --name-only $lastTag..HEAD".runCommand().lineSequence().any {
|
||||
it.startsWith(projectPath)
|
||||
}
|
||||
|
||||
tasks.matching { it.name.startsWith("publish") }.configureEach {
|
||||
onlyIf {
|
||||
hasChangesInThisModule
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.manager.plugin.downloader"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
aidl = true
|
||||
}
|
||||
}
|
||||
|
||||
apiValidation {
|
||||
nonPublicMarkers += "app.revanced.manager.plugin.downloader.PluginHostApi"
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/revanced/revanced-manager")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR") ?: extra["gpr.user"] as String?
|
||||
password = System.getenv("GITHUB_TOKEN") ?: extra["gpr.key"] as String?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publications {
|
||||
create<MavenPublication>("Api") {
|
||||
afterEvaluate {
|
||||
from(components["release"])
|
||||
}
|
||||
|
||||
groupId = "app.revanced"
|
||||
artifactId = "revanced-manager-api"
|
||||
version = project.version.toString()
|
||||
|
||||
pom {
|
||||
name = "ReVanced Manager API"
|
||||
description = "API for ReVanced Manager."
|
||||
url = "https://revanced.app"
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name = "GNU General Public License v3.0"
|
||||
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = "ReVanced"
|
||||
name = "ReVanced"
|
||||
email = "contact@revanced.app"
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection = "scm:git:git://github.com/revanced/revanced-manager.git"
|
||||
developerConnection = "scm:git:git@github.com:revanced/revanced-manager.git"
|
||||
url = "https://github.com/revanced/revanced-manager"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
useGpgCmd()
|
||||
sign(publishing.publications["Api"])
|
||||
}
|
@ -8,12 +8,106 @@ plugins {
|
||||
alias(libs.plugins.compose.compiler)
|
||||
alias(libs.plugins.devtools)
|
||||
alias(libs.plugins.about.libraries)
|
||||
signing
|
||||
}
|
||||
|
||||
val outputApkFileName = "${rootProject.name}-$version.apk"
|
||||
android {
|
||||
namespace = "app.revanced.manager"
|
||||
compileSdk = 35
|
||||
buildToolsVersion = "35.0.0"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "app.revanced.manager"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "0.0.1"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
resValue("string", "app_name", "ReVanced Manager (dev)")
|
||||
|
||||
buildConfigField("long", "BUILD_ID", "${Random.nextLong()}L")
|
||||
}
|
||||
|
||||
release {
|
||||
if (!project.hasProperty("noProguard")) {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
|
||||
if (project.hasProperty("signAsDebug")) {
|
||||
applicationIdSuffix = ".debug"
|
||||
resValue("string", "app_name", "ReVanced Manager Debug")
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
|
||||
buildConfigField("long", "BUILD_ID", "0L")
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
includeInBundle = false
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources.excludes.addAll(listOf(
|
||||
"/prebuilt/**",
|
||||
"META-INF/DEPENDENCIES",
|
||||
"META-INF/**.version",
|
||||
"DebugProbesKt.bin",
|
||||
"kotlin-tooling-metadata.json",
|
||||
"org/bouncycastle/pqc/**.properties",
|
||||
"org/bouncycastle/x509/**.properties",
|
||||
))
|
||||
jniLibs {
|
||||
useLegacyPackaging = true
|
||||
}
|
||||
}
|
||||
|
||||
ksp {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
aidl = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
android {
|
||||
androidResources {
|
||||
generateLocaleConfig = true
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path = file("src/main/cpp/CMakeLists.txt")
|
||||
version = "3.22.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
// AndroidX Core
|
||||
implementation(libs.androidx.ktx)
|
||||
implementation(libs.runtime.ktx)
|
||||
@ -64,7 +158,7 @@ dependencies {
|
||||
implementation(libs.revanced.library)
|
||||
|
||||
// Downloader plugins
|
||||
implementation(project(":api"))
|
||||
implementation(project(":downloader-plugin"))
|
||||
|
||||
// Native processes
|
||||
implementation(libs.kotlin.process)
|
||||
@ -112,145 +206,3 @@ dependencies {
|
||||
// Compose Icons
|
||||
implementation(libs.compose.icons.fontawesome)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.manager"
|
||||
compileSdk = 35
|
||||
buildToolsVersion = "35.0.1"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "app.revanced.manager"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "0.0.1"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
resValue("string", "app_name", "ReVanced Manager (Debug)")
|
||||
isPseudoLocalesEnabled = true
|
||||
|
||||
buildConfigField("long", "BUILD_ID", "${Random.nextLong()}L")
|
||||
}
|
||||
|
||||
release {
|
||||
if (!project.hasProperty("noProguard")) {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
|
||||
val keystoreFile = file("keystore.jks")
|
||||
|
||||
if (project.hasProperty("signAsDebug") || !keystoreFile.exists()) {
|
||||
applicationIdSuffix = ".debug_signed"
|
||||
resValue("string", "app_name", "ReVanced Manager (Debug signed)")
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
|
||||
isPseudoLocalesEnabled = true
|
||||
} else {
|
||||
signingConfig = signingConfigs.create("release") {
|
||||
storeFile = keystoreFile
|
||||
storePassword = System.getenv("KEYSTORE_PASSWORD")
|
||||
keyAlias = System.getenv("KEYSTORE_ENTRY_ALIAS")
|
||||
keyPassword = System.getenv("KEYSTORE_ENTRY_PASSWORD")
|
||||
}
|
||||
}
|
||||
|
||||
buildConfigField("long", "BUILD_ID", "0L")
|
||||
}
|
||||
}
|
||||
|
||||
applicationVariants.all {
|
||||
outputs.all {
|
||||
this as com.android.build.gradle.internal.api.ApkVariantOutputImpl
|
||||
|
||||
outputFileName = outputApkFileName
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
includeInBundle = false
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources.excludes.addAll(
|
||||
listOf(
|
||||
"/prebuilt/**",
|
||||
"META-INF/DEPENDENCIES",
|
||||
"META-INF/**.version",
|
||||
"DebugProbesKt.bin",
|
||||
"kotlin-tooling-metadata.json",
|
||||
"org/bouncycastle/pqc/**.properties",
|
||||
"org/bouncycastle/x509/**.properties",
|
||||
)
|
||||
)
|
||||
jniLibs {
|
||||
useLegacyPackaging = true
|
||||
}
|
||||
}
|
||||
|
||||
ksp {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
aidl = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
android {
|
||||
androidResources {
|
||||
generateLocaleConfig = true
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path = file("src/main/cpp/CMakeLists.txt")
|
||||
version = "3.22.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
tasks {
|
||||
// Needed by gradle-semantic-release-plugin.
|
||||
// Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435.
|
||||
val publish by registering {
|
||||
group = "publishing"
|
||||
description = "Build the release APK"
|
||||
|
||||
dependsOn("assembleRelease")
|
||||
|
||||
val apk = project.layout.buildDirectory.file("outputs/apk/release/${outputApkFileName}")
|
||||
val ascFile = apk.map { it.asFile.resolveSibling("${it.asFile.name}.asc") }
|
||||
|
||||
inputs.file(apk).withPropertyName("inputApk")
|
||||
outputs.file(ascFile).withPropertyName("outputAsc")
|
||||
|
||||
doLast {
|
||||
signing {
|
||||
useGpgCmd()
|
||||
sign(apk.get().asFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,6 @@
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.ENFORCE_UPDATE_OWNERSHIP" />
|
||||
|
||||
<application
|
||||
android:name=".ManagerApplication"
|
||||
|
@ -9,8 +9,6 @@ import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@ -25,32 +23,10 @@ import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.navigation
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.toRoute
|
||||
import app.revanced.manager.ui.model.navigation.AppSelector
|
||||
import app.revanced.manager.ui.model.navigation.ComplexParameter
|
||||
import app.revanced.manager.ui.model.navigation.Dashboard
|
||||
import app.revanced.manager.ui.model.navigation.InstalledApplicationInfo
|
||||
import app.revanced.manager.ui.model.navigation.Patcher
|
||||
import app.revanced.manager.ui.model.navigation.SelectedApplicationInfo
|
||||
import app.revanced.manager.ui.model.navigation.Settings
|
||||
import app.revanced.manager.ui.model.navigation.Update
|
||||
import app.revanced.manager.ui.screen.AppSelectorScreen
|
||||
import app.revanced.manager.ui.screen.DashboardScreen
|
||||
import app.revanced.manager.ui.screen.InstalledAppInfoScreen
|
||||
import app.revanced.manager.ui.screen.PatcherScreen
|
||||
import app.revanced.manager.ui.screen.PatchesSelectorScreen
|
||||
import app.revanced.manager.ui.screen.RequiredOptionsScreen
|
||||
import app.revanced.manager.ui.screen.SelectedAppInfoScreen
|
||||
import app.revanced.manager.ui.screen.SettingsScreen
|
||||
import app.revanced.manager.ui.screen.UpdateScreen
|
||||
import app.revanced.manager.ui.screen.settings.AboutSettingsScreen
|
||||
import app.revanced.manager.ui.screen.settings.AdvancedSettingsScreen
|
||||
import app.revanced.manager.ui.screen.settings.ContributorSettingsScreen
|
||||
import app.revanced.manager.ui.screen.settings.DeveloperSettingsScreen
|
||||
import app.revanced.manager.ui.screen.settings.DownloadsSettingsScreen
|
||||
import app.revanced.manager.ui.screen.settings.GeneralSettingsScreen
|
||||
import app.revanced.manager.ui.screen.settings.ImportExportSettingsScreen
|
||||
import app.revanced.manager.ui.screen.settings.LicensesSettingsScreen
|
||||
import app.revanced.manager.ui.screen.settings.update.ChangelogsSettingsScreen
|
||||
import app.revanced.manager.ui.model.navigation.*
|
||||
import app.revanced.manager.ui.screen.*
|
||||
import app.revanced.manager.ui.screen.settings.*
|
||||
import app.revanced.manager.ui.screen.settings.update.ChangelogsScreen
|
||||
import app.revanced.manager.ui.screen.settings.update.UpdatesSettingsScreen
|
||||
import app.revanced.manager.ui.theme.ReVancedManagerTheme
|
||||
import app.revanced.manager.ui.theme.Theme
|
||||
@ -113,10 +89,6 @@ private fun ReVancedManager(vm: MainViewModel) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Dashboard,
|
||||
enterTransition = { slideInHorizontally(initialOffsetX = { it }) },
|
||||
exitTransition = { slideOutHorizontally(targetOffsetX = { -it / 3 }) },
|
||||
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it / 3 }) },
|
||||
popExitTransition = { slideOutHorizontally(targetOffsetX = { it }) },
|
||||
) {
|
||||
composable<Dashboard> {
|
||||
DashboardScreen(
|
||||
@ -164,7 +136,7 @@ private fun ReVancedManager(vm: MainViewModel) {
|
||||
}
|
||||
}
|
||||
},
|
||||
viewModel = koinViewModel { parametersOf(it.getComplexArg<Patcher.ViewModelParams>()) }
|
||||
vm = koinViewModel { parametersOf(it.getComplexArg<Patcher.ViewModelParams>()) }
|
||||
)
|
||||
}
|
||||
|
||||
@ -234,7 +206,7 @@ private fun ReVancedManager(vm: MainViewModel) {
|
||||
selectedAppInfoVm.updateConfiguration(patches, options)
|
||||
navController.popBackStack()
|
||||
},
|
||||
viewModel = koinViewModel { parametersOf(data) }
|
||||
vm = koinViewModel { parametersOf(data) }
|
||||
)
|
||||
}
|
||||
|
||||
@ -277,10 +249,6 @@ private fun ReVancedManager(vm: MainViewModel) {
|
||||
AdvancedSettingsScreen(onBackClick = navController::popBackStack)
|
||||
}
|
||||
|
||||
composable<Settings.Developer> {
|
||||
DeveloperSettingsScreen(onBackClick = navController::popBackStack)
|
||||
}
|
||||
|
||||
composable<Settings.Updates> {
|
||||
UpdatesSettingsScreen(
|
||||
onBackClick = navController::popBackStack,
|
||||
@ -305,17 +273,20 @@ private fun ReVancedManager(vm: MainViewModel) {
|
||||
}
|
||||
|
||||
composable<Settings.Changelogs> {
|
||||
ChangelogsSettingsScreen(onBackClick = navController::popBackStack)
|
||||
ChangelogsScreen(onBackClick = navController::popBackStack)
|
||||
}
|
||||
|
||||
composable<Settings.Contributors> {
|
||||
ContributorSettingsScreen(onBackClick = navController::popBackStack)
|
||||
ContributorScreen(onBackClick = navController::popBackStack)
|
||||
}
|
||||
|
||||
composable<Settings.Licenses> {
|
||||
LicensesSettingsScreen(onBackClick = navController::popBackStack)
|
||||
LicensesScreen(onBackClick = navController::popBackStack)
|
||||
}
|
||||
|
||||
composable<Settings.DeveloperOptions> {
|
||||
DeveloperOptionsScreen(onBackClick = navController::popBackStack)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ interface PatchBundleDao {
|
||||
fun getPropsById(uid: Int): Flow<BundleProperties?>
|
||||
|
||||
@Query("UPDATE patch_bundles SET version = :patches WHERE uid = :uid")
|
||||
suspend fun updateVersionHash(uid: Int, patches: String?)
|
||||
suspend fun updateVersion(uid: Int, patches: String?)
|
||||
|
||||
@Query("UPDATE patch_bundles SET auto_update = :value WHERE uid = :uid")
|
||||
suspend fun setAutoUpdate(uid: Int, value: Boolean)
|
||||
@ -26,7 +26,7 @@ interface PatchBundleDao {
|
||||
@Transaction
|
||||
suspend fun reset() {
|
||||
purgeCustomBundles()
|
||||
updateVersionHash(0, null) // Reset the main source
|
||||
updateVersion(0, null) // Reset the main source
|
||||
}
|
||||
|
||||
@Query("DELETE FROM patch_bundles WHERE uid = :uid")
|
||||
|
@ -33,12 +33,12 @@ sealed class Source {
|
||||
data class PatchBundleEntity(
|
||||
@PrimaryKey val uid: Int,
|
||||
@ColumnInfo(name = "name") val name: String,
|
||||
@ColumnInfo(name = "version") val versionHash: String? = null,
|
||||
@ColumnInfo(name = "version") val version: String? = null,
|
||||
@ColumnInfo(name = "source") val source: Source,
|
||||
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
|
||||
)
|
||||
|
||||
data class BundleProperties(
|
||||
@ColumnInfo(name = "version") val versionHash: String? = null,
|
||||
@ColumnInfo(name = "version") val version: String? = null,
|
||||
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
|
||||
)
|
@ -27,10 +27,10 @@ abstract class OptionDao {
|
||||
abstract suspend fun createOptionGroup(group: OptionGroup)
|
||||
|
||||
@Query("DELETE FROM option_groups WHERE patch_bundle = :uid")
|
||||
abstract suspend fun resetOptionsForPatchBundle(uid: Int)
|
||||
abstract suspend fun clearForPatchBundle(uid: Int)
|
||||
|
||||
@Query("DELETE FROM option_groups WHERE package_name = :packageName")
|
||||
abstract suspend fun resetOptionsForPackage(packageName: String)
|
||||
abstract suspend fun clearForPackage(packageName: String)
|
||||
|
||||
@Query("DELETE FROM option_groups")
|
||||
abstract suspend fun reset()
|
||||
|
@ -5,7 +5,6 @@ import androidx.room.Insert
|
||||
import androidx.room.MapColumn
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
abstract class SelectionDao {
|
||||
@ -35,14 +34,11 @@ abstract class SelectionDao {
|
||||
@Insert
|
||||
abstract suspend fun createSelection(selection: PatchSelection)
|
||||
|
||||
@Query("SELECT package_name FROM patch_selections")
|
||||
abstract fun getPackagesWithSelection(): Flow<List<String>>
|
||||
|
||||
@Query("DELETE FROM patch_selections WHERE patch_bundle = :uid")
|
||||
abstract suspend fun resetForPatchBundle(uid: Int)
|
||||
abstract suspend fun clearForPatchBundle(uid: Int)
|
||||
|
||||
@Query("DELETE FROM patch_selections WHERE package_name = :packageName")
|
||||
abstract suspend fun resetForPackage(packageName: String)
|
||||
abstract suspend fun clearForPackage(packageName: String)
|
||||
|
||||
@Query("DELETE FROM patch_selections")
|
||||
abstract suspend fun reset()
|
||||
|
@ -15,7 +15,7 @@ class LocalPatchBundle(name: String, id: Int, directory: File) :
|
||||
}
|
||||
|
||||
reload()?.also {
|
||||
saveVersionHash(it.readManifestAttribute("Version"))
|
||||
saveVersion(it.readManifestAttribute("Version"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,9 +38,6 @@ sealed class PatchBundleSource(initialName: String, val uid: Int, directory: Fil
|
||||
|
||||
suspend fun getName() = nameFlow.first()
|
||||
|
||||
val versionFlow = state.map { it.patchBundleOrNull()?.readManifestAttribute("Version") }
|
||||
val patchCountFlow = state.map { it.patchBundleOrNull()?.patches?.size ?: 0 }
|
||||
|
||||
/**
|
||||
* Returns true if the bundle has been downloaded to local storage.
|
||||
*/
|
||||
@ -87,9 +84,9 @@ sealed class PatchBundleSource(initialName: String, val uid: Int, directory: Fil
|
||||
fun propsFlow() = configRepository.getProps(uid).flowOn(Dispatchers.Default)
|
||||
suspend fun getProps() = propsFlow().first()!!
|
||||
|
||||
suspend fun currentVersionHash() = getProps().versionHash
|
||||
protected suspend fun saveVersionHash(version: String?) =
|
||||
configRepository.updateVersionHash(uid, version)
|
||||
suspend fun currentVersion() = getProps().version
|
||||
protected suspend fun saveVersion(version: String?) =
|
||||
configRepository.updateVersion(uid, version)
|
||||
|
||||
suspend fun setName(name: String) {
|
||||
configRepository.setName(uid, name)
|
||||
|
@ -25,7 +25,7 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo
|
||||
}
|
||||
}
|
||||
|
||||
saveVersionHash(info.version)
|
||||
saveVersion(info.version)
|
||||
reload()
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo
|
||||
|
||||
suspend fun update(): Boolean = withContext(Dispatchers.IO) {
|
||||
val info = getLatestInfo()
|
||||
if (hasInstalled() && info.version == currentVersionHash())
|
||||
if (hasInstalled() && info.version == currentVersion())
|
||||
return@withContext false
|
||||
|
||||
download(info)
|
||||
|
@ -3,7 +3,6 @@ package app.revanced.manager.domain.manager
|
||||
import android.content.Context
|
||||
import app.revanced.manager.domain.manager.base.BasePreferencesManager
|
||||
import app.revanced.manager.ui.theme.Theme
|
||||
import app.revanced.manager.util.isDebuggable
|
||||
|
||||
class PreferencesManager(
|
||||
context: Context
|
||||
@ -25,10 +24,8 @@ class PreferencesManager(
|
||||
|
||||
val disablePatchVersionCompatCheck = booleanPreference("disable_patch_version_compatibility_check", false)
|
||||
val disableSelectionWarning = booleanPreference("disable_selection_warning", false)
|
||||
val disableUniversalPatchCheck = booleanPreference("disable_patch_universal_check", false)
|
||||
val disableUniversalPatchWarning = booleanPreference("disable_universal_patch_warning", false)
|
||||
val suggestedVersionSafeguard = booleanPreference("suggested_version_safeguard", true)
|
||||
|
||||
val acknowledgedDownloaderPlugins = stringSetPreference("acknowledged_downloader_plugins", emptySet())
|
||||
|
||||
val showDeveloperSettings = booleanPreference("show_developer_settings", context.isDebuggable)
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
|
||||
PatchBundleEntity(
|
||||
uid = generateUid(),
|
||||
name = name,
|
||||
versionHash = null,
|
||||
version = null,
|
||||
source = source,
|
||||
autoUpdate = autoUpdate
|
||||
).also {
|
||||
@ -34,11 +34,8 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
|
||||
|
||||
suspend fun delete(uid: Int) = dao.remove(uid)
|
||||
|
||||
/**
|
||||
* Sets the version hash used for updates.
|
||||
*/
|
||||
suspend fun updateVersionHash(uid: Int, versionHash: String?) =
|
||||
dao.updateVersionHash(uid, versionHash)
|
||||
suspend fun updateVersion(uid: Int, version: String?) =
|
||||
dao.updateVersion(uid, version)
|
||||
|
||||
suspend fun setAutoUpdate(uid: Int, value: Boolean) = dao.setAutoUpdate(uid, value)
|
||||
|
||||
@ -50,7 +47,7 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
|
||||
val defaultSource = PatchBundleEntity(
|
||||
uid = 0,
|
||||
name = "",
|
||||
versionHash = null,
|
||||
version = null,
|
||||
source = Source.API,
|
||||
autoUpdate = false
|
||||
)
|
||||
|
@ -76,7 +76,7 @@ class PatchOptionsRepository(db: AppDatabase) {
|
||||
fun getPackagesWithSavedOptions() =
|
||||
dao.getPackagesWithOptions().map(Iterable<String>::toSet).distinctUntilChanged()
|
||||
|
||||
suspend fun resetOptionsForPackage(packageName: String) = dao.resetOptionsForPackage(packageName)
|
||||
suspend fun resetOptionsForPatchBundle(uid: Int) = dao.resetOptionsForPatchBundle(uid)
|
||||
suspend fun clearOptionsForPackage(packageName: String) = dao.clearForPackage(packageName)
|
||||
suspend fun clearOptionsForPatchBundle(uid: Int) = dao.clearForPatchBundle(uid)
|
||||
suspend fun reset() = dao.reset()
|
||||
}
|
@ -3,8 +3,6 @@ package app.revanced.manager.domain.repository
|
||||
import app.revanced.manager.data.room.AppDatabase
|
||||
import app.revanced.manager.data.room.AppDatabase.Companion.generateUid
|
||||
import app.revanced.manager.data.room.selection.PatchSelection
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class PatchSelectionRepository(db: AppDatabase) {
|
||||
private val dao = db.selectionDao()
|
||||
@ -27,15 +25,8 @@ class PatchSelectionRepository(db: AppDatabase) {
|
||||
)
|
||||
})
|
||||
|
||||
fun getPackagesWithSavedSelection() =
|
||||
dao.getPackagesWithSelection().map(Iterable<String>::toSet).distinctUntilChanged()
|
||||
|
||||
suspend fun resetSelectionForPackage(packageName: String) {
|
||||
dao.resetForPackage(packageName)
|
||||
}
|
||||
|
||||
suspend fun resetSelectionForPatchBundle(uid: Int) {
|
||||
dao.resetForPatchBundle(uid)
|
||||
suspend fun clearSelection(packageName: String) {
|
||||
dao.clearForPackage(packageName)
|
||||
}
|
||||
|
||||
suspend fun reset() = dao.reset()
|
||||
@ -43,7 +34,7 @@ class PatchSelectionRepository(db: AppDatabase) {
|
||||
suspend fun export(bundleUid: Int): SerializedSelection = dao.exportSelection(bundleUid)
|
||||
|
||||
suspend fun import(bundleUid: Int, selection: SerializedSelection) {
|
||||
dao.resetForPatchBundle(bundleUid)
|
||||
dao.clearForPatchBundle(bundleUid)
|
||||
dao.updateSelections(selection.entries.associate { (packageName, patches) ->
|
||||
getOrCreateSelection(bundleUid, packageName) to patches.toSet()
|
||||
})
|
||||
|
@ -7,7 +7,7 @@ import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
/**
|
||||
* A container for [Parcelable] data returned from downloader. Instances of this class can be safely stored in a bundle without needing to set the [ClassLoader].
|
||||
* A container for [Parcelable] data returned from downloaders. Instances of this class can be safely stored in a bundle without needing to set the [ClassLoader].
|
||||
*/
|
||||
class ParceledDownloaderData private constructor(
|
||||
val pluginPackageName: String,
|
||||
|
@ -111,7 +111,6 @@ class ProcessRuntime(private val context: Context) : Runtime(context) {
|
||||
}
|
||||
|
||||
val patching = CompletableDeferred<Unit>()
|
||||
val scope = this
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
val binder = awaitBinderConnection()
|
||||
@ -125,7 +124,7 @@ class ProcessRuntime(private val context: Context) : Runtime(context) {
|
||||
override fun log(level: String, msg: String) = logger.log(enumValueOf(level), msg)
|
||||
|
||||
override fun patchSucceeded() {
|
||||
scope.launch { onPatchCompleted() }
|
||||
launch { onPatchCompleted() }
|
||||
}
|
||||
|
||||
override fun progress(name: String?, state: String?, msg: String?) =
|
||||
@ -180,7 +179,7 @@ class ProcessRuntime(private val context: Context) : Runtime(context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* An [Exception] occurred in the remote process while patching.
|
||||
* An [Exception] occured in the remote process while patching.
|
||||
*
|
||||
* @param originalStackTrace The stack trace of the original [Exception].
|
||||
*/
|
||||
|
@ -1,10 +1,8 @@
|
||||
package app.revanced.manager.patcher.runtime.process
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityThread
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Looper
|
||||
import app.revanced.manager.BuildConfig
|
||||
@ -97,10 +95,6 @@ class PatcherProcess(private val context: Context) : IPatcherProcess.Stub() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val longArrayClass = LongArray::class.java
|
||||
private val emptyLongArray = LongArray(0)
|
||||
|
||||
@SuppressLint("PrivateApi")
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
Looper.prepare()
|
||||
@ -111,15 +105,6 @@ class PatcherProcess(private val context: Context) : IPatcherProcess.Stub() {
|
||||
val systemContext = ActivityThread.systemMain().systemContext as Context
|
||||
val appContext = systemContext.createPackageContext(managerPackageName, 0)
|
||||
|
||||
// Avoid annoying logs. See https://github.com/robolectric/robolectric/blob/ad0484c6b32c7d11176c711abeb3cb4a900f9258/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java#L376-L388
|
||||
Class.forName("android.app.AppCompatCallbacks").apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
||||
getDeclaredMethod("install", longArrayClass, longArrayClass).also { it.isAccessible = true }(null, emptyLongArray, emptyLongArray)
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
getDeclaredMethod("install", longArrayClass).also { it.isAccessible = true }(null, emptyLongArray)
|
||||
}
|
||||
}
|
||||
|
||||
val ipcInterface = PatcherProcess(appContext)
|
||||
|
||||
appContext.sendBroadcast(Intent().apply {
|
||||
|
@ -4,20 +4,9 @@ import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -55,27 +44,17 @@ fun AppTopBar(
|
||||
)
|
||||
},
|
||||
actions: @Composable (RowScope.() -> Unit) = {},
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||
applyContainerColor: Boolean = false
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||
) {
|
||||
val containerColor = if (applyContainerColor) {
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp)
|
||||
} else {
|
||||
Color.Unspecified
|
||||
}
|
||||
val containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp)
|
||||
|
||||
TopAppBar(
|
||||
title = { Text(title) },
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
if (onBackClick != null) {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.back),
|
||||
) {
|
||||
IconButton(onClick = onBackClick) {
|
||||
backIcon()
|
||||
}
|
||||
IconButton(onClick = onBackClick) {
|
||||
backIcon()
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -86,46 +65,3 @@ fun AppTopBar(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AppTopBar(
|
||||
title: @Composable () -> Unit,
|
||||
onBackClick: (() -> Unit)? = null,
|
||||
backIcon: @Composable (() -> Unit) = @Composable {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(
|
||||
R.string.back
|
||||
)
|
||||
)
|
||||
},
|
||||
actions: @Composable (RowScope.() -> Unit) = {},
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||
applyContainerColor: Boolean = false
|
||||
) {
|
||||
val containerColor = if (applyContainerColor) {
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp)
|
||||
} else {
|
||||
Color.Unspecified
|
||||
}
|
||||
|
||||
TopAppBar(
|
||||
title = title,
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
if (onBackClick != null) {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.back),
|
||||
) {
|
||||
IconButton(onClick = onBackClick) {
|
||||
backIcon()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
actions = actions,
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = containerColor
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -27,19 +27,14 @@ fun ArrowButton(
|
||||
)
|
||||
|
||||
onClick?.let {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(description),
|
||||
) {
|
||||
IconButton(onClick = it) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.KeyboardArrowUp,
|
||||
contentDescription = stringResource(description),
|
||||
modifier = Modifier
|
||||
.rotate(rotation)
|
||||
.then(modifier)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = it) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.KeyboardArrowUp,
|
||||
contentDescription = stringResource(description),
|
||||
modifier = Modifier
|
||||
.rotate(rotation)
|
||||
.then(modifier)
|
||||
)
|
||||
}
|
||||
} ?: Icon(
|
||||
imageVector = Icons.Filled.KeyboardArrowUp,
|
||||
|
@ -1,41 +0,0 @@
|
||||
package app.revanced.manager.ui.component
|
||||
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.revanced.manager.R
|
||||
|
||||
@Composable
|
||||
fun ConfirmDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
title: String,
|
||||
description: String,
|
||||
icon: ImageVector
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
dismissButton = {
|
||||
TextButton(onDismiss) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onConfirm()
|
||||
onDismiss()
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.confirm))
|
||||
}
|
||||
},
|
||||
title = { Text(title) },
|
||||
icon = { Icon(icon, null) },
|
||||
text = { Text(description) }
|
||||
)
|
||||
}
|
@ -16,6 +16,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.ui.component.bundle.BundleTopBar
|
||||
|
||||
@ -24,8 +26,12 @@ import app.revanced.manager.ui.component.bundle.BundleTopBar
|
||||
fun ExceptionViewerDialog(text: String, onDismiss: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
|
||||
FullscreenDialog(
|
||||
Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
properties = DialogProperties(
|
||||
usePlatformDefaultWidth = false,
|
||||
dismissOnBackPress = true
|
||||
)
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@ -39,30 +45,25 @@ fun ExceptionViewerDialog(text: String, onDismiss: () -> Unit) {
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.share),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(
|
||||
Intent.EXTRA_TEXT,
|
||||
text
|
||||
)
|
||||
type = "text/plain"
|
||||
}
|
||||
|
||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||
context.startActivity(shareIntent)
|
||||
IconButton(
|
||||
onClick = {
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(
|
||||
Intent.EXTRA_TEXT,
|
||||
text
|
||||
)
|
||||
type = "text/plain"
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Outlined.Share,
|
||||
contentDescription = stringResource(R.string.share)
|
||||
)
|
||||
|
||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||
context.startActivity(shareIntent)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Outlined.Share,
|
||||
contentDescription = stringResource(R.string.share)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -1,34 +0,0 @@
|
||||
package app.revanced.manager.ui.component
|
||||
|
||||
import android.view.WindowManager
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.compose.ui.window.DialogWindowProvider
|
||||
|
||||
private val properties = DialogProperties(
|
||||
usePlatformDefaultWidth = false,
|
||||
dismissOnBackPress = true,
|
||||
decorFitsSystemWindows = false,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun FullscreenDialog(onDismissRequest: () -> Unit, content: @Composable () -> Unit) {
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
properties = properties
|
||||
) {
|
||||
val window = (LocalView.current.parent as DialogWindowProvider).window
|
||||
LaunchedEffect(Unit) {
|
||||
window.statusBarColor = Color.Transparent.toArgb()
|
||||
window.navigationBarColor = Color.Transparent.toArgb()
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
||||
}
|
||||
|
||||
content()
|
||||
}
|
||||
}
|
@ -138,17 +138,12 @@ fun NotificationCard(
|
||||
)
|
||||
}
|
||||
if (onDismiss != null) {
|
||||
TooltipWrap(
|
||||
modifier = modifier,
|
||||
tooltip = stringResource(R.string.close),
|
||||
) {
|
||||
IconButton(onClick = onDismiss) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Close,
|
||||
contentDescription = stringResource(R.string.close),
|
||||
tint = color,
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onDismiss) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Close,
|
||||
contentDescription = stringResource(R.string.close),
|
||||
tint = color,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,18 +33,13 @@ fun PasswordField(modifier: Modifier = Modifier, value: String, onValueChange: (
|
||||
label = label,
|
||||
modifier = modifier,
|
||||
trailingIcon = {
|
||||
TooltipWrap(
|
||||
modifier = modifier,
|
||||
tooltip = if (visible) stringResource(R.string.show_password_field) else stringResource(R.string.hide_password_field),
|
||||
) {
|
||||
IconButton(onClick = {
|
||||
visible = !visible
|
||||
}) {
|
||||
val (icon, description) = remember(visible) {
|
||||
if (visible) Icons.Outlined.VisibilityOff to R.string.hide_password_field else Icons.Outlined.Visibility to R.string.show_password_field
|
||||
}
|
||||
Icon(icon, stringResource(description))
|
||||
IconButton(onClick = {
|
||||
visible = !visible
|
||||
}) {
|
||||
val (icon, description) = remember(visible) {
|
||||
if (visible) Icons.Outlined.VisibilityOff to R.string.hide_password_field else Icons.Outlined.Visibility to R.string.show_password_field
|
||||
}
|
||||
Icon(icon, stringResource(description))
|
||||
}
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(
|
||||
|
@ -48,16 +48,11 @@ fun SearchView(
|
||||
onExpandedChange = onActiveChange,
|
||||
placeholder = placeholder,
|
||||
leadingIcon = {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.back),
|
||||
) {
|
||||
IconButton(onClick = { onActiveChange(false) }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.ArrowBack,
|
||||
stringResource(R.string.back)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { onActiveChange(false) }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.ArrowBack,
|
||||
stringResource(R.string.back)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -1,94 +0,0 @@
|
||||
package app.revanced.manager.ui.component
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TooltipBox
|
||||
import androidx.compose.material3.TooltipDefaults
|
||||
import androidx.compose.material3.rememberTooltipState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.window.PopupPositionProvider
|
||||
|
||||
/**
|
||||
* Wraps a composable with a tooltip.
|
||||
*
|
||||
* @param modifier the [Modifier] to be applied to the [TooltipBox]
|
||||
* @param tooltip the text to be displayed in the [TooltipBox]
|
||||
* @param positionProvider Anchor point for the tooltip, defaults to [TooltipDefaults.rememberPlainTooltipPositionProvider]
|
||||
* @param content The composable UI to be wrapped with [TooltipBox]
|
||||
*
|
||||
* @see [TooltipBox]
|
||||
*/
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
fun TooltipWrap(
|
||||
modifier: Modifier,
|
||||
tooltip: String,
|
||||
positionProvider: PopupPositionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val tooltipState = rememberTooltipState()
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
LaunchedEffect(tooltipState.isVisible) {
|
||||
if (tooltipState.isVisible) {
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
}
|
||||
|
||||
TooltipBox(
|
||||
modifier = modifier,
|
||||
positionProvider = positionProvider,
|
||||
tooltip = {
|
||||
PlainTooltip { Text(tooltip) }
|
||||
},
|
||||
state = tooltipState
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a composable with a tooltip.
|
||||
*
|
||||
* @param modifier the [Modifier] to be applied to the [TooltipBox]
|
||||
* @param tooltip the R.string to be displayed in the [TooltipBox]
|
||||
* @param positionProvider Anchor point for the tooltip, defaults to [TooltipDefaults.rememberPlainTooltipPositionProvider]
|
||||
* @param content The composable UI to be wrapped with [TooltipBox]
|
||||
*
|
||||
* @see [TooltipBox]
|
||||
*/
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
fun TooltipWrap(
|
||||
modifier: Modifier,
|
||||
@StringRes tooltip: Int,
|
||||
positionProvider: PopupPositionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val tooltipState = rememberTooltipState()
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
LaunchedEffect(tooltipState.isVisible) {
|
||||
if (tooltipState.isVisible) {
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
}
|
||||
|
||||
TooltipBox(
|
||||
modifier = modifier,
|
||||
positionProvider = positionProvider,
|
||||
tooltip = {
|
||||
PlainTooltip { Text(stringResource(tooltip)) }
|
||||
},
|
||||
state = tooltipState
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
@ -12,19 +12,17 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.data.platform.NetworkInfo
|
||||
import app.revanced.manager.domain.bundles.LocalPatchBundle
|
||||
import app.revanced.manager.domain.bundles.PatchBundleSource
|
||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.asRemoteOrNull
|
||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.isDefault
|
||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState
|
||||
import app.revanced.manager.ui.component.ExceptionViewerDialog
|
||||
import app.revanced.manager.ui.component.FullscreenDialog
|
||||
import app.revanced.manager.ui.component.TooltipWrap
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@ -34,8 +32,6 @@ fun BundleInformationDialog(
|
||||
bundle: PatchBundleSource,
|
||||
onUpdate: () -> Unit,
|
||||
) {
|
||||
val networkInfo = koinInject<NetworkInfo>()
|
||||
val hasNetwork = remember { networkInfo.isConnected() }
|
||||
val composableScope = rememberCoroutineScope()
|
||||
var viewCurrentBundlePatches by remember { mutableStateOf(false) }
|
||||
val isLocal = bundle is LocalPatchBundle
|
||||
@ -43,8 +39,9 @@ fun BundleInformationDialog(
|
||||
val props by remember(bundle) {
|
||||
bundle.propsFlow()
|
||||
}.collectAsStateWithLifecycle(null)
|
||||
val patchCount by bundle.patchCountFlow.collectAsStateWithLifecycle(0)
|
||||
val version by bundle.versionFlow.collectAsStateWithLifecycle(null)
|
||||
val patchCount = remember(state) {
|
||||
state.patchBundleOrNull()?.patches?.size ?: 0
|
||||
}
|
||||
|
||||
if (viewCurrentBundlePatches) {
|
||||
BundlePatchesDialog(
|
||||
@ -55,8 +52,12 @@ fun BundleInformationDialog(
|
||||
)
|
||||
}
|
||||
|
||||
FullscreenDialog(
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
properties = DialogProperties(
|
||||
usePlatformDefaultWidth = false,
|
||||
dismissOnBackPress = true
|
||||
)
|
||||
) {
|
||||
val bundleName by bundle.nameState
|
||||
|
||||
@ -73,29 +74,19 @@ fun BundleInformationDialog(
|
||||
},
|
||||
actions = {
|
||||
if (!bundle.isDefault) {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.delete),
|
||||
) {
|
||||
IconButton(onClick = onDeleteRequest) {
|
||||
Icon(
|
||||
Icons.Outlined.DeleteOutline,
|
||||
stringResource(R.string.delete)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onDeleteRequest) {
|
||||
Icon(
|
||||
Icons.Outlined.DeleteOutline,
|
||||
stringResource(R.string.delete)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!isLocal && hasNetwork) {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.refresh),
|
||||
) {
|
||||
IconButton(onClick = onUpdate) {
|
||||
Icon(
|
||||
Icons.Outlined.Update,
|
||||
stringResource(R.string.refresh)
|
||||
)
|
||||
}
|
||||
if (!isLocal) {
|
||||
IconButton(onClick = onUpdate) {
|
||||
Icon(
|
||||
Icons.Outlined.Update,
|
||||
stringResource(R.string.refresh)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -108,8 +99,8 @@ fun BundleInformationDialog(
|
||||
name = bundleName,
|
||||
remoteUrl = bundle.asRemoteOrNull?.endpoint,
|
||||
patchCount = patchCount,
|
||||
version = version,
|
||||
autoUpdate = props?.autoUpdate == true,
|
||||
version = props?.version,
|
||||
autoUpdate = props?.autoUpdate ?: false,
|
||||
onAutoUpdateChange = {
|
||||
composableScope.launch {
|
||||
bundle.asRemoteOrNull?.setAutoUpdate(it)
|
||||
|
@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.icons.outlined.ErrorOutline
|
||||
import androidx.compose.material.icons.outlined.Warning
|
||||
import androidx.compose.material3.Icon
|
||||
@ -27,9 +26,8 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.domain.bundles.PatchBundleSource
|
||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState
|
||||
import app.revanced.manager.ui.component.ConfirmDialog
|
||||
import app.revanced.manager.ui.component.haptics.HapticCheckbox
|
||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@ -44,35 +42,25 @@ fun BundleItem(
|
||||
toggleSelection: (Boolean) -> Unit,
|
||||
) {
|
||||
var viewBundleDialogPage by rememberSaveable { mutableStateOf(false) }
|
||||
var showDeleteConfirmationDialog by rememberSaveable { mutableStateOf(false) }
|
||||
val state by bundle.state.collectAsStateWithLifecycle()
|
||||
|
||||
val version by bundle.versionFlow.collectAsStateWithLifecycle(null)
|
||||
val patchCount by bundle.patchCountFlow.collectAsStateWithLifecycle(0)
|
||||
val version by remember(bundle) {
|
||||
bundle.propsFlow().map { props -> props?.version }
|
||||
}.collectAsStateWithLifecycle(null)
|
||||
val name by bundle.nameState
|
||||
|
||||
if (viewBundleDialogPage) {
|
||||
BundleInformationDialog(
|
||||
onDismissRequest = { viewBundleDialogPage = false },
|
||||
onDeleteRequest = { showDeleteConfirmationDialog = true },
|
||||
onDeleteRequest = {
|
||||
viewBundleDialogPage = false
|
||||
onDelete()
|
||||
},
|
||||
bundle = bundle,
|
||||
onUpdate = onUpdate,
|
||||
)
|
||||
}
|
||||
|
||||
if (showDeleteConfirmationDialog) {
|
||||
ConfirmDialog(
|
||||
onDismiss = { showDeleteConfirmationDialog = false },
|
||||
onConfirm = {
|
||||
onDelete()
|
||||
viewBundleDialogPage = false
|
||||
},
|
||||
title = stringResource(R.string.bundle_delete_single_dialog_title),
|
||||
description = stringResource(R.string.bundle_delete_single_dialog_description, name),
|
||||
icon = Icons.Outlined.Delete
|
||||
)
|
||||
}
|
||||
|
||||
ListItem(
|
||||
modifier = Modifier
|
||||
.height(64.dp)
|
||||
@ -92,7 +80,7 @@ fun BundleItem(
|
||||
|
||||
headlineContent = { Text(name) },
|
||||
supportingContent = {
|
||||
if (state is PatchBundleSource.State.Loaded) {
|
||||
state.patchBundleOrNull()?.patches?.size?.let { patchCount ->
|
||||
Text(pluralStringResource(R.plurals.patch_count, patchCount, patchCount))
|
||||
}
|
||||
},
|
||||
|
@ -22,12 +22,13 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.domain.bundles.PatchBundleSource
|
||||
import app.revanced.manager.patcher.patch.PatchInfo
|
||||
import app.revanced.manager.ui.component.ArrowButton
|
||||
import app.revanced.manager.ui.component.FullscreenDialog
|
||||
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ -40,8 +41,12 @@ fun BundlePatchesDialog(
|
||||
var showOptions by rememberSaveable { mutableStateOf(false) }
|
||||
val state by bundle.state.collectAsStateWithLifecycle()
|
||||
|
||||
FullscreenDialog(
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
properties = DialogProperties(
|
||||
usePlatformDefaultWidth = false,
|
||||
dismissOnBackPress = true
|
||||
)
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
|
@ -13,14 +13,11 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import app.revanced.manager.domain.bundles.PatchBundleSource
|
||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@ -57,8 +54,6 @@ fun BundleSelector(bundles: List<PatchBundleSource>, onFinish: (PatchBundleSourc
|
||||
}
|
||||
bundles.forEach {
|
||||
val name by it.nameState
|
||||
val version by it.versionFlow.collectAsStateWithLifecycle(null)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
@ -70,7 +65,7 @@ fun BundleSelector(bundles: List<PatchBundleSource>, onFinish: (PatchBundleSourc
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
"$name $version",
|
||||
name,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
|
@ -10,11 +10,7 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.ui.component.TooltipWrap
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@ -37,13 +33,8 @@ fun BundleTopBar(
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
if (onBackClick != null) {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.back),
|
||||
) {
|
||||
IconButton(onClick = onBackClick) {
|
||||
backIcon()
|
||||
}
|
||||
IconButton(onClick = onBackClick) {
|
||||
backIcon()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -189,7 +189,6 @@ fun ImportBundleStep(
|
||||
},
|
||||
supportingContent = { Text(stringResource(if (patchBundle != null) R.string.file_field_set else R.string.file_field_not_set)) },
|
||||
trailingContent = {
|
||||
// TODO: Determine if this button should be [TooltipWrap]'ped
|
||||
IconButton(onClick = launchPatchActivity) {
|
||||
Icon(imageVector = Icons.Default.Topic, contentDescription = null)
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListItemInfo
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
@ -22,25 +21,8 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.DragHandle
|
||||
import androidx.compose.material.icons.outlined.Add
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.icons.outlined.Edit
|
||||
import androidx.compose.material.icons.outlined.Folder
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
import androidx.compose.material.icons.outlined.Restore
|
||||
import androidx.compose.material.icons.outlined.SelectAll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
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.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisallowComposableCalls
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
@ -56,16 +38,11 @@ import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.data.platform.Filesystem
|
||||
import app.revanced.manager.patcher.patch.Option
|
||||
import app.revanced.manager.ui.component.AlertDialogExtended
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.FloatInputDialog
|
||||
import app.revanced.manager.ui.component.FullscreenDialog
|
||||
import app.revanced.manager.ui.component.IntInputDialog
|
||||
import app.revanced.manager.ui.component.LongInputDialog
|
||||
import app.revanced.manager.ui.component.TooltipWrap
|
||||
import app.revanced.manager.ui.component.*
|
||||
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
|
||||
import app.revanced.manager.ui.component.haptics.HapticRadioButton
|
||||
import app.revanced.manager.ui.component.haptics.HapticSwitch
|
||||
@ -80,10 +57,11 @@ import org.koin.compose.koinInject
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
import sh.calvin.reorderable.ReorderableItem
|
||||
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||
import sh.calvin.reorderable.rememberReorderableLazyColumnState
|
||||
import java.io.Serializable
|
||||
import kotlin.random.Random
|
||||
import kotlin.reflect.typeOf
|
||||
import androidx.compose.ui.window.Dialog as ComposeDialog
|
||||
|
||||
private class OptionEditorScope<T : Any>(
|
||||
private val editor: OptionEditor<T>,
|
||||
@ -112,13 +90,8 @@ private interface OptionEditor<T : Any> {
|
||||
|
||||
@Composable
|
||||
fun ListItemTrailingContent(scope: OptionEditorScope<T>) {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.edit),
|
||||
) {
|
||||
IconButton(onClick = { clickAction(scope) }) {
|
||||
Icon(Icons.Outlined.Edit, stringResource(R.string.edit))
|
||||
}
|
||||
IconButton(onClick = { clickAction(scope) }) {
|
||||
Icon(Icons.Outlined.Edit, stringResource(R.string.edit))
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,18 +226,13 @@ private object StringOptionEditor : OptionEditor<String> {
|
||||
},
|
||||
trailingIcon = {
|
||||
var showDropdownMenu by rememberSaveable { mutableStateOf(false) }
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.string_option_menu_description),
|
||||
IconButton(
|
||||
onClick = { showDropdownMenu = true }
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { showDropdownMenu = true }
|
||||
) {
|
||||
Icon(
|
||||
Icons.Outlined.MoreVert,
|
||||
stringResource(R.string.string_option_menu_description)
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
Icons.Outlined.MoreVert,
|
||||
stringResource(R.string.string_option_menu_description)
|
||||
)
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
@ -508,8 +476,7 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
|
||||
|
||||
val lazyListState = rememberLazyListState()
|
||||
val reorderableLazyColumnState =
|
||||
// Update the list
|
||||
rememberReorderableLazyListState(lazyListState) { from, to ->
|
||||
rememberReorderableLazyColumnState(lazyListState) { from, to ->
|
||||
// Update the list
|
||||
items.add(to.index, items.removeAt(from.index))
|
||||
}
|
||||
@ -536,8 +503,12 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
|
||||
scope.submitDialog(items.mapNotNull { it.value })
|
||||
}
|
||||
|
||||
FullscreenDialog(
|
||||
ComposeDialog(
|
||||
onDismissRequest = back,
|
||||
properties = DialogProperties(
|
||||
usePlatformDefaultWidth = false,
|
||||
dismissOnBackPress = true
|
||||
),
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@ -560,47 +531,32 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
|
||||
},
|
||||
actions = {
|
||||
if (deleteMode) {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.select_deselect_all),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (items.size == deletionTargets.size) deletionTargets.clear()
|
||||
else deletionTargets.addAll(items.map { it.key })
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Outlined.SelectAll,
|
||||
stringResource(R.string.select_deselect_all)
|
||||
)
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (items.size == deletionTargets.size) deletionTargets.clear()
|
||||
else deletionTargets.addAll(items.map { it.key })
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Outlined.SelectAll,
|
||||
stringResource(R.string.select_deselect_all)
|
||||
)
|
||||
}
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.delete),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
items.removeIf { it.key in deletionTargets }
|
||||
deletionTargets.clear()
|
||||
deleteMode = false
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Outlined.Delete,
|
||||
stringResource(R.string.delete)
|
||||
)
|
||||
IconButton(
|
||||
onClick = {
|
||||
items.removeIf { it.key in deletionTargets }
|
||||
deletionTargets.clear()
|
||||
deleteMode = false
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Outlined.Delete,
|
||||
stringResource(R.string.delete)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.reset),
|
||||
) {
|
||||
IconButton(onClick = items::clear) {
|
||||
Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
|
||||
}
|
||||
IconButton(onClick = items::clear) {
|
||||
Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -646,10 +602,8 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
|
||||
interactionSource = interactionSource,
|
||||
onLongClickLabel = stringResource(R.string.select),
|
||||
onLongClick = {
|
||||
if (!deleteMode) {
|
||||
deletionTargets.add(item.key)
|
||||
deleteMode = true
|
||||
}
|
||||
deletionTargets.add(item.key)
|
||||
deleteMode = true
|
||||
},
|
||||
onClick = {
|
||||
if (!deleteMode) {
|
||||
@ -667,19 +621,14 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
|
||||
),
|
||||
tonalElevation = if (deleteMode && item.key in deletionTargets) 8.dp else 0.dp,
|
||||
leadingContent = {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.drag_handle),
|
||||
IconButton(
|
||||
modifier = Modifier.draggableHandle(interactionSource = interactionSource),
|
||||
onClick = {},
|
||||
) {
|
||||
IconButton(
|
||||
modifier = Modifier.draggableHandle(interactionSource = interactionSource),
|
||||
onClick = {},
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.DragHandle,
|
||||
stringResource(R.string.drag_handle)
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
Icons.Filled.DragHandle,
|
||||
stringResource(R.string.drag_handle)
|
||||
)
|
||||
}
|
||||
},
|
||||
headlineContent = {
|
||||
|
@ -23,9 +23,10 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.FullscreenDialog
|
||||
import app.revanced.manager.ui.component.GroupHeader
|
||||
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
|
||||
import app.revanced.manager.util.saver.PathSaver
|
||||
@ -47,8 +48,12 @@ fun PathSelectorDialog(root: Path, onSelect: (Path?) -> Unit) {
|
||||
currentDirectory.listDirectoryEntries().filter(Path::isReadable).partition(Path::isDirectory)
|
||||
}
|
||||
|
||||
FullscreenDialog(
|
||||
Dialog(
|
||||
onDismissRequest = { onSelect(null) },
|
||||
properties = DialogProperties(
|
||||
usePlatformDefaultWidth = false,
|
||||
dismissOnBackPress = true
|
||||
)
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
|
@ -7,7 +7,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.NewReleases
|
||||
import androidx.compose.material.icons.outlined.CalendarToday
|
||||
import androidx.compose.material.icons.outlined.Campaign
|
||||
import androidx.compose.material.icons.outlined.FileDownload
|
||||
import androidx.compose.material.icons.outlined.Sell
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
@ -34,18 +37,28 @@ fun Changelog(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.NewReleases,
|
||||
imageVector = Icons.Outlined.Campaign,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
)
|
||||
Text(
|
||||
"${version.removePrefix("v")} ($publishDate)",
|
||||
version.removePrefix("v"),
|
||||
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight(800)),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Tag(
|
||||
Icons.Outlined.CalendarToday,
|
||||
publishDate
|
||||
)
|
||||
}
|
||||
}
|
||||
Markdown(
|
||||
markdown,
|
||||
|
@ -17,7 +17,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.domain.manager.base.Preference
|
||||
import app.revanced.manager.ui.component.IntInputDialog
|
||||
import app.revanced.manager.ui.component.TooltipWrap
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@ -66,16 +65,11 @@ fun IntegerItem(
|
||||
headlineContent = stringResource(headline),
|
||||
supportingContent = stringResource(description),
|
||||
trailingContent = {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.edit),
|
||||
) {
|
||||
IconButton(onClick = { dialogOpen = true }) {
|
||||
Icon(
|
||||
Icons.Outlined.Edit,
|
||||
contentDescription = stringResource(R.string.edit)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { dialogOpen = true }) {
|
||||
Icon(
|
||||
Icons.Outlined.Edit,
|
||||
contentDescription = stringResource(R.string.edit)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -1,8 +1,6 @@
|
||||
package app.revanced.manager.ui.component.settings
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.WarningAmber
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@ -11,9 +9,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.domain.manager.base.Preference
|
||||
import app.revanced.manager.ui.component.ConfirmDialog
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@ -32,15 +28,13 @@ fun SafeguardBooleanItem(
|
||||
}
|
||||
|
||||
if (showSafeguardWarning) {
|
||||
ConfirmDialog(
|
||||
SafeguardConfirmationDialog(
|
||||
onDismiss = { showSafeguardWarning = false },
|
||||
onConfirm = {
|
||||
coroutineScope.launch { preference.update(!value) }
|
||||
showSafeguardWarning = false
|
||||
},
|
||||
title = stringResource(id = R.string.warning),
|
||||
description = stringResource(confirmationText),
|
||||
icon = Icons.Outlined.WarningAmber
|
||||
body = stringResource(confirmationText)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,46 @@
|
||||
package app.revanced.manager.ui.component.settings
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.WarningAmber
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import app.revanced.manager.R
|
||||
|
||||
@Composable
|
||||
fun SafeguardConfirmationDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
body: String,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
TextButton(onClick = onConfirm) {
|
||||
Text(stringResource(R.string.yes))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.no))
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
Icon(Icons.Outlined.WarningAmber, null)
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.warning),
|
||||
style = MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center)
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(body)
|
||||
}
|
||||
)
|
||||
}
|
@ -1,17 +1,6 @@
|
||||
package app.revanced.manager.ui.component.settings
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ExpandLess
|
||||
import androidx.compose.material.icons.filled.ExpandMore
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItemColors
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@ -21,10 +10,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
|
||||
@Composable
|
||||
fun SettingsListItem(
|
||||
@ -82,48 +67,4 @@ fun SettingsListItem(
|
||||
colors = colors,
|
||||
tonalElevation = tonalElevation,
|
||||
shadowElevation = shadowElevation
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun ExpandableSettingListItem(
|
||||
headlineContent: String,
|
||||
supportingContent: String,
|
||||
expandableContent: @Composable () -> Unit
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.animateContentSize()
|
||||
) {
|
||||
SettingsListItem(
|
||||
modifier = Modifier
|
||||
.clickable{ expanded = !expanded },
|
||||
headlineContent = headlineContent,
|
||||
supportingContent = supportingContent,
|
||||
trailingContent = {
|
||||
Icon(
|
||||
imageVector = if (expanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
AnimatedVisibility(visible = expanded) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp, start = 16.dp, end = 16.dp)
|
||||
.animateContentSize(
|
||||
animationSpec = tween(
|
||||
durationMillis = 500,
|
||||
easing = FastOutSlowInEasing
|
||||
)
|
||||
)
|
||||
) {
|
||||
expandableContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
@ -11,36 +11,35 @@ import kotlinx.coroutines.flow.map
|
||||
*/
|
||||
data class BundleInfo(
|
||||
val name: String,
|
||||
val version: String?,
|
||||
val uid: Int,
|
||||
val compatible: List<PatchInfo>,
|
||||
val incompatible: List<PatchInfo>,
|
||||
val supported: List<PatchInfo>,
|
||||
val unsupported: List<PatchInfo>,
|
||||
val universal: List<PatchInfo>
|
||||
) {
|
||||
val all = sequence {
|
||||
yieldAll(compatible)
|
||||
yieldAll(incompatible)
|
||||
yieldAll(supported)
|
||||
yieldAll(unsupported)
|
||||
yieldAll(universal)
|
||||
}
|
||||
|
||||
val patchCount get() = compatible.size + incompatible.size + universal.size
|
||||
val patchCount get() = supported.size + unsupported.size + universal.size
|
||||
|
||||
fun patchSequence(allowIncompatible: Boolean) = if (allowIncompatible) {
|
||||
fun patchSequence(allowUnsupported: Boolean) = if (allowUnsupported) {
|
||||
all
|
||||
} else {
|
||||
sequence {
|
||||
yieldAll(compatible)
|
||||
yieldAll(supported)
|
||||
yieldAll(universal)
|
||||
}
|
||||
}
|
||||
|
||||
companion object Extensions {
|
||||
inline fun Iterable<BundleInfo>.toPatchSelection(
|
||||
allowIncompatible: Boolean,
|
||||
allowUnsupported: Boolean,
|
||||
condition: (Int, PatchInfo) -> Boolean
|
||||
): PatchSelection = this.associate { bundle ->
|
||||
val patches =
|
||||
bundle.patchSequence(allowIncompatible)
|
||||
bundle.patchSequence(allowUnsupported)
|
||||
.mapNotNullTo(mutableSetOf()) { patch ->
|
||||
patch.name.takeIf {
|
||||
condition(
|
||||
@ -61,8 +60,8 @@ data class BundleInfo(
|
||||
source.state.map { state ->
|
||||
val bundle = state.patchBundleOrNull() ?: return@map null
|
||||
|
||||
val compatible = mutableListOf<PatchInfo>()
|
||||
val incompatible = mutableListOf<PatchInfo>()
|
||||
val supported = mutableListOf<PatchInfo>()
|
||||
val unsupported = mutableListOf<PatchInfo>()
|
||||
val universal = mutableListOf<PatchInfo>()
|
||||
|
||||
bundle.patches.filter { it.compatibleWith(packageName) }.forEach {
|
||||
@ -71,15 +70,15 @@ data class BundleInfo(
|
||||
it.supports(
|
||||
packageName,
|
||||
version
|
||||
) -> compatible
|
||||
) -> supported
|
||||
|
||||
else -> incompatible
|
||||
else -> unsupported
|
||||
}
|
||||
|
||||
targetList.add(it)
|
||||
}
|
||||
|
||||
BundleInfo(source.getName(), bundle.readManifestAttribute("Version"), source.uid, compatible, incompatible, universal)
|
||||
BundleInfo(source.getName(), source.uid, supported, unsupported, universal)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,5 +92,5 @@ object Settings {
|
||||
data object Licenses : Destination
|
||||
|
||||
@Serializable
|
||||
data object Developer : Destination
|
||||
data object DeveloperOptions : Destination
|
||||
}
|
@ -3,26 +3,12 @@ package app.revanced.manager.ui.screen
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Storage
|
||||
import androidx.compose.material.icons.outlined.Search
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@ -31,7 +17,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -44,7 +29,6 @@ import app.revanced.manager.ui.component.LazyColumnWithScrollbar
|
||||
import app.revanced.manager.ui.component.LoadingIndicator
|
||||
import app.revanced.manager.ui.component.NonSuggestedVersionDialog
|
||||
import app.revanced.manager.ui.component.SearchView
|
||||
import app.revanced.manager.ui.component.TooltipWrap
|
||||
import app.revanced.manager.ui.model.SelectedApp
|
||||
import app.revanced.manager.ui.viewmodel.AppSelectorViewModel
|
||||
import app.revanced.manager.util.APK_MIMETYPE
|
||||
@ -154,27 +138,18 @@ fun AppSelectorScreen(
|
||||
}
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.select_app),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBackClick = onBackClick,
|
||||
actions = {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.search_patches),
|
||||
) {
|
||||
IconButton(onClick = { search = true }) {
|
||||
Icon(Icons.Outlined.Search, stringResource(R.string.search))
|
||||
}
|
||||
IconButton(onClick = { search = true }) {
|
||||
Icon(Icons.Outlined.Search, stringResource(R.string.search))
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
}
|
||||
) { paddingValues ->
|
||||
LazyColumnWithScrollbar(
|
||||
modifier = Modifier
|
||||
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -20,19 +19,13 @@ fun BundleListScreen(
|
||||
selectedSources: SnapshotStateList<PatchBundleSource>,
|
||||
bundlesSelectable: Boolean,
|
||||
) {
|
||||
val sortedSources = remember(sources) {
|
||||
sources.sortedByDescending { source ->
|
||||
source.state.value.patchBundleOrNull()?.patches?.size ?: 0
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumnWithScrollbar(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
) {
|
||||
items(
|
||||
sortedSources,
|
||||
sources,
|
||||
key = { it.uid }
|
||||
) { source ->
|
||||
BundleItem(
|
||||
|
@ -6,49 +6,18 @@ import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.BatteryAlert
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.outlined.Apps
|
||||
import androidx.compose.material.icons.outlined.BugReport
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.icons.outlined.DeleteOutline
|
||||
import androidx.compose.material.icons.outlined.Download
|
||||
import androidx.compose.material.icons.outlined.Refresh
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material.icons.outlined.Source
|
||||
import androidx.compose.material.icons.outlined.Update
|
||||
import androidx.compose.material.icons.outlined.WarningAmber
|
||||
import androidx.compose.material3.Badge
|
||||
import androidx.compose.material3.BadgedBox
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@ -62,13 +31,11 @@ import app.revanced.manager.ui.component.AlertDialogExtended
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.AutoUpdatesDialog
|
||||
import app.revanced.manager.ui.component.AvailableUpdateDialog
|
||||
import app.revanced.manager.ui.component.ConfirmDialog
|
||||
import app.revanced.manager.ui.component.NotificationCard
|
||||
import app.revanced.manager.ui.component.TooltipWrap
|
||||
import app.revanced.manager.ui.component.bundle.BundleTopBar
|
||||
import app.revanced.manager.ui.component.bundle.ImportPatchBundleDialog
|
||||
import app.revanced.manager.ui.component.haptics.HapticFloatingActionButton
|
||||
import app.revanced.manager.ui.component.haptics.HapticTab
|
||||
import app.revanced.manager.ui.component.bundle.ImportPatchBundleDialog
|
||||
import app.revanced.manager.ui.viewmodel.DashboardViewModel
|
||||
import app.revanced.manager.util.RequestInstallAppsContract
|
||||
import app.revanced.manager.util.toast
|
||||
@ -157,20 +124,6 @@ fun DashboardScreen(
|
||||
}
|
||||
)
|
||||
|
||||
var showDeleteConfirmationDialog by rememberSaveable { mutableStateOf(false) }
|
||||
if (showDeleteConfirmationDialog) {
|
||||
ConfirmDialog(
|
||||
onDismiss = { showDeleteConfirmationDialog = false },
|
||||
onConfirm = {
|
||||
vm.selectedSources.forEach { if (!it.isDefault) vm.delete(it) }
|
||||
vm.cancelSourceSelection()
|
||||
},
|
||||
title = stringResource(R.string.bundle_delete_multiple_dialog_title),
|
||||
description = stringResource(R.string.bundle_delete_multiple_dialog_description),
|
||||
icon = Icons.Outlined.Delete
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
if (bundlesSelectable) {
|
||||
@ -184,36 +137,27 @@ fun DashboardScreen(
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.delete),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
showDeleteConfirmationDialog = true
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Outlined.DeleteOutline,
|
||||
stringResource(R.string.delete)
|
||||
)
|
||||
IconButton(
|
||||
onClick = {
|
||||
vm.selectedSources.forEach { if (!it.isDefault) vm.delete(it) }
|
||||
vm.cancelSourceSelection()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Outlined.DeleteOutline,
|
||||
stringResource(R.string.delete)
|
||||
)
|
||||
}
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.refresh),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
vm.selectedSources.forEach { vm.update(it) }
|
||||
vm.cancelSourceSelection()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Outlined.Refresh,
|
||||
stringResource(R.string.refresh)
|
||||
)
|
||||
IconButton(
|
||||
onClick = {
|
||||
vm.selectedSources.forEach { vm.update(it) }
|
||||
vm.cancelSourceSelection()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Outlined.Refresh,
|
||||
stringResource(R.string.refresh)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -222,71 +166,59 @@ fun DashboardScreen(
|
||||
title = stringResource(R.string.app_name),
|
||||
actions = {
|
||||
if (!vm.updatedManagerVersion.isNullOrEmpty()) {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.update),
|
||||
IconButton(
|
||||
onClick = onUpdateClick,
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onUpdateClick,
|
||||
) {
|
||||
BadgedBox(
|
||||
badge = {
|
||||
Badge(modifier = Modifier.size(6.dp))
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Outlined.Update, stringResource(R.string.update))
|
||||
BadgedBox(
|
||||
badge = {
|
||||
Badge(
|
||||
// A size value above 6.dp forces the Badge icon to be closer to the center, fixing a clipping issue
|
||||
modifier = Modifier.size(7.dp),
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Outlined.Update, stringResource(R.string.update))
|
||||
}
|
||||
}
|
||||
}
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.settings),
|
||||
) {
|
||||
IconButton(onClick = onSettingsClick) {
|
||||
Icon(Icons.Outlined.Settings, stringResource(R.string.settings))
|
||||
}
|
||||
IconButton(onClick = onSettingsClick) {
|
||||
Icon(Icons.Outlined.Settings, stringResource(R.string.settings))
|
||||
}
|
||||
},
|
||||
applyContainerColor = true
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.add),
|
||||
) {
|
||||
HapticFloatingActionButton(
|
||||
onClick = {
|
||||
vm.cancelSourceSelection()
|
||||
HapticFloatingActionButton(
|
||||
onClick = {
|
||||
vm.cancelSourceSelection()
|
||||
|
||||
when (pagerState.currentPage) {
|
||||
DashboardPage.DASHBOARD.ordinal -> {
|
||||
if (availablePatches < 1) {
|
||||
androidContext.toast(androidContext.getString(R.string.patches_unavailable))
|
||||
composableScope.launch {
|
||||
pagerState.animateScrollToPage(
|
||||
DashboardPage.BUNDLES.ordinal
|
||||
)
|
||||
}
|
||||
return@HapticFloatingActionButton
|
||||
when (pagerState.currentPage) {
|
||||
DashboardPage.DASHBOARD.ordinal -> {
|
||||
if (availablePatches < 1) {
|
||||
androidContext.toast(androidContext.getString(R.string.patches_unavailable))
|
||||
composableScope.launch {
|
||||
pagerState.animateScrollToPage(
|
||||
DashboardPage.BUNDLES.ordinal
|
||||
)
|
||||
}
|
||||
if (vm.android11BugActive) {
|
||||
showAndroid11Dialog = true
|
||||
return@HapticFloatingActionButton
|
||||
}
|
||||
|
||||
onAppSelectorClick()
|
||||
return@HapticFloatingActionButton
|
||||
}
|
||||
if (vm.android11BugActive) {
|
||||
showAndroid11Dialog = true
|
||||
return@HapticFloatingActionButton
|
||||
}
|
||||
|
||||
DashboardPage.BUNDLES.ordinal -> {
|
||||
showAddBundleDialog = true
|
||||
}
|
||||
onAppSelectorClick()
|
||||
}
|
||||
|
||||
DashboardPage.BUNDLES.ordinal -> {
|
||||
showAddBundleDialog = true
|
||||
}
|
||||
}
|
||||
) { Icon(Icons.Default.Add, stringResource(R.string.add)) }
|
||||
}
|
||||
}
|
||||
) { Icon(Icons.Default.Add, stringResource(R.string.add)) }
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(Modifier.padding(paddingValues)) {
|
||||
@ -306,6 +238,7 @@ fun DashboardScreen(
|
||||
}
|
||||
}
|
||||
|
||||
val showBatteryOptimizationsWarning by vm.showBatteryOptimizationsWarningFlow.collectAsStateWithLifecycle(false)
|
||||
Notifications(
|
||||
if (!Aapt.supportsDevice()) {
|
||||
{
|
||||
@ -317,23 +250,16 @@ fun DashboardScreen(
|
||||
)
|
||||
}
|
||||
} else null,
|
||||
if (vm.showBatteryOptimizationsWarning) {
|
||||
if (showBatteryOptimizationsWarning) {
|
||||
{
|
||||
val batteryOptimizationsLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
vm.updateBatteryOptimizationsWarning()
|
||||
}
|
||||
NotificationCard(
|
||||
isWarning = true,
|
||||
icon = Icons.Default.BatteryAlert,
|
||||
text = stringResource(R.string.battery_optimization_notification),
|
||||
onClick = {
|
||||
batteryOptimizationsLauncher.launch(
|
||||
Intent(
|
||||
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
|
||||
Uri.fromParts("package", androidContext.packageName, null)
|
||||
)
|
||||
)
|
||||
androidContext.startActivity(Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
|
||||
data = Uri.parse("package:${androidContext.packageName}")
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -21,8 +21,6 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@ -31,7 +29,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@ -67,17 +64,13 @@ fun InstalledAppInfoScreen(
|
||||
onConfirm = { viewModel.uninstall() }
|
||||
)
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.app_info),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBackClick = onBackClick
|
||||
)
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
}
|
||||
) { paddingValues ->
|
||||
ColumnWithScrollbar(
|
||||
modifier = Modifier
|
||||
|
@ -7,28 +7,15 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.OpenInNew
|
||||
import androidx.compose.material.icons.outlined.Cancel
|
||||
import androidx.compose.material.icons.outlined.FileDownload
|
||||
import androidx.compose.material.icons.outlined.PostAdd
|
||||
import androidx.compose.material.icons.outlined.Save
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.BottomAppBar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
@ -46,9 +33,7 @@ import app.revanced.manager.R
|
||||
import app.revanced.manager.data.room.apps.installed.InstallType
|
||||
import app.revanced.manager.ui.component.AppScaffold
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.ConfirmDialog
|
||||
import app.revanced.manager.ui.component.InstallerStatusDialog
|
||||
import app.revanced.manager.ui.component.TooltipWrap
|
||||
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
|
||||
import app.revanced.manager.ui.component.patcher.InstallPickerDialog
|
||||
import app.revanced.manager.ui.component.patcher.Steps
|
||||
@ -61,34 +46,25 @@ import app.revanced.manager.util.EventEffect
|
||||
@Composable
|
||||
fun PatcherScreen(
|
||||
onBackClick: () -> Unit,
|
||||
viewModel: PatcherViewModel
|
||||
vm: PatcherViewModel
|
||||
) {
|
||||
fun onLeave() {
|
||||
viewModel.onBack()
|
||||
fun leaveScreen() {
|
||||
vm.onBack()
|
||||
onBackClick()
|
||||
}
|
||||
BackHandler(onBack = ::leaveScreen)
|
||||
|
||||
val context = LocalContext.current
|
||||
val exportApkLauncher =
|
||||
rememberLauncherForActivityResult(CreateDocument(APK_MIMETYPE), viewModel::export)
|
||||
rememberLauncherForActivityResult(CreateDocument(APK_MIMETYPE), vm::export)
|
||||
|
||||
val patcherSucceeded by viewModel.patcherSucceeded.observeAsState(null)
|
||||
val canInstall by remember { derivedStateOf { patcherSucceeded == true && (viewModel.installedPackageName != null || !viewModel.isInstalling) } }
|
||||
val patcherSucceeded by vm.patcherSucceeded.observeAsState(null)
|
||||
val canInstall by remember { derivedStateOf { patcherSucceeded == true && (vm.installedPackageName != null || !vm.isInstalling) } }
|
||||
var showInstallPicker by rememberSaveable { mutableStateOf(false) }
|
||||
var showDismissConfirmationDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
fun onPageBack() {
|
||||
if(patcherSucceeded == null)
|
||||
showDismissConfirmationDialog = true
|
||||
else
|
||||
onLeave()
|
||||
}
|
||||
|
||||
BackHandler(onBack = ::onPageBack)
|
||||
|
||||
val steps by remember {
|
||||
derivedStateOf {
|
||||
viewModel.steps.groupBy { it.category }
|
||||
vm.steps.groupBy { it.category }
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,44 +81,34 @@ fun PatcherScreen(
|
||||
if (showInstallPicker)
|
||||
InstallPickerDialog(
|
||||
onDismiss = { showInstallPicker = false },
|
||||
onConfirm = viewModel::install
|
||||
onConfirm = vm::install
|
||||
)
|
||||
|
||||
if (showDismissConfirmationDialog) {
|
||||
ConfirmDialog(
|
||||
onDismiss = { showDismissConfirmationDialog = false },
|
||||
onConfirm = ::onLeave,
|
||||
title = stringResource(R.string.patcher_stop_confirm_title),
|
||||
description = stringResource(R.string.patcher_stop_confirm_description),
|
||||
icon = Icons.Outlined.Cancel
|
||||
)
|
||||
}
|
||||
|
||||
viewModel.packageInstallerStatus?.let {
|
||||
InstallerStatusDialog(it, viewModel, viewModel::dismissPackageInstallerDialog)
|
||||
vm.packageInstallerStatus?.let {
|
||||
InstallerStatusDialog(it, vm, vm::dismissPackageInstallerDialog)
|
||||
}
|
||||
|
||||
val activityLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult(),
|
||||
onResult = viewModel::handleActivityResult
|
||||
onResult = vm::handleActivityResult
|
||||
)
|
||||
EventEffect(flow = viewModel.launchActivityFlow) { intent ->
|
||||
EventEffect(flow = vm.launchActivityFlow) { intent ->
|
||||
activityLauncher.launch(intent)
|
||||
}
|
||||
|
||||
viewModel.activityPromptDialog?.let { title ->
|
||||
vm.activityPromptDialog?.let { title ->
|
||||
AlertDialog(
|
||||
onDismissRequest = viewModel::rejectInteraction,
|
||||
onDismissRequest = vm::rejectInteraction,
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = viewModel::allowInteraction
|
||||
onClick = vm::allowInteraction
|
||||
) {
|
||||
Text(stringResource(R.string.continue_))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = viewModel::rejectInteraction
|
||||
onClick = vm::rejectInteraction
|
||||
) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
@ -155,37 +121,26 @@ fun PatcherScreen(
|
||||
}
|
||||
|
||||
AppScaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.patcher),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBackClick = ::onPageBack
|
||||
onBackClick = ::leaveScreen
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
BottomAppBar(
|
||||
actions = {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.save_apk),
|
||||
IconButton(
|
||||
onClick = { exportApkLauncher.launch("${vm.packageName}.apk") },
|
||||
enabled = patcherSucceeded == true
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { exportApkLauncher.launch("${viewModel.packageName}_${viewModel.version}_revanced_patched.apk") },
|
||||
enabled = patcherSucceeded == true
|
||||
) {
|
||||
Icon(Icons.Outlined.Save, stringResource(id = R.string.save_apk))
|
||||
}
|
||||
Icon(Icons.Outlined.Save, stringResource(id = R.string.save_apk))
|
||||
}
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.save_logs),
|
||||
IconButton(
|
||||
onClick = { vm.exportLogs(context) },
|
||||
enabled = patcherSucceeded != null
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { viewModel.exportLogs(context) },
|
||||
enabled = patcherSucceeded != null
|
||||
) {
|
||||
Icon(Icons.Outlined.PostAdd, stringResource(id = R.string.save_logs))
|
||||
}
|
||||
Icon(Icons.Outlined.PostAdd, stringResource(id = R.string.save_logs))
|
||||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
@ -193,11 +148,11 @@ fun PatcherScreen(
|
||||
HapticExtendedFloatingActionButton(
|
||||
text = {
|
||||
Text(
|
||||
stringResource(if (viewModel.installedPackageName == null) R.string.install_app else R.string.open_app)
|
||||
stringResource(if (vm.installedPackageName == null) R.string.install_app else R.string.open_app)
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
viewModel.installedPackageName?.let {
|
||||
vm.installedPackageName?.let {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Outlined.OpenInNew,
|
||||
stringResource(R.string.open_app)
|
||||
@ -208,10 +163,10 @@ fun PatcherScreen(
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
if (viewModel.installedPackageName == null)
|
||||
if (viewModel.isDeviceRooted()) showInstallPicker = true
|
||||
else viewModel.install(InstallType.DEFAULT)
|
||||
else viewModel.open()
|
||||
if (vm.installedPackageName == null)
|
||||
if (vm.isDeviceRooted()) showInstallPicker = true
|
||||
else vm.install(InstallType.DEFAULT)
|
||||
else vm.open()
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -225,7 +180,7 @@ fun PatcherScreen(
|
||||
.fillMaxSize()
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
progress = { viewModel.progress },
|
||||
progress = { vm.progress },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
@ -241,11 +196,11 @@ fun PatcherScreen(
|
||||
Steps(
|
||||
category = category,
|
||||
steps = steps,
|
||||
stepCount = if (category == StepCategory.PATCHING) viewModel.patchesProgress else null,
|
||||
stepProgressProvider = viewModel
|
||||
stepCount = if (category == StepCategory.PATCHING) vm.patchesProgress else null,
|
||||
stepProgressProvider = vm
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,24 +58,25 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.patcher.patch.Option
|
||||
import app.revanced.manager.patcher.patch.PatchInfo
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.CheckedFilterChip
|
||||
import app.revanced.manager.ui.component.FullscreenDialog
|
||||
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
|
||||
import app.revanced.manager.ui.component.SafeguardDialog
|
||||
import app.revanced.manager.ui.component.SearchBar
|
||||
import app.revanced.manager.ui.component.TooltipWrap
|
||||
import app.revanced.manager.ui.component.haptics.HapticCheckbox
|
||||
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
|
||||
import app.revanced.manager.ui.component.haptics.HapticTab
|
||||
import app.revanced.manager.ui.component.patches.OptionItem
|
||||
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
|
||||
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_INCOMPATIBLE
|
||||
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_UNSUPPORTED
|
||||
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_UNIVERSAL
|
||||
import app.revanced.manager.util.Options
|
||||
import app.revanced.manager.util.PatchSelection
|
||||
@ -88,9 +89,9 @@ import kotlinx.coroutines.launch
|
||||
fun PatchesSelectorScreen(
|
||||
onSave: (PatchSelection?, Options) -> Unit,
|
||||
onBackClick: () -> Unit,
|
||||
viewModel: PatchesSelectorViewModel
|
||||
vm: PatchesSelectorViewModel
|
||||
) {
|
||||
val bundles by viewModel.bundlesFlow.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||
val bundles by vm.bundlesFlow.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||
val pagerState = rememberPagerState(
|
||||
initialPage = 0,
|
||||
initialPageOffsetFraction = 0f
|
||||
@ -106,15 +107,15 @@ fun PatchesSelectorScreen(
|
||||
}
|
||||
var showBottomSheet by rememberSaveable { mutableStateOf(false) }
|
||||
val showSaveButton by remember {
|
||||
derivedStateOf { viewModel.selectionIsValid(bundles) }
|
||||
derivedStateOf { vm.selectionIsValid(bundles) }
|
||||
}
|
||||
|
||||
val defaultPatchSelectionCount by viewModel.defaultSelectionCount
|
||||
val defaultPatchSelectionCount by vm.defaultSelectionCount
|
||||
.collectAsStateWithLifecycle(initialValue = 0)
|
||||
|
||||
val selectedPatchCount by remember {
|
||||
derivedStateOf {
|
||||
viewModel.customPatchSelection?.values?.sumOf { it.size } ?: defaultPatchSelectionCount
|
||||
vm.customPatchSelection?.values?.sumOf { it.size } ?: defaultPatchSelectionCount
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,14 +147,14 @@ fun PatchesSelectorScreen(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
CheckedFilterChip(
|
||||
selected = viewModel.filter and SHOW_INCOMPATIBLE == 0,
|
||||
onClick = { viewModel.toggleFlag(SHOW_INCOMPATIBLE) },
|
||||
label = { Text(stringResource(R.string.this_version)) }
|
||||
selected = vm.filter and SHOW_UNSUPPORTED == 0,
|
||||
onClick = { vm.toggleFlag(SHOW_UNSUPPORTED) },
|
||||
label = { Text(stringResource(R.string.supported)) }
|
||||
)
|
||||
|
||||
CheckedFilterChip(
|
||||
selected = viewModel.filter and SHOW_UNIVERSAL != 0,
|
||||
onClick = { viewModel.toggleFlag(SHOW_UNIVERSAL) },
|
||||
selected = vm.filter and SHOW_UNIVERSAL != 0,
|
||||
onClick = { vm.toggleFlag(SHOW_UNIVERSAL) },
|
||||
label = { Text(stringResource(R.string.universal)) },
|
||||
)
|
||||
}
|
||||
@ -161,45 +162,49 @@ fun PatchesSelectorScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (viewModel.compatibleVersions.isNotEmpty())
|
||||
IncompatiblePatchDialog(
|
||||
appVersion = viewModel.appVersion ?: stringResource(R.string.any_version),
|
||||
compatibleVersions = viewModel.compatibleVersions,
|
||||
onDismissRequest = viewModel::dismissDialogs
|
||||
if (vm.compatibleVersions.isNotEmpty())
|
||||
UnsupportedPatchDialog(
|
||||
appVersion = vm.appVersion ?: stringResource(R.string.any_version),
|
||||
supportedVersions = vm.compatibleVersions,
|
||||
onDismissRequest = vm::dismissDialogs
|
||||
)
|
||||
var showIncompatiblePatchesDialog by rememberSaveable {
|
||||
var showUnsupportedPatchesDialog by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
if (showIncompatiblePatchesDialog)
|
||||
IncompatiblePatchesDialog(
|
||||
appVersion = viewModel.appVersion ?: stringResource(R.string.any_version),
|
||||
onDismissRequest = { showIncompatiblePatchesDialog = false }
|
||||
if (showUnsupportedPatchesDialog)
|
||||
UnsupportedPatchesDialog(
|
||||
appVersion = vm.appVersion ?: stringResource(R.string.any_version),
|
||||
onDismissRequest = { showUnsupportedPatchesDialog = false }
|
||||
)
|
||||
|
||||
viewModel.optionsDialog?.let { (bundle, patch) ->
|
||||
vm.optionsDialog?.let { (bundle, patch) ->
|
||||
OptionsDialog(
|
||||
onDismissRequest = viewModel::dismissDialogs,
|
||||
onDismissRequest = vm::dismissDialogs,
|
||||
patch = patch,
|
||||
values = viewModel.getOptions(bundle, patch),
|
||||
reset = { viewModel.resetOptions(bundle, patch) },
|
||||
set = { key, value -> viewModel.setOption(bundle, patch, key, value) }
|
||||
values = vm.getOptions(bundle, patch),
|
||||
reset = { vm.resetOptions(bundle, patch) },
|
||||
set = { key, value -> vm.setOption(bundle, patch, key, value) }
|
||||
)
|
||||
}
|
||||
|
||||
var showSelectionWarning by rememberSaveable { mutableStateOf(false) }
|
||||
var showUniversalWarning by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
if (showSelectionWarning)
|
||||
var showSelectionWarning by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
if (showSelectionWarning) {
|
||||
SelectionWarningDialog(onDismiss = { showSelectionWarning = false })
|
||||
|
||||
if (showUniversalWarning)
|
||||
UniversalPatchWarningDialog(onDismiss = { showUniversalWarning = false })
|
||||
}
|
||||
vm.pendingUniversalPatchAction?.let {
|
||||
UniversalPatchWarningDialog(
|
||||
onCancel = vm::dismissUniversalPatchWarning,
|
||||
onConfirm = vm::confirmUniversalPatchWarning
|
||||
)
|
||||
}
|
||||
|
||||
fun LazyListScope.patchList(
|
||||
uid: Int,
|
||||
patches: List<PatchInfo>,
|
||||
visible: Boolean,
|
||||
compatible: Boolean,
|
||||
supported: Boolean,
|
||||
header: (@Composable () -> Unit)? = null
|
||||
) {
|
||||
if (patches.isNotEmpty() && visible) {
|
||||
@ -217,28 +222,30 @@ fun PatchesSelectorScreen(
|
||||
PatchItem(
|
||||
patch = patch,
|
||||
onOptionsDialog = {
|
||||
viewModel.optionsDialog = uid to patch
|
||||
vm.optionsDialog = uid to patch
|
||||
},
|
||||
selected = compatible && viewModel.isSelected(
|
||||
selected = supported && vm.isSelected(
|
||||
uid,
|
||||
patch
|
||||
),
|
||||
onToggle = {
|
||||
when {
|
||||
// Open incompatible dialog if the patch is not supported
|
||||
!compatible -> viewModel.openIncompatibleDialog(patch)
|
||||
// Open unsupported dialog if the patch is not supported
|
||||
!supported -> vm.openUnsupportedDialog(patch)
|
||||
|
||||
// Show selection warning if enabled
|
||||
viewModel.selectionWarningEnabled -> showSelectionWarning = true
|
||||
vm.selectionWarningEnabled -> showSelectionWarning = true
|
||||
|
||||
// Show universal warning if universal patch is selected and the toggle is off
|
||||
patch.compatiblePackages == null && viewModel.universalPatchWarningEnabled -> showUniversalWarning = true
|
||||
// Set pending universal patch action if the universal patch warning is enabled and there are no compatible packages
|
||||
vm.universalPatchWarningEnabled && patch.compatiblePackages == null -> {
|
||||
vm.pendingUniversalPatchAction = { vm.togglePatch(uid, patch) }
|
||||
}
|
||||
|
||||
// Toggle the patch otherwise
|
||||
else -> viewModel.togglePatch(uid, patch)
|
||||
else -> vm.togglePatch(uid, patch)
|
||||
}
|
||||
},
|
||||
compatible = compatible
|
||||
supported = supported
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -260,25 +267,20 @@ fun PatchesSelectorScreen(
|
||||
animationSpec = tween(durationMillis = 400, easing = EaseInOut),
|
||||
label = "SearchBar back button"
|
||||
)
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.back),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (searchExpanded) {
|
||||
setSearchExpanded(false)
|
||||
} else {
|
||||
onBackClick()
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (searchExpanded) {
|
||||
setSearchExpanded(false)
|
||||
} else {
|
||||
onBackClick()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.rotate(rotation),
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.back)
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.rotate(rotation),
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.back)
|
||||
)
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
@ -288,31 +290,21 @@ fun PatchesSelectorScreen(
|
||||
transitionSpec = { fadeIn() togetherWith fadeOut() }
|
||||
) { searchExpanded ->
|
||||
if (searchExpanded) {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.clear),
|
||||
IconButton(
|
||||
onClick = { setQuery("") },
|
||||
enabled = query.isNotEmpty()
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { setQuery("") },
|
||||
enabled = query.isNotEmpty()
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Close,
|
||||
contentDescription = stringResource(R.string.clear)
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Close,
|
||||
contentDescription = stringResource(R.string.clear)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.more),
|
||||
) {
|
||||
IconButton(onClick = { showBottomSheet = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.FilterList,
|
||||
contentDescription = stringResource(R.string.more)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { showBottomSheet = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.FilterList,
|
||||
contentDescription = stringResource(R.string.more)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -329,15 +321,15 @@ fun PatchesSelectorScreen(
|
||||
|
||||
patchList(
|
||||
uid = bundle.uid,
|
||||
patches = bundle.compatible.searched(),
|
||||
patches = bundle.supported.searched(),
|
||||
visible = true,
|
||||
compatible = true
|
||||
supported = true
|
||||
)
|
||||
patchList(
|
||||
uid = bundle.uid,
|
||||
patches = bundle.universal.searched(),
|
||||
visible = viewModel.filter and SHOW_UNIVERSAL != 0,
|
||||
compatible = true
|
||||
visible = vm.filter and SHOW_UNIVERSAL != 0,
|
||||
supported = true
|
||||
) {
|
||||
ListHeader(
|
||||
title = stringResource(R.string.universal_patches),
|
||||
@ -346,13 +338,13 @@ fun PatchesSelectorScreen(
|
||||
|
||||
patchList(
|
||||
uid = bundle.uid,
|
||||
patches = bundle.incompatible.searched(),
|
||||
visible = viewModel.filter and SHOW_INCOMPATIBLE != 0,
|
||||
compatible = viewModel.allowIncompatiblePatches
|
||||
patches = bundle.unsupported.searched(),
|
||||
visible = vm.filter and SHOW_UNSUPPORTED != 0,
|
||||
supported = vm.allowIncompatiblePatches
|
||||
) {
|
||||
ListHeader(
|
||||
title = stringResource(R.string.incompatible_patches),
|
||||
onHelpClick = { showIncompatiblePatchesDialog = true }
|
||||
title = stringResource(R.string.unsupported_patches),
|
||||
onHelpClick = { showUnsupportedPatchesDialog = true }
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -370,36 +362,23 @@ fun PatchesSelectorScreen(
|
||||
horizontalAlignment = Alignment.End,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.reset),
|
||||
SmallFloatingActionButton(
|
||||
onClick = vm::reset,
|
||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer
|
||||
) {
|
||||
SmallFloatingActionButton(
|
||||
onClick = viewModel::reset,
|
||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer
|
||||
) {
|
||||
Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
|
||||
}
|
||||
Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
|
||||
}
|
||||
HapticExtendedFloatingActionButton(
|
||||
text = {
|
||||
Text(
|
||||
stringResource(
|
||||
R.string.save_with_count,
|
||||
selectedPatchCount
|
||||
)
|
||||
)
|
||||
},
|
||||
text = { Text(stringResource(R.string.save_with_count, selectedPatchCount)) },
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Save,
|
||||
contentDescription = stringResource(R.string.save)
|
||||
)
|
||||
},
|
||||
expanded = patchLazyListStates.getOrNull(pagerState.currentPage)?.isScrollingUp
|
||||
?: true,
|
||||
expanded = patchLazyListStates.getOrNull(pagerState.currentPage)?.isScrollingUp ?: true,
|
||||
onClick = {
|
||||
onSave(viewModel.getCustomSelection(), viewModel.getOptions())
|
||||
onSave(vm.getCustomSelection(), vm.getOptions())
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -426,19 +405,7 @@ fun PatchesSelectorScreen(
|
||||
)
|
||||
}
|
||||
},
|
||||
text = {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(
|
||||
text = bundle.name,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Text(
|
||||
text = bundle.version!!,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
},
|
||||
text = { Text(bundle.name) },
|
||||
selectedContentColor = MaterialTheme.colorScheme.primary,
|
||||
unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
@ -460,15 +427,15 @@ fun PatchesSelectorScreen(
|
||||
) {
|
||||
patchList(
|
||||
uid = bundle.uid,
|
||||
patches = bundle.compatible,
|
||||
patches = bundle.supported,
|
||||
visible = true,
|
||||
compatible = true
|
||||
supported = true
|
||||
)
|
||||
patchList(
|
||||
uid = bundle.uid,
|
||||
patches = bundle.universal,
|
||||
visible = viewModel.filter and SHOW_UNIVERSAL != 0,
|
||||
compatible = true
|
||||
visible = vm.filter and SHOW_UNIVERSAL != 0,
|
||||
supported = true
|
||||
) {
|
||||
ListHeader(
|
||||
title = stringResource(R.string.universal_patches),
|
||||
@ -476,13 +443,13 @@ fun PatchesSelectorScreen(
|
||||
}
|
||||
patchList(
|
||||
uid = bundle.uid,
|
||||
patches = bundle.incompatible,
|
||||
visible = viewModel.filter and SHOW_INCOMPATIBLE != 0,
|
||||
compatible = viewModel.allowIncompatiblePatches
|
||||
patches = bundle.unsupported,
|
||||
visible = vm.filter and SHOW_UNSUPPORTED != 0,
|
||||
supported = vm.allowIncompatiblePatches
|
||||
) {
|
||||
ListHeader(
|
||||
title = stringResource(R.string.incompatible_patches),
|
||||
onHelpClick = { showIncompatiblePatchesDialog = true }
|
||||
title = stringResource(R.string.unsupported_patches),
|
||||
onHelpClick = { showUnsupportedPatchesDialog = true }
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -493,9 +460,7 @@ fun PatchesSelectorScreen(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SelectionWarningDialog(
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
private fun SelectionWarningDialog(onDismiss: () -> Unit) {
|
||||
SafeguardDialog(
|
||||
onDismiss = onDismiss,
|
||||
title = R.string.warning,
|
||||
@ -505,12 +470,33 @@ private fun SelectionWarningDialog(
|
||||
|
||||
@Composable
|
||||
private fun UniversalPatchWarningDialog(
|
||||
onDismiss: () -> Unit
|
||||
onCancel: () -> Unit,
|
||||
onConfirm: () -> Unit
|
||||
) {
|
||||
SafeguardDialog(
|
||||
onDismiss = onDismiss,
|
||||
title = R.string.warning,
|
||||
body = stringResource(R.string.universal_patch_warning_description),
|
||||
AlertDialog(
|
||||
onDismissRequest = onCancel,
|
||||
confirmButton = {
|
||||
TextButton(onClick = onConfirm) {
|
||||
Text(stringResource(R.string.continue_))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onCancel) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
Icon(Icons.Outlined.WarningAmber, null)
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.warning),
|
||||
style = MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center)
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(stringResource(R.string.universal_patch_warning_description))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -520,25 +506,24 @@ private fun PatchItem(
|
||||
onOptionsDialog: () -> Unit,
|
||||
selected: Boolean,
|
||||
onToggle: () -> Unit,
|
||||
compatible: Boolean = true
|
||||
supported: Boolean = true
|
||||
) = ListItem(
|
||||
modifier = Modifier
|
||||
.let { if (!compatible) it.alpha(0.5f) else it }
|
||||
.let { if (!supported) it.alpha(0.5f) else it }
|
||||
.clickable(onClick = onToggle)
|
||||
.fillMaxSize(),
|
||||
leadingContent = {
|
||||
HapticCheckbox(
|
||||
checked = selected,
|
||||
onCheckedChange = { onToggle() },
|
||||
enabled = compatible
|
||||
enabled = supported
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(patch.name) },
|
||||
supportingContent = patch.description?.let { { Text(it) } },
|
||||
trailingContent = {
|
||||
if (patch.options?.isNotEmpty() == true) {
|
||||
// TODO: Determine if this button should be [TooltipWrap]
|
||||
IconButton(onClick = onOptionsDialog, enabled = compatible) {
|
||||
IconButton(onClick = onOptionsDialog, enabled = supported) {
|
||||
Icon(Icons.Outlined.Settings, null)
|
||||
}
|
||||
}
|
||||
@ -561,16 +546,11 @@ fun ListHeader(
|
||||
},
|
||||
trailingContent = onHelpClick?.let {
|
||||
{
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.help),
|
||||
) {
|
||||
IconButton(onClick = it) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
stringResource(R.string.help)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = it) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
stringResource(R.string.help)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -579,7 +559,7 @@ fun ListHeader(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun IncompatiblePatchesDialog(
|
||||
private fun UnsupportedPatchesDialog(
|
||||
appVersion: String,
|
||||
onDismissRequest: () -> Unit
|
||||
) = AlertDialog(
|
||||
@ -592,11 +572,11 @@ private fun IncompatiblePatchesDialog(
|
||||
Text(stringResource(R.string.ok))
|
||||
}
|
||||
},
|
||||
title = { Text(stringResource(R.string.incompatible_patches)) },
|
||||
title = { Text(stringResource(R.string.unsupported_patches)) },
|
||||
text = {
|
||||
Text(
|
||||
stringResource(
|
||||
R.string.incompatible_patches_dialog,
|
||||
R.string.unsupported_patches_dialog,
|
||||
appVersion
|
||||
)
|
||||
)
|
||||
@ -604,9 +584,9 @@ private fun IncompatiblePatchesDialog(
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun IncompatiblePatchDialog(
|
||||
private fun UnsupportedPatchDialog(
|
||||
appVersion: String,
|
||||
compatibleVersions: List<String>,
|
||||
supportedVersions: List<String>,
|
||||
onDismissRequest: () -> Unit
|
||||
) = AlertDialog(
|
||||
icon = {
|
||||
@ -618,13 +598,13 @@ private fun IncompatiblePatchDialog(
|
||||
Text(stringResource(R.string.ok))
|
||||
}
|
||||
},
|
||||
title = { Text(stringResource(R.string.incompatible_patch)) },
|
||||
title = { Text(stringResource(R.string.unsupported_patch)) },
|
||||
text = {
|
||||
Text(
|
||||
stringResource(
|
||||
R.string.app_version_not_compatible,
|
||||
R.string.app_not_supported,
|
||||
appVersion,
|
||||
compatibleVersions.joinToString(", ")
|
||||
supportedVersions.joinToString(", ")
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -638,20 +618,21 @@ private fun OptionsDialog(
|
||||
reset: () -> Unit,
|
||||
set: (String, Any?) -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
) = FullscreenDialog(onDismissRequest = onDismissRequest) {
|
||||
) = Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
properties = DialogProperties(
|
||||
usePlatformDefaultWidth = false,
|
||||
dismissOnBackPress = true
|
||||
)
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = patch.name,
|
||||
onBackClick = onDismissRequest,
|
||||
actions = {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.reset),
|
||||
) {
|
||||
IconButton(onClick = reset) {
|
||||
Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
|
||||
}
|
||||
IconButton(onClick = reset) {
|
||||
Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -15,8 +15,6 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.ScrollableTabRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
@ -24,7 +22,6 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
@ -69,13 +66,10 @@ fun RequiredOptionsScreen(
|
||||
}
|
||||
val composableScope = rememberCoroutineScope()
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.required_options_screen),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBackClick = onBackClick
|
||||
)
|
||||
},
|
||||
@ -96,8 +90,7 @@ fun RequiredOptionsScreen(
|
||||
onContinue(vm.getCustomSelection(), vm.getOptions())
|
||||
}
|
||||
)
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
Modifier
|
||||
|
@ -4,8 +4,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@ -14,28 +12,17 @@ import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowRight
|
||||
import androidx.compose.material.icons.filled.AutoFixHigh
|
||||
import androidx.compose.material.icons.outlined.WarningAmber
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.data.platform.NetworkInfo
|
||||
import app.revanced.manager.data.room.apps.installed.InstallType
|
||||
import app.revanced.manager.data.room.apps.installed.InstalledApp
|
||||
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
|
||||
@ -44,7 +31,6 @@ import app.revanced.manager.ui.component.AppInfo
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.ColumnWithScrollbar
|
||||
import app.revanced.manager.ui.component.LoadingIndicator
|
||||
import app.revanced.manager.ui.component.NotificationCard
|
||||
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
|
||||
import app.revanced.manager.ui.model.SelectedApp
|
||||
import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel
|
||||
@ -55,7 +41,6 @@ import app.revanced.manager.util.enabled
|
||||
import app.revanced.manager.util.toast
|
||||
import app.revanced.manager.util.transparentListItemColors
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@ -67,9 +52,6 @@ fun SelectedAppInfoScreen(
|
||||
vm: SelectedAppInfoViewModel
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val networkInfo = koinInject<NetworkInfo>()
|
||||
val networkConnected = remember { networkInfo.isConnected() }
|
||||
val networkMetered = remember { !networkInfo.isUnmetered() }
|
||||
|
||||
val packageName = vm.selectedApp.packageName
|
||||
val version = vm.selectedApp.version
|
||||
@ -93,14 +75,10 @@ fun SelectedAppInfoScreen(
|
||||
val composableScope = rememberCoroutineScope()
|
||||
|
||||
val error by vm.errorFlow.collectAsStateWithLifecycle(null)
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.app_info),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBackClick = onBackClick
|
||||
)
|
||||
},
|
||||
@ -136,8 +114,7 @@ fun SelectedAppInfoScreen(
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
}
|
||||
) { paddingValues ->
|
||||
val plugins by vm.plugins.collectAsStateWithLifecycle(emptyList())
|
||||
|
||||
@ -217,35 +194,6 @@ fun SelectedAppInfoScreen(
|
||||
modifier = Modifier.padding(horizontal = 24.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
val needsInternet =
|
||||
vm.selectedApp.let { it is SelectedApp.Search || it is SelectedApp.Download }
|
||||
|
||||
when {
|
||||
!needsInternet -> {}
|
||||
!networkConnected -> {
|
||||
NotificationCard(
|
||||
isWarning = true,
|
||||
icon = Icons.Outlined.WarningAmber,
|
||||
text = stringResource(R.string.network_unavailable_warning),
|
||||
onDismiss = null
|
||||
)
|
||||
}
|
||||
|
||||
networkMetered -> {
|
||||
NotificationCard(
|
||||
isWarning = true,
|
||||
icon = Icons.Outlined.WarningAmber,
|
||||
text = stringResource(R.string.network_metered_warning),
|
||||
onDismiss = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package app.revanced.manager.ui.screen
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@ -8,79 +7,50 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.domain.manager.PreferencesManager
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.ColumnWithScrollbar
|
||||
import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||
import app.revanced.manager.ui.model.navigation.Settings
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
private data class Section(
|
||||
@StringRes val name: Int,
|
||||
@StringRes val description: Int,
|
||||
val image: ImageVector,
|
||||
val destination: Settings.Destination,
|
||||
private val settingsSections = listOf(
|
||||
Triple(
|
||||
R.string.general,
|
||||
R.string.general_description,
|
||||
Icons.Outlined.Settings
|
||||
) to Settings.General,
|
||||
Triple(
|
||||
R.string.updates,
|
||||
R.string.updates_description,
|
||||
Icons.Outlined.Update
|
||||
) to Settings.Updates,
|
||||
Triple(
|
||||
R.string.downloads,
|
||||
R.string.downloads_description,
|
||||
Icons.Outlined.Download
|
||||
) to Settings.Downloads,
|
||||
Triple(
|
||||
R.string.import_export,
|
||||
R.string.import_export_description,
|
||||
Icons.Outlined.SwapVert
|
||||
) to Settings.ImportExport,
|
||||
Triple(
|
||||
R.string.advanced,
|
||||
R.string.advanced_description,
|
||||
Icons.Outlined.Tune
|
||||
) to Settings.Advanced,
|
||||
Triple(
|
||||
R.string.about,
|
||||
R.string.app_name,
|
||||
Icons.Outlined.Info
|
||||
) to Settings.About,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsScreen(onBackClick: () -> Unit, navigate: (Settings.Destination) -> Unit) {
|
||||
val prefs: PreferencesManager = koinInject()
|
||||
val showDeveloperSettings by prefs.showDeveloperSettings.getAsState()
|
||||
|
||||
val settingsSections = remember(showDeveloperSettings) {
|
||||
listOfNotNull(
|
||||
Section(
|
||||
R.string.general,
|
||||
R.string.general_description,
|
||||
Icons.Outlined.Settings,
|
||||
Settings.General
|
||||
),
|
||||
Section(
|
||||
R.string.updates,
|
||||
R.string.updates_description,
|
||||
Icons.Outlined.Update,
|
||||
Settings.Updates
|
||||
),
|
||||
Section(
|
||||
R.string.downloads,
|
||||
R.string.downloads_description,
|
||||
Icons.Outlined.Download,
|
||||
Settings.Downloads
|
||||
),
|
||||
Section(
|
||||
R.string.import_export,
|
||||
R.string.import_export_description,
|
||||
Icons.Outlined.SwapVert,
|
||||
Settings.ImportExport
|
||||
),
|
||||
Section(
|
||||
R.string.advanced,
|
||||
R.string.advanced_description,
|
||||
Icons.Outlined.Tune,
|
||||
Settings.Advanced
|
||||
),
|
||||
Section(
|
||||
R.string.about,
|
||||
R.string.app_name,
|
||||
Icons.Outlined.Info,
|
||||
Settings.About
|
||||
),
|
||||
Section(
|
||||
R.string.developer_options,
|
||||
R.string.developer_options_description,
|
||||
Icons.Outlined.Code,
|
||||
Settings.Developer
|
||||
).takeIf { showDeveloperSettings }
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
@ -94,12 +64,12 @@ fun SettingsScreen(onBackClick: () -> Unit, navigate: (Settings.Destination) ->
|
||||
.padding(paddingValues)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
settingsSections.forEach { (name, description, icon, destination) ->
|
||||
settingsSections.forEach { (titleDescIcon, destination) ->
|
||||
SettingsListItem(
|
||||
modifier = Modifier.clickable { navigate(destination) },
|
||||
headlineContent = stringResource(name),
|
||||
supportingContent = stringResource(description),
|
||||
leadingContent = { Icon(icon, null) }
|
||||
headlineContent = stringResource(titleDescIcon.first),
|
||||
supportingContent = stringResource(titleDescIcon.second),
|
||||
leadingContent = { Icon(titleDescIcon.third, null) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -5,36 +5,36 @@ import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Cancel
|
||||
import androidx.compose.material.icons.outlined.InstallMobile
|
||||
import androidx.compose.material.icons.outlined.Update
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.BuildConfig
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.network.dto.ReVancedAsset
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
|
||||
import app.revanced.manager.ui.component.settings.Changelog
|
||||
import app.revanced.manager.ui.viewmodel.UpdateViewModel
|
||||
import app.revanced.manager.ui.viewmodel.UpdateViewModel.State
|
||||
@ -52,86 +52,38 @@ fun UpdateScreen(
|
||||
onBackClick: () -> Unit,
|
||||
vm: UpdateViewModel = koinViewModel()
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = {
|
||||
Column {
|
||||
Text(stringResource(vm.state.title))
|
||||
|
||||
if (vm.state == State.DOWNLOADING) {
|
||||
Text(
|
||||
text = "${vm.downloadedSize.div(1000000)} MB / ${
|
||||
vm.totalSize.div(1000000)
|
||||
} MB (${vm.downloadProgress.times(100).toInt()}%)",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
title = stringResource(R.string.update),
|
||||
onBackClick = onBackClick
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
val buttonConfig = when (vm.state) {
|
||||
State.CAN_DOWNLOAD -> Triple(
|
||||
{ vm.downloadUpdate() },
|
||||
R.string.download,
|
||||
Icons.Outlined.InstallMobile
|
||||
)
|
||||
|
||||
State.DOWNLOADING -> Triple(onBackClick, R.string.cancel, Icons.Outlined.Cancel)
|
||||
State.CAN_INSTALL -> Triple(
|
||||
{ vm.installUpdate() },
|
||||
R.string.install_app,
|
||||
Icons.Outlined.InstallMobile
|
||||
)
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
buttonConfig?.let { (onClick, textRes, icon) ->
|
||||
HapticExtendedFloatingActionButton(
|
||||
onClick = onClick::invoke,
|
||||
icon = { Icon(icon, null) },
|
||||
text = { Text(stringResource(textRes)) }
|
||||
)
|
||||
}
|
||||
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
}
|
||||
) { paddingValues ->
|
||||
AnimatedVisibility(visible = vm.showInternetCheckDialog) {
|
||||
MeteredDownloadConfirmationDialog(
|
||||
onDismiss = { vm.showInternetCheckDialog = false },
|
||||
onDownloadAnyways = { vm.downloadUpdate(true) }
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues),
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.padding(vertical = 16.dp, horizontal = 24.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(32.dp)
|
||||
) {
|
||||
if (vm.state == State.DOWNLOADING)
|
||||
LinearProgressIndicator(
|
||||
progress = { vm.downloadProgress },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
AnimatedVisibility(visible = vm.showInternetCheckDialog) {
|
||||
MeteredDownloadConfirmationDialog(
|
||||
onDismiss = { vm.showInternetCheckDialog = false },
|
||||
onDownloadAnyways = { vm.downloadUpdate(true) }
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(32.dp)
|
||||
) {
|
||||
vm.releaseInfo?.let { changelog ->
|
||||
Changelog(changelog)
|
||||
}
|
||||
}
|
||||
Header(
|
||||
vm.state,
|
||||
vm.releaseInfo,
|
||||
DownloadData(vm.downloadProgress, vm.downloadedSize, vm.totalSize)
|
||||
)
|
||||
vm.releaseInfo?.let { changelog ->
|
||||
HorizontalDivider()
|
||||
Changelog(changelog)
|
||||
} ?: Spacer(modifier = Modifier.weight(1f))
|
||||
Buttons(vm.state, vm::downloadUpdate, vm::installUpdate, onBackClick)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -164,6 +116,58 @@ private fun MeteredDownloadConfirmationDialog(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Header(state: State, releaseInfo: ReVancedAsset?, downloadData: DownloadData) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
Text(
|
||||
text = stringResource(state.title),
|
||||
style = MaterialTheme.typography.headlineMedium
|
||||
)
|
||||
if (state == State.CAN_DOWNLOAD) {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.current_version,
|
||||
BuildConfig.VERSION_NAME
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
releaseInfo?.version?.let {
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.new_version,
|
||||
it.replace("v", "")
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (state == State.DOWNLOADING) {
|
||||
LinearProgressIndicator(
|
||||
progress = { downloadData.downloadProgress },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Text(
|
||||
text =
|
||||
"${downloadData.downloadedSize.div(1000000)} MB / ${
|
||||
downloadData.totalSize.div(
|
||||
1000000
|
||||
)
|
||||
} MB (${
|
||||
downloadData.downloadProgress.times(
|
||||
100
|
||||
).toInt()
|
||||
}%)",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.Changelog(releaseInfo: ReVancedAsset) {
|
||||
val scrollState = rememberScrollState()
|
||||
@ -194,4 +198,40 @@ private fun ColumnScope.Changelog(releaseInfo: ReVancedAsset) {
|
||||
publishDate = releaseInfo.createdAt.relativeTime(LocalContext.current)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Buttons(
|
||||
state: State,
|
||||
onDownloadClick: () -> Unit,
|
||||
onInstallClick: () -> Unit,
|
||||
onBackClick: () -> Unit
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
if (state.showCancel) {
|
||||
TextButton(
|
||||
onClick = onBackClick,
|
||||
) {
|
||||
Text(text = stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
if (state == State.CAN_DOWNLOAD) {
|
||||
Button(onClick = onDownloadClick) {
|
||||
Text(text = stringResource(R.string.update))
|
||||
}
|
||||
} else if (state == State.CAN_INSTALL) {
|
||||
Button(
|
||||
onClick = onInstallClick
|
||||
) {
|
||||
Text(text = stringResource(R.string.install_app))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class DownloadData(
|
||||
val downloadProgress: Float,
|
||||
val downloadedSize: Long,
|
||||
val totalSize: Long
|
||||
)
|
@ -23,40 +23,24 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.hideFromAccessibility
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.BuildConfig
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.network.dto.ReVancedSocial
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.ColumnWithScrollbar
|
||||
import app.revanced.manager.ui.component.TooltipWrap
|
||||
import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||
import app.revanced.manager.ui.model.navigation.Settings
|
||||
import app.revanced.manager.ui.viewmodel.AboutViewModel
|
||||
import app.revanced.manager.ui.viewmodel.AboutViewModel.Companion.DEVELOPER_OPTIONS_TAPS
|
||||
import app.revanced.manager.ui.viewmodel.AboutViewModel.Companion.getSocialIcon
|
||||
import app.revanced.manager.util.openUrl
|
||||
import app.revanced.manager.util.toast
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@ -121,8 +105,7 @@ fun AboutSettingsScreen(
|
||||
}
|
||||
|
||||
val listItems = listOfNotNull(
|
||||
Triple(
|
||||
stringResource(R.string.submit_feedback),
|
||||
Triple(stringResource(R.string.submit_feedback),
|
||||
stringResource(R.string.submit_feedback_description),
|
||||
third = {
|
||||
context.openUrl("https://github.com/ReVanced/revanced-manager/issues/new/choose")
|
||||
@ -130,14 +113,12 @@ fun AboutSettingsScreen(
|
||||
Triple(
|
||||
stringResource(R.string.contributors),
|
||||
stringResource(R.string.contributors_description),
|
||||
third = nav@{
|
||||
if (!viewModel.isConnected) {
|
||||
context.toast(context.getString(R.string.no_network_toast))
|
||||
return@nav
|
||||
}
|
||||
|
||||
navigate(Settings.Contributors)
|
||||
}
|
||||
third = { navigate(Settings.Contributors) }
|
||||
),
|
||||
Triple(
|
||||
stringResource(R.string.developer_options),
|
||||
stringResource(R.string.developer_options_description),
|
||||
third = { navigate(Settings.DeveloperOptions) }
|
||||
),
|
||||
Triple(
|
||||
stringResource(R.string.opensource_licenses),
|
||||
@ -146,49 +127,13 @@ fun AboutSettingsScreen(
|
||||
)
|
||||
)
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
val showDeveloperSettings by viewModel.showDeveloperSettings.getAsState()
|
||||
var developerTaps by rememberSaveable { mutableIntStateOf(0) }
|
||||
LaunchedEffect(developerTaps) {
|
||||
if (developerTaps == 0) return@LaunchedEffect
|
||||
if (showDeveloperSettings) {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.developer_options_already_enabled))
|
||||
developerTaps = 0
|
||||
return@LaunchedEffect
|
||||
}
|
||||
|
||||
val remaining = DEVELOPER_OPTIONS_TAPS - developerTaps
|
||||
if (remaining > 0) {
|
||||
snackbarHostState.showSnackbar(
|
||||
context.getString(
|
||||
R.string.developer_options_taps,
|
||||
remaining
|
||||
),
|
||||
duration = SnackbarDuration.Long
|
||||
)
|
||||
} else if (remaining == 0) {
|
||||
viewModel.showDeveloperSettings.update(true)
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.developer_options_enabled))
|
||||
}
|
||||
|
||||
// Reset the counter
|
||||
developerTaps = 0
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.about),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBackClick = onBackClick
|
||||
)
|
||||
},
|
||||
snackbarHost = {
|
||||
SnackbarHost(hostState = snackbarHostState)
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
}
|
||||
) { paddingValues ->
|
||||
ColumnWithScrollbar(
|
||||
modifier = Modifier
|
||||
@ -198,11 +143,9 @@ fun AboutSettingsScreen(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.padding(top = 16.dp)
|
||||
.clickable { developerTaps += 1 },
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
painter = icon,
|
||||
contentDescription = stringResource(R.string.app_name)
|
||||
contentDescription = null
|
||||
)
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
@ -210,11 +153,7 @@ fun AboutSettingsScreen(
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier.semantics {
|
||||
// Icon already has this information for the purpose of being clickable.
|
||||
hideFromAccessibility()
|
||||
}
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.version) + " " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")",
|
||||
@ -253,21 +192,16 @@ fun AboutSettingsScreen(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)
|
||||
) {
|
||||
socialButtons.forEach { (icon, text, onClick) ->
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = text,
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
) {
|
||||
Icon(
|
||||
icon,
|
||||
contentDescription = text,
|
||||
modifier = Modifier.size(28.dp),
|
||||
tint = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
icon,
|
||||
contentDescription = text,
|
||||
modifier = Modifier.size(28.dp),
|
||||
tint = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,33 +10,14 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Api
|
||||
import androidx.compose.material.icons.outlined.Restore
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
@ -48,7 +29,6 @@ import app.revanced.manager.R
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.ColumnWithScrollbar
|
||||
import app.revanced.manager.ui.component.GroupHeader
|
||||
import app.revanced.manager.ui.component.TooltipWrap
|
||||
import app.revanced.manager.ui.component.settings.BooleanItem
|
||||
import app.revanced.manager.ui.component.settings.IntegerItem
|
||||
import app.revanced.manager.ui.component.settings.SafeguardBooleanItem
|
||||
@ -62,7 +42,7 @@ import org.koin.androidx.compose.koinViewModel
|
||||
@Composable
|
||||
fun AdvancedSettingsScreen(
|
||||
onBackClick: () -> Unit,
|
||||
viewModel: AdvancedSettingsViewModel = koinViewModel()
|
||||
vm: AdvancedSettingsViewModel = koinViewModel()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val memoryLimit = remember {
|
||||
@ -73,17 +53,14 @@ fun AdvancedSettingsScreen(
|
||||
activityManager.largeMemoryClass
|
||||
)
|
||||
}
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.advanced),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBackClick = onBackClick
|
||||
)
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
}
|
||||
) { paddingValues ->
|
||||
ColumnWithScrollbar(
|
||||
modifier = Modifier
|
||||
@ -92,16 +69,16 @@ fun AdvancedSettingsScreen(
|
||||
) {
|
||||
GroupHeader(stringResource(R.string.manager))
|
||||
|
||||
val apiUrl by viewModel.prefs.api.getAsState()
|
||||
val apiUrl by vm.prefs.api.getAsState()
|
||||
var showApiUrlDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
if (showApiUrlDialog) {
|
||||
APIUrlDialog(
|
||||
currentUrl = apiUrl,
|
||||
defaultUrl = viewModel.prefs.api.default,
|
||||
defaultUrl = vm.prefs.api.default,
|
||||
onSubmit = {
|
||||
showApiUrlDialog = false
|
||||
it?.let(viewModel::setApiUrl)
|
||||
it?.let(vm::setApiUrl)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -113,58 +90,58 @@ fun AdvancedSettingsScreen(
|
||||
}
|
||||
)
|
||||
|
||||
GroupHeader(stringResource(R.string.patcher))
|
||||
BooleanItem(
|
||||
preference = vm.prefs.useProcessRuntime,
|
||||
coroutineScope = vm.viewModelScope,
|
||||
headline = R.string.process_runtime,
|
||||
description = R.string.process_runtime_description,
|
||||
)
|
||||
IntegerItem(
|
||||
preference = vm.prefs.patcherProcessMemoryLimit,
|
||||
coroutineScope = vm.viewModelScope,
|
||||
headline = R.string.process_runtime_memory_limit,
|
||||
description = R.string.process_runtime_memory_limit_description,
|
||||
)
|
||||
|
||||
GroupHeader(stringResource(R.string.safeguards))
|
||||
SafeguardBooleanItem(
|
||||
preference = viewModel.prefs.disablePatchVersionCompatCheck,
|
||||
coroutineScope = viewModel.viewModelScope,
|
||||
preference = vm.prefs.disablePatchVersionCompatCheck,
|
||||
coroutineScope = vm.viewModelScope,
|
||||
headline = R.string.patch_compat_check,
|
||||
description = R.string.patch_compat_check_description,
|
||||
confirmationText = R.string.patch_compat_check_confirmation
|
||||
)
|
||||
SafeguardBooleanItem(
|
||||
preference = viewModel.prefs.suggestedVersionSafeguard,
|
||||
coroutineScope = viewModel.viewModelScope,
|
||||
preference = vm.prefs.disableUniversalPatchWarning,
|
||||
coroutineScope = vm.viewModelScope,
|
||||
headline = R.string.universal_patches_safeguard,
|
||||
description = R.string.universal_patches_safeguard_description,
|
||||
confirmationText = R.string.universal_patches_safeguard_confirmation
|
||||
)
|
||||
SafeguardBooleanItem(
|
||||
preference = vm.prefs.suggestedVersionSafeguard,
|
||||
coroutineScope = vm.viewModelScope,
|
||||
headline = R.string.suggested_version_safeguard,
|
||||
description = R.string.suggested_version_safeguard_description,
|
||||
confirmationText = R.string.suggested_version_safeguard_confirmation
|
||||
)
|
||||
SafeguardBooleanItem(
|
||||
preference = viewModel.prefs.disableSelectionWarning,
|
||||
coroutineScope = viewModel.viewModelScope,
|
||||
preference = vm.prefs.disableSelectionWarning,
|
||||
coroutineScope = vm.viewModelScope,
|
||||
headline = R.string.patch_selection_safeguard,
|
||||
description = R.string.patch_selection_safeguard_description,
|
||||
confirmationText = R.string.patch_selection_safeguard_confirmation
|
||||
)
|
||||
SafeguardBooleanItem(
|
||||
preference = viewModel.prefs.disableUniversalPatchCheck,
|
||||
coroutineScope = viewModel.viewModelScope,
|
||||
headline = R.string.universal_patches_safeguard,
|
||||
description = R.string.universal_patches_safeguard_description,
|
||||
confirmationText = R.string.universal_patches_safeguard_confirmation
|
||||
)
|
||||
|
||||
GroupHeader(stringResource(R.string.patcher))
|
||||
BooleanItem(
|
||||
preference = viewModel.prefs.useProcessRuntime,
|
||||
coroutineScope = viewModel.viewModelScope,
|
||||
headline = R.string.process_runtime,
|
||||
description = R.string.process_runtime_description,
|
||||
)
|
||||
IntegerItem(
|
||||
preference = viewModel.prefs.patcherProcessMemoryLimit,
|
||||
coroutineScope = viewModel.viewModelScope,
|
||||
headline = R.string.process_runtime_memory_limit,
|
||||
description = R.string.process_runtime_memory_limit_description,
|
||||
)
|
||||
|
||||
GroupHeader(stringResource(R.string.debugging))
|
||||
val exportDebugLogsLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) {
|
||||
it?.let(viewModel::exportDebugLogs)
|
||||
it?.let(vm::exportDebugLogs)
|
||||
}
|
||||
SettingsListItem(
|
||||
headlineContent = stringResource(R.string.debug_logs_export),
|
||||
modifier = Modifier.clickable { exportDebugLogsLauncher.launch(viewModel.debugLogFileName) }
|
||||
modifier = Modifier.clickable { exportDebugLogsLauncher.launch(vm.debugLogFileName) }
|
||||
)
|
||||
val clipboard = remember { context.getSystemService<ClipboardManager>()!! }
|
||||
val deviceContent = """
|
||||
@ -244,13 +221,8 @@ private fun APIUrlDialog(currentUrl: String, defaultUrl: String, onSubmit: (Stri
|
||||
onValueChange = { url = it },
|
||||
label = { Text(stringResource(R.string.api_url)) },
|
||||
trailingIcon = {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.api_url_dialog_reset),
|
||||
) {
|
||||
IconButton(onClick = { url = defaultUrl }) {
|
||||
Icon(Icons.Outlined.Restore, stringResource(R.string.api_url_dialog_reset))
|
||||
}
|
||||
IconButton(onClick = { url = defaultUrl }) {
|
||||
Icon(Icons.Outlined.Restore, stringResource(R.string.api_url_dialog_reset))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -25,14 +25,11 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@ -51,22 +48,18 @@ import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ContributorSettingsScreen(
|
||||
fun ContributorScreen(
|
||||
onBackClick: () -> Unit,
|
||||
viewModel: ContributorViewModel = koinViewModel()
|
||||
) {
|
||||
val repositories = viewModel.repositories
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.contributors),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBackClick = onBackClick
|
||||
)
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
) { paddingValues ->
|
||||
LazyColumnWithScrollbar(
|
||||
modifier = Modifier
|
||||
@ -97,14 +90,7 @@ fun ContributorSettingsScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
} ?: item {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
LoadingIndicator()
|
||||
}
|
||||
}
|
||||
} ?: item { LoadingIndicator() }
|
||||
}
|
||||
}
|
||||
}
|
@ -5,49 +5,31 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.domain.manager.PreferencesManager
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.GroupHeader
|
||||
import app.revanced.manager.ui.component.settings.BooleanItem
|
||||
import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||
import app.revanced.manager.ui.viewmodel.DeveloperOptionsViewModel
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DeveloperSettingsScreen(
|
||||
fun DeveloperOptionsScreen(
|
||||
onBackClick: () -> Unit,
|
||||
vm: DeveloperOptionsViewModel = koinViewModel()
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val prefs: PreferencesManager = koinInject()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.developer_options),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBackClick = onBackClick
|
||||
)
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(modifier = Modifier.padding(paddingValues)) {
|
||||
GroupHeader(stringResource(R.string.manager))
|
||||
BooleanItem(
|
||||
preference = prefs.showDeveloperSettings,
|
||||
headline = R.string.developer_options,
|
||||
description = R.string.developer_options_description,
|
||||
)
|
||||
|
||||
GroupHeader(stringResource(R.string.patch_bundles_section))
|
||||
SettingsListItem(
|
||||
headlineContent = stringResource(R.string.patch_bundles_force_download),
|
@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
@ -18,11 +17,9 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
||||
import androidx.compose.material3.pulltorefresh.pullToRefresh
|
||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@ -31,7 +28,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -44,8 +40,6 @@ import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.ExceptionViewerDialog
|
||||
import app.revanced.manager.ui.component.GroupHeader
|
||||
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
|
||||
import app.revanced.manager.ui.component.ConfirmDialog
|
||||
import app.revanced.manager.ui.component.TooltipWrap
|
||||
import app.revanced.manager.ui.component.haptics.HapticCheckbox
|
||||
import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||
import app.revanced.manager.ui.viewmodel.DownloadsViewModel
|
||||
@ -61,40 +55,21 @@ fun DownloadsSettingsScreen(
|
||||
val pullRefreshState = rememberPullToRefreshState()
|
||||
val downloadedApps by viewModel.downloadedApps.collectAsStateWithLifecycle(emptyList())
|
||||
val pluginStates by viewModel.downloaderPluginStates.collectAsStateWithLifecycle()
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
var showDeleteConfirmationDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
if (showDeleteConfirmationDialog) {
|
||||
ConfirmDialog(
|
||||
onDismiss = { showDeleteConfirmationDialog = false },
|
||||
onConfirm = { viewModel.deleteApps() },
|
||||
title = stringResource(R.string.downloader_plugin_delete_apps_title),
|
||||
description = stringResource(R.string.downloader_plugin_delete_apps_description),
|
||||
icon = Icons.Outlined.Delete
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.downloads),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBackClick = onBackClick,
|
||||
actions = {
|
||||
if (viewModel.appSelection.isNotEmpty()) {
|
||||
TooltipWrap(
|
||||
modifier = Modifier,
|
||||
tooltip = stringResource(R.string.delete),
|
||||
) {
|
||||
IconButton(onClick = { showDeleteConfirmationDialog = true }) {
|
||||
Icon(Icons.Default.Delete, stringResource(R.string.delete))
|
||||
}
|
||||
IconButton(onClick = { viewModel.deleteApps() }) {
|
||||
Icon(Icons.Default.Delete, stringResource(R.string.delete))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
}
|
||||
) { paddingValues ->
|
||||
Box(
|
||||
contentAlignment = Alignment.TopCenter,
|
||||
|
@ -13,8 +13,6 @@ import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@ -22,7 +20,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.R
|
||||
@ -54,17 +51,14 @@ fun GeneralSettingsScreen(
|
||||
onConfirm = { viewModel.setTheme(it) }
|
||||
)
|
||||
}
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.general),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBackClick = onBackClick
|
||||
)
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
}
|
||||
) { paddingValues ->
|
||||
ColumnWithScrollbar(
|
||||
modifier = Modifier
|
||||
|
@ -13,28 +13,15 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Key
|
||||
import androidx.compose.material.icons.outlined.WarningAmber
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
@ -44,16 +31,12 @@ import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.ColumnWithScrollbar
|
||||
import app.revanced.manager.ui.component.ConfirmDialog
|
||||
import app.revanced.manager.ui.component.GroupHeader
|
||||
import app.revanced.manager.ui.component.PasswordField
|
||||
import app.revanced.manager.ui.component.bundle.BundleSelector
|
||||
import app.revanced.manager.ui.component.settings.ExpandableSettingListItem
|
||||
import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||
import app.revanced.manager.ui.viewmodel.ImportExportViewModel
|
||||
import app.revanced.manager.ui.viewmodel.ResetDialogState
|
||||
import app.revanced.manager.util.toast
|
||||
import app.revanced.manager.util.uiSafe
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@ -64,8 +47,6 @@ fun ImportExportSettingsScreen(
|
||||
vm: ImportExportViewModel = koinViewModel()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var selectorDialog by rememberSaveable { mutableStateOf<(@Composable () -> Unit)?>(null) }
|
||||
|
||||
val importKeystoreLauncher =
|
||||
rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) {
|
||||
@ -77,7 +58,6 @@ fun ImportExportSettingsScreen(
|
||||
}
|
||||
|
||||
val patchBundles by vm.patchBundles.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||
val packagesWithSelections by vm.packagesWithSelection.collectAsStateWithLifecycle(initialValue = emptySet())
|
||||
val packagesWithOptions by vm.packagesWithOptions.collectAsStateWithLifecycle(initialValue = emptySet())
|
||||
|
||||
vm.selectionAction?.let { action ->
|
||||
@ -106,49 +86,27 @@ fun ImportExportSettingsScreen(
|
||||
onDismissRequest = vm::cancelKeystoreImport,
|
||||
onSubmit = { cn, pass ->
|
||||
vm.viewModelScope.launch {
|
||||
uiSafe(context, R.string.failed_to_import_keystore, "Failed to import keystore") {
|
||||
val result = vm.tryKeystoreImport(cn, pass)
|
||||
if (!result) context.toast(context.getString(R.string.import_keystore_wrong_credentials))
|
||||
}
|
||||
val result = vm.tryKeystoreImport(cn, pass)
|
||||
if (!result) context.toast(context.getString(R.string.import_keystore_wrong_credentials))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
vm.resetDialogState?.let {
|
||||
with(vm.resetDialogState!!) {
|
||||
ConfirmDialog(
|
||||
onDismiss = { vm.resetDialogState = null },
|
||||
onConfirm = onConfirm,
|
||||
title = stringResource(titleResId),
|
||||
description = dialogOptionName?.let {
|
||||
stringResource(descriptionResId, it)
|
||||
} ?: stringResource(descriptionResId),
|
||||
icon = Icons.Outlined.WarningAmber
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.import_export),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBackClick = onBackClick
|
||||
)
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
}
|
||||
) { paddingValues ->
|
||||
ColumnWithScrollbar(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
selectorDialog?.invoke()
|
||||
|
||||
GroupHeader(stringResource(R.string.import_))
|
||||
GroupHeader(stringResource(R.string.signing))
|
||||
GroupItem(
|
||||
onClick = {
|
||||
importKeystoreLauncher.launch("*/*")
|
||||
@ -156,13 +114,6 @@ fun ImportExportSettingsScreen(
|
||||
headline = R.string.import_keystore,
|
||||
description = R.string.import_keystore_description
|
||||
)
|
||||
GroupItem(
|
||||
onClick = vm::importSelection,
|
||||
headline = R.string.import_patch_selection,
|
||||
description = R.string.import_patch_selection_description
|
||||
)
|
||||
|
||||
GroupHeader(stringResource(R.string.export))
|
||||
GroupItem(
|
||||
onClick = {
|
||||
if (!vm.canExport()) {
|
||||
@ -175,133 +126,67 @@ fun ImportExportSettingsScreen(
|
||||
description = R.string.export_keystore_description
|
||||
)
|
||||
GroupItem(
|
||||
onClick = vm::exportSelection,
|
||||
headline = R.string.export_patch_selection,
|
||||
description = R.string.export_patch_selection_description
|
||||
)
|
||||
|
||||
GroupHeader(stringResource(R.string.reset))
|
||||
GroupItem(
|
||||
onClick = {
|
||||
vm.resetDialogState = ResetDialogState.Keystore {
|
||||
vm.regenerateKeystore()
|
||||
}
|
||||
},
|
||||
onClick = vm::regenerateKeystore,
|
||||
headline = R.string.regenerate_keystore,
|
||||
description = R.string.regenerate_keystore_description
|
||||
)
|
||||
|
||||
ExpandableSettingListItem(
|
||||
headlineContent = stringResource(R.string.reset_patch_selection),
|
||||
supportingContent = stringResource(R.string.reset_patch_selection_description),
|
||||
expandableContent = {
|
||||
GroupItem(
|
||||
onClick = {
|
||||
vm.resetDialogState = ResetDialogState.PatchSelectionAll {
|
||||
vm.resetSelection()
|
||||
}
|
||||
},
|
||||
headline = R.string.patch_selection_reset_all,
|
||||
description = R.string.patch_selection_reset_all_description
|
||||
)
|
||||
|
||||
GroupItem(
|
||||
onClick = {
|
||||
selectorDialog = {
|
||||
PackageSelector(packages = packagesWithSelections) { packageName ->
|
||||
packageName?.also {
|
||||
vm.resetDialogState =
|
||||
ResetDialogState.PatchSelectionPackage(packageName) {
|
||||
vm.resetSelectionForPackage(packageName)
|
||||
}
|
||||
}
|
||||
selectorDialog = null
|
||||
}
|
||||
}
|
||||
},
|
||||
headline = R.string.patch_selection_reset_package,
|
||||
description = R.string.patch_selection_reset_package_description
|
||||
)
|
||||
|
||||
if (patchBundles.isNotEmpty()) {
|
||||
GroupItem(
|
||||
onClick = {
|
||||
selectorDialog = {
|
||||
BundleSelector(bundles = patchBundles) { bundle ->
|
||||
bundle?.also {
|
||||
coroutineScope.launch {
|
||||
vm.resetDialogState =
|
||||
ResetDialogState.PatchSelectionBundle(bundle.getName()) {
|
||||
vm.resetSelectionForPatchBundle(bundle)
|
||||
}
|
||||
}
|
||||
}
|
||||
selectorDialog = null
|
||||
}
|
||||
}
|
||||
},
|
||||
headline = R.string.patch_selection_reset_bundle,
|
||||
description = R.string.patch_selection_reset_bundle_description
|
||||
)
|
||||
}
|
||||
}
|
||||
GroupHeader(stringResource(R.string.patches))
|
||||
GroupItem(
|
||||
onClick = vm::importSelection,
|
||||
headline = R.string.import_patch_selection,
|
||||
description = R.string.import_patch_selection_description
|
||||
)
|
||||
GroupItem(
|
||||
onClick = vm::exportSelection,
|
||||
headline = R.string.export_patch_selection,
|
||||
description = R.string.export_patch_selection_description
|
||||
)
|
||||
// TODO: allow resetting selection for specific bundle or package name.
|
||||
GroupItem(
|
||||
onClick = vm::resetSelection,
|
||||
headline = R.string.reset_patch_selection,
|
||||
description = R.string.reset_patch_selection_description
|
||||
)
|
||||
|
||||
ExpandableSettingListItem(
|
||||
headlineContent = stringResource(R.string.reset_patch_options),
|
||||
supportingContent = stringResource(R.string.reset_patch_options_description),
|
||||
expandableContent = {
|
||||
GroupItem(
|
||||
onClick = {
|
||||
vm.resetDialogState = ResetDialogState.PatchOptionsAll {
|
||||
vm.resetOptions()
|
||||
}
|
||||
}, // TODO: patch options import/export.
|
||||
headline = R.string.patch_options_reset_all,
|
||||
description = R.string.patch_options_reset_all_description,
|
||||
)
|
||||
var showPackageSelector by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
var showBundleSelector by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
GroupItem(
|
||||
onClick = {
|
||||
selectorDialog = {
|
||||
PackageSelector(packages = packagesWithOptions) { packageName ->
|
||||
packageName?.also {
|
||||
vm.resetDialogState =
|
||||
ResetDialogState.PatchOptionPackage(packageName) {
|
||||
vm.resetOptionsForPackage(packageName)
|
||||
}
|
||||
}
|
||||
selectorDialog = null
|
||||
}
|
||||
}
|
||||
},
|
||||
headline = R.string.patch_options_reset_package,
|
||||
description = R.string.patch_options_reset_package_description
|
||||
)
|
||||
if (showPackageSelector)
|
||||
PackageSelector(packages = packagesWithOptions) { selected ->
|
||||
selected?.let(vm::resetOptionsForPackage)
|
||||
|
||||
if (patchBundles.isNotEmpty()) {
|
||||
GroupItem(
|
||||
onClick = {
|
||||
selectorDialog = {
|
||||
BundleSelector(bundles = patchBundles) { bundle ->
|
||||
bundle?.also {
|
||||
coroutineScope.launch {
|
||||
vm.resetDialogState =
|
||||
ResetDialogState.PatchOptionBundle(bundle.getName()) {
|
||||
vm.resetOptionsForBundle(bundle)
|
||||
}
|
||||
}
|
||||
}
|
||||
selectorDialog = null
|
||||
}
|
||||
}
|
||||
},
|
||||
headline = R.string.patch_options_reset_bundle,
|
||||
description = R.string.patch_options_reset_bundle_description,
|
||||
)
|
||||
}
|
||||
showPackageSelector = false
|
||||
}
|
||||
|
||||
if (showBundleSelector)
|
||||
BundleSelector(bundles = patchBundles) { bundle ->
|
||||
bundle?.let(vm::clearOptionsForBundle)
|
||||
|
||||
showBundleSelector = false
|
||||
}
|
||||
|
||||
// TODO: patch options import/export.
|
||||
GroupItem(
|
||||
onClick = vm::resetOptions,
|
||||
headline = R.string.patch_options_reset_all,
|
||||
description = R.string.patch_options_reset_all_description,
|
||||
)
|
||||
GroupItem(
|
||||
onClick = { showPackageSelector = true },
|
||||
headline = R.string.patch_options_reset_package,
|
||||
description = R.string.patch_options_reset_package_description
|
||||
)
|
||||
if (patchBundles.size > 1)
|
||||
GroupItem(
|
||||
onClick = { showBundleSelector = true },
|
||||
headline = R.string.patch_options_reset_bundle,
|
||||
description = R.string.patch_options_reset_bundle_description,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,10 @@ import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.Scrollbar
|
||||
import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer
|
||||
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
|
||||
import com.mikepenz.aboutlibraries.ui.compose.libraryColors
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LicensesSettingsScreen(
|
||||
fun LicensesScreen(
|
||||
onBackClick: () -> Unit,
|
||||
) {
|
||||
AppScaffold(
|
@ -7,12 +7,9 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -27,21 +24,17 @@ import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ChangelogsSettingsScreen(
|
||||
fun ChangelogsScreen(
|
||||
onBackClick: () -> Unit,
|
||||
vm: ChangelogsViewModel = koinViewModel()
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.changelog),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBackClick = onBackClick
|
||||
)
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
}
|
||||
) { paddingValues ->
|
||||
ColumnWithScrollbar(
|
||||
modifier = Modifier
|
@ -5,13 +5,9 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
@ -19,7 +15,6 @@ import app.revanced.manager.ui.component.ColumnWithScrollbar
|
||||
import app.revanced.manager.ui.component.settings.BooleanItem
|
||||
import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||
import app.revanced.manager.ui.viewmodel.UpdatesSettingsViewModel
|
||||
import app.revanced.manager.util.toast
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@ -31,19 +26,15 @@ fun UpdatesSettingsScreen(
|
||||
onUpdateClick: () -> Unit,
|
||||
vm: UpdatesSettingsViewModel = koinViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.updates),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBackClick = onBackClick
|
||||
)
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
}
|
||||
) { paddingValues ->
|
||||
ColumnWithScrollbar(
|
||||
modifier = Modifier
|
||||
@ -53,10 +44,6 @@ fun UpdatesSettingsScreen(
|
||||
SettingsListItem(
|
||||
modifier = Modifier.clickable {
|
||||
coroutineScope.launch {
|
||||
if (!vm.isConnected) {
|
||||
context.toast(context.getString(R.string.no_network_toast))
|
||||
return@launch
|
||||
}
|
||||
if (vm.checkForUpdates()) onUpdateClick()
|
||||
}
|
||||
},
|
||||
@ -65,13 +52,7 @@ fun UpdatesSettingsScreen(
|
||||
)
|
||||
|
||||
SettingsListItem(
|
||||
modifier = Modifier.clickable {
|
||||
if (!vm.isConnected) {
|
||||
context.toast(context.getString(R.string.no_network_toast))
|
||||
return@clickable
|
||||
}
|
||||
onChangelogClick()
|
||||
},
|
||||
modifier = Modifier.clickable(onClick = onChangelogClick),
|
||||
headlineContent = stringResource(R.string.changelog),
|
||||
supportingContent = stringResource(
|
||||
R.string.changelog_description
|
||||
@ -87,7 +68,7 @@ fun UpdatesSettingsScreen(
|
||||
BooleanItem(
|
||||
preference = vm.showManagerUpdateDialogOnLaunch,
|
||||
headline = R.string.show_manager_update_dialog_on_launch,
|
||||
description = R.string.show_manager_update_dialog_on_launch_description
|
||||
description = R.string.update_checking_manager_description
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.data.platform.NetworkInfo
|
||||
import app.revanced.manager.domain.manager.PreferencesManager
|
||||
import app.revanced.manager.network.api.ReVancedAPI
|
||||
import app.revanced.manager.network.dto.ReVancedDonationLink
|
||||
import app.revanced.manager.network.dto.ReVancedSocial
|
||||
@ -25,27 +23,16 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class AboutViewModel(
|
||||
private val reVancedAPI: ReVancedAPI,
|
||||
private val network: NetworkInfo,
|
||||
prefs: PreferencesManager,
|
||||
) : ViewModel() {
|
||||
class AboutViewModel(private val reVancedAPI: ReVancedAPI) : ViewModel() {
|
||||
var socials by mutableStateOf(emptyList<ReVancedSocial>())
|
||||
private set
|
||||
private set
|
||||
var contact by mutableStateOf<String?>(null)
|
||||
private set
|
||||
private set
|
||||
var donate by mutableStateOf<String?>(null)
|
||||
private set
|
||||
val isConnected: Boolean
|
||||
get() = network.isConnected()
|
||||
|
||||
val showDeveloperSettings = prefs.showDeveloperSettings
|
||||
private set
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
if (!isConnected) {
|
||||
return@launch
|
||||
}
|
||||
withContext(Dispatchers.IO) {
|
||||
reVancedAPI.getInfo("https://api.revanced.app").getOrNull()
|
||||
}?.let {
|
||||
@ -57,8 +44,6 @@ class AboutViewModel(
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DEVELOPER_OPTIONS_TAPS = 5
|
||||
|
||||
private val socialIcons = mapOf(
|
||||
"Discord" to FontAwesomeIcons.Brands.Discord,
|
||||
"GitHub" to FontAwesomeIcons.Brands.Github,
|
||||
|
@ -24,7 +24,9 @@ import app.revanced.manager.network.api.ReVancedAPI
|
||||
import app.revanced.manager.util.PM
|
||||
import app.revanced.manager.util.toast
|
||||
import app.revanced.manager.util.uiSafe
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@ -56,13 +58,19 @@ class DashboardViewModel(
|
||||
|
||||
var updatedManagerVersion: String? by mutableStateOf(null)
|
||||
private set
|
||||
var showBatteryOptimizationsWarning by mutableStateOf(false)
|
||||
private set
|
||||
val showBatteryOptimizationsWarningFlow = flow {
|
||||
while (true) {
|
||||
// There is no callback for this, so we have to poll it.
|
||||
val result = !powerManager.isIgnoringBatteryOptimizations(app.packageName)
|
||||
emit(result)
|
||||
if (!result) return@flow
|
||||
delay(500L)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
checkForManagerUpdates()
|
||||
updateBatteryOptimizationsWarning()
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,10 +90,6 @@ class DashboardViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun updateBatteryOptimizationsWarning() {
|
||||
showBatteryOptimizationsWarning = !powerManager.isIgnoringBatteryOptimizations(app.packageName)
|
||||
}
|
||||
|
||||
fun setShowManagerUpdateDialogOnLaunch(value: Boolean) {
|
||||
viewModelScope.launch {
|
||||
prefs.showManagerUpdateDialogOnLaunch.update(value)
|
||||
|
@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.domain.manager.PreferencesManager
|
||||
import app.revanced.manager.ui.theme.Theme
|
||||
import app.revanced.manager.util.resetListItemColorsCached
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class GeneralSettingsViewModel(
|
||||
@ -12,6 +11,5 @@ class GeneralSettingsViewModel(
|
||||
) : ViewModel() {
|
||||
fun setTheme(theme: Theme) = viewModelScope.launch {
|
||||
prefs.theme.update(theme)
|
||||
resetListItemColorsCached()
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ import android.app.Application
|
||||
import android.net.Uri
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@ -35,59 +34,6 @@ import java.nio.file.StandardCopyOption
|
||||
import kotlin.io.path.deleteExisting
|
||||
import kotlin.io.path.inputStream
|
||||
|
||||
sealed class ResetDialogState(
|
||||
@StringRes val titleResId: Int,
|
||||
@StringRes val descriptionResId: Int,
|
||||
val onConfirm: () -> Unit,
|
||||
val dialogOptionName: String? = null
|
||||
) {
|
||||
class Keystore(onConfirm: () -> Unit) : ResetDialogState(
|
||||
titleResId = R.string.regenerate_keystore,
|
||||
descriptionResId = R.string.regenerate_keystore_dialog_description,
|
||||
onConfirm = onConfirm
|
||||
)
|
||||
|
||||
class PatchSelectionAll(onConfirm: () -> Unit) : ResetDialogState(
|
||||
titleResId = R.string.patch_selection_reset_all,
|
||||
descriptionResId = R.string.patch_selection_reset_all_dialog_description,
|
||||
onConfirm = onConfirm
|
||||
)
|
||||
|
||||
class PatchSelectionPackage(dialogOptionName:String, onConfirm: () -> Unit) : ResetDialogState(
|
||||
titleResId = R.string.patch_selection_reset_package,
|
||||
descriptionResId = R.string.patch_selection_reset_package_dialog_description,
|
||||
onConfirm = onConfirm,
|
||||
dialogOptionName = dialogOptionName
|
||||
)
|
||||
|
||||
class PatchSelectionBundle(dialogOptionName: String, onConfirm: () -> Unit) : ResetDialogState(
|
||||
titleResId = R.string.patch_selection_reset_bundle,
|
||||
descriptionResId = R.string.patch_selection_reset_bundle_dialog_description,
|
||||
onConfirm = onConfirm,
|
||||
dialogOptionName = dialogOptionName
|
||||
)
|
||||
|
||||
class PatchOptionsAll(onConfirm: () -> Unit) : ResetDialogState(
|
||||
titleResId = R.string.patch_options_reset_all,
|
||||
descriptionResId = R.string.patch_options_reset_all_dialog_description,
|
||||
onConfirm = onConfirm
|
||||
)
|
||||
|
||||
class PatchOptionPackage(dialogOptionName:String, onConfirm: () -> Unit) : ResetDialogState(
|
||||
titleResId = R.string.patch_options_reset_package,
|
||||
descriptionResId = R.string.patch_options_reset_package_dialog_description,
|
||||
onConfirm = onConfirm,
|
||||
dialogOptionName = dialogOptionName
|
||||
)
|
||||
|
||||
class PatchOptionBundle(dialogOptionName: String, onConfirm: () -> Unit) : ResetDialogState(
|
||||
titleResId = R.string.patch_options_reset_bundle,
|
||||
descriptionResId = R.string.patch_options_reset_bundle_dialog_description,
|
||||
onConfirm = onConfirm,
|
||||
dialogOptionName = dialogOptionName
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
class ImportExportViewModel(
|
||||
private val app: Application,
|
||||
@ -105,18 +51,15 @@ class ImportExportViewModel(
|
||||
private var keystoreImportPath by mutableStateOf<Path?>(null)
|
||||
val showCredentialsDialog by derivedStateOf { keystoreImportPath != null }
|
||||
|
||||
var resetDialogState by mutableStateOf<ResetDialogState?>(null)
|
||||
|
||||
val packagesWithOptions = optionsRepository.getPackagesWithSavedOptions()
|
||||
val packagesWithSelection = selectionRepository.getPackagesWithSavedSelection()
|
||||
|
||||
fun resetOptionsForPackage(packageName: String) = viewModelScope.launch {
|
||||
optionsRepository.resetOptionsForPackage(packageName)
|
||||
optionsRepository.clearOptionsForPackage(packageName)
|
||||
app.toast(app.getString(R.string.patch_options_reset_toast))
|
||||
}
|
||||
|
||||
fun resetOptionsForBundle(patchBundle: PatchBundleSource) = viewModelScope.launch {
|
||||
optionsRepository.resetOptionsForPatchBundle(patchBundle.uid)
|
||||
fun clearOptionsForBundle(patchBundle: PatchBundleSource) = viewModelScope.launch {
|
||||
optionsRepository.clearOptionsForPatchBundle(patchBundle.uid)
|
||||
app.toast(app.getString(R.string.patch_options_reset_toast))
|
||||
}
|
||||
|
||||
@ -126,27 +69,25 @@ class ImportExportViewModel(
|
||||
}
|
||||
|
||||
fun startKeystoreImport(content: Uri) = viewModelScope.launch {
|
||||
uiSafe(app, R.string.failed_to_import_keystore, "Failed to import keystore") {
|
||||
val path = withContext(Dispatchers.IO) {
|
||||
File.createTempFile("signing", "ks", app.cacheDir).toPath().also {
|
||||
Files.copy(
|
||||
contentResolver.openInputStream(content)!!,
|
||||
it,
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
}
|
||||
val path = withContext(Dispatchers.IO) {
|
||||
File.createTempFile("signing", "ks", app.cacheDir).toPath().also {
|
||||
Files.copy(
|
||||
contentResolver.openInputStream(content)!!,
|
||||
it,
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
}
|
||||
|
||||
aliases.forEach { alias ->
|
||||
knownPasswords.forEach { pass ->
|
||||
if (tryKeystoreImport(alias, pass, path)) {
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keystoreImportPath = path
|
||||
}
|
||||
|
||||
aliases.forEach { alias ->
|
||||
knownPasswords.forEach { pass ->
|
||||
if (tryKeystoreImport(alias, pass, path)) {
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keystoreImportPath = path
|
||||
}
|
||||
|
||||
fun cancelKeystoreImport() {
|
||||
@ -192,16 +133,6 @@ class ImportExportViewModel(
|
||||
app.toast(app.getString(R.string.reset_patch_selection_success))
|
||||
}
|
||||
|
||||
fun resetSelectionForPackage(packageName: String) = viewModelScope.launch {
|
||||
selectionRepository.resetSelectionForPackage(packageName)
|
||||
app.toast(app.getString(R.string.reset_patch_selection_success))
|
||||
}
|
||||
|
||||
fun resetSelectionForPatchBundle(patchBundle: PatchBundleSource) = viewModelScope.launch {
|
||||
selectionRepository.resetSelectionForPatchBundle(patchBundle.uid)
|
||||
app.toast(app.getString(R.string.reset_patch_selection_success))
|
||||
}
|
||||
|
||||
fun executeSelectionAction(target: Uri) = viewModelScope.launch {
|
||||
val source = selectedBundle!!
|
||||
val action = selectionAction!!
|
||||
|
@ -83,9 +83,7 @@ class PatcherViewModel(
|
||||
private val savedStateHandle: SavedStateHandle = get()
|
||||
|
||||
private var installedApp: InstalledApp? = null
|
||||
private val selectedApp = input.selectedApp
|
||||
val packageName = selectedApp.packageName
|
||||
val version = selectedApp.version
|
||||
val packageName = input.selectedApp.packageName
|
||||
|
||||
var installedPackageName by savedStateHandle.saveable(
|
||||
key = "installedPackageName",
|
||||
|
@ -3,11 +3,11 @@ package app.revanced.manager.ui.viewmodel
|
||||
import android.app.Application
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
@ -30,20 +30,15 @@ import app.revanced.manager.util.saver.persistentMapSaver
|
||||
import app.revanced.manager.util.saver.persistentSetSaver
|
||||
import app.revanced.manager.util.saver.snapshotStateMapSaver
|
||||
import app.revanced.manager.util.toast
|
||||
import kotlinx.collections.immutable.PersistentMap
|
||||
import kotlinx.collections.immutable.PersistentSet
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import kotlinx.collections.immutable.toPersistentMap
|
||||
import kotlinx.collections.immutable.toPersistentSet
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
import kotlinx.collections.immutable.*
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@OptIn(SavedStateHandleSaveableApi::class)
|
||||
class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.ViewModelParams) :
|
||||
@ -55,6 +50,8 @@ class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.Vi
|
||||
private val packageName = input.app.packageName
|
||||
val appVersion = input.app.version
|
||||
|
||||
var pendingUniversalPatchAction by mutableStateOf<(() -> Unit)?>(null)
|
||||
|
||||
var selectionWarningEnabled by mutableStateOf(true)
|
||||
private set
|
||||
var universalPatchWarningEnabled by mutableStateOf(true)
|
||||
@ -67,9 +64,7 @@ class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.Vi
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
if (prefs.disableUniversalPatchCheck.get()) {
|
||||
universalPatchWarningEnabled = false
|
||||
}
|
||||
universalPatchWarningEnabled = !prefs.disableUniversalPatchWarning.get()
|
||||
|
||||
if (prefs.disableSelectionWarning.get()) {
|
||||
selectionWarningEnabled = false
|
||||
@ -160,6 +155,17 @@ class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.Vi
|
||||
customPatchSelection = selection.put(bundle, newPatches)
|
||||
}
|
||||
|
||||
fun confirmUniversalPatchWarning() {
|
||||
universalPatchWarningEnabled = false
|
||||
|
||||
pendingUniversalPatchAction?.invoke()
|
||||
pendingUniversalPatchAction = null
|
||||
}
|
||||
|
||||
fun dismissUniversalPatchWarning() {
|
||||
pendingUniversalPatchAction = null
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
patchOptions.clear()
|
||||
customPatchSelection = null
|
||||
@ -202,8 +208,8 @@ class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.Vi
|
||||
compatibleVersions.clear()
|
||||
}
|
||||
|
||||
fun openIncompatibleDialog(incompatiblePatch: PatchInfo) {
|
||||
compatibleVersions.addAll(incompatiblePatch.compatiblePackages?.find { it.packageName == packageName }?.versions.orEmpty())
|
||||
fun openUnsupportedDialog(unsupportedPatch: PatchInfo) {
|
||||
compatibleVersions.addAll(unsupportedPatch.compatiblePackages?.find { it.packageName == packageName }?.versions.orEmpty())
|
||||
}
|
||||
|
||||
fun toggleFlag(flag: Int) {
|
||||
@ -211,7 +217,7 @@ class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.Vi
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SHOW_INCOMPATIBLE = 1 // 2^0
|
||||
const val SHOW_UNSUPPORTED = 1 // 2^0
|
||||
const val SHOW_UNIVERSAL = 2 // 2^1
|
||||
|
||||
private val optionsSaver: Saver<PersistentOptions, Options> = snapshotStateMapSaver(
|
||||
|
@ -30,13 +30,14 @@ import app.revanced.manager.domain.repository.PatchOptionsRepository
|
||||
import app.revanced.manager.domain.repository.PatchSelectionRepository
|
||||
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
|
||||
import app.revanced.manager.network.downloader.ParceledDownloaderData
|
||||
import app.revanced.manager.patcher.patch.PatchInfo
|
||||
import app.revanced.manager.plugin.downloader.GetScope
|
||||
import app.revanced.manager.plugin.downloader.PluginHostApi
|
||||
import app.revanced.manager.plugin.downloader.UserInteractionException
|
||||
import app.revanced.manager.ui.model.BundleInfo
|
||||
import app.revanced.manager.ui.model.BundleInfo.Extensions.bundleInfoFlow
|
||||
import app.revanced.manager.ui.model.BundleInfo.Extensions.requiredOptionsSet
|
||||
import app.revanced.manager.ui.model.BundleInfo.Extensions.toPatchSelection
|
||||
import app.revanced.manager.ui.model.BundleInfo.Extensions.requiredOptionsSet
|
||||
import app.revanced.manager.ui.model.SelectedApp
|
||||
import app.revanced.manager.ui.model.navigation.Patcher
|
||||
import app.revanced.manager.ui.model.navigation.SelectedApplicationInfo
|
||||
@ -131,7 +132,7 @@ class SelectedAppInfoViewModel(
|
||||
viewModelScope.launch {
|
||||
if (!persistConfiguration) return@launch // TODO: save options for patched apps.
|
||||
|
||||
options = withContext(Dispatchers.Default) {
|
||||
state.value = withContext(Dispatchers.Default) {
|
||||
val bundlePatches = bundleRepository.bundles.first()
|
||||
.mapValues { (_, bundle) -> bundle.patches.associateBy { it.name } }
|
||||
|
||||
@ -143,7 +144,7 @@ class SelectedAppInfoViewModel(
|
||||
}
|
||||
private set
|
||||
|
||||
private var selectionState: SelectionState by savedStateHandle.saveable {
|
||||
private var selectionState by savedStateHandle.saveable {
|
||||
if (input.patches != null)
|
||||
return@saveable mutableStateOf(SelectionState.Customized(input.patches))
|
||||
|
||||
@ -155,7 +156,7 @@ class SelectedAppInfoViewModel(
|
||||
|
||||
val previous = selectionRepository.getSelection(packageName)
|
||||
if (previous.values.sumOf { it.size } == 0) return@launch
|
||||
selectionState = SelectionState.Customized(previous)
|
||||
selection.value = SelectionState.Customized(previous)
|
||||
}
|
||||
|
||||
selection
|
||||
@ -274,25 +275,25 @@ class SelectedAppInfoViewModel(
|
||||
)
|
||||
|
||||
suspend fun getPatcherParams(): Patcher.ViewModelParams {
|
||||
val allowIncompatible = prefs.disablePatchVersionCompatCheck.get()
|
||||
val allowUnsupported = prefs.disablePatchVersionCompatCheck.get()
|
||||
val bundles = bundleInfoFlow.first()
|
||||
return Patcher.ViewModelParams(
|
||||
selectedApp,
|
||||
getPatches(bundles, allowIncompatible),
|
||||
getPatches(bundles, allowUnsupported),
|
||||
getOptionsFiltered(bundles)
|
||||
)
|
||||
}
|
||||
|
||||
fun getOptionsFiltered(bundles: List<BundleInfo>) = options.filtered(bundles)
|
||||
|
||||
fun getPatches(bundles: List<BundleInfo>, allowIncompatible: Boolean) =
|
||||
selectionState.patches(bundles, allowIncompatible)
|
||||
fun getPatches(bundles: List<BundleInfo>, allowUnsupported: Boolean) =
|
||||
selectionState.patches(bundles, allowUnsupported)
|
||||
|
||||
fun getCustomPatches(
|
||||
bundles: List<BundleInfo>,
|
||||
allowIncompatible: Boolean
|
||||
allowUnsupported: Boolean
|
||||
): PatchSelection? =
|
||||
(selectionState as? SelectionState.Customized)?.patches(bundles, allowIncompatible)
|
||||
(selectionState as? SelectionState.Customized)?.patches(bundles, allowUnsupported)
|
||||
|
||||
fun updateConfiguration(selection: PatchSelection?, options: Options) = viewModelScope.launch {
|
||||
val bundles = bundleInfoFlow.first()
|
||||
@ -305,7 +306,7 @@ class SelectedAppInfoViewModel(
|
||||
if (!persistConfiguration) return@launch
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
selection?.let { selectionRepository.updateSelection(packageName, it) }
|
||||
?: selectionRepository.resetSelectionForPackage(packageName)
|
||||
?: selectionRepository.clearSelection(packageName)
|
||||
|
||||
optionsRepository.saveOptions(packageName, filteredOptions)
|
||||
}
|
||||
@ -342,13 +343,13 @@ class SelectedAppInfoViewModel(
|
||||
}
|
||||
|
||||
private sealed interface SelectionState : Parcelable {
|
||||
fun patches(bundles: List<BundleInfo>, allowIncompatible: Boolean): PatchSelection
|
||||
fun patches(bundles: List<BundleInfo>, allowUnsupported: Boolean): PatchSelection
|
||||
|
||||
@Parcelize
|
||||
data class Customized(val patchSelection: PatchSelection) : SelectionState {
|
||||
override fun patches(bundles: List<BundleInfo>, allowIncompatible: Boolean) =
|
||||
override fun patches(bundles: List<BundleInfo>, allowUnsupported: Boolean) =
|
||||
bundles.toPatchSelection(
|
||||
allowIncompatible
|
||||
allowUnsupported
|
||||
) { uid, patch ->
|
||||
patchSelection[uid]?.contains(patch.name) ?: false
|
||||
}
|
||||
@ -356,8 +357,8 @@ private sealed interface SelectionState : Parcelable {
|
||||
|
||||
@Parcelize
|
||||
data object Default : SelectionState {
|
||||
override fun patches(bundles: List<BundleInfo>, allowIncompatible: Boolean) =
|
||||
bundles.toPatchSelection(allowIncompatible) { _, patch -> patch.include }
|
||||
override fun patches(bundles: List<BundleInfo>, allowUnsupported: Boolean) =
|
||||
bundles.toPatchSelection(allowUnsupported) { _, patch -> patch.include }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@ import android.content.pm.PackageInstaller
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.content.ContextCompat
|
||||
@ -43,9 +42,9 @@ class UpdateViewModel(
|
||||
private val networkInfo: NetworkInfo by inject()
|
||||
private val fs: Filesystem by inject()
|
||||
|
||||
var downloadedSize by mutableLongStateOf(0L)
|
||||
var downloadedSize by mutableStateOf(0L)
|
||||
private set
|
||||
var totalSize by mutableLongStateOf(0L)
|
||||
var totalSize by mutableStateOf(0L)
|
||||
private set
|
||||
val downloadProgress by derivedStateOf {
|
||||
if (downloadedSize == 0L || totalSize == 0L) return@derivedStateOf 0f
|
||||
@ -90,7 +89,7 @@ class UpdateViewModel(
|
||||
totalSize = contentLength
|
||||
}
|
||||
}
|
||||
installUpdate()
|
||||
state = State.CAN_INSTALL
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -109,19 +108,14 @@ class UpdateViewModel(
|
||||
val extra =
|
||||
intent.getStringExtra(InstallService.EXTRA_INSTALL_STATUS_MESSAGE)!!
|
||||
|
||||
when(pmStatus) {
|
||||
PackageInstaller.STATUS_SUCCESS -> {
|
||||
app.toast(app.getString(R.string.install_app_success))
|
||||
state = State.SUCCESS
|
||||
}
|
||||
PackageInstaller.STATUS_FAILURE_ABORTED -> {
|
||||
state = State.CAN_INSTALL
|
||||
}
|
||||
else -> {
|
||||
app.toast(app.getString(R.string.install_app_fail, extra))
|
||||
installError = extra
|
||||
state = State.FAILED
|
||||
}
|
||||
if (pmStatus == PackageInstaller.STATUS_SUCCESS) {
|
||||
app.toast(app.getString(R.string.install_app_success))
|
||||
state = State.SUCCESS
|
||||
} else {
|
||||
state = State.FAILED
|
||||
// TODO: handle install fail with a popup
|
||||
installError = extra
|
||||
app.toast(app.getString(R.string.install_app_fail, extra))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -141,10 +135,10 @@ class UpdateViewModel(
|
||||
location.delete()
|
||||
}
|
||||
|
||||
enum class State(@StringRes val title: Int) {
|
||||
enum class State(@StringRes val title: Int, val showCancel: Boolean = false) {
|
||||
CAN_DOWNLOAD(R.string.update_available),
|
||||
DOWNLOADING(R.string.downloading_manager_update),
|
||||
CAN_INSTALL(R.string.ready_to_install_update),
|
||||
DOWNLOADING(R.string.downloading_manager_update, true),
|
||||
CAN_INSTALL(R.string.ready_to_install_update, true),
|
||||
INSTALLING(R.string.installing_manager_update),
|
||||
FAILED(R.string.install_update_manager_failed),
|
||||
SUCCESS(R.string.update_completed)
|
||||
|
@ -3,7 +3,6 @@ package app.revanced.manager.ui.viewmodel
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.ViewModel
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.data.platform.NetworkInfo
|
||||
import app.revanced.manager.domain.manager.PreferencesManager
|
||||
import app.revanced.manager.network.api.ReVancedAPI
|
||||
import app.revanced.manager.util.toast
|
||||
@ -13,14 +12,10 @@ class UpdatesSettingsViewModel(
|
||||
prefs: PreferencesManager,
|
||||
private val app: Application,
|
||||
private val reVancedAPI: ReVancedAPI,
|
||||
private val network: NetworkInfo,
|
||||
) : ViewModel() {
|
||||
val managerAutoUpdates = prefs.managerAutoUpdates
|
||||
val showManagerUpdateDialogOnLaunch = prefs.showManagerUpdateDialogOnLaunch
|
||||
|
||||
val isConnected: Boolean
|
||||
get() = network.isConnected()
|
||||
|
||||
suspend fun checkForUpdates(): Boolean {
|
||||
uiSafe(app, R.string.failed_to_check_updates, "Failed to check for updates") {
|
||||
app.toast(app.getString(R.string.update_check))
|
||||
|
@ -112,7 +112,7 @@ class PM(
|
||||
app.packageManager.getPackageInfo(packageName, PackageInfoFlags.of(flags.toLong()))
|
||||
else
|
||||
app.packageManager.getPackageInfo(packageName, flags)
|
||||
} catch (_: NameNotFoundException) {
|
||||
} catch (e: NameNotFoundException) {
|
||||
null
|
||||
}
|
||||
|
||||
@ -184,8 +184,6 @@ class PM(
|
||||
get() = PackageInstaller.SessionParams(
|
||||
PackageInstaller.SessionParams.MODE_FULL_INSTALL
|
||||
).apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||
setRequestUpdateOwnership(true)
|
||||
setInstallReason(PackageManager.INSTALL_REASON_USER)
|
||||
}
|
||||
|
||||
|
@ -180,10 +180,6 @@ fun LocalDateTime.relativeTime(context: Context): String {
|
||||
|
||||
private var transparentListItemColorsCached: ListItemColors? = null
|
||||
|
||||
fun resetListItemColorsCached() {
|
||||
transparentListItemColorsCached = null
|
||||
}
|
||||
|
||||
/**
|
||||
* The default ListItem colors, but with [ListItemColors.containerColor] set to [Color.Transparent].
|
||||
*/
|
||||
|
@ -38,29 +38,24 @@
|
||||
<string name="android_11_bug_dialog_title">Android 11 bug</string>
|
||||
<string name="android_11_bug_dialog_description">The app installation permission must be granted ahead of time to avoid a bug in the Android 11 system that will negatively affect the user experience.</string>
|
||||
|
||||
<string name="no_network_toast">No internet connection available</string>
|
||||
|
||||
<string name="selected_app_meta_any_version">Any available version</string>
|
||||
<string name="app_source_dialog_title">Select source</string>
|
||||
<string name="app_source_dialog_option_auto">Auto</string>
|
||||
<string name="app_source_dialog_option_auto_description">Use all available downloader to download the app</string>
|
||||
<string name="app_source_dialog_option_auto_description">Use all installed downloaders to find a suitable APK file</string>
|
||||
<string name="app_source_dialog_option_auto_unavailable">No plugins available</string>
|
||||
<string name="app_source_dialog_option_installed_no_root">Mounted apps cannot be patched again without root access</string>
|
||||
<string name="app_source_dialog_option_installed_version_not_suggested">Version %s does not match the suggested version</string>
|
||||
|
||||
<string name="patch_item_description">Start patching the application</string>
|
||||
<string name="patch_selector_item">Select patches</string>
|
||||
<string name="patch_selector_item">Patch selection and options</string>
|
||||
<string name="patch_selector_item_description">%d patches selected</string>
|
||||
<string name="no_patches_selected">No patches selected</string>
|
||||
|
||||
<string name="network_unavailable_warning">Your device is not connected to the internet. Downloading will fail later.</string>
|
||||
<string name="network_metered_warning">You are currently on a metered connection. Data charges from your service provider may apply.</string>
|
||||
|
||||
<string name="apk_source_selector_item">Select APK source</string>
|
||||
<string name="apk_source_auto">Using all APK downloader</string>
|
||||
<string name="apk_source_downloader">Using %s</string>
|
||||
<string name="apk_source_installed">Using installed APK</string>
|
||||
<string name="apk_source_local">Using a local APK file</string>
|
||||
<string name="apk_source_selector_item">Change source</string>
|
||||
<string name="apk_source_auto">Current: All downloaders</string>
|
||||
<string name="apk_source_downloader">Current: %s</string>
|
||||
<string name="apk_source_installed">Current: Installed</string>
|
||||
<string name="apk_source_local">Current: File</string>
|
||||
|
||||
<string name="legacy_import_failed">Could not import legacy settings</string>
|
||||
|
||||
@ -92,7 +87,7 @@
|
||||
<string name="theme_description">Choose between light or dark theme</string>
|
||||
<string name="safeguards">Safeguards</string>
|
||||
<string name="patch_compat_check">Disable version compatibility check</string>
|
||||
<string name="patch_compat_check_description">The check restricts patches to compatible app versions</string>
|
||||
<string name="patch_compat_check_description">The check restricts patches to supported app versions</string>
|
||||
<string name="patch_compat_check_confirmation">Selecting incompatible patches can result in a broken app.\n\nDo you want to proceed anyways?</string>
|
||||
<string name="suggested_version_safeguard">Require suggested app version</string>
|
||||
<string name="suggested_version_safeguard_description">Enforce selection of the suggested app version</string>
|
||||
@ -100,8 +95,8 @@
|
||||
<string name="patch_selection_safeguard">Allow changing patch selection</string>
|
||||
<string name="patch_selection_safeguard_description">Do not prevent selecting or deselecting patches</string>
|
||||
<string name="patch_selection_safeguard_confirmation">Changing the selection of patches may cause unexpected issues.\n\nEnable anyways?</string>
|
||||
<string name="universal_patches_safeguard">Allow using universal patches</string>
|
||||
<string name="universal_patches_safeguard_description">Do not prevent using universal patches</string>
|
||||
<string name="universal_patches_safeguard">Disable universal patch warning</string>
|
||||
<string name="universal_patches_safeguard_description">Disables the warning that appears when you try to select universal patches</string>
|
||||
<string name="universal_patches_safeguard_confirmation">Universal patches are not as well tested as those that target specific apps.\n\nEnable anyways?</string>
|
||||
<string name="import_keystore">Import keystore</string>
|
||||
<string name="import_keystore_description">Import a custom keystore</string>
|
||||
@ -118,7 +113,6 @@
|
||||
<string name="export_keystore_success">Exported keystore</string>
|
||||
<string name="regenerate_keystore">Regenerate keystore</string>
|
||||
<string name="regenerate_keystore_description">Generate a new keystore</string>
|
||||
<string name="regenerate_keystore_dialog_description">You are about to regenerate your keystore the manager will use during the patching process.\n\nYou will not be able to update the previously installed apps from this source.</string>
|
||||
<string name="regenerate_keystore_success">The keystore has been successfully replaced</string>
|
||||
<string name="import_patch_selection">Import patch selection</string>
|
||||
<string name="import_patch_selection_description">Import patch selection from a JSON file</string>
|
||||
@ -130,26 +124,12 @@
|
||||
<string name="export_patch_selection_success">Exported patch selection</string>
|
||||
<string name="reset_patch_selection">Reset patch selection</string>
|
||||
<string name="reset_patch_selection_description">Reset the stored patch selection</string>
|
||||
<string name="reset_patch_options">Reset patch options</string>
|
||||
<string name="reset_patch_options_description">Reset the stored patch options</string>
|
||||
<string name="reset_patch_selection_success">Patch selection has been reset</string>
|
||||
<string name="patch_selection_reset_all">Reset all patch selection</string>
|
||||
<string name="patch_selection_reset_all_dialog_description">You are about to reset all the patch selections. You will need to manually select each patch again.</string>
|
||||
<string name="patch_selection_reset_all_description">Reset all the patch selections</string>
|
||||
<string name="patch_selection_reset_package">Reset patch selection for app</string>
|
||||
<string name="patch_selection_reset_package_dialog_description">You are about to reset the patch selection for the app \"%s\". You will have to manually select each patch again.</string>
|
||||
<string name="patch_selection_reset_package_description">Resets patch selection for a single app</string>
|
||||
<string name="patch_selection_reset_bundle">Resets patch selection for bundle</string>
|
||||
<string name="patch_selection_reset_bundle_dialog_description">You are about to reset the patch selection for the bundle \"%s\". You will have to manually select each patch again.</string>
|
||||
<string name="patch_selection_reset_bundle_description">Resets the patch selection for all patches in a bundle</string>
|
||||
<string name="patch_options_reset_package">Reset patch options for app</string>
|
||||
<string name="patch_options_reset_package_dialog_description">You are about to reset the patch options for the app \"%s\". You will have to reapply each option again.</string>
|
||||
<string name="patch_options_reset_package_description">Resets patch options for a single app</string>
|
||||
<string name="patch_options_reset_bundle">Resets patch options for bundle</string>
|
||||
<string name="patch_options_reset_bundle_dialog_description">You are about to reset the patch options for the bundle \"%s\". You will have to reapply each option again.</string>
|
||||
<string name="patch_options_reset_bundle_description">Resets patch options for all patches in a bundle</string>
|
||||
<string name="patch_options_reset_all">Reset patch options</string>
|
||||
<string name="patch_options_reset_all_dialog_description">You are about to reset patch options. You will have to reapply each option again.</string>
|
||||
<string name="patch_options_reset_all_description">Resets all patch options</string>
|
||||
<string name="downloader_plugins">Plugins</string>
|
||||
<string name="downloader_plugin_state_trusted">Trusted</string>
|
||||
@ -158,8 +138,6 @@
|
||||
<string name="downloader_plugin_trust_dialog_title">Trust plugin?</string>
|
||||
<string name="downloader_plugin_revoke_trust_dialog_title">Revoke trust?</string>
|
||||
<string name="downloader_plugin_trust_dialog_body">Package name: %1$s\nSignature (SHA-256): %2$s</string>
|
||||
<string name="downloader_plugin_delete_apps_title">Delete selected apps</string>
|
||||
<string name="downloader_plugin_delete_apps_description">Are you sure you want to delete the selected apps?</string>
|
||||
<string name="downloader_settings_no_apps">No downloaded apps found</string>
|
||||
|
||||
<string name="search_apps">Search apps…</string>
|
||||
@ -233,7 +211,7 @@
|
||||
<string name="no_patched_apps_found">No patched apps found</string>
|
||||
<string name="tap_on_patches">Tap on the patches to get more information about them</string>
|
||||
<string name="bundles_selected">%s selected</string>
|
||||
<string name="incompatible_patches">Incompatible patches</string>
|
||||
<string name="unsupported_patches">Incompatible patches</string>
|
||||
<string name="universal_patches">Universal patches</string>
|
||||
<string name="patch_selection_reset_toast">Patch selection and options has been reset to recommended defaults</string>
|
||||
<string name="patch_options_reset_toast">Patch options have been reset</string>
|
||||
@ -241,13 +219,14 @@
|
||||
<string name="non_suggested_version_warning_description">The version of the app you have selected does not match the suggested version.\nPlease use the suggested version: %s\n\nTo continue anyway, disable \"Require suggested app version\" in the advanced settings.</string>
|
||||
<string name="selection_warning_title">Stop using defaults?</string>
|
||||
<string name="selection_warning_description">It is recommended to use the default patch selection and options. Changing them may result in unexpected issues.\n\nYou need to turn on \"Allow changing patch selection\" in the advanced settings before toggling patches.</string>
|
||||
<string name="universal_patch_warning_description">Universal patches have a more generalized use and do not work as reliably as patches that target specific apps. You may encounter issues while using them.\n\nYou need to turn on \"Allow using universal patches\" in the advanced settings before using universal patches.</string>
|
||||
<string name="this_version">This version</string>
|
||||
<string name="universal_patch_warning_description">Universal patches have a more generalized use and do not work as reliably as patches that target specific apps. You may encounter issues while using them.\n\nThis warning can be disabled in the advanced settings.</string>
|
||||
<string name="supported">This version</string>
|
||||
<string name="universal">Any app</string>
|
||||
<string name="unsupported">Unsupported</string>
|
||||
<string name="search_patches">Search patches</string>
|
||||
<string name="app_version_not_compatible">This patch is not compatible with the selected app version (%1$s).\n\nIt is only compatible with the following version(s): %2$s.</string>
|
||||
<string name="app_not_supported">This patch is not compatible with the selected app version (%1$s).\n\nIt only supports the following version(s): %2$s.</string>
|
||||
<string name="continue_with_version">Continue with this version?</string>
|
||||
<string name="version_not_compatible">Not all patches are compatible with this version (%s). Do you want to continue anyway?</string>
|
||||
<string name="version_not_supported">Not all patches support this version (%s). Do you want to continue anyway?</string>
|
||||
<string name="download_application">Download application?</string>
|
||||
<string name="app_not_installed">The app you selected isn\'t installed. Do you want to download it?</string>
|
||||
<string name="failed_to_load_apk">Failed to load APK</string>
|
||||
@ -278,8 +257,8 @@
|
||||
<string name="downloader_invalid_version">Downloader did not fetch the correct version</string>
|
||||
<string name="downloader_app_not_found">Downloader did not find the app</string>
|
||||
<string name="downloader_error">Downloader error: %s</string>
|
||||
<string name="downloader_no_plugins_installed">No downloader installed.</string>
|
||||
<string name="downloader_no_plugins_available">There are downloader installed but none is trusted. Check your settings.</string>
|
||||
<string name="downloader_no_plugins_installed">No plugins installed.</string>
|
||||
<string name="downloader_no_plugins_available">No trusted plugins available for use. Check your settings.</string>
|
||||
<string name="already_patched">Already patched</string>
|
||||
|
||||
<string name="patch_selector_sheet_filter_title">Filter</string>
|
||||
@ -318,8 +297,6 @@
|
||||
<string name="patcher_step_write_patched">Write patched APK file</string>
|
||||
<string name="patcher_step_sign_apk">Sign patched APK file</string>
|
||||
<string name="patcher_notification_message">Patching in progress…</string>
|
||||
<string name="patcher_stop_confirm_title">Stop patcher</string>
|
||||
<string name="patcher_stop_confirm_description">Are you sure you want to stop the patching process?</string>
|
||||
<string name="execute_patches">Execute patches</string>
|
||||
<string name="executing_patch">Execute %s</string>
|
||||
<string name="failed_to_execute_patch">Failed to execute %s</string>
|
||||
@ -355,17 +332,9 @@
|
||||
<string name="bundle_view_patches">View patches</string>
|
||||
<string name="bundle_view_patches_any_version">Any version</string>
|
||||
<string name="bundle_view_patches_any_package">Any package</string>
|
||||
<string name="bundle_delete_single_dialog_title">Delete bundle</string>
|
||||
<string name="bundle_delete_multiple_dialog_title">Delete bundles</string>
|
||||
<string name="bundle_delete_single_dialog_description">Are you sure you want to delete the bundle \"%s\"?</string>
|
||||
<string name="bundle_delete_multiple_dialog_description">Are you sure you want to delete the selected bundles?</string>
|
||||
|
||||
|
||||
<string name="about_revanced_manager">About ReVanced Manager</string>
|
||||
<string name="revanced_manager_description">ReVanced Manager is an Android application that uses ReVanced Patcher to patch Android apps. It allows you to download and patch apps with custom patches, and manage the patching process.</string>
|
||||
<string name="developer_options_taps">%d taps remaining</string>
|
||||
<string name="developer_options_enabled">Developer options enabled</string>
|
||||
<string name="developer_options_already_enabled">Developer options are already enabled</string>
|
||||
<string name="revanced_manager_description">ReVanced Manager is an application designed to work with ReVanced Patcher, which allows for long-lasting patches to be created for Android apps. The patching system is designed to automatically work with new versions of apps with minimal maintenance.</string>
|
||||
<string name="update_available">An update is available</string>
|
||||
<string name="current_version">Current version: %s</string>
|
||||
<string name="new_version">New version: %s</string>
|
||||
@ -374,7 +343,7 @@
|
||||
<string name="install_update_manager_failed">Failed to install update</string>
|
||||
<string name="manual_update_check">Check for updates</string>
|
||||
<string name="manual_update_check_description">Manually check for updates</string>
|
||||
<string name="update_checking_manager">Check for updates on launch</string>
|
||||
<string name="update_checking_manager">Auto check for updates</string>
|
||||
<string name="update_checking_manager_description">Check for new versions of ReVanced Manager when the application starts</string>
|
||||
<string name="changelog">View changelogs</string>
|
||||
<string name="changelog_loading">Loading changelog</string>
|
||||
@ -431,7 +400,7 @@
|
||||
<string name="installation_aborted_description">The installation was cancelled manually. Try again?</string>
|
||||
<string name="installation_blocked_description">The installation was blocked. Review your device security settings and try again.</string>
|
||||
<string name="installation_conflict_description">The installation was prevented by an existing installation of the app. Uninstall the installed app and try again?</string>
|
||||
<string name="installation_incompatible_description">The app is incompatible with this device. Use an APK that is compatible by this device and try again.</string>
|
||||
<string name="installation_incompatible_description">The app is incompatible with this device. Use an APK that is supported by this device and try again.</string>
|
||||
<string name="installation_invalid_description">The app is invalid. Uninstall the app and try again?</string>
|
||||
<string name="installation_storage_issue_description">The app could not be installed due to insufficient storage. Free up some space and try again.</string>
|
||||
<string name="installation_timeout_description">The installation took too long. Try again?</string>
|
||||
@ -444,13 +413,10 @@
|
||||
<string name="add_patch_bundle">Add patch bundle</string>
|
||||
<string name="bundle_url">Bundle URL</string>
|
||||
<string name="auto_update">Auto update</string>
|
||||
<string name="incompatible_patches_dialog">These patches are not compatible with the selected app version (%1$s).\n\nClick on the patches to see more details.</string>
|
||||
<string name="incompatible_patch">Incompatible patch</string>
|
||||
<string name="unsupported_patches_dialog">These patches are not compatible with the selected app version (%1$s).\n\nClick on the patches to see more details.</string>
|
||||
<string name="unsupported_patch">Unsupported patch</string>
|
||||
<string name="any_version">Any</string>
|
||||
<string name="never_show_again">Never show again</string>
|
||||
<string name="show_manager_update_dialog_on_launch">Show update message on launch</string>
|
||||
<string name="show_manager_update_dialog_on_launch_description">Shows a popup notification whenever there is a new update available on launch.</string>
|
||||
<string name="failed_to_import_keystore">Failed to import keystore</string>
|
||||
<string name="export">Export</string>
|
||||
<string name="confirm">Confirm</string>
|
||||
</resources>
|
||||
|
@ -7,4 +7,10 @@ plugins {
|
||||
alias(libs.plugins.kotlin.parcelize) apply false
|
||||
alias(libs.plugins.about.libraries) apply false
|
||||
alias(libs.plugins.compose.compiler) apply false
|
||||
alias(libs.plugins.binary.compatibility.validator)
|
||||
}
|
||||
|
||||
apiValidation {
|
||||
ignoredProjects.addAll(listOf("app", "example-downloader-plugin"))
|
||||
nonPublicMarkers += "app.revanced.manager.plugin.downloader.PluginHostApi"
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
# 💼 Prerequisites
|
||||
|
||||
In order to use ReVanced Manager, certain requirements must be met.
|
||||
To use ReVanced Manager, certain requirements have to be met.
|
||||
|
||||
## 🤝 Requirements
|
||||
|
||||
- An Android device running Android 8 or higher
|
||||
- Android device running Android 8.0 or higher
|
||||
- **Optionally** Rooted Android device with latest version of [KernelSU](https://github.com/tiann/KernelSU) or [Magisk](https://github.com/topjohnwu/Magisk)
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
|
@ -1,14 +1,26 @@
|
||||
# ⬇️ Installation
|
||||
|
||||
In order to use ReVanced on your Android device, ReVanced Manager must be installed.
|
||||
To use ReVanced on your Android device, ReVanced Manager have to be install,
|
||||
refer to the [Prerequisites](0_prerequisites.md) if haven't already.
|
||||
|
||||
## ✅ Installation steps
|
||||
## 🪜 Installation steps
|
||||
|
||||
1. Download the latest version of ReVanced Manager at [revanced.app/download](https://revanced.app/download) or from [GitHub releases](https://github.com/ReVanced/revanced-manager/releases/latest)
|
||||
1. Get the latest version of ReVanced Manager from the [ReVanced site][Official ReVanced Download].
|
||||
2. Install ReVanced Manager
|
||||
|
||||
### ✒️ Verifying authenticity of ReVanced Manager
|
||||
|
||||
> [!NOTE]
|
||||
> It's always advisable that you download from trusted sources like the [ReVanced site][Official ReVanced Download]
|
||||
> or GitHub releases as it's the safest way to download ReVanced without potential risk of malware.
|
||||
|
||||
To verify if your ReVanced Manager is provided without sign of tampering,
|
||||
we have signed the APK with hashes corresponding to `b6362c6ea7888efd15c0800f480786ad0f5b133b4f84e12d46afba5f9eac1223` *unless otherwise stated*.
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will guide you through using ReVanced Manager.
|
||||
|
||||
Continue: [🛠️ Usage](2_usage.md)
|
||||
|
||||
[Official ReVanced Download]: https://revanced.app/download
|
||||
|
@ -1,26 +1,35 @@
|
||||
# 💉 Patching apps
|
||||
# 🧩 Patching apps
|
||||
|
||||
Learn how to use ReVanced Manager to patch apps.
|
||||
The following pages will guide you through using ReVanced Manager to patch apps.
|
||||
|
||||
## ✅ Steps to patch apps
|
||||
|
||||
1. Navigate to the `Apps` tab at the top navigation bar
|
||||
2. Tap on the `+` button in the bottom right corner
|
||||
3. Select an app to patch.[^1]
|
||||
4. Optionally change the selection of patches[^2]
|
||||
5. Optionally select a specific downloader to download the app[^3]
|
||||
6. Tap on the `Patch` button to begin the patching process
|
||||
7. Tap on the `Install` button to install the patched app[^4]
|
||||
1. Navigate to the Apps tab from the top navigation bar
|
||||
2. Tap the + button in the bottom right corner
|
||||
3. Choose an app to patch[^1]
|
||||
4. Tap on the version of the app you want to patch[^2]
|
||||
5. Tap the 🪄 **Patch** button
|
||||
6. Tap on the 📲 **Install** button
|
||||
> [!Note]
|
||||
> If the **Install** Button isn't there, it could be that something went wrong. Refer to [3. ❔ Troubleshooting](3_troubleshooting.md)
|
||||
> [!Tip]
|
||||
> If you are rooted, you can mount the patched app on top of the original app.[^3]
|
||||
> Optionally, you may export the patched app to storage using the options in the bottom left corner.
|
||||
|
||||
[^1]: Here you can see all the apps that are supported by ReVanced.
|
||||
You can also add custom apps by tapping on the `+` button in the top right corner.
|
||||
[^2]: It is recommended to use the default set of patches by tapping on the `Reset` button in the bottom right corner.
|
||||
[^3]: By default, all available downloader will be used to download the app.
|
||||
If you want to use a specific downloader, you can change it here.
|
||||
[^4]: You can export the patched app or the patch logs in the bottom left corner.
|
||||
[^1]: You need to select an APK from storage, in which case you have to source the APK file yourself. ReVanced does not provide any APK files.
|
||||
[^2]: It is suggested to use the version with the most patches to get the most out of ReVanced.
|
||||
[^3]: Mounting the patched app on top of the original app will only work if the installed app version matches the version of the app selected in step 4. above.
|
||||
|
||||
### Change patch selection and options
|
||||
|
||||
Before you can change the selection of patches, follow the step below to allow changing the selection of patches.
|
||||
|
||||
1. Tap ⚙️ Settings button in the top right corner
|
||||
2. Go to Advanced Settings
|
||||
3. Toggle "Allow changing patch selection" on
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will explain how to view, open, re-patch or uninstall apps.
|
||||
The next page will bring you back to the usage page.
|
||||
|
||||
Continue: [🧰 Managing apps](2_2_managing_apps.md)
|
||||
Continue: [🛠️ Usage](2_usage.md)
|
||||
|
18
docs/2_2_managing.md
Normal file
18
docs/2_2_managing.md
Normal file
@ -0,0 +1,18 @@
|
||||
# 🧰 Managing patched apps
|
||||
|
||||
After patching an app, you may want to manage it. This page will guide you through managing patched apps.
|
||||
|
||||
## ✅ Steps to manage patched apps
|
||||
|
||||
1. Navigate to the Apps tab from the top navigation bar
|
||||
2. Select the app you want to manage
|
||||
|
||||
Inside you will be able to open, and uninstall the app within the manager,
|
||||
additionally you will be able to repatch the app by patching the app again, see [🧩 Patching apps](2_1_patching.md),
|
||||
and view all patches applied to the patched app.
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will bring you back to the usage page.
|
||||
|
||||
Continue: [🛠️ Usage](2_usage.md)
|
@ -1,15 +0,0 @@
|
||||
# 🧰 Managing apps
|
||||
|
||||
Learn how to view, open, re-patch or uninstall apps.
|
||||
|
||||
## ✅ Steps to manage patched apps
|
||||
|
||||
1. Navigate to the `Apps` tab at the top navigation bar
|
||||
2. Select the app you want to view, open, re-patch or uninstall
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will explain how to add new, manage existing or remove patches.
|
||||
|
||||
Continue: [🧩 Managing patches](2_3_managing_patches.md)
|
||||
|
@ -1,22 +0,0 @@
|
||||
# 🧩 Managing patches
|
||||
|
||||
Learn how to add new, manage existing or remove patches.
|
||||
|
||||
## ✅ Steps to manage patches
|
||||
|
||||
1. Navigate to the `Patches` tab at the top navigation bar
|
||||
2. Tap on the `+` button in the bottom right corner
|
||||
3. Chose one of the options to add new patches[^1]:
|
||||
- **Enter URL**: Add patches from a remote location
|
||||
- **Select from storage**: Add patches from your local storage
|
||||
4. Select one of the patches from the list to manage or remove it
|
||||
|
||||
[^1]: The selected file must be a JSON using the [ReVanced API](https://github.com/revanced/revanced-api) format
|
||||
(Example: `https://api.revanced.app/v4/patches`).
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will explain how to update ReVanced Manager.
|
||||
|
||||
Continue: [🔄 Updating ReVanced Manager](2_4_updating.md)
|
||||
|
31
docs/2_3_updating.md
Normal file
31
docs/2_3_updating.md
Normal file
@ -0,0 +1,31 @@
|
||||
# 🔄 Updating ReVanced Manager
|
||||
|
||||
In order to keep up with the latest features and bug fixes, it is recommended to keep ReVanced Manager up to date.
|
||||
|
||||
## ✅ Updating steps
|
||||
|
||||
1. Tap the ↻ Update button in the top right corner right next to the ⚙️ Settings icon
|
||||
2. Tap the **Update** button in the bottom right corner
|
||||
3. Wait for the update to be downloaded
|
||||
4. Tap the **Install** button to install[^1]
|
||||
> [!Note]
|
||||
> If you see a prompt regarding your device is unable to install application for security reason
|
||||
> you may need to allow ReVanced Manager to install APK file on your device.
|
||||
> 1. Tap on Settings
|
||||
> 2. Toggle "Allow from this source" on
|
||||
5. Wait for installation prompt to show then tap **Install** again
|
||||
6. Open ReVanced Manager again to see the new version
|
||||
|
||||
## 🔎 Check for update
|
||||
|
||||
Usually ReVanced Manager always check update on first startup, so it's not need to manually check for update yourself.
|
||||
To trigger the check for update function follow the step bellow:
|
||||
|
||||
1. Go to ⚙️ Settings menu and ↻ Update section
|
||||
2. Tap on "Check for updates"
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will bring you back to the usage page.
|
||||
|
||||
Continue: [🛠️ Usage](2_usage.md)
|
53
docs/2_4_settings.md
Normal file
53
docs/2_4_settings.md
Normal file
@ -0,0 +1,53 @@
|
||||
# ⚙️ Configuring ReVanced Manager
|
||||
|
||||
ReVanced Manager has settings that can be configured to your liking.
|
||||
|
||||
## ⭐ Essential settings
|
||||
|
||||
- ### 🔗 API URL
|
||||
|
||||
Specify the URL of the API to use. This is used to fetch ReVanced Patches and update ReVanced Manager.
|
||||
|
||||
Location: `⚙️ Settings -> Advanced -> API URL`
|
||||
|
||||
- ### 🧬 Sources
|
||||
|
||||
Override the API and change the source of ReVanced Patches.
|
||||
|
||||
Location: `Patch bundles -> + Button`
|
||||
|
||||
- ### 🧪 Experimental patches
|
||||
|
||||
Lift app version constraints from ReVanced Patches. This allows you to patch any version of an app, even if the patch is not explicitly compatible with it.
|
||||
|
||||
Location: `⚙️ Settings -> Advanced -> Safeguards`
|
||||
|
||||
- ### 🧑🔬 Experimental universal support
|
||||
|
||||
This will show or hide ReVanced Patches, which are not meant for any app in particular but rather for all apps but may not work on all apps.
|
||||
|
||||
Location: `⚙️ Settings -> Advanced -> Safeguards`
|
||||
|
||||
- ### 🔑 Export, import or delete keystore
|
||||
|
||||
Manage the keystore used to sign patched apps.
|
||||
|
||||
Location: `⚙️ Settings -> Import & export -> Signing`
|
||||
|
||||
- ### 📄 Export, import or reset patch selection
|
||||
|
||||
Manage the ReVanced Patches selection. This is useful if you want to share your patch selection with others or reset it to the default selection.
|
||||
|
||||
Location: `⚙️ Settings -> Import & export -> Patches`
|
||||
|
||||
- ### ℹ️ About
|
||||
|
||||
View information about your device and ReVanced Manager. This includes the version of ReVanced Manager and supported architectures of your device.
|
||||
|
||||
Location: `⚙️ Settings -> About`
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will bring you back to the usage page.
|
||||
|
||||
Continue: [🛠️ Usage](2_usage.md)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user