Compare commits

..

406 Commits

Author SHA1 Message Date
a5d09b2bf5 Consolidate optional root device requirements in docs 2025-02-19 23:27:05 +07:00
8731e7267a Fix grammar in GitHub Registry section 2025-02-19 23:24:01 +07:00
c2a942b46c Update docs/2_4_settings.md
Co-authored-by: Ax333l <main@axelen.xyz>
2025-01-29 17:50:04 +07:00
b9401b5f89 Update docs/3_troubleshooting.md
Co-authored-by: Ax333l <main@axelen.xyz>
2025-01-29 17:49:08 +07:00
0eee869b99 Update docs/2_4_settings.md
Co-authored-by: Ax333l <main@axelen.xyz>
2025-01-29 17:48:43 +07:00
15cfeb6ba7 Update docs/0_prerequisites.md 2025-01-29 17:47:48 +07:00
d47819cae7 Update docs/developer/3_verifying.md
Co-authored-by: Ax333l <main@axelen.xyz>
2025-01-29 00:09:26 +07:00
351e00c380 format 2025-01-27 17:31:42 +07:00
046179db1b Remove copy-pasta 2025-01-27 17:30:13 +07:00
5993dc34cb Add manage app 2025-01-27 17:29:56 +07:00
91f6672711 Add linkj 2025-01-27 16:47:56 +07:00
d3748cf305 Easily distinguishness 2025-01-27 16:47:36 +07:00
db304a70c8 Merge suggestions from Copilot 2025-01-27 16:41:18 +07:00
8f9ea54790 Update ref 2025-01-27 15:43:16 +07:00
a53a0bd2d4 Comfortable docs edit viewing 2025-01-27 15:36:30 +07:00
1c6a9dbbbd Developer - Transparency 2025-01-27 15:34:59 +07:00
802330c062 Verification step 2025-01-27 15:34:47 +07:00
d1ef14a25a format 2025-01-27 15:34:37 +07:00
617481f26f End of section 2025-01-27 15:34:25 +07:00
6598db5078 Update Manager guide!! 2025-01-27 15:33:36 +07:00
5b790a86e2 Comfortable docs viewing 2025-01-19 19:53:32 +07:00
bec0d823dc grammartical corerct 2025-01-19 19:53:20 +07:00
2616576b3d grammartical corerct 2025-01-19 19:53:12 +07:00
44fc4551aa Add settings location 2025-01-19 19:49:00 +07:00
6dfb2a2de0 Try or report 2025-01-19 19:42:02 +07:00
03c91687ae Split to section 2025-01-19 19:41:36 +07:00
91e06cbce6 OOM error troubleshooting 2025-01-19 19:41:21 +07:00
b0624a3544 OOM error troubleshooting 2025-01-19 19:40:06 +07:00
04a2a78914 Change wording to remove redundancy 2025-01-19 19:24:38 +07:00
4364f2e4c2 Standardised download site 2025-01-19 19:24:13 +07:00
177b716fd0 🔮 Merge repository updated to latest snapshot!
Script Execution UTC Time: null

Signed-off-by: validcube <pun.butrach@gmail.com>
2025-01-19 18:45:06 +07:00
818dc09aa4 build: Bump AGP to 8.8.0
build: Bump AGP to 8.8.0
2025-01-19 17:47:56 +07:00
a762969966 docs: Merge documentation from Flutter to Compose 2025-01-19 17:08:07 +07:00
74338931b8 feat: Redesign the patches screen (#2381) 2025-01-18 17:03:38 +01:00
0ab424bfdb fix: available updates dialog list item color 2025-01-05 00:12:00 +01:00
fff1a41fee refactor: use EventEffect for legacy import 2025-01-05 00:08:29 +01:00
7644a74648 feat: add required options screen (#2378) 2025-01-03 22:26:40 +01:00
9db3bd5b3f feat: Add confirm dialogs when toggling dangerous settings (#2072)
Co-authored-by: Ax333l <main@axelen.xyz>
2024-12-23 16:35:27 +01:00
b81bd17fbc chore: add .kotlin to gitignore 2024-12-23 14:40:28 +01:00
cf3866f892 fix: remove battery optimization notification if user grants the permission 2024-12-23 14:39:57 +01:00
5d3a81f4b9 feat: switch to androidx.navigation (#2362) 2024-12-23 14:31:31 +01:00
f9831d4da5 refactor: remove unnecessary function 2024-12-23 13:13:08 +01:00
8a20d8cf9b fix: contributors screen repository name 2024-12-22 22:32:15 +01:00
49f75f9edd fix: process death resilience and account for android 11 bug (#2355) 2024-12-22 22:28:54 +01:00
9916e4da4d fix: Screen turns off while patching due to wrong WakeLock (#2147) 2024-12-21 10:25:59 +01:00
2ec1c0238d feat: Add downloader plugin system (#2041) 2024-12-19 21:41:04 +01:00
9dc716b1c8 feat: switch to revanced api v4 2024-12-12 17:52:21 +01:00
31fb8b1404 chore: Nitpick on misspelling of comment 2024-12-01 01:13:03 +07:00
d987845bba docs: Actually link to documentation 2024-11-24 22:11:46 +07:00
50c46dc20d docs(readme): Merge building to contributing section 2024-11-24 22:09:45 +07:00
5125084279 docs: Merge valid suggestions from Copilot review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2024-11-24 19:38:53 +07:00
0685479d53 feat: Make patch bundles list scrollable (#2322) 2024-11-22 16:57:23 +03:00
20c13ee71c chore: update dependencies
🦀 integrations are gone! 🦀
2024-11-13 22:11:36 +01:00
cf322147d5 fix: only perform haptics on events 2024-11-12 21:17:02 +01:00
b4c37e6ddc feat: Add haptic feedback (#1457)
Co-authored-by: Ushie <ushiekane@gmail.com>
2024-11-06 23:48:40 +03:00
911201ad9f Signed off to switch branch
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-10-18 01:53:51 +07:00
8442bf2e14 🔮 Merge repository updated to latest snapshot!
Script Execution UTC Time: null

Signed-off-by: validcube <pun.butrach@gmail.com>
2024-10-01 17:08:55 +07:00
697386c36c fix: Match "Installation incompatible" dialog message with Flutter Manager (#2231) 2024-09-30 18:51:22 +02:00
f6f72387b9 feat(patcher): Improve installation (#2185) 2024-09-19 21:17:38 +02:00
d201bdc422 feat: Add installer status dialog (#1473)
Co-authored-by: Benjamin Halko <benjaminhalko@hotmail.com>
Co-authored-by: Benjamin <73490201+BenjaminHalko@users.noreply.github.com>
Co-authored-by: Ushie <ushiekane@gmail.com>
Co-authored-by: Ax333l <main@axelen.xyz>
2024-08-29 18:28:13 +02:00
2055400565 feat: View bundle patches (#2065) 2024-08-25 01:23:13 +03:00
05bf940cdf nitpicking on style 2024-08-18 20:43:29 +07:00
3bbad156c4 part: 10 (1/?) 2024-08-18 20:41:45 +07:00
db4ce6bb77 docs: Use correct asset directory 2024-08-18 20:01:45 +07:00
a290369d8d docs: Pull change from ReVanced Manager Flutter 2024-08-18 20:00:44 +07:00
adf5f9f6e8 docs: Pull change from ReVanced Branding 2024-08-18 20:00:17 +07:00
ace6701aaf 🔮 Merge repository updated to latest snapshot!
Script Execution UTC Time: null

Signed-off-by: validcube <pun.butrach@gmail.com>
2024-08-18 18:56:57 +07:00
10e7e4b39f feat: Open the app-specific manage all files permission dialog (#2148) 2024-08-18 17:54:07 +07:00
edb4e421e2 feat: Improve patch bundle screen (#2070) 2024-08-17 01:58:43 +03:00
747017a5f9 feat: Improve Settings order (#2060)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: Ax333l <main@axelen.xyz>
2024-08-15 14:59:06 +02:00
e992a99783 fix: remove the unique constraint for patch bundle names 2024-08-12 22:42:55 +02:00
e869db0555 fix: Move temporary files outside of the cache directory (#2122) 2024-08-12 18:23:31 +02:00
936a9efd0b refactor: Add parameters for custom rotation values in ArrowButton 2024-08-08 02:49:07 +03:00
edc11b6e1d ci: Actually enable caching of Gradle 2024-08-07 22:51:04 +07:00
32f2710763 fix: Turn off filters by default (#2079) 2024-08-06 03:46:50 +03:00
4257c32bf5 fix: ExtendedFloatingActionButton not accessible by screen readers (#2080) 2024-08-06 03:46:41 +03:00
c18901c35b feat: Improve unsupported patch warnings (#2066)
Closes #2052
2024-07-29 18:21:10 +03:00
f126fe9fa8 fix: show available and selected patches in patch selector screen 2024-07-29 15:00:24 +02:00
4cbd480e84 feat: Add reset button to custom API (#2076)
Closes #2051
2024-07-29 13:14:13 +03:00
afc72ffd85 feat: Show manager update dialog (#2069)
Closes #1963, closes #1958
2024-07-29 00:09:56 +03:00
cdc69ea8ff fix: Support patching on ARMv7 by updating AAPT2 (#2084) 2024-07-28 22:43:10 +02:00
52a89b1638 feat: Improve update setting tile titles
Closes #1968
2024-07-24 04:48:53 +03:00
641d518b6e build: Enable Gradle Configuration Cache (#2059) 2024-07-22 15:41:50 +07:00
2eca45d397 fix: always use default patch selection if customization is disabled 2024-07-20 16:17:41 +02:00
de6fddf405 fix: android icon not loading in app selector 2024-07-20 12:24:21 +02:00
45f32040f8 feat: Improve custom API URL dialog (#2033)
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-07-18 04:22:37 +03:00
42ef2ca99d fix: Broken header padding in AlertDialogExtended when using an Icon 2024-07-18 00:56:18 +03:00
563f2f37f7 fix: Remove unnecessary screen padding
Closes #2062
2024-07-18 00:55:15 +03:00
881b13740a feat: Improve overall UI and fix inconsistencies (#2028) 2024-07-11 01:09:45 +03:00
d39804f7ed Merge branch 'compose-dev' into fix/minor-issues 2024-07-11 01:08:34 +03:00
b8a85c4891 refactor: Use lastIndex
Co-authored-by: Ax333l <main@axelen.xyz>
2024-07-11 01:07:57 +03:00
37e9630b9c fix: Inconsistent padding for battery optimisation warning
The problem came after moving the card to DashboardScreen, this is because the card specified padding modifier but others does not. This commit remove the modifier completely.
2024-07-07 17:58:51 +07:00
40492b67d1 refactor: Use TextButton instead of FilledButton for consistency 2024-07-07 17:54:04 +07:00
f655a6e03a ci: Bump dependencies to latest (#2039) 2024-07-07 15:42:56 +07:00
36f864efbb chore: update dependencies 2024-07-06 22:47:27 +02:00
a995f43b7b fix: move battery warning to dashboard 2024-07-06 18:12:44 +02:00
1ee1330e47 feat: improve the safeguards (#2038) 2024-07-06 15:43:37 +00:00
3f4a234915 fix: run props flow on correct dispatcher (#2035) 2024-07-05 18:41:29 +00:00
032ca39cf6 feat: Automatic language detection (#2032) 2024-07-05 18:34:13 +00:00
6f9a984541 fix: improve bundle page strings 2024-07-05 20:31:49 +02:00
e6e043f168 fix: cleanup advanced settings screen 2024-07-05 19:41:27 +02:00
7c7fb7b343 feat: dont ask for root on launch 2024-07-05 19:06:10 +02:00
b26fe30861 Merge branch 'compose-dev' of https://github.com/ReVanced/revanced-manager into fix/minor-issues 2024-07-04 23:34:22 +03:00
f99cdfe926 feat: improve UX for failed or missing bundles 2024-07-04 19:36:31 +02:00
ec0a077539 feat: implement more patch option types (#2015) 2024-07-04 17:34:55 +00:00
a22158d070 fix: crash when removing used bundles 2024-07-04 13:58:55 +02:00
48fe3a707e fix: import export screen UX 2024-07-03 14:46:00 +02:00
8d3d500b7b feat: add ability to share debug logs 2024-07-03 13:54:37 +02:00
d8248cc915 fix: import bundles on another thread 2024-07-03 12:27:39 +02:00
397a1f8f9c Merge branch 'compose-dev' of https://github.com/ReVanced/revanced-manager into fix/minor-issues 2024-07-03 03:55:12 +03:00
a0f187354f feat: Remove tag from changelog 2024-07-03 03:48:19 +03:00
1bf004ddee feat: Progressive AlertDialog for adding bundles
Closes #1992
2024-07-03 03:48:04 +03:00
495100dea9 fix: Use the correct icon in API URL dialog
Closes #1972
2024-07-03 02:49:16 +03:00
e3bd8a8b22 feat: Add sensitivity to isScrollingUp 2024-07-03 02:46:01 +03:00
d987ac6c7a feat: Add isScrollingUp support for ScrollState 2024-07-03 02:45:45 +03:00
9883dcd0a7 fix: Use FAB instead of ListItem to patch in App Overview
Closes #1995
2024-07-03 02:44:10 +03:00
d63133189d feat: Improve device information in debugging section
Closes #1977
2024-07-03 01:34:01 +03:00
39fbb87010 fix: Change the title in the Update screen from "Updates" to "Update"
Closes #1960
2024-07-03 01:28:33 +03:00
f197760efd feat: Change "Update" to "Show" in Update Available notification
Closes #1959
2024-07-03 01:27:13 +03:00
fa13d4a538 feat: Highlight links in Markdown
Closes #1962
2024-07-03 01:26:05 +03:00
71b5f539c1 feat: Improve initial update popup wording
Closes #1956
2024-07-03 01:25:25 +03:00
603a827e45 chore: Remove unused ARMv7 AAPT binary
Closes #1954
2024-07-03 01:24:55 +03:00
47bdc69a43 refactor: Improve naming consistency in libs.version.toml
Closes #1953
2024-07-03 01:24:10 +03:00
1ce56af3b1 feat: get bundle information from jar manifest (#2027) 2024-07-02 19:50:28 +00:00
a12c5c583b fix: add bounds checks in patch selector 2024-07-02 15:43:06 +02:00
5455cf20ab feat: rename main bundle to Default 2024-06-25 23:54:59 +02:00
75fcdb139c fix: use proper update icon 2024-06-25 23:49:28 +02:00
269fa79136 feat: improve patcher screen labels 2024-06-23 21:01:20 +02:00
16fea59605 fix: scrolling in patch selector 2024-06-23 20:17:42 +02:00
c7d183ee8d feat: rename debug build to ReVanced Manager (dev) 2024-06-23 17:27:40 +02:00
413083c58d fix(downloader): versions not loading correctly 2024-05-29 23:25:15 +02:00
1f671aba33 fix: automatically focus search views 2024-05-27 22:49:08 +02:00
6bfd9098d6 feat: move update to notification card (#1917) 2024-05-27 21:50:02 +02:00
4e7d96e91d feat: revert to blue theme colors 2024-05-22 20:05:27 +02:00
ac0a036035 refactor: fix more warnings 2024-04-05 20:04:27 +02:00
2a1445d61f build(deps): update ksp 2024-04-05 19:22:54 +02:00
0df39a1136 refactor: replace deprecated functions 2024-04-05 19:09:39 +02:00
634d793839 fix: crash caused by compose inlining bug
This is a bug in jetpack compose. Inlining this function wasn't very
important anyways so it's best to just stop inlining it to avoid the
crash.
2024-04-04 17:50:31 +02:00
afd6c5d6b7 build(deps): bump aboutLibrariesGradlePlugin from 11.1.0 to 11.1.1 (#1813)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-04 15:28:19 +00:00
ab0682cc5c build(deps): bump androidx.compose.ui:ui-tooling from 1.6.3 to 1.6.4 (#1814)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-04 15:27:59 +00:00
60ca901ac7 build(deps): bump androidx.compose:compose-bom from 2024.02.02 to 2024.03.00 (#1812)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-04 15:27:45 +00:00
91ca5be57a build(deps): bump libsu from 5.2.1 to 5.2.2 (#1810)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-04 15:27:24 +00:00
ac47a7eaa4 build(deps): bump plugin.serialization from 1.9.22 to 1.9.23 (#1811)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-04 15:26:17 +00:00
ce134224a8 fix: correctly patch apk files 2024-04-03 17:07:29 +02:00
ca49d3a465 feat: add external process runtime (#1799) 2024-03-29 15:00:52 +00:00
5d7f9d1387 feat: check if the version being used is the recommended version (#1675) 2024-03-15 17:57:53 +00:00
8d5d86fea8 feat: add social links (#1294)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: Ax333l <main@axelen.xyz>
2024-03-12 17:09:39 -07:00
6709505e9e chore: Upgrade dependencies (#1761)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-10 11:05:47 -07:00
088de60c91 chore: upgrade dependencies (#1670) 2024-03-05 06:35:13 -08:00
ef041869e5 fix(VersionSelector): use correct LazyColumn item key 2024-03-05 14:34:45 +01:00
16cdc7aca4 refactor: Disable update for dev build (#1673)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2024-02-17 18:17:27 +00:00
39536c0e18 feat: Collapse ExtendedFAB on scroll (#1630) 2024-02-03 19:12:12 +00:00
607d8b67c9 feat: add toast feedback to the bundle update button 2024-01-21 14:34:18 +01:00
0b9889ea44 fix: patch options reset button being broken 2024-01-21 14:24:39 +01:00
4acef776b2 refactor: use consistent wording for the version compat check 2024-01-20 17:48:08 +01:00
e186dfdaa9 docs(security): init (#1612)
Co-authored-by: Ax333l <main@axelen.xyz>
2024-01-20 16:35:32 +00:00
c0f3d02e6f refactor: fix terminology and wording related to patches (#1623) 2024-01-18 19:50:24 +00:00
36c8f59d6f feat: Scrollbars (#1479) 2024-01-08 01:28:16 +03:00
f38b31a591 fix: progress bar not updating 2024-01-07 13:29:09 +01:00
3232bb10e6 feat: improve patcher UI (#1494) 2024-01-06 16:51:11 +01:00
b7cb6b94f5 feat: updater UI and code improvements (#1597) 2024-01-05 22:05:02 +00:00
aa6e612fba feat: Select bundle type before adding bundle (#1490) 2024-01-04 21:54:44 +00:00
d9d7b98409 feat: Purple default theme (#1601) 2023-12-27 16:35:23 -08:00
4fdd6bbe5f chore: upgrade AGP to 8.2.0 + migrate deprecated functions (#1574) 2023-12-26 20:37:42 +00:00
439f6250f3 chore(deps): update jetpack compose 2023-12-25 23:28:42 +01:00
d55abf5dda feat(app-selector): show patchable installed apps first (#1496) 2023-12-14 11:50:14 +01:00
94de170490 docs: part 9.5
merge changes from `dev` (flutter)
2023-12-09 21:40:43 +07:00
3c56db4121 Merge branch 'compose-dev' into docs/readme 2023-12-09 21:35:37 +07:00
a8b9d9316f docs: update revanced url 2023-12-02 17:18:21 +07:00
55c7800f39 build: bump Gradle to v8.5
build: update Gradle wrapper
2023-12-02 17:17:03 +07:00
d9eb1c42bc refactor: slight formatting of build.gradle.kts 2023-12-02 17:00:52 +07:00
8cd617e32d chore(template): update label name for feature 2023-12-02 16:58:47 +07:00
a17a05995a ci: caching with gradle-build-action
Allow for automatic capture of buildscan in job summary, and smarter
caching than the one provided by `setup-java`.
2023-12-02 16:55:04 +07:00
de4e616dcc chore(deps): bump revanced patcher and library 2023-12-01 13:21:20 +01:00
12b00e5c8d fix: specify multithreadingDexFileWriter in PatcherOptions (#1402)
Co-authored-by: Ax333l <main@axelen.xyz>
2023-11-29 21:33:00 +00:00
9cab91959e fix: load patch bundles earlier 2023-11-29 22:11:57 +01:00
bd9778a3d1 feat(Update Screen): changelogs & handle states (#1464)
Co-authored-by: Ax333l <main@axelen.xyz>
2023-11-19 23:28:28 +03:00
62a5fce66c feat(Contributors Screen): implement design from Figma (#1465)
Co-authored-by: Robert <72943079+CnC-Robert@users.noreply.github.com>
Co-authored-by: Ax333l <main@axelen.xyz>
2023-11-19 23:27:13 +03:00
2bd84636d6 fix: parcel error for nullable types 2023-11-15 21:32:54 +01:00
62bb0d34ce docs(accessibility): part 9.4
Heading 1 -> 2 -> 3 not 1 -> 3
2023-11-09 00:01:02 +07:00
1521d21e4e docs: part 9.3 2023-11-08 23:46:31 +07:00
35996b6c69 docs: part 9.2
Co-authored-by: Palm <palmpasuthorn@gmail.com>
2023-11-08 22:41:12 +07:00
d32a213457 docs: part 9.1 2023-11-08 22:31:19 +07:00
2a3395ce15 docs: part 9
This commit removes the developer version, preventing this PR from being locked up with additional developer experiences.
2023-11-08 22:25:33 +07:00
944b57c336 docs: part 8.7
Co-authored-by: KobeW50 <84587632+KobeW50@users.noreply.github.com>
2023-11-08 22:19:54 +07:00
ac561e7aca feat: Use correct casing in module description 2023-11-07 23:59:33 +01:00
59daceef99 chore: bump patcher 2023-11-06 19:33:06 +01:00
1dc41badd9 feat: check for updates on startup (#1462) 2023-11-05 13:19:55 +01:00
1a83315424 feat(Changelogs): overall improvement (#1429) 2023-11-03 18:03:14 +00:00
3c5776214f feat(Installer): use BottomAppBar (#1428) 2023-11-03 09:15:17 +00:00
5fff0a2923 fix: option state crash (#1456)
Co-authored-by: Ax333l <main@axelen.xyz>
2023-11-02 15:46:53 +00:00
8df7f2992d refactor(ui-components): deduplicate colors and move to settings folder 2023-11-01 21:57:00 +01:00
7741394c9c feat(NotificationCard): rewrite & consistent usage (#1426) 2023-11-01 20:54:06 +03:00
25bd91debc feat(Settings): use SettingsListItem consistently and overall improvements (#1427) 2023-11-01 20:11:43 +03:00
7fe4724e10 feat: remember patch options (#1449) 2023-10-31 20:16:02 +00:00
123ae37524 chore: add issue template (#1432) 2023-10-28 12:54:33 +07:00
172604fcdb feat(installer): sign apk in patcher worker 2023-10-27 23:30:45 +02:00
7d887c73e8 fix: use correct checksum 2023-10-27 17:33:11 +02:00
6abaac25d9 chore: upgrade dependencies (#1401) 2023-10-26 07:25:12 -07:00
cc897840e2 fix: perform selected app operations in the correct order 2023-10-26 09:06:42 +02:00
757840b76f feat(bundles tab): add BackHandler 2023-10-26 09:03:26 +02:00
bc09af9d0b docs: part 8.6.1 (test) 2023-10-23 15:28:56 +07:00
0419b2f86b docs: part 8.6 2023-10-23 15:16:34 +07:00
2f31fc7d6e docs: part 8.5
TODO: see PR #1411 suggestions

Co-authored-by: Palm <palmpasuthorn@gmail.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-10-23 15:07:17 +07:00
dcf51c1777 docs: part 8.4-2 2023-10-23 00:52:19 +07:00
94eb893a01 docs: 8.4-1.1 2023-10-23 00:51:58 +07:00
af49457bfc docs: part 8.4-1 2023-10-23 00:50:38 +07:00
a8682d671d docs: part 8.3 2023-10-23 00:49:46 +07:00
10815c8f73 docs: part 7.2 2023-10-23 00:49:28 +07:00
891fb57531 docs: part 8.1 2023-10-22 12:41:45 +07:00
07ee005613 docs: part 8
Co-authored-by: Ushie <ushiekane@gmail.com>
2023-10-22 12:38:49 +07:00
a3c48d14ab docs: part 7.1 2023-10-21 22:45:41 +07:00
f14b697769 docs: part 7
Co-authored-by: Ushie <ushiekane@gmail.com>
2023-10-21 22:43:40 +07:00
50e8d1f8f4 docs: clarify license 2023-10-21 16:10:10 +02:00
d06fb08239 docs: part 6.9 2023-10-21 20:46:06 +07:00
cf9a14c762 docs: part 6.8 2023-10-21 20:35:25 +07:00
7b49af21d0 docs: part 6.7 2023-10-21 20:32:34 +07:00
18774084c9 docs: part 6.6 2023-10-21 20:30:53 +07:00
a70ad3d666 docs: part 6.5 2023-10-21 20:13:42 +07:00
0d2d879603 docs 6.4 2023-10-21 20:13:12 +07:00
8acdc17cc4 docs: part 6.3 2023-10-21 19:57:45 +07:00
1f5331dbfe docs: part 6.2 2023-10-21 19:57:00 +07:00
98747f4afb docs: part 6.1 2023-10-21 19:54:44 +07:00
2f782b4963 docs: part 6 2023-10-21 19:45:49 +07:00
3c083edfda docs: part 5.2 2023-10-21 19:45:00 +07:00
7bf1a5af65 docs: part 5.1 2023-10-21 19:44:05 +07:00
8a3d163266 docs: part 5 2023-10-21 19:39:20 +07:00
d58fd96bb0 docs: part 4.10 2023-10-21 18:29:51 +07:00
259f76379e docs: part 4.9 2023-10-21 17:50:59 +07:00
b8378fbb02 docs: part 4.8 2023-10-21 17:44:01 +07:00
2f0cdfff59 docs: part 4.7 2023-10-21 17:35:29 +07:00
cd5787a4f7 docs: part 4.6 2023-10-21 17:30:32 +07:00
a280fc2446 docs: part 4.5.1 (test) 2023-10-21 17:21:21 +07:00
a503f4830b docs: part 4.5 2023-10-21 17:15:39 +07:00
f732df1b9a docs: part 4.4 2023-10-21 17:11:43 +07:00
9b9525ece5 docs: part 4.3 2023-10-21 17:06:43 +07:00
e0dfbaf4a3 docs: part 4.2 2023-10-21 17:05:53 +07:00
949b1dad0e docs: part 4.1 2023-10-21 17:03:28 +07:00
830a666a58 docs: part 4 2023-10-21 16:58:45 +07:00
26b9652135 docs: part 3 2023-10-21 16:38:29 +07:00
85c6e7283d docs: add ReVanced assets 2023-10-21 16:21:19 +07:00
67603f64cd docs: part 2.1 2023-10-21 16:09:55 +07:00
65f8d38c59 feat: show toast when no patches are selected 2023-10-20 23:16:00 +02:00
e70c10adbd feat: add checkboxes to the downloaded apps page 2023-10-20 23:02:35 +02:00
64ec73d821 fix: more android 34 fixes 2023-10-20 22:59:16 +02:00
32e8a37f33 fix: handle exceptions when checking for bundle updates 2023-10-20 19:43:26 +02:00
5290713504 feat(patch-selector): remove TODO about an unplanned feature 2023-10-20 19:24:17 +02:00
18cfb56b45 fix: bundles not loading on Android 14 2023-10-20 18:49:44 +02:00
4b12ae1531 fix: jvm signature clash error 2023-10-20 17:21:59 +02:00
9df98edca5 fix: use upsert when modifying installed apps 2023-10-19 22:01:32 +02:00
c3af6acb2c feat: selected app info page (#1395) 2023-10-19 19:44:50 +00:00
7ba00cafd9 refactor: move mount code to when block 2023-10-17 09:21:06 +02:00
5aefb3bc59 fix: hide patch button (#1284) 2023-10-16 12:48:51 -07:00
212e55ffd8 feat: add user agent (#1382) 2023-10-16 17:39:17 +00:00
bf54d38c91 chore: bump patcher 2023-10-15 14:03:53 +02:00
cee2240cdc chore: bump compose 2023-10-15 13:17:07 +02:00
4c1ad868a9 fix: broken logo in about page on release builds 2023-10-15 00:22:12 +02:00
f5b3b29d6d feat: hide unfinished pages in release mode 2023-10-14 18:48:07 +02:00
8f5449527d feat: armv7 warning 2023-10-14 18:24:40 +02:00
8f6d720454 refactor(downloaders): improve file system code (#1379) 2023-10-14 15:42:10 +00:00
56a4a7043d feat: settings migration (compose) (#1309) 2023-10-13 10:39:10 -07:00
5762859906 feat: add patches selector bottom sheet (#1360) 2023-10-13 16:11:40 +00:00
676eb1e9e2 docs: part 2 2023-10-09 17:09:34 +07:00
e7b94868d6 docs: merge branding to readme (SEE TODO)
TODO:
2023-10-08 16:47:28 +07:00
608bac6854 feat: use revanced api for changelogs 2023-10-07 17:07:19 +02:00
723f9cd98c fix: delete temporary files (#1341) 2023-10-07 14:09:02 +00:00
abf4d91703 fix: use correct classes to determine option type
I can't believe this happened
2023-10-06 16:41:00 +02:00
d8392ad3eb feat(settings): move experimental patches option to advanced 2023-10-05 22:16:58 +02:00
39caad18a5 feat(installer): adjust arrow icon size 2023-10-05 22:06:20 +02:00
6437f7bb65 feat(installer): adjust step icon size and alignment 2023-10-05 21:51:48 +02:00
e232044157 chore: switch to revanced library and bump patcher (#1314) 2023-10-05 15:36:33 +00:00
f78b56ef0a feat(patch-selector): default patches selection (#1272) 2023-10-01 18:56:16 +00:00
ca3c9af3b8 feat: remove dead help icons
These never did anything and were removed from the figma a while ago.
2023-10-01 19:02:11 +02:00
b8b2e74151 chore: fully remove idea project files 2023-10-01 18:12:08 +02:00
1f8341ac42 fix: remove misc.xml and kotlinc.xml 2023-09-27 10:20:18 -07:00
0964f15475 docs: init (#1224) 2023-09-26 16:54:02 -07:00
63fd7957c6 ci: Add release workflow (#1235) 2023-09-25 19:02:18 +02:00
65377ffd9e fix: Updates popup shows incorrect names (#1283) 2023-09-23 19:42:27 -07:00
f79320c013 fix: use ReVanced ring logo in about section (#1302) 2023-09-22 14:19:48 -07:00
cf71ea26ec feat: implement Submit Issue button (#1276) 2023-09-21 19:18:42 +00:00
ee96c37c20 refactor: update progress onBackClick function (#1277) 2023-09-21 19:16:39 +00:00
a86923aee1 fix: disable WebView history (#1278) 2023-09-21 19:13:57 +00:00
e0f8d06152 fix(ui): make entire patches view button selectable (#1271) 2023-09-21 19:13:25 +00:00
4cb4ce298a feat: change appID and name of debug builds 2023-09-20 20:15:55 -07:00
36de61a57f ci: build pull requests (#1228) 2023-09-20 20:14:26 -07:00
6f2ca5bb89 fix: typo in string name import_keystore_description (#1273) 2023-09-18 16:59:03 +07:00
940885768d fix: contributors screen fix (#1256) 2023-09-15 16:33:14 +00:00
fc577b4c3e chore: update dependencies (#1247) 2023-09-15 16:30:46 +00:00
bf10af2ae2 feat: root installation (#1243) 2023-09-09 13:18:00 +00:00
b4dfcf1bb4 fix: minify crash on building release (#1245) 2023-09-06 17:49:11 +00:00
0b0ba21852 fix: providers.gradleProperty (#1223) 2023-09-06 17:48:48 +00:00
42e0346e25 feat: make bundles selectable (#1237) 2023-09-04 07:05:55 +02:00
212db84d0b ci(config): appreciation for first-time contributors
Show appreciation message for new contributors
2023-09-03 21:47:05 +07:00
e6eb8accf2 docs: update readme badges (#1227) 2023-09-03 09:40:54 +07:00
8bd73c3afa ci(release): don't build when not necessary
Add paths-ignore to all markdown files, and .idea folder
2023-09-02 21:59:23 +07:00
5369a25fa2 docs: update badge's repository
The repository was moved from `revanced-manager-compose` to the main one, which is `revanced-manager`.

The organisation's name has also switched to `ReVanced` (used to be `revanced`).
2023-09-02 21:51:48 +07:00
eeae46a415 chore: bump kotlinx.serialization plugin and patcher 2023-09-01 10:50:13 +02:00
c0badbe96b build: updates (#85) 2023-08-26 14:03:44 +00:00
2bb51c136a fix(deps): use correct work-runtime version string 2023-08-26 15:52:40 +02:00
3cfa4ea6d6 feat: more info for the select from application screen (#81) 2023-08-23 20:05:21 +02:00
f01adf5eb0 ci(release): migrate from node12 to node16
This bump `actions/upload-artifact`@v2 to `actions/upload-artifact`@v3
2023-08-19 16:00:01 +07:00
a0b92554e9 feat: store patched apps (#79)
* feat: store patched apps

* fix: missing string

* feat: save patch selection

* feat: things

* fix: fix broken query

* fix: remove redundant `withContext`

* fix: fix
2023-08-17 17:42:10 +02:00
ac4c7e06e7 ci(release): use correct vars context object
why am i so stupid
2023-08-17 19:30:31 +07:00
0f9a6f4340 ci(release): no longer store keystore alias in secrets
fixes an issue where GitHub Actions logs would be censored
2023-08-17 19:13:18 +07:00
9586a9c0dd fix: patches not being reloaded 2023-08-14 18:29:56 +02:00
f6563b265b fix: permission error when using installed app 2023-08-12 14:52:34 +02:00
7aea9473de feat: patch options UI (#80) 2023-08-12 08:41:22 +00:00
3f059d7748 feat: switch to the new api (#75) 2023-08-07 09:03:50 +00:00
7e3c31c4b2 chore: bump patcher 2023-08-04 12:55:14 +02:00
1707a9690a feat: improve bundle dialog UI 2023-08-04 12:46:07 +02:00
379ce917a9 feat: finish implementing the sources system (#70) 2023-08-03 11:15:42 +00:00
299aaa2b68 fix: library info not being embedded 2023-08-01 21:14:15 +02:00
5cf5e87fa8 ci(release): task naming consistency 2023-08-01 15:30:56 +07:00
55f22562eb fix: don't store app list in parcel 2023-07-31 13:24:49 +02:00
272d911464 fix(installer): progress tracking 2023-07-31 12:16:13 +02:00
6beb34baa8 ci: init 2023-07-30 20:23:51 +00:00
61de0b67fa feat: show installed app in version selector 2023-07-30 19:45:40 +02:00
aec8cec9b8 feat: download apps in patcher screen (#73) 2023-07-30 10:29:22 +00:00
Pun
83b9573b52 docs(readme): minor fix to displaying url
When you hover on Commit & Activity badges, `revanced` will appear in url display, but on License badge, `ReVanced` will display

This commit fix that by changing the organisation to what we're supposed to be using which is {org_name}/{repo_name} (ReVanced/...)
2023-07-24 16:24:21 +07:00
21d99a1f24 feat: add patch bundle info screen (#55) 2023-07-23 15:27:07 +00:00
1331479072 fix: serialization not working 2023-07-17 16:48:29 +02:00
b472a36a9a fix: buildfile syntax (#66)
Signed-off-by: Patryk Miś <foss@patrykmis.com>
2023-07-17 14:41:17 +00:00
3238fcdae7 build: updates (#63)
* Update Java base to 17
* update Kotlin to 1.8.22
* update Bouncycastle
* update all dependencies
* follow the manifest on jni libs packaging
* enhance app optimization by specifying resource configurations, excluding dependencies info and unnessesary files
* Remove obsolete SDK check as we are already using minSdk 26

Signed-off-by: Patryk Miś <foss@patrykmis.com>
2023-07-17 12:27:19 +00:00
cd2587b1fd feat: improve accessibility (#64)
* Label Back button
* Mark group section headings as headings

Signed-off-by: Patryk Miś <foss@patrykmis.com>
2023-07-17 12:20:54 +00:00
879884a9fa feat: switch to Preferences DataStore (#60) 2023-07-15 09:52:12 +00:00
5d3b963682 feat: disable filter chips when there are no patches 2023-07-14 21:37:50 +02:00
955e7a4f1c feat: ReVanced theme colors 2023-07-14 21:35:17 +02:00
d2dcd4209d fix: release builds not working properly 2023-07-14 13:11:34 +02:00
6299ff5b48 chore: migrate dependencies to version catalogs (#58) 2023-07-14 10:33:42 +00:00
94a4dbaba1 feat: app downloader (#43) 2023-07-14 08:54:42 +00:00
c36deea045 build: update gradle to v8.2.1 2023-07-12 20:36:23 +07:00
Pun
7030d43aa5 docs(readme): minor changes to how badges works
* Better description for the repository license badge

* Clicking on badges open you the relevant url
2023-07-08 20:43:10 +07:00
aa02e9f8cf feat: improve keystore UI and UX (#52) 2023-07-07 18:48:36 +00:00
37e177b56e revert: downgrade Kotlin to 1.8.21
"A what? 1.8.22 isn't compatible, but the version bump indicate that it's supposed to be bug fixes????"
2023-07-07 23:16:07 +07:00
453f4da8ec feat: advanced settings page with device info (#51) 2023-07-07 15:35:36 +00:00
400163b820 build: update dependencies
There are 9 dependencies update, changelog of this commit are available
below here.

Android Gradle Plugin: 8.0.1 -> 8.0.2
Kotlin: 1.8.21 -> 1.8.22
Android Compose BOM: 2023.05.01 -> 2023.06.01
Room: 2.5.1 -> 2.5.2
ReVanced Patcher: 11.0.1 -> 11.0.4
APKsig: 8.2.0-alpha05 -> 8.2.0-alpha10
Koin (Android, workmanager): 3.4.0 -> 3.4.2
Koin (Androidx Compose): 3.4.4 -> 3.4.5
Ktor: 2.3.0 -> 2.3.1
2023-07-07 20:27:53 +07:00
4ae9904c8a fix(installer): sign and install on threads
This is needed to avoid ANRs because it takes a while if the Apk is 100+
MB.
2023-07-07 12:31:31 +02:00
fe5e191cb5 feat: updater changelogs (#48)
---------

Co-authored-by: Aunali321 <aunvakil.aa@gmail.com>
2023-07-07 08:56:04 +00:00
d9d83df9de feat: allow user to save logs 2023-07-06 20:01:44 +02:00
8dd8f88d2b feat: save patch options and selected patches in bundle (#50) 2023-07-04 09:09:16 +00:00
01fd4c8ffa feat: patch options (#45) 2023-07-03 09:12:34 +00:00
7ac3bb74e0 refactor: use getDir instead of filesDir directly 2023-07-03 10:09:01 +02:00
3b65cd0edc fix: use correct directory 2023-07-01 16:02:34 +02:00
a9606728bf build: bump patcher 2023-07-01 15:09:21 +02:00
4d4f1a242c build: update gradle to v8.2 2023-07-01 12:40:01 +07:00
6b7143dd8f feat: licenses screen (#47) 2023-06-29 20:05:43 +00:00
7e4ee00cb2 chore: update links in about page 2023-06-29 18:10:40 +02:00
4868c45b43 feat: animate the arrow button 2023-06-29 17:26:49 +02:00
81f485da6b refactor: use correct coroutine scopes 2023-06-29 10:35:23 +02:00
18cbe51e6b fix(installer): save step incorrectly being marked as completed 2023-06-29 10:11:44 +02:00
149c8cc8b2 fix: sources screen being misaligned during transitions 2023-06-29 10:03:15 +02:00
0dccb8c27b feat: contributors screen (#42)
* Contributors page
- https://github.com/revanced/revanced-manager-compose/issues/34

* feat: adding ContributorScreen as clickable icons like the website

* feat: adding ContributorScreen
- Made changes that were asked for in prev PR
- Currently just waiting on a git merge to get ArrowButton in

* feat: adding ContributorScreen
- Made changes that were asked for in prev PR
- ArrowButton is also in use

* feat: adding ContributorScreen
- Made changes that were asked for in prev PR
- ArrowButton is also in use
- Fixed other PR comment changes

* Apply suggestions from code review

* Remove unused string resources

---------

Co-authored-by: Ax333l <main@axelen.xyz>
2023-06-28 19:51:12 +00:00
4302ea8832 fix: pass worker inputs without serialization (#44)
Because androidx.work.Data sucks and causes our app to crash.
2023-06-27 14:39:30 +00:00
1eac42dab8 fix(installer): make the correct column scrollable 2023-06-27 15:12:55 +02:00
9dd74f1f22 feat: experimental patches setting 2023-06-27 15:05:31 +02:00
923ce74735 feat: save patch selection using room db (#38) 2023-06-22 10:20:30 +00:00
2d9f9adfee refactor: better PatchBundle docs and naming 2023-06-17 13:59:37 +02:00
9a55e51a3a build: bump patcher 2023-06-17 13:47:55 +02:00
5681c917c5 feat: show stacktrace in installer ui (#36) 2023-06-17 11:45:52 +00:00
6309e8bdf5 feat: filter options for patches 2023-06-15 22:20:17 +02:00
535efa3d73 fix: run blocking IO operations in the correct context 2023-06-11 17:52:43 +02:00
b8a51d32f5 fix(patcher): add notification and wakelock to worker; chore: add app icon 2023-06-11 17:49:42 +02:00
919b6b7014 feat: keystore import/export (#30) 2023-06-11 14:38:56 +00:00
971277ed39 fix(installer): properly track worker state (#32) 2023-06-09 15:34:10 +00:00
7ce4de7a8b feat(koin): use the android logger 2023-06-06 12:27:42 +02:00
9591f4e14f feat: ProGuard 2023-06-04 19:37:23 +02:00
27426b1390 feat: rename package to app.revanced.manager 2023-06-04 18:27:40 +02:00
fcb75dd780 feat: improved compose stability 2023-06-04 17:50:40 +02:00
1be9c9c1bd fix: use correct getViewModel 2023-06-04 17:42:21 +02:00
e088d053ab feat: rename ViewModels for consistency 2023-06-03 23:27:01 +02:00
ffa8d9c063 feat: hide tabs when 1 bundle is used 2023-06-03 20:12:03 +02:00
7a5596a281 refactor: PackageManager (#31)
* refactor: refactor `PM`

* feat: use plurals for patch count

* fix: support apk's from storage

* feat: use ViewModel for loading apps and bundles

* fix: fix file selector that has no reason to be broken

* refactor: rename parameter

* refactor: `MainViewModel`

* feat: make all apps use `path`

* build: target java 11
2023-06-03 18:03:14 +00:00
9f46f74357 refactor(logs): use consistent tag 2023-05-26 15:39:56 +02:00
36c4e2dfe0 refactor(di): use constructor DSL for VMs
Instead of doing it manually with viewModel { }
2023-05-26 15:25:08 +02:00
5cb31dbe9d chore(deps): bump revanced-patcher to 9.0.0 2023-05-26 15:17:57 +02:00
399fc98dec feat: better installer ui (#29)
based cossale

Co-authored-by: Aunali321 <aunvakil.aa@gmail.com>
2023-05-26 13:14:21 +00:00
c22371e0c5 feat: patch bundle sources system (#24) 2023-05-26 12:58:14 +00:00
a4842c078b feat: in-app updater (#25) 2023-05-23 11:02:22 +02:00
c332760786 feat(settings screen): add battery optimization notification 2023-05-22 05:17:26 +05:30
ea4247c688 feat(update screen): complete main update screen 2023-05-22 04:14:43 +05:30
fec8c0cc14 feat(about screen): complete about screen 2023-05-22 03:23:14 +05:30
9b585c73fb feat(settings screen): match typography from figma 2023-05-22 02:34:07 +05:30
c695fa525f refactor(settings screen): clean code up a bit 2023-05-22 02:29:19 +05:30
93f3e27d48 fix: dont crash when the bundle cannot be downloaded 2023-05-20 17:14:05 +02:00
52ab7937bd feat(installer): apk signing and installation 2023-05-20 12:30:24 +02:00
762bfa8514 fix(patches selector): copy the selected patches list 2023-05-20 09:47:42 +02:00
ca20996b62 refactor(ui): move PatchItem to the only file where it is used 2023-05-19 21:21:37 +02:00
ad14818de8 refactor(net apis): remove unnecessary interfaces
Having interfaces like that is only really useful if you have unit
tests, which we don't.

Other similar compose projects don't make interfaces either.
Not having them is more readable.
2023-05-19 21:01:33 +02:00
32839656f8 style: run formatter 2023-05-19 20:58:44 +02:00
a48faad17a build: updates (#23) 2023-05-19 18:54:21 +00:00
40487923f9 feat: integrate revanced patcher (#22) 2023-05-19 18:49:32 +00:00
f1656c6d1e feat: improved dashboard screen 2023-05-18 13:46:59 +02:00
4c3dbbd8d5 feat: patches selector screen 2023-05-18 13:44:19 +02:00
4088ed747e feat: settings screen 2023-05-18 13:38:02 +02:00
bca8df8efd build: updates (#21)
* perf: obsolete sdk check

Signed-off-by: Patryk Mis <foss@patrykmis.com>

* chore: bump dependencies

Signed-off-by: Patryk Mis <foss@patrykmis.com>

---------

Signed-off-by: Patryk Mis <foss@patrykmis.com>
2023-05-13 12:00:06 +00:00
54f0a69596 feat: app selector screen 2023-05-06 12:42:30 +02:00
9065c0d260 feat: Dashboard Screen (#18)
* feat: add Dashboard Screen and Sources Screen

* fix: fix tab onClick not working

* refactor: remove AppBar

---------

Co-authored-by: CnC-Robert <CnC.Rob3rt@gmail.com>
2023-04-30 19:27:14 +00:00
cb0150a0f9 fix: gradlew permissions on unix 2023-04-23 14:45:07 +02:00
ec0f7e3f7a build: dependency and syntax updates (#17)
* build: Update Gradle to v8.1.1

* build: Bump dependencies

* build: move repo configurations to settings

---------

Co-authored-by: Patryk Mis <24607131+PatrickMis@users.noreply.github.com>
2023-04-23 12:36:52 +00:00
e5d898f025 feat: backend 2023-03-18 11:53:25 +01:00
52bdb1cd6a Create README.md 2023-01-31 19:14:18 -03:00
49f9dfcf95 feat: splash screen 2023-01-28 02:30:39 +03:00
9536cdcae1 feat: implement navigation 2023-01-28 02:28:39 +03:00
57e2632f38 feat: implement DI 2023-01-28 02:19:44 +03:00
b372f7ee84 feat: initialize project 2023-01-28 02:00:52 +03:00
70e8253b63 Migrate to compose branch
This commit was made to allow cherry-picking the first commit of the followup commits according to https://github.com/ReVanced/revanced-manager-compose/issues/65#issue-1806335545
2023-08-26 18:07:57 +02:00
148 changed files with 2260 additions and 11461 deletions

61
.github/ISSUE_TEMPLATE/bug-issue.yml vendored Normal file
View 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

View File

@ -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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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

View File

@ -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

View 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

View File

@ -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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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
View File

@ -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.

View File

@ -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

View File

@ -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
View 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
View 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

View File

@ -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

View File

@ -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
View File

@ -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/

View File

@ -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
}
]
]
}

View File

@ -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

View File

@ -60,8 +60,8 @@
# 💊 ReVanced Manager
![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/ReVanced/revanced-manager/release.yml)
![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/ReVanced/revanced-manager/release.yml)](https://github.com/ReVanced/revanced-manager/actions/workflows/release.yml)
[![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg)](#-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
View 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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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"

View File

@ -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"])
}

View File

@ -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)
}
}
}
}

View File

@ -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"

View File

@ -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)
}
}
}
}

View File

@ -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")

View File

@ -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
)

View File

@ -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()

View File

@ -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()

View File

@ -15,7 +15,7 @@ class LocalPatchBundle(name: String, id: Int, directory: File) :
}
reload()?.also {
saveVersionHash(it.readManifestAttribute("Version"))
saveVersion(it.readManifestAttribute("Version"))
}
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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)
}

View File

@ -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
)

View File

@ -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()
}

View File

@ -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()
})

View File

@ -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,

View File

@ -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].
*/

View File

@ -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 {

View File

@ -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
)
)
}

View File

@ -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,

View File

@ -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) }
)
}

View File

@ -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)
)
}
}
)

View File

@ -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()
}
}

View File

@ -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,
)
}
}
}

View File

@ -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(

View File

@ -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)
)
}
}
)

View File

@ -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()
}
}

View File

@ -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)

View File

@ -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))
}
},

View File

@ -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 = {

View File

@ -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
)

View File

@ -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()
}
}
},

View File

@ -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)
}

View File

@ -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 = {

View File

@ -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 = {

View File

@ -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,

View File

@ -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)
)
}
}
)

View File

@ -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)
)
}

View File

@ -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)
}
)
}

View File

@ -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()
}
}
}
}
)

View File

@ -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)
}
}

View File

@ -92,5 +92,5 @@ object Settings {
data object Licenses : Destination
@Serializable
data object Developer : Destination
data object DeveloperOptions : Destination
}

View File

@ -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

View File

@ -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(

View File

@ -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}")
})
}
)
}

View File

@ -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

View File

@ -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
)
}
}
}
}
}
}

View File

@ -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))
}
}
)

View File

@ -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

View File

@ -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
)
}
}
}
}
}
}

View File

@ -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) }
)
}
}

View File

@ -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
)

View File

@ -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
)
}
}
}

View File

@ -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))
}
}
)

View File

@ -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() }
}
}
}

View File

@ -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),

View File

@ -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,

View File

@ -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

View File

@ -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,
)
}
}
}

View File

@ -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(

View File

@ -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

View File

@ -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
)
}
}

View File

@ -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,

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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!!

View File

@ -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",

View File

@ -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(

View File

@ -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 }
}
}

View File

@ -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)

View File

@ -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))

View File

@ -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)
}

View File

@ -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].
*/

View File

@ -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>

View File

@ -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"
}

View File

@ -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

View File

@ -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

View File

@ -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
View 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)

View File

@ -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)

View File

@ -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
View 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
View 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