Compare commits

..

979 Commits

Author SHA1 Message Date
0de45e2d79 ci: Fetch commit history when needed 2025-07-02 12:41:00 +00:00
5fc54eb53b fix: Correct preference description (#2619) 2025-07-02 13:49:59 +02:00
88b0b8c078 feat: Set app ownership when installing apps (#2558) 2025-06-16 21:55:32 +02:00
7959c36e71 fix: Selected patch count (#2559) 2025-06-10 16:43:50 +02:00
e9542c6cf0 fix: CI flows (#2598) 2025-06-10 16:38:37 +02:00
0992e63c28 feat: Add confirmation dialog to "Reset" options (#2576) 2025-06-10 16:38:05 +02:00
aebad0b0e2 chore: Merge branch 'dev' into compose-dev 2025-05-31 14:26:25 +07:00
bdb0317a9e fix: "Save patched app" attempts to copy APK when patching fails (#2565) 2025-05-31 14:22:55 +07:00
c0d7cf7c2c ci: Upload artifacts when building pull requests 2025-05-30 15:19:50 +02:00
2c1ff4d2cd fix: Patch process cancelation dialog conditions (#2554) 2025-05-30 15:13:30 +02:00
7c5552f93f fix: Correctly display universal patches warning (#2570) 2025-05-24 14:47:27 +02:00
1c5373ff61 fix: Handle open source licenses page crash (#2569) 2025-05-24 14:14:03 +02:00
e629d2df0c ci: Set build attestation subject name 2025-05-22 14:54:04 +02:00
7ca003a30d build: Do not sign all releases with debug key 2025-05-22 14:29:43 +02:00
70a695017e docs: Update docs with all manager features and improve consistency 2025-05-22 14:15:44 +02:00
9b2c99da05 feat: Use simpler strings 2025-05-22 14:15:19 +02:00
07158ae1d1 chore: Move API project from separate repo to this
Set up CI to publish the API library package as well as release the app.
2025-05-22 10:56:23 +02:00
2b380b0d7c fix(Compose): Adjusted universal patches safeguard and warnings (#2550) 2025-05-20 14:12:38 +02:00
5153e5e0cb feat(Compose): hide developer settings (#2551) 2025-05-20 14:12:37 +02:00
a1f5dd3c26 fix: handle edge-to-edge properly in fullscreen dialogs 2025-05-20 14:12:36 +02:00
2b0784865a feat(Compose): Add confirmation dialog on multiple operations (#2529) 2025-05-20 14:12:34 +02:00
d7c0913277 refactor: Rename settings screens for consistency (#2547) 2025-05-20 14:12:33 +02:00
18199bb968 feat(Compose): Improve patches selector tab by adding the bundle version (#2545) 2025-05-20 14:12:32 +02:00
9d329e0f54 ci: Adjust and modernize workflow files to match other repos 2025-05-20 14:12:30 +02:00
e9fcb4a383 docs: Adjust issue templates to match other repos 2025-05-20 14:12:29 +02:00
68f74f1651 docs: Add contribution guidelines and adjust README 2025-05-20 14:12:27 +02:00
6264800a05 build: Update Gradle Wrapper 2025-05-20 14:12:26 +02:00
658699dd81 fix: patch count remaining at zero when using process runtime (#2542) 2025-05-20 14:12:24 +02:00
222089a7ec feat: Order bundles by number of patches 2025-05-20 14:12:23 +02:00
eff5c4860b style: Apply formatting 2025-05-20 14:12:21 +02:00
d4e60acbaa build: Sign releases using keystore if available 2025-05-20 14:12:20 +02:00
1ab74acf1d feat: Use "Debug" and "Debug signed" for build names respectively 2025-05-20 14:12:19 +02:00
28aad879ba feat: Move safeguards above patcher preference group 2025-05-20 14:12:17 +02:00
9f44541bbd fix: Reset cached theme on theme change to avoid broken colors (#2527) 2025-05-20 14:12:15 +02:00
02d2153195 feat(Compose): Move developer options to top level (#2528) 2025-05-20 14:12:14 +02:00
39e821738f build: Remove repos that are not required 2025-05-20 14:12:12 +02:00
7863fbb604 fix: Ignore long click when already in delete mode
closes #2503
2025-05-20 14:12:11 +02:00
94ab6996ae feat: add network checks for features that require it 2025-05-20 14:12:10 +02:00
1319a03651 feat: move plugin api to another repository 2025-05-20 14:12:08 +02:00
f93085f782 fix: Do not poll battery optimization status (#2491) 2025-05-20 14:12:07 +02:00
40a4317993 feat: Improve update screen design (#2487) 2025-05-20 14:12:06 +02:00
8095a1f963 fix: Use compatible rather than support when referring to patch compatibility (#2422) 2025-05-20 14:12:04 +02:00
90c7600586 feat: Improve APK file name formatting on save (#2421) 2025-05-20 14:12:03 +02:00
105492bfa5 build(deps): bump the gradle-compose group with 16 updates (#2407) 2025-05-20 14:12:01 +02:00
ce63b799e6 feat: Reorder Import & Export settings (#2403) 2025-05-20 14:12:00 +02:00
2fe2d46c72 feat: TopAppBar scroll behavior (#2397) 2025-05-20 14:11:58 +02:00
bc3888da79 ci: Generate release artifact provenance (#2324)
Signed-off-by: validcube <pun.butrach@gmail.com>
2025-05-20 14:11:57 +02:00
52b982d81f fix: improve keystore import error handling and show toast 2025-05-20 14:11:56 +02:00
ab48672621 build: Enable pseudo locale for debug variant 2025-05-20 14:11:54 +02:00
0027c90ed3 chore: Update project's dependencies to latest 2025-05-20 14:11:53 +02:00
b6ad686a26 fix: show install button when installation has been cancelled 2025-05-20 14:11:51 +02:00
f59d57499d feat: Screen slide transition (#2396) 2025-05-20 14:11:50 +02:00
5f65c12ec1 fix: Offset badge 2025-05-20 14:11:49 +02:00
65e44dc5a8 build: Bump AGP to 8.8.0
build: Bump AGP to 8.8.0
2025-05-20 14:11:47 +02:00
e6ed4a88c9 docs: Merge documentation from Flutter to Compose 2025-05-20 14:11:46 +02:00
390e3533c9 feat: Redesign the patches screen (#2381) 2025-05-20 14:11:44 +02:00
c1ff2f9924 fix: available updates dialog list item color 2025-05-20 14:11:42 +02:00
d084925c0f refactor: use EventEffect for legacy import 2025-05-20 14:11:41 +02:00
3cf540f190 feat: add required options screen (#2378) 2025-05-20 14:11:39 +02:00
5514c75061 feat: Add confirm dialogs when toggling dangerous settings (#2072)
Co-authored-by: Ax333l <main@axelen.xyz>
2025-05-20 14:11:37 +02:00
ca147cc6dc chore: add .kotlin to gitignore 2025-05-20 14:11:36 +02:00
3c3e995f31 fix: remove battery optimization notification if user grants the permission 2025-05-20 14:11:35 +02:00
67809700c7 feat: switch to androidx.navigation (#2362) 2025-05-20 14:11:33 +02:00
32c7eddb48 refactor: remove unnecessary function 2025-05-20 14:11:31 +02:00
756e3a815f fix: contributors screen repository name 2025-05-20 14:11:29 +02:00
f8f915563e fix: process death resilience and account for android 11 bug (#2355) 2025-05-20 14:11:27 +02:00
2733ce4915 fix: Screen turns off while patching due to wrong WakeLock (#2147) 2025-05-20 14:11:25 +02:00
04a78fabff feat: Add downloader plugin system (#2041) 2025-05-20 14:11:23 +02:00
94e26ba053 feat: switch to revanced api v4 2025-05-20 14:11:21 +02:00
1704947c52 chore: Nitpick on misspelling of comment 2025-05-20 14:11:20 +02:00
e027f8cc9c feat: Make patch bundles list scrollable (#2322) 2025-05-20 14:11:18 +02:00
37e612febc chore: update dependencies
🦀 integrations are gone! 🦀
2025-05-20 14:11:17 +02:00
374531237f fix: only perform haptics on events 2025-05-20 14:11:15 +02:00
abe5a20c4a feat: Add haptic feedback (#1457)
Co-authored-by: Ushie <ushiekane@gmail.com>
2025-05-20 14:11:14 +02:00
0a29ff48ca fix: Match "Installation incompatible" dialog message with Flutter Manager (#2231) 2025-05-20 14:11:12 +02:00
95cffcc0a0 feat(patcher): Improve installation (#2185) 2025-05-20 14:11:10 +02:00
45ff64f26e 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>
2025-05-20 14:11:09 +02:00
7973b367ec feat: View bundle patches (#2065) 2025-05-20 14:11:07 +02:00
641f6af6da feat: Open the app-specific manage all files permission dialog (#2148) 2025-05-20 14:11:06 +02:00
6d142e72a6 feat: Improve patch bundle screen (#2070) 2025-05-20 14:11:05 +02:00
e812f69740 feat: Improve Settings order (#2060)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: Ax333l <main@axelen.xyz>
2025-05-20 14:11:03 +02:00
df79e3d13a fix: remove the unique constraint for patch bundle names 2025-05-20 14:11:01 +02:00
207b005d56 fix: Move temporary files outside of the cache directory (#2122) 2025-05-20 14:11:00 +02:00
4a1695a766 refactor: Add parameters for custom rotation values in ArrowButton 2025-05-20 14:10:58 +02:00
737e709287 ci: Actually enable caching of Gradle 2025-05-20 14:10:57 +02:00
4727e8243c fix: Turn off filters by default (#2079) 2025-05-20 14:10:55 +02:00
5e0ba77f4a fix: ExtendedFloatingActionButton not accessible by screen readers (#2080) 2025-05-20 14:10:54 +02:00
a76a58d6ee feat: Improve unsupported patch warnings (#2066)
Closes #2052
2025-05-20 14:10:52 +02:00
4ebc33cd2a fix: show available and selected patches in patch selector screen 2025-05-20 14:10:51 +02:00
89a1a3026e feat: Add reset button to custom API (#2076)
Closes #2051
2025-05-20 14:10:49 +02:00
16f16e859b feat: Show manager update dialog (#2069)
Closes #1963, closes #1958
2025-05-20 14:10:48 +02:00
83eb1a9fd7 fix: Support patching on ARMv7 by updating AAPT2 (#2084) 2025-05-20 14:10:46 +02:00
9a336aa3ef feat: Improve update setting tile titles
Closes #1968
2025-05-20 14:10:45 +02:00
d0b8cba2bf build: Enable Gradle Configuration Cache (#2059) 2025-05-20 14:10:43 +02:00
7436d99532 fix: always use default patch selection if customization is disabled 2025-05-20 14:10:40 +02:00
c982babaeb fix: android icon not loading in app selector 2025-05-20 14:10:38 +02:00
211f7d2fa2 feat: Improve custom API URL dialog (#2033)
Signed-off-by: validcube <pun.butrach@gmail.com>
2025-05-20 14:10:35 +02:00
d432ffbbe0 fix: Broken header padding in AlertDialogExtended when using an Icon 2025-05-20 14:10:34 +02:00
500cd63507 fix: Remove unnecessary screen padding
Closes #2062
2025-05-20 14:10:33 +02:00
9404c3c297 feat: Remove tag from changelog 2025-05-20 14:09:41 +02:00
f2f89aa185 feat: Progressive AlertDialog for adding bundles
Closes #1992
2025-05-20 14:07:57 +02:00
d6e931a876 fix: Use the correct icon in API URL dialog
Closes #1972
2025-05-20 14:06:10 +02:00
260964c633 feat: Add sensitivity to isScrollingUp 2025-05-20 14:06:08 +02:00
87addbff55 feat: Add isScrollingUp support for ScrollState 2025-05-20 14:06:05 +02:00
5ff5298e0e fix: Use FAB instead of ListItem to patch in App Overview
Closes #1995
2025-05-20 14:06:04 +02:00
0bb08c7afc feat: Improve device information in debugging section
Closes #1977
2025-05-20 14:06:01 +02:00
a0e67a42e0 fix: Change the title in the Update screen from "Updates" to "Update"
Closes #1960
2025-05-20 14:05:59 +02:00
585d54a8a8 feat: Change "Update" to "Show" in Update Available notification
Closes #1959
2025-05-20 14:05:57 +02:00
abdae89434 feat: Highlight links in Markdown
Closes #1962
2025-05-20 14:05:54 +02:00
c7c4da54fb feat: Improve initial update popup wording
Closes #1956
2025-05-20 14:05:53 +02:00
2dacfce61d chore: Remove unused ARMv7 AAPT binary
Closes #1954
2025-05-20 14:05:52 +02:00
f8da11e684 refactor: Improve naming consistency in libs.version.toml
Closes #1953
2025-05-20 14:05:50 +02:00
f384c66dd6 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.
2025-05-20 14:05:03 +02:00
1a9031193c refactor: Use TextButton instead of FilledButton for consistency 2025-05-20 14:05:02 +02:00
dc743021c3 ci: Bump dependencies to latest (#2039) 2025-05-20 14:05:00 +02:00
a81913c2f7 chore: update dependencies 2025-05-20 14:04:58 +02:00
494197b5dd fix: move battery warning to dashboard 2025-05-20 14:04:57 +02:00
285c55228d feat: improve the safeguards (#2038) 2025-05-20 14:04:54 +02:00
aedc6e9970 fix: run props flow on correct dispatcher (#2035) 2025-05-20 14:04:51 +02:00
3ed2c87f45 feat: Automatic language detection (#2032) 2025-05-20 14:04:49 +02:00
d5b22258a6 fix: improve bundle page strings 2025-05-20 14:04:46 +02:00
e6361118a7 fix: cleanup advanced settings screen 2025-05-20 14:04:45 +02:00
edf2f28eca feat: dont ask for root on launch 2025-05-20 14:04:44 +02:00
b5abe1bbc3 feat: improve UX for failed or missing bundles 2025-05-20 14:04:41 +02:00
8654da0dfe feat: implement more patch option types (#2015) 2025-05-20 14:04:39 +02:00
c48698334c fix: crash when removing used bundles 2025-05-20 14:04:36 +02:00
f53299b2a6 fix: import export screen UX 2025-05-20 14:04:34 +02:00
8c1b8e1ee1 feat: add ability to share debug logs 2025-05-20 14:04:32 +02:00
500e0ad9b7 fix: import bundles on another thread 2025-05-20 14:04:30 +02:00
0d6ee98609 feat: get bundle information from jar manifest (#2027) 2025-05-20 14:04:29 +02:00
231cf52f30 fix: add bounds checks in patch selector 2025-05-20 14:04:28 +02:00
994cb6c4b0 feat: rename main bundle to Default 2025-05-20 14:04:26 +02:00
7365fc241a fix: use proper update icon 2025-05-20 14:04:24 +02:00
e2f02ebf22 feat: improve patcher screen labels 2025-05-20 14:04:21 +02:00
5150adeaff fix: scrolling in patch selector 2025-05-20 14:04:19 +02:00
92612f9aec feat: rename debug build to ReVanced Manager (dev) 2025-05-20 14:04:17 +02:00
44b5f7b3bc fix(downloader): versions not loading correctly 2025-05-20 14:04:15 +02:00
46bd2f48a8 fix: automatically focus search views 2025-05-20 14:04:13 +02:00
3ac2062992 feat: move update to notification card (#1917) 2025-05-20 14:04:11 +02:00
95be465b39 feat: revert to blue theme colors 2025-05-20 14:04:09 +02:00
226d9c9c23 refactor: fix more warnings 2025-05-20 14:04:06 +02:00
dabf16a436 build(deps): update ksp 2025-05-20 14:04:04 +02:00
ff0bf43c7d refactor: replace deprecated functions 2025-05-20 14:04:02 +02:00
016de8bb0d 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.
2025-05-20 14:04:00 +02:00
b6c02b7be1 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>
2025-05-20 14:03:58 +02:00
9e7b26b1c8 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>
2025-05-20 14:03:56 +02:00
05eb0d7457 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>
2025-05-20 14:03:55 +02:00
4b0706f8b0 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>
2025-05-20 14:03:52 +02:00
9c6f0c324b 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>
2025-05-20 14:03:48 +02:00
c98ca70e08 fix: correctly patch apk files 2025-05-20 14:03:46 +02:00
424fe25dfb feat: add external process runtime (#1799) 2025-05-20 14:03:44 +02:00
666deda0b5 feat: check if the version being used is the recommended version (#1675) 2025-05-20 14:03:42 +02:00
5e4510eed5 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>
2025-05-20 14:03:39 +02:00
a9147ed0c0 chore: Upgrade dependencies (#1761)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-20 14:03:37 +02:00
bd7c4aa554 chore: upgrade dependencies (#1670) 2025-05-20 14:03:36 +02:00
9d0f3a3605 fix(VersionSelector): use correct LazyColumn item key 2025-05-20 14:03:33 +02:00
093a4ebf49 refactor: Disable update for dev build (#1673)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2025-05-20 14:03:31 +02:00
17cc9f9e9e feat: Collapse ExtendedFAB on scroll (#1630) 2025-05-20 14:03:29 +02:00
bd85b254e4 feat: add toast feedback to the bundle update button 2025-05-20 14:03:26 +02:00
1460fd7be2 fix: patch options reset button being broken 2025-05-20 14:03:24 +02:00
bc3fe3f0f2 refactor: use consistent wording for the version compat check 2025-05-20 14:03:21 +02:00
bdc0fc89c3 docs(security): init (#1612)
Co-authored-by: Ax333l <main@axelen.xyz>
2025-05-20 14:03:20 +02:00
aeefe644c2 refactor: fix terminology and wording related to patches (#1623) 2025-05-20 14:03:17 +02:00
48604804f9 feat: Scrollbars (#1479) 2025-05-20 14:03:14 +02:00
03ccea46e2 fix: progress bar not updating 2025-05-20 14:03:13 +02:00
b4bc14e4ed feat: improve patcher UI (#1494) 2025-05-20 14:03:09 +02:00
6dcbe271a7 feat: updater UI and code improvements (#1597) 2025-05-20 14:03:07 +02:00
3317fd5649 feat: Select bundle type before adding bundle (#1490) 2025-05-20 14:03:05 +02:00
c9eb3ffa14 feat: Purple default theme (#1601) 2025-05-20 14:03:02 +02:00
fe1e65ce9c chore: upgrade AGP to 8.2.0 + migrate deprecated functions (#1574) 2025-05-20 14:03:00 +02:00
f7426309b4 chore(deps): update jetpack compose 2025-05-20 14:02:58 +02:00
7f67a86413 feat(app-selector): show patchable installed apps first (#1496) 2025-05-20 14:02:56 +02:00
a22ef4d9b8 docs: update revanced url 2025-05-20 14:02:54 +02:00
ada99e80f9 build: bump Gradle to v8.5
build: update Gradle wrapper
2025-05-20 14:02:51 +02:00
111d8b6543 refactor: slight formatting of build.gradle.kts 2025-05-20 14:02:50 +02:00
15599dbb21 chore(template): update label name for feature 2025-05-20 14:02:49 +02:00
24cd2dca75 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`.
2025-05-20 14:02:46 +02:00
c48f5b2488 chore(deps): bump revanced patcher and library 2025-05-20 14:02:44 +02:00
06d4485032 fix: specify multithreadingDexFileWriter in PatcherOptions (#1402)
Co-authored-by: Ax333l <main@axelen.xyz>
2025-05-20 14:02:43 +02:00
73fdf92780 fix: load patch bundles earlier 2025-05-20 14:02:42 +02:00
0fda344952 feat(Update Screen): changelogs & handle states (#1464)
Co-authored-by: Ax333l <main@axelen.xyz>
2025-05-20 14:02:38 +02:00
9c0665acb2 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>
2025-05-20 14:02:36 +02:00
6bafa23bb4 fix: parcel error for nullable types 2025-05-20 14:02:34 +02:00
8387ada245 feat: Use correct casing in module description 2025-05-20 14:02:32 +02:00
048ba12703 chore: bump patcher 2025-05-20 14:02:31 +02:00
1b6a77a463 feat: check for updates on startup (#1462) 2025-05-20 14:02:29 +02:00
ff9d021a2b feat(Changelogs): overall improvement (#1429) 2025-05-20 14:02:26 +02:00
6ac4819478 feat(Installer): use BottomAppBar (#1428) 2025-05-20 14:02:25 +02:00
4b5e2e97f7 fix: option state crash (#1456)
Co-authored-by: Ax333l <main@axelen.xyz>
2025-05-20 14:02:21 +02:00
7a4b0bd7c8 refactor(ui-components): deduplicate colors and move to settings folder 2025-05-20 14:02:18 +02:00
205650865a feat(NotificationCard): rewrite & consistent usage (#1426) 2025-05-20 14:02:17 +02:00
39ff42db01 feat(Settings): use SettingsListItem consistently and overall improvements (#1427) 2025-05-20 14:02:13 +02:00
62f5acee1a feat: remember patch options (#1449) 2025-05-20 14:02:11 +02:00
67ecc13a28 chore: add issue template (#1432) 2025-05-20 14:02:09 +02:00
ad10a19acd feat(installer): sign apk in patcher worker 2025-05-20 14:02:07 +02:00
a35c62a99d fix: use correct checksum 2025-05-20 14:02:05 +02:00
8450243ddc chore: upgrade dependencies (#1401) 2025-05-20 14:02:03 +02:00
d239efcf14 fix: perform selected app operations in the correct order 2025-05-20 14:02:01 +02:00
691b615b02 feat(bundles tab): add BackHandler 2025-05-20 14:01:59 +02:00
3f34407741 docs: clarify license 2025-05-20 14:01:57 +02:00
da4153039c feat: show toast when no patches are selected 2025-05-20 14:01:54 +02:00
1de59f420b feat: add checkboxes to the downloaded apps page 2025-05-20 14:01:51 +02:00
464aa753f4 fix: more android 34 fixes 2025-05-20 14:01:48 +02:00
0cf49998e0 fix: handle exceptions when checking for bundle updates 2025-05-20 14:01:47 +02:00
991a8cb5d1 feat(patch-selector): remove TODO about an unplanned feature 2025-05-20 14:01:45 +02:00
ac75d1da27 fix: bundles not loading on Android 14 2025-05-20 14:01:42 +02:00
5907659cc8 fix: jvm signature clash error 2025-05-20 14:01:40 +02:00
d8d2478d0f fix: use upsert when modifying installed apps 2025-05-20 14:01:38 +02:00
4ad3c3fb72 feat: selected app info page (#1395) 2025-05-20 14:01:35 +02:00
0e00b9f526 refactor: move mount code to when block 2025-05-20 14:01:32 +02:00
88f3701a6c fix: hide patch button (#1284) 2025-05-20 14:01:31 +02:00
a15924617e feat: add user agent (#1382) 2025-05-20 14:01:30 +02:00
db04672d72 chore: bump patcher 2025-05-20 14:01:27 +02:00
977345e5aa chore: bump compose 2025-05-20 14:01:25 +02:00
76e5731eb8 fix: broken logo in about page on release builds 2025-05-20 14:01:23 +02:00
99bfd84e03 feat: hide unfinished pages in release mode 2025-05-20 14:01:20 +02:00
edb387e1a8 feat: armv7 warning 2025-05-20 14:01:18 +02:00
80ff6711f4 refactor(downloaders): improve file system code (#1379) 2025-05-20 14:01:16 +02:00
cadbb3f46d feat: settings migration (compose) (#1309) 2025-05-20 14:01:15 +02:00
e1742fd4c0 feat: add patches selector bottom sheet (#1360) 2025-05-20 14:01:14 +02:00
c2c4895a29 feat: use revanced api for changelogs 2025-05-20 14:01:11 +02:00
ffe5c058e0 fix: delete temporary files (#1341) 2025-05-20 14:01:08 +02:00
59ddd9f393 fix: use correct classes to determine option type
I can't believe this happened
2025-05-20 14:01:06 +02:00
57ba3ad374 feat(settings): move experimental patches option to advanced 2025-05-20 14:01:05 +02:00
6fed17705b feat(installer): adjust arrow icon size 2025-05-20 14:01:03 +02:00
f915b544c4 feat(installer): adjust step icon size and alignment 2025-05-20 14:01:02 +02:00
11a383a13a chore: switch to revanced library and bump patcher (#1314) 2025-05-20 14:01:01 +02:00
4b178d947c feat(patch-selector): default patches selection (#1272) 2025-05-20 14:00:58 +02:00
f2e7661b5c feat: remove dead help icons
These never did anything and were removed from the figma a while ago.
2025-05-20 14:00:57 +02:00
e33862f436 chore: fully remove idea project files 2025-05-20 14:00:55 +02:00
51dc429330 fix: remove misc.xml and kotlinc.xml 2025-05-20 14:00:53 +02:00
5a41cc1162 docs: init (#1224) 2025-05-20 14:00:32 +02:00
28ab79d962 ci: Add release workflow (#1235) 2025-05-20 14:00:30 +02:00
8f2c18585f fix: Updates popup shows incorrect names (#1283) 2025-05-20 14:00:29 +02:00
2f533d12b9 fix: use ReVanced ring logo in about section (#1302) 2025-05-20 14:00:27 +02:00
44cec48a7f feat: implement Submit Issue button (#1276) 2025-05-20 14:00:25 +02:00
395da595a2 refactor: update progress onBackClick function (#1277) 2025-05-20 14:00:23 +02:00
18ea6adb20 fix: disable WebView history (#1278) 2025-05-20 14:00:21 +02:00
daeb534692 fix(ui): make entire patches view button selectable (#1271) 2025-05-20 14:00:19 +02:00
0b2ddbe0bf feat: change appID and name of debug builds 2025-05-20 14:00:17 +02:00
3c3ff64b18 ci: build pull requests (#1228) 2025-05-20 14:00:15 +02:00
43befa8713 fix: typo in string name import_keystore_description (#1273) 2025-05-20 14:00:13 +02:00
3c820405a8 fix: contributors screen fix (#1256) 2025-05-20 14:00:10 +02:00
d65e830467 chore: update dependencies (#1247) 2025-05-20 14:00:08 +02:00
154b23202c feat: root installation (#1243) 2025-05-20 14:00:05 +02:00
91e0d48721 fix: minify crash on building release (#1245) 2025-05-20 14:00:02 +02:00
ae5eef0f2c fix: providers.gradleProperty (#1223) 2025-05-20 14:00:01 +02:00
382c068a03 feat: make bundles selectable (#1237) 2025-05-20 13:59:58 +02:00
cfaf874326 ci(config): appreciation for first-time contributors
Show appreciation message for new contributors
2025-05-20 13:59:55 +02:00
97d25b5602 docs: update readme badges (#1227) 2025-05-20 13:59:54 +02:00
cde470f867 ci(release): don't build when not necessary
Add paths-ignore to all markdown files, and .idea folder
2025-05-20 13:59:52 +02:00
1ad3e3423c 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`).
2025-05-20 13:59:50 +02:00
d62f0a96fb chore: bump kotlinx.serialization plugin and patcher 2025-05-20 13:59:47 +02:00
d4ee3334e0 build: updates (#85) 2025-05-20 13:59:44 +02:00
384fb19ddf fix(deps): use correct work-runtime version string 2025-05-20 13:59:42 +02:00
ab04ef99c3 feat: more info for the select from application screen (#81) 2025-05-20 13:59:41 +02:00
c812ce2011 ci(release): migrate from node12 to node16
This bump `actions/upload-artifact`@v2 to `actions/upload-artifact`@v3
2025-05-20 13:59:39 +02:00
fa8f154d65 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
2025-05-20 13:59:36 +02:00
af03eec4b5 ci(release): use correct vars context object
why am i so stupid
2025-05-20 13:59:33 +02:00
da66b43497 ci(release): no longer store keystore alias in secrets
fixes an issue where GitHub Actions logs would be censored
2025-05-20 13:59:32 +02:00
e302ea9f9e fix: patches not being reloaded 2025-05-20 13:59:30 +02:00
6aa3b6c4b0 fix: permission error when using installed app 2025-05-20 13:59:27 +02:00
5cb887ebe6 feat: patch options UI (#80) 2025-05-20 13:59:25 +02:00
4cd00c122d feat: switch to the new api (#75) 2025-05-20 13:59:22 +02:00
5744bdda80 chore: bump patcher 2025-05-20 13:59:20 +02:00
89e373f98c feat: improve bundle dialog UI 2025-05-20 13:59:19 +02:00
ecd4b01108 feat: finish implementing the sources system (#70) 2025-05-20 13:59:15 +02:00
08686252bb fix: library info not being embedded 2025-05-20 13:59:13 +02:00
ab1dd8862d ci(release): task naming consistency 2025-05-20 13:59:11 +02:00
c4abf8a324 fix: don't store app list in parcel 2025-05-20 13:59:09 +02:00
05adb78932 fix(installer): progress tracking 2025-05-20 13:59:07 +02:00
3ae1d3374a ci: init 2025-05-20 13:59:06 +02:00
067f8adf4b feat: show installed app in version selector 2025-05-20 13:59:05 +02:00
83f6d287b3 feat: download apps in patcher screen (#73) 2025-05-20 13:59:01 +02:00
Pun
3dde82fc18 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/...)
2025-05-20 13:58:59 +02:00
de0af4c489 feat: add patch bundle info screen (#55) 2025-05-20 13:58:56 +02:00
f98386dcc8 fix: serialization not working 2025-05-20 13:58:54 +02:00
49209ca562 fix: buildfile syntax (#66)
Signed-off-by: Patryk Miś <foss@patrykmis.com>
2025-05-20 13:58:53 +02:00
d68e7f71e9 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>
2025-05-20 13:58:49 +02:00
fffdb314a1 feat: improve accessibility (#64)
* Label Back button
* Mark group section headings as headings

Signed-off-by: Patryk Miś <foss@patrykmis.com>
2025-05-20 13:58:47 +02:00
ee41e315fb feat: switch to Preferences DataStore (#60) 2025-05-20 13:58:45 +02:00
cd3d654318 feat: disable filter chips when there are no patches 2025-05-20 13:58:43 +02:00
3bd1ef3de7 feat: ReVanced theme colors 2025-05-20 13:58:41 +02:00
ba1a152231 fix: release builds not working properly 2025-05-20 13:58:40 +02:00
55573eb94f chore: migrate dependencies to version catalogs (#58) 2025-05-20 13:58:38 +02:00
80e78f544b feat: app downloader (#43) 2025-05-20 13:58:35 +02:00
7572944c9e build: update gradle to v8.2.1 2025-05-20 13:58:34 +02:00
Pun
f5e9826dfb docs(readme): minor changes to how badges works
* Better description for the repository license badge

* Clicking on badges open you the relevant url
2025-05-20 13:58:32 +02:00
c8ac94d82d feat: improve keystore UI and UX (#52) 2025-05-20 13:58:29 +02:00
d9ff833100 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????"
2025-05-20 13:58:27 +02:00
7150fb4435 feat: advanced settings page with device info (#51) 2025-05-20 13:58:25 +02:00
34c331f39b 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
2025-05-20 13:58:24 +02:00
1ff76cf584 fix(installer): sign and install on threads
This is needed to avoid ANRs because it takes a while if the Apk is 100+
MB.
2025-05-20 13:58:23 +02:00
1de0e87983 feat: updater changelogs (#48)
---------

Co-authored-by: Aunali321 <aunvakil.aa@gmail.com>
2025-05-20 13:58:20 +02:00
93b2dd6176 feat: allow user to save logs 2025-05-20 13:58:18 +02:00
f3e2435fef feat: save patch options and selected patches in bundle (#50) 2025-05-20 13:58:17 +02:00
b42d8842d5 feat: patch options (#45) 2025-05-20 13:58:15 +02:00
c052a0c0f5 refactor: use getDir instead of filesDir directly 2025-05-20 13:58:13 +02:00
34cf91d4b6 fix: use correct directory 2025-05-20 13:58:10 +02:00
f99504d3e4 build: bump patcher 2025-05-20 13:58:08 +02:00
3ec1df9650 build: update gradle to v8.2 2025-05-20 13:58:05 +02:00
871a34df23 feat: licenses screen (#47) 2025-05-20 13:58:03 +02:00
b65ec4560f chore: update links in about page 2025-05-20 13:58:01 +02:00
0eaeb5d5ea feat: animate the arrow button 2025-05-20 13:57:59 +02:00
060f39fb9b refactor: use correct coroutine scopes 2025-05-20 13:57:57 +02:00
722dfadb3c fix(installer): save step incorrectly being marked as completed 2025-05-20 13:57:56 +02:00
6567be40cb fix: sources screen being misaligned during transitions 2025-05-20 13:57:54 +02:00
9539d23c12 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>
2025-05-20 13:57:51 +02:00
d0d0a17a55 fix: pass worker inputs without serialization (#44)
Because androidx.work.Data sucks and causes our app to crash.
2025-05-20 13:57:50 +02:00
d2e965f056 fix(installer): make the correct column scrollable 2025-05-20 13:57:49 +02:00
cda0e127d9 feat: experimental patches setting 2025-05-20 13:57:45 +02:00
fea11dfef6 feat: save patch selection using room db (#38) 2025-05-20 13:57:43 +02:00
dcc4477e3e refactor: better PatchBundle docs and naming 2025-05-20 13:57:40 +02:00
6eb21e1fab build: bump patcher 2025-05-20 13:57:38 +02:00
b8902d04d7 feat: show stacktrace in installer ui (#36) 2025-05-20 13:57:36 +02:00
99efdb130f feat: filter options for patches 2025-05-20 13:57:34 +02:00
5177cd3083 fix: run blocking IO operations in the correct context 2025-05-20 13:57:32 +02:00
ff4b9ab960 fix(patcher): add notification and wakelock to worker; chore: add app icon 2025-05-20 13:57:30 +02:00
ad998ac22d feat: keystore import/export (#30) 2025-05-20 13:57:27 +02:00
881d2430c3 fix(installer): properly track worker state (#32) 2025-05-20 13:57:25 +02:00
b07ae90c86 feat(koin): use the android logger 2025-05-20 13:57:23 +02:00
8e6519cfb0 feat: ProGuard 2025-05-20 13:57:22 +02:00
bb90cc6e81 feat: rename package to app.revanced.manager 2025-05-20 13:57:18 +02:00
fd02e0799c feat: improved compose stability 2025-05-20 13:57:17 +02:00
f07204460c fix: use correct getViewModel 2025-05-20 13:57:15 +02:00
66be0f96e0 feat: rename ViewModels for consistency 2025-05-20 13:57:13 +02:00
a1ca19b289 feat: hide tabs when 1 bundle is used 2025-05-20 13:57:09 +02:00
af779153d5 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
2025-05-20 13:57:05 +02:00
78966e13c4 refactor(logs): use consistent tag 2025-05-20 13:57:03 +02:00
8bdcf76832 refactor(di): use constructor DSL for VMs
Instead of doing it manually with viewModel { }
2025-05-20 13:57:00 +02:00
05ecbde6c2 chore(deps): bump revanced-patcher to 9.0.0 2025-05-20 13:56:57 +02:00
e558a47204 feat: better installer ui (#29)
based cossale

Co-authored-by: Aunali321 <aunvakil.aa@gmail.com>
2025-05-20 13:56:56 +02:00
61de7568cb feat: patch bundle sources system (#24) 2025-05-20 13:56:51 +02:00
2e7f8457d3 feat: in-app updater (#25) 2025-05-20 13:56:47 +02:00
332bad699d feat(settings screen): add battery optimization notification 2025-05-20 13:56:43 +02:00
0b5ab33b3e feat(update screen): complete main update screen 2025-05-20 13:56:40 +02:00
5b4242d28b feat(about screen): complete about screen 2025-05-20 13:56:38 +02:00
0c76ed3af0 feat(settings screen): match typography from figma 2025-05-20 13:56:36 +02:00
39d698e545 refactor(settings screen): clean code up a bit 2025-05-20 13:56:34 +02:00
18e91e7cbc fix: dont crash when the bundle cannot be downloaded 2025-05-20 13:56:32 +02:00
14dfe07795 feat(installer): apk signing and installation 2025-05-20 13:56:30 +02:00
8e011a5d6b fix(patches selector): copy the selected patches list 2025-05-20 13:56:28 +02:00
fc5f97e54b refactor(ui): move PatchItem to the only file where it is used 2025-05-20 13:56:27 +02:00
78728c1f2a 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.
2025-05-20 13:56:25 +02:00
90c95c0669 style: run formatter 2025-05-20 13:56:23 +02:00
fbd1e221da build: updates (#23) 2025-05-20 13:56:21 +02:00
c35c776ce2 feat: integrate revanced patcher (#22) 2025-05-20 13:56:18 +02:00
f275f57c11 feat: improved dashboard screen 2025-05-20 13:56:15 +02:00
520b86df0a feat: patches selector screen 2025-05-20 13:56:13 +02:00
8991827ac7 feat: settings screen 2025-05-20 13:56:11 +02:00
0871180dcc 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>
2025-05-20 13:56:08 +02:00
7103bd2ec1 feat: app selector screen 2025-05-20 13:56:06 +02:00
e5029c7d2c 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>
2025-05-20 13:56:05 +02:00
a512af50b5 fix: gradlew permissions on unix 2025-05-20 13:54:32 +02:00
cc59d60dfd 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>
2025-05-20 13:54:31 +02:00
4d894e908e feat: backend 2025-05-20 13:54:29 +02:00
77b499ef29 Create README.md 2025-05-20 13:54:27 +02:00
0142b85ede feat: splash screen 2025-05-20 13:54:25 +02:00
d9633906f5 feat: implement navigation 2025-05-20 13:54:22 +02:00
3dd14fd34b feat: implement DI 2025-05-20 13:54:21 +02:00
0b19a9865d chore: Migrate to compose-dev branch 2025-05-20 13:49:36 +02:00
b0464408f1 ci: Run Crowdin Cron task on dev branch (#2543) 2025-05-12 14:51:42 +02:00
767fa77436 chore(release): 1.25.0-dev.1 [skip ci]
# [1.25.0-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.24.1-dev.5...v1.25.0-dev.1) (2025-05-05)

### Bug Fixes

* Fix installation being cancelled at installation by not prematurely deleting patched APK  ([#2490](https://github.com/ReVanced/revanced-manager/issues/2490)) ([dedcb3c](dedcb3c51a))
* Use device locale for app language (Default to English) ([#2488](https://github.com/ReVanced/revanced-manager/issues/2488)) ([3074766](3074766ff2))

### Features

* Add toggle to use pre-releases ([#2485](https://github.com/ReVanced/revanced-manager/issues/2485)) ([89b48ce](89b48cebcf))
2025-05-05 14:15:17 +00:00
89b48cebcf feat: Add toggle to use pre-releases (#2485)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-05-05 16:00:41 +02:00
dedcb3c51a fix: Fix installation being cancelled at installation by not prematurely deleting patched APK (#2490) 2025-05-05 15:56:28 +02:00
3074766ff2 fix: Use device locale for app language (Default to English) (#2488) 2025-05-05 15:54:42 +02:00
722f2b645f chore(release): 1.24.1-dev.5 [skip ci]
## [1.24.1-dev.5](https://github.com/ReVanced/revanced-manager/compare/v1.24.1-dev.4...v1.24.1-dev.5) (2025-04-17)

### Bug Fixes

* Use device locale when no preference is set ([#2483](https://github.com/ReVanced/revanced-manager/issues/2483)) ([f79aa9e](f79aa9edd7))
2025-04-17 13:17:52 +00:00
f79aa9edd7 fix: Use device locale when no preference is set (#2483) 2025-04-17 20:06:57 +07:00
5127c7f599 ci: Cache Gradle on PR build 2025-04-16 02:12:11 +07:00
23dcbbecb2 ci: Avoid repository push access token 2025-04-14 19:08:01 +07:00
1c84265dc4 chore: Sync translations (#2384)
Signed-off-by: Pun Butrach <pun.butrach@gmail.com>
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2025-04-14 18:53:36 +07:00
7ccc689bd3 chore: Modify language assets to match Nuke 2025-04-14 17:50:42 +07:00
007dce800e build: Nuke script now able to remove empty JSON/File 2025-04-14 17:50:15 +07:00
ba758f3d8b ci: Flutter caching for build PR 2025-04-14 17:20:20 +07:00
a1f9a2786f chore: Miscellaneous code clean-up 2025-04-14 17:17:51 +07:00
b11a720621 chore: Remove x86 libaapt2.so binary (#2480) 2025-04-14 17:16:22 +07:00
aedf475310 ci: Use correct directory for Gradle cache 2025-04-14 15:51:28 +07:00
10f35eb727 chore(release): 1.24.1-dev.4 [skip ci]
## [1.24.1-dev.4](https://github.com/ReVanced/revanced-manager/compare/v1.24.1-dev.3...v1.24.1-dev.4) (2025-04-13)

### Bug Fixes

* Log errors and warnings when compiling resources ([5c7d52c](5c7d52c8e9))
2025-04-13 15:48:47 +00:00
5c7d52c8e9 fix: Log errors and warnings when compiling resources 2025-04-13 17:38:20 +02:00
841d61278b chore: Ignore Dart analysis on generated slang file 2025-04-12 01:27:28 +07:00
534e4c2453 ci: Point to Android directory 2025-04-10 23:59:39 +07:00
062f28387f ci: Modernize workflows (#2473)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2025-04-10 23:47:26 +07:00
a8a4ffabed chore(release): 1.24.1-dev.3 [skip ci]
## [1.24.1-dev.3](https://github.com/ReVanced/revanced-manager/compare/v1.24.1-dev.2...v1.24.1-dev.3) (2025-04-10)

### Bug Fixes

* Correct supported required patch option types  ([#2475](https://github.com/ReVanced/revanced-manager/issues/2475)) ([cde3f8d](cde3f8d62c))
2025-04-10 15:26:56 +00:00
cde3f8d62c fix: Correct supported required patch option types (#2475)
Signed-off-by: Pun Butrach <pun.butrach@gmail.com>
2025-04-10 22:15:29 +07:00
8182228a46 chore: Default project Flutter devtools options configuration for debugging 2025-04-10 22:08:04 +07:00
7fa5daf623 chore: Remove Android cxx generated file
Signed-off-by: Pun Butrach <pun.butrach@gmail.com>
2025-04-10 22:05:38 +07:00
221e663e47 chore(release): 1.24.1-dev.2 [skip ci]
## [1.24.1-dev.2](https://github.com/ReVanced/revanced-manager/compare/v1.24.1-dev.1...v1.24.1-dev.2) (2025-04-09)

### Bug Fixes

* Unable to Share Logs due to missing ProGuard rules ([#2474](https://github.com/ReVanced/revanced-manager/issues/2474)) ([915ec0e](915ec0e260))
2025-04-09 20:20:00 +00:00
915ec0e260 fix: Unable to Share Logs due to missing ProGuard rules (#2474)
Technical commit message: fix: Unable to Share Logs due to CCE in FileProvider

Signed-off-by: Pun Butrach <pun.butrach@gmail.com>
2025-04-10 03:09:15 +07:00
783f313ed8 chore(release): 1.24.1-dev.1 [skip ci]
## [1.24.1-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.24.0...v1.24.1-dev.1) (2025-04-04)

### Bug Fixes

* Crash using when Integer type in Patch Options ([#2453](https://github.com/ReVanced/revanced-manager/issues/2453)) ([05575cc](05575cccfb))
2025-04-04 16:11:35 +00:00
05575cccfb fix: Crash using when Integer type in Patch Options (#2453) 2025-04-04 22:59:06 +07:00
a8e192b85f chore(release): 1.24.0 [skip ci]
# [1.24.0](https://github.com/ReVanced/revanced-manager/compare/v1.23.3...v1.24.0) (2025-03-07)

### Bug Fixes

* Build failure caused by Internal R8 NPE on field "b" ([08a9d2a](08a9d2a64f))
* Change duplicated app suffix and allow profile variant compilation ([5b6426c](5b6426c453))
* Flutter Impeller renderer causing artifacts on rare occasions ([7462291](746229120c))
* Slight tweak and use Flutter suggested config ([4b7b05a](4b7b05ac0f))

### Features

* Distinguish between release, debug, and profile variants ([64cbb68](64cbb68344))
2025-03-07 17:53:22 +00:00
4b591effed chore: Merge branch dev to main (#2406) 2025-03-08 00:42:05 +07:00
123c51794b chore(release): 1.24.0-dev.1 [skip ci]
# [1.24.0-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.23.4-dev.1...v1.24.0-dev.1) (2025-03-06)

### Bug Fixes

* Flutter Impeller renderer causing artifacts on rare occasions ([7462291](746229120c))

### Features

* Distinguish between release, debug, and profile variants ([64cbb68](64cbb68344))
2025-03-06 16:56:44 +00:00
53d64a0636 build: Use dependency meant for Stable branch of Flutter
Signed-off-by: validcube <pun.butrach@gmail.com>
2025-03-06 23:44:47 +07:00
64cbb68344 feat: Distinguish between release, debug, and profile variants
Signed-off-by: validcube <pun.butrach@gmail.com>
2025-03-06 23:12:35 +07:00
00950c79f0 build: Bump pubspec dependencies
Signed-off-by: validcube <pun.butrach@gmail.com>
2025-03-06 23:06:09 +07:00
746229120c fix: Flutter Impeller renderer causing artifacts on rare occasions
This sets minimum flutter version to 3.29.0 so that it will discourage any version lower than that.

Signed-off-by: validcube <pun.butrach@gmail.com>
2025-03-06 22:36:06 +07:00
133f6ee28f chore(release): 1.23.4-dev.1 [skip ci]
## [1.23.4-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.23.3...v1.23.4-dev.1) (2025-03-06)

### Bug Fixes

* Build failure caused by Internal R8 NPE on field "b" ([08a9d2a](08a9d2a64f))
* Change duplicated app suffix and allow profile variant compilation ([5b6426c](5b6426c453))
* Slight tweak and use Flutter suggested config ([4b7b05a](4b7b05ac0f))
2025-03-06 14:56:29 +00:00
08a9d2a64f fix: Build failure caused by Internal R8 NPE on field "b"
See: https://issuetracker.google.com/issues/389508413
2025-03-06 21:45:11 +07:00
4b7b05ac0f fix: Slight tweak and use Flutter suggested config
Signed-off-by: validcube <pun.butrach@gmail.com>
2025-02-15 17:25:29 +07:00
5b6426c453 fix: Change duplicated app suffix and allow profile variant compilation 2025-02-15 13:54:57 +07:00
235489dcdf ci: Use american spelling
ReVanced uses US EN spelling.
2025-02-14 21:35:19 +07:00
b4e5c66f9c ci: Fix commit hash related issues 2025-02-14 21:22:42 +07:00
5c58f624de ci: Cover before build failure and registry authentication 2025-02-14 21:20:47 +07:00
89ad88c0ef ci: Improve build pull request v3 2025-02-14 21:06:18 +07:00
d3036105bb ci: Make attestation runs on every release (stable)
Sometimes build will not be made due to CI Configuration
2025-02-11 19:54:39 +07:00
3e8862ea5a ci: Generate release artifact provenance (#2315) 2025-02-10 19:34:12 +07:00
0c59bbb7be build(deps-dev): bump the npm group with 2 updates (#2404) 2025-02-01 10:23:30 +07:00
f4b279d1fd chore(release): 1.23.3 [skip ci]
## [1.23.3](https://github.com/ReVanced/revanced-manager/compare/v1.23.2...v1.23.3) (2025-01-15)

### Bug Fixes

* Delete cached APK only after successful patch ([#2331](https://github.com/ReVanced/revanced-manager/issues/2331)) ([4ba7ec1](4ba7ec1854))
2025-01-15 15:21:07 +00:00
c652d196fc ci: Move Nuke script to after normalisation 2025-01-15 21:39:44 +07:00
632b9502b2 chore: Sync translations (#2316)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2025-01-15 21:32:42 +07:00
dfb3fe3749 build(deps): bump slang_flutter and slang (#2374)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2025-01-15 21:15:30 +07:00
b3ba0fcd6a chore: Remove deprecated linter rules 2025-01-03 23:39:32 +07:00
8a98b8448b build(deps-dev): bump semantic-release-pub from 0.9.1 to 0.9.3 in the npm group (#2372)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-01 11:21:20 +07:00
da604e89ac build(deps): bump the pubspec group with 2 updates (#2373)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-01 11:18:54 +07:00
624b43733e chore(release): 1.23.3-dev.1 [skip ci]
## [1.23.3-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.23.2...v1.23.3-dev.1) (2024-12-24)

### Bug Fixes

* Delete cached APK only after successful patch ([#2331](https://github.com/ReVanced/revanced-manager/issues/2331)) ([4ba7ec1](4ba7ec1854))
2024-12-24 00:59:50 +00:00
4ba7ec1854 fix: Delete cached APK only after successful patch (#2331) 2024-12-24 01:48:57 +01:00
1d6b074856 build(deps): bump the pubspec group with 13 updates (#2338)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
Signed-off-by: Pun Butrach <pun.butrach@gmail.com>
2024-12-19 19:35:55 +07:00
695329088b docs: Navigate to next sub-page and explain mounting [skip ci] 2024-12-18 04:18:23 +01:00
f2b20e6e7f build(deps-dev): bump the npm group with 2 updates (#2334)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-01 11:03:14 +07:00
a5c3aa3744 chore: Sync translations (#2314) 2024-11-13 22:56:59 +07:00
900058a6e5 chore: Sync translations (#2296) 2024-11-13 22:50:41 +07:00
4c51ad3650 chore(release): 1.23.2 [skip ci]
## [1.23.2](https://github.com/ReVanced/revanced-manager/compare/v1.23.1...v1.23.2) (2024-11-12)

### Bug Fixes

* Handle migration of default URL used in v1.22 as well ([851b06b](851b06b0d2))
2024-11-12 23:45:30 +00:00
a0c2a17bab chore: Merge branch dev to main (#2311) 2024-11-13 00:34:25 +01:00
851b06b0d2 fix: Handle migration of default URL used in v1.22 as well 2024-11-13 00:33:58 +01:00
57018a65df chore(release): 1.23.1 [skip ci]
## [1.23.1](https://github.com/ReVanced/revanced-manager/compare/v1.23.0...v1.23.1) (2024-11-10)
2024-11-10 16:24:53 +00:00
b44419133a build(Needs bump): Put keystore to correct file path 2024-11-10 17:14:10 +01:00
8961cf9044 chore(release): 1.23.0 [skip ci]
# [1.23.0](https://github.com/ReVanced/revanced-manager/compare/v1.22.0...v1.23.0) (2024-11-10)

### Bug Fixes

* Don't show toasts when export cancelled ([#2230](https://github.com/ReVanced/revanced-manager/issues/2230)) ([bd79496](bd79496433))
* Fix the connectivity check toast again ([#2216](https://github.com/ReVanced/revanced-manager/issues/2216)) ([a7e2281](a7e2281805))
* Get changelogs for alternative sources ([#1766](https://github.com/ReVanced/revanced-manager/issues/1766)) ([c729842](c7298424e5))
* missing parameter in translations ([1c6c5d5](1c6c5d53ae))
* Resolve EACCESS error in special cases ([#2135](https://github.com/ReVanced/revanced-manager/issues/2135)) ([1f95767](1f95767aeb))
* Restore apk renaming during compile ([abdd9dc](abdd9dc430))

### Features

* Also show new patches in the removed patches dialog ([#2257](https://github.com/ReVanced/revanced-manager/issues/2257)) ([8872165](8872165a99))
* Import and export manager settings ([#2268](https://github.com/ReVanced/revanced-manager/issues/2268)) ([a45d959](a45d9598cc))
* Show changelogs from the latest to the last used patches version ([#2219](https://github.com/ReVanced/revanced-manager/issues/2219)) ([daba737](daba737ecb))
* Use ReVanced API v4 ([7b7d91d](7b7d91d661))

### Performance Improvements

* Don't recalculate universal patches or compatible packages if not necessary ([7e3afe0](7e3afe0cb2))
2024-11-10 15:04:46 +00:00
5139873f79 chore: Merge branch dev to main (#2217) 2024-11-10 15:56:03 +01:00
5caa79eb0d chore: Sync translations (#2267) 2024-11-10 05:57:57 +01:00
9552b2ebc5 chore(release): 1.23.0-dev.7 [skip ci]
# [1.23.0-dev.7](https://github.com/ReVanced/revanced-manager/compare/v1.23.0-dev.6...v1.23.0-dev.7) (2024-11-10)

### Features

* Use ReVanced API v4 ([7b7d91d](7b7d91d661))

### Performance Improvements

* Don't recalculate universal patches or compatible packages if not necessary ([7e3afe0](7e3afe0cb2))
2024-11-10 04:04:12 +00:00
7e3afe0cb2 perf: Don't recalculate universal patches or compatible packages if not necessary 2024-11-10 04:53:59 +01:00
7b7d91d661 feat: Use ReVanced API v4 2024-11-10 04:53:59 +01:00
44b8d4ceee build: Configure output file name 2024-11-08 19:43:45 +01:00
aaa97ebb71 chore(release): 1.23.0-dev.6 [skip ci]
# [1.23.0-dev.6](https://github.com/ReVanced/revanced-manager/compare/v1.23.0-dev.5...v1.23.0-dev.6) (2024-11-08)
2024-11-08 17:49:03 +00:00
d99e5af384 build: Fix build 2024-11-08 18:39:02 +01:00
c47c7c0a88 build(Needs bump): Bump dependencies 2024-11-05 20:13:08 +01:00
3e32c0fd90 chore(release): 1.23.0-dev.5 [skip ci]
# [1.23.0-dev.5](https://github.com/ReVanced/revanced-manager/compare/v1.23.0-dev.4...v1.23.0-dev.5) (2024-11-05)

### Features

* Import and export manager settings ([#2268](https://github.com/ReVanced/revanced-manager/issues/2268)) ([a45d959](a45d9598cc))
2024-11-05 18:52:52 +00:00
a45d9598cc feat: Import and export manager settings (#2268) 2024-11-05 19:43:35 +01:00
8c8df698d4 chore(release): 1.23.0-dev.4 [skip ci]
# [1.23.0-dev.4](https://github.com/ReVanced/revanced-manager/compare/v1.23.0-dev.3...v1.23.0-dev.4) (2024-10-26)
2024-10-26 15:50:06 +00:00
8d0d782ab5 build(Needs bump): Bump ReVanced Patcher (#2242)
Co-authored-by: aAbed <aabedhkhan@gmail.com>
2024-10-26 17:41:49 +02:00
4db4789a06 chore(release): 1.23.0-dev.3 [skip ci]
# [1.23.0-dev.3](https://github.com/ReVanced/revanced-manager/compare/v1.23.0-dev.2...v1.23.0-dev.3) (2024-10-22)

### Bug Fixes

* Restore apk renaming during compile ([abdd9dc](abdd9dc430))
2024-10-22 16:16:20 +00:00
abdd9dc430 fix: Restore apk renaming during compile
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-10-22 23:01:51 +07:00
5193042e6b chore(release): 1.23.0-dev.2 [skip ci]
# [1.23.0-dev.2](https://github.com/ReVanced/revanced-manager/compare/v1.23.0-dev.1...v1.23.0-dev.2) (2024-10-21)

### Features

* Show changelogs from the latest to the last used patches version ([#2219](https://github.com/ReVanced/revanced-manager/issues/2219)) ([daba737](daba737ecb))
2024-10-21 10:27:00 +00:00
daba737ecb feat: Show changelogs from the latest to the last used patches version (#2219) 2024-10-21 17:17:23 +07:00
bd96701103 chore: Sync translations (#2233) 2024-10-21 17:16:15 +07:00
69c20b74cf chore: Restore format consistency 2024-10-21 04:27:53 +07:00
7297436ab4 ci: Update config 2024-10-21 03:18:21 +07:00
a329626715 build(deps-dev): bump semantic-release from 23.1.1 to 24.1.3 (#2265)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-20 03:47:39 +07:00
50a20d0535 ci: Use semantic-release-pub for updating build number (#2263) 2024-10-19 05:34:26 +02:00
c52edc99c0 chore(release): 1.23.0-dev.1 [skip ci]
# [1.23.0-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.22.1-dev.2...v1.23.0-dev.1) (2024-10-17)

### Bug Fixes

* Don't show toasts when export cancelled ([#2230](https://github.com/ReVanced/revanced-manager/issues/2230)) ([bd79496](bd79496433))
* missing parameter in translations ([1c6c5d5](1c6c5d53ae))

### Features

* Also show new patches in the removed patches dialog ([#2257](https://github.com/ReVanced/revanced-manager/issues/2257)) ([8872165](8872165a99))
2024-10-17 19:10:51 +00:00
1c6c5d53ae fix: missing parameter in translations 2024-10-18 00:02:54 +05:45
1b110e5bd5 fixup: string correction 2024-10-17 22:00:11 +05:45
8872165a99 feat: Also show new patches in the removed patches dialog (#2257)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2024-10-17 17:48:19 +02:00
bd79496433 fix: Don't show toasts when export cancelled (#2230) 2024-10-17 17:48:04 +02:00
820cd720b5 chore: Modernize project setup
Update dependencies, move to Gradle KTs and overall improve build and project files.
2024-10-06 03:14:12 +02:00
31ff1e0492 chore: Sync translations (#2200) 2024-10-01 00:02:20 +07:00
8b429f03c2 ci: Use the correct expression to grab translation files (#2232) 2024-09-30 23:59:43 +07:00
a68a83940d chore(release): 1.22.1-dev.2 [skip ci]
## [1.22.1-dev.2](https://github.com/ReVanced/revanced-manager/compare/v1.22.1-dev.1...v1.22.1-dev.2) (2024-09-19)

### Bug Fixes

* Get changelogs for alternative sources ([#1766](https://github.com/ReVanced/revanced-manager/issues/1766)) ([c729842](c7298424e5))
* Resolve EACCESS error in special cases ([#2135](https://github.com/ReVanced/revanced-manager/issues/2135)) ([1f95767](1f95767aeb))
2024-09-19 23:07:33 +00:00
1f95767aeb fix: Resolve EACCESS error in special cases (#2135)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2024-09-20 00:59:27 +02:00
c7298424e5 fix: Get changelogs for alternative sources (#1766) 2024-09-20 00:57:07 +02:00
d53f8cf130 chore(release): 1.22.1-dev.1 [skip ci]
## [1.22.1-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.22.0...v1.22.1-dev.1) (2024-09-17)

### Bug Fixes

* Fix the connectivity check toast again ([#2216](https://github.com/ReVanced/revanced-manager/issues/2216)) ([a7e2281](a7e2281805))
2024-09-17 12:56:13 +00:00
a7e2281805 fix: Fix the connectivity check toast again (#2216) 2024-09-17 19:48:05 +07:00
0ce23d2d60 chore(release): 1.22.0 [skip ci]
# [1.22.0](https://github.com/ReVanced/revanced-manager/compare/v1.21.2...v1.22.0) (2024-09-09)

### Bug Fixes

* Bump SDK of each plugin using SDK lower than 31 ([01e4a76](01e4a76caa)), closes [/github.com/flutter/flutter/issues/153281#issuecomment-2292201697](https://github.com//github.com/flutter/flutter/issues/153281/issues/issuecomment-2292201697)
* Empty “tmp-XXXXXX” directory keeps growing in cacheDir ([#2194](https://github.com/ReVanced/revanced-manager/issues/2194)) ([f5a12e0](f5a12e01bd))
* Fix missing notification icon when shrinking resouces ([#2195](https://github.com/ReVanced/revanced-manager/issues/2195)) ([224be29](224be29a3d))
* Handle selecting files and folders for patch options correctly ([#2144](https://github.com/ReVanced/revanced-manager/issues/2144)) ([f1c2f41](f1c2f4146c))
* Lack of connectivity toast not showing due to incorrect comparison ([81f05e1](81f05e1b19))
* Migrate to onPopInvokedWithResult ([43d5888](43d5888182))
* Move temporary files outside of the cache directory ([#2193](https://github.com/ReVanced/revanced-manager/issues/2193)) ([1ef1f8d](1ef1f8d47a))
* Reland commit 01e4a76caa06b4ab7934cc786ca57204501dd983 ([3dc695e](3dc695eafb))
* Specify our own FGS Type ([37c912b](37c912b598))

### Features

* Improve "Installation incompatible" dialog message ([#2164](https://github.com/ReVanced/revanced-manager/issues/2164)) ([51c0f14](51c0f14055))
* Support Flutter 3.24 ([3d8318d](3d8318da14))
2024-09-09 10:59:13 +00:00
d0fe57970f chore: Merge branch dev to main (#2149) 2024-09-09 13:51:11 +03:00
4dcdc57ffd chore(release): 1.22.0-dev.4 [skip ci]
# [1.22.0-dev.4](https://github.com/ReVanced/revanced-manager/compare/v1.22.0-dev.3...v1.22.0-dev.4) (2024-09-09)

### Bug Fixes

* Move temporary files outside of the cache directory ([#2193](https://github.com/ReVanced/revanced-manager/issues/2193)) ([1ef1f8d](1ef1f8d47a))
2024-09-09 10:50:05 +00:00
1ef1f8d47a fix: Move temporary files outside of the cache directory (#2193)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2024-09-09 17:42:03 +07:00
d688f38a63 chore: Sync translations (#2166)
Signed-off-by: validcube <pun.butrach@gmail.com>
Co-authored-by: validcube <pun.butrach@gmail.com>
2024-09-09 14:14:59 +07:00
582db18d83 chore(release): 1.22.0-dev.3 [skip ci]
# [1.22.0-dev.3](https://github.com/ReVanced/revanced-manager/compare/v1.22.0-dev.2...v1.22.0-dev.3) (2024-09-07)

### Bug Fixes

* Empty “tmp-XXXXXX” directory keeps growing in cacheDir ([#2194](https://github.com/ReVanced/revanced-manager/issues/2194)) ([f5a12e0](f5a12e01bd))
2024-09-07 03:09:04 +00:00
f5a12e01bd fix: Empty “tmp-XXXXXX” directory keeps growing in cacheDir (#2194) 2024-09-07 09:59:37 +07:00
09fd9c4e04 chore(release): 1.22.0-dev.2 [skip ci]
# [1.22.0-dev.2](https://github.com/ReVanced/revanced-manager/compare/v1.22.0-dev.1...v1.22.0-dev.2) (2024-09-06)

### Bug Fixes

* Bump SDK of each plugin using SDK lower than 31 ([01e4a76](01e4a76caa)), closes [/github.com/flutter/flutter/issues/153281#issuecomment-2292201697](https://github.com//github.com/flutter/flutter/issues/153281/issues/issuecomment-2292201697)
* Fix missing notification icon when shrinking resouces ([#2195](https://github.com/ReVanced/revanced-manager/issues/2195)) ([224be29](224be29a3d))
* Lack of connectivity toast not showing due to incorrect comparison ([81f05e1](81f05e1b19))
* Migrate to onPopInvokedWithResult ([43d5888](43d5888182))
* Reland commit 01e4a76caa06b4ab7934cc786ca57204501dd983 ([3dc695e](3dc695eafb))
* Specify our own FGS Type ([37c912b](37c912b598))

### Features

* Support Flutter 3.24 ([3d8318d](3d8318da14))
2024-09-06 14:53:03 +00:00
43d5888182 fix: Migrate to onPopInvokedWithResult
Reference: https://docs.flutter.dev/release/breaking-changes/popscope-with-result
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-09-06 21:44:56 +07:00
646feae0ec build: Bump dependency and resolve removed function(s)
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-09-06 21:44:55 +07:00
e73ce99f1d build: Restrict Flutter 4.0.0
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-09-06 21:44:55 +07:00
af2d8226de docs: Specify Android 8.x version & CPU arch requirements
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-09-06 21:44:55 +07:00
37c912b598 fix: Specify our own FGS Type
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-09-06 21:44:54 +07:00
3102272a31 build: Reduce clutter in buildTypes
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-09-06 21:44:54 +07:00
3dc695eafb fix: Reland commit 01e4a76caa06b4ab7934cc786ca57204501dd983
Amended: Remove secret @ Fri Sep 6 21:06:58 2024 +0700

Signed-off-by: validcube <pun.butrach@gmail.com>
2024-09-06 21:44:33 +07:00
6f0721b066 chore: Temporary disable workaround for testing
The local enviroment deemed unreliable, before applying it doesn't work - after applying the workaround, it work - after removing the workaround with clearing cache, it doesn't work. (what?)

Signed-off-by: validcube <pun.butrach@gmail.com>
2024-09-06 20:52:21 +07:00
3dfe6b1080 ci: Attempt to group all dependabot dependency update
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-09-06 20:44:40 +07:00
02e5641227 build: Bump Gradle to v8.10
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-09-06 20:18:53 +07:00
01e4a76caa fix: Bump SDK of each plugin using SDK lower than 31
Reference: https://github.com/flutter/flutter/issues/153281#issuecomment-2292201697
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-09-06 20:12:01 +07:00
59838a51ca ci: Potientially improve Gradle detection
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-09-06 19:27:55 +07:00
224be29a3d fix: Fix missing notification icon when shrinking resouces (#2195) 2024-09-06 18:10:30 +07:00
921052acce build(deps): bump burrunan/gradle-cache-action from 1 to 2 (#2177)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-03 06:02:32 +07:00
81f05e1b19 fix: Lack of connectivity toast not showing due to incorrect comparison
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-08-28 00:26:41 +07:00
b769a66d16 chore: Sync translations (#2090)
Signed-off-by: validcube <pun.butrach@gmail.com>
Co-authored-by: validcube <pun.butrach@gmail.com>
2024-08-27 22:01:29 +07:00
5d612fe790 ci: Run nuke in correct steps 2024-08-27 21:11:52 +07:00
61251deffa ci: Fix permission not given to Slang 2024-08-27 21:09:33 +07:00
f2931443d9 ci: Fix translation normalization feature (#2165) 2024-08-27 21:04:15 +07:00
3d8318da14 feat: Support Flutter 3.24
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-08-27 21:01:41 +07:00
f6bfe0d3f9 chore(release): 1.22.0-dev.1 [skip ci]
# [1.22.0-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.21.3-dev.1...v1.22.0-dev.1) (2024-08-27)

### Features

* Improve "Installation incompatible" dialog message ([#2164](https://github.com/ReVanced/revanced-manager/issues/2164)) ([51c0f14](51c0f14055))
2024-08-27 08:07:13 +00:00
51c0f14055 feat: Improve "Installation incompatible" dialog message (#2164) 2024-08-27 09:59:18 +02:00
2a6dc09a9b chore(release): 1.21.3-dev.1 [skip ci]
## [1.21.3-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.21.2...v1.21.3-dev.1) (2024-08-18)

### Bug Fixes

* Handle selecting files and folders for patch options correctly ([#2144](https://github.com/ReVanced/revanced-manager/issues/2144)) ([f1c2f41](f1c2f4146c))
2024-08-18 08:19:05 +00:00
f1c2f4146c fix: Handle selecting files and folders for patch options correctly (#2144) 2024-08-18 12:11:10 +04:00
2f46b3c84e chore(release): 1.21.2 [skip ci]
## [1.21.2](https://github.com/ReVanced/revanced-manager/compare/v1.21.1...v1.21.2) (2024-08-11)

### Bug Fixes

* Add haptics to save last APK switch ([#2133](https://github.com/ReVanced/revanced-manager/issues/2133)) ([e063b3d](e063b3d102))
* Don't crash installation when saving last APK is disabled ([#2128](https://github.com/ReVanced/revanced-manager/issues/2128)) ([427928e](427928e542))
* Don't crash when installing the last patched APK ([#2131](https://github.com/ReVanced/revanced-manager/issues/2131)) ([cb722f2](cb722f2634))
* Unable to scroll in the removed patches dialog ([#2113](https://github.com/ReVanced/revanced-manager/issues/2113)) ([295c5a7](295c5a74ea))
2024-08-11 16:08:42 +00:00
5f388abf95 chore: Merge branch dev to main (#2118) 2024-08-11 09:00:47 -07:00
7acdb8e660 chore(release): 1.21.2-dev.3 [skip ci]
## [1.21.2-dev.3](https://github.com/ReVanced/revanced-manager/compare/v1.21.2-dev.2...v1.21.2-dev.3) (2024-08-11)

### Bug Fixes

* Add haptics to save last APK switch ([#2133](https://github.com/ReVanced/revanced-manager/issues/2133)) ([e063b3d](e063b3d102))
* Don't crash when installing the last patched APK ([#2131](https://github.com/ReVanced/revanced-manager/issues/2131)) ([cb722f2](cb722f2634))
2024-08-11 15:59:16 +00:00
e063b3d102 fix: Add haptics to save last APK switch (#2133) 2024-08-11 08:51:18 -07:00
cb722f2634 fix: Don't crash when installing the last patched APK (#2131) 2024-08-11 08:51:02 -07:00
0499d2b08a chore(release): 1.21.2-dev.2 [skip ci]
## [1.21.2-dev.2](https://github.com/ReVanced/revanced-manager/compare/v1.21.2-dev.1...v1.21.2-dev.2) (2024-08-11)

### Bug Fixes

* Don't crash installation when saving last APK is disabled ([#2128](https://github.com/ReVanced/revanced-manager/issues/2128)) ([427928e](427928e542))
2024-08-11 15:39:29 +00:00
427928e542 fix: Don't crash installation when saving last APK is disabled (#2128) 2024-08-11 19:31:18 +04:00
ceb9d66f17 chore(release): 1.21.2-dev.1 [skip ci]
## [1.21.2-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.21.1...v1.21.2-dev.1) (2024-08-06)

### Bug Fixes

* Unable to scroll in the removed patches dialog ([#2113](https://github.com/ReVanced/revanced-manager/issues/2113)) ([295c5a7](295c5a74ea))
2024-08-06 00:55:10 +00:00
295c5a74ea fix: Unable to scroll in the removed patches dialog (#2113) 2024-08-06 03:46:01 +03:00
400df69528 chore(release): 1.21.1 [skip ci]
## [1.21.1](https://github.com/ReVanced/revanced-manager/compare/v1.21.0...v1.21.1) (2024-07-29)

### Bug Fixes

* Revert commit b26760b2 to fix file and folder selection ([e707e51](e707e51719))
2024-07-29 20:47:18 +00:00
5bc7b135d5 chore: Merge branch dev to main (#2092) 2024-07-30 02:22:56 +05:45
6dce353d78 chore(release): 1.21.1-dev.1 [skip ci]
## [1.21.1-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.21.0...v1.21.1-dev.1) (2024-07-29)

### Bug Fixes

* Revert commit b26760b2 to fix file and folder selection ([e707e51](e707e51719))
2024-07-29 20:12:35 +00:00
e707e51719 fix: Revert commit b26760b2 to fix file and folder selection 2024-07-29 22:00:25 +02:00
1cea9600a2 chore(release): 1.21.0 [skip ci]
# [1.21.0](https://github.com/ReVanced/revanced-manager/compare/v1.20.1...v1.21.0) (2024-07-29)

### Bug Fixes

* Add missing import to patch options field ([d60f9aa](d60f9aa1d8))
* Adjust scroll from clipping children form fields in `AlertDialog` from `showSourcesDialog` ([#1782](https://github.com/ReVanced/revanced-manager/issues/1782)) ([bbeb836](bbeb836923))
* Cache external API calls  ([#1911](https://github.com/ReVanced/revanced-manager/issues/1911)) ([2c3e2e6](2c3e2e639f))
* Change problematic translation string ([6b03f3a](6b03f3a169))
* Correct architecture to armeabi-v7a ([63c6412](63c6412736))
* Download latest integrations non-pre-release ([4a72267](4a72267d41))
* Follow language update immediately ([#1944](https://github.com/ReVanced/revanced-manager/issues/1944)) ([c13827e](c13827e8e1))
* Follow system theme immediately ([#1942](https://github.com/ReVanced/revanced-manager/issues/1942)) ([694f2a9](694f2a9fae))
* Handle selecting files and folders for patch options correctly ([#1941](https://github.com/ReVanced/revanced-manager/issues/1941)) ([b26760b](b26760b216))
* Increase dashboard RefreshIndicator edge offset ([#1859](https://github.com/ReVanced/revanced-manager/issues/1859)) ([232b702](232b702789))
* Patching Screen draw-behind Navigation Bar ([#1945](https://github.com/ReVanced/revanced-manager/issues/1945)) ([f1b25d0](f1b25d09da))
* Restore consistency with the app ([ea9654e](ea9654edec))
* SecurityException when patching application ([#1856](https://github.com/ReVanced/revanced-manager/issues/1856)) ([e0a6de2](e0a6de2c2b))
* Select previously applied patches when loading patch selection ([#1865](https://github.com/ReVanced/revanced-manager/issues/1865)) ([7ef8f04](7ef8f0454b))
* Unable to install application regardless of preference ([c7627ce](c7627ced8e))
* Unsupported patch toast says "patchItem.unsupportedPatchVersion" ([#2011](https://github.com/ReVanced/revanced-manager/issues/2011)) ([3209c0e](3209c0e430))
* Update dialog shows dev version & loading gets stuck in certain circumstances ([#1792](https://github.com/ReVanced/revanced-manager/issues/1792)) ([fc52560](fc52560244))

### Features

* Add ability to set `null` in patch options ([#1947](https://github.com/ReVanced/revanced-manager/issues/1947)) ([5c68d51](5c68d513a3))
* Include primary architecture in external search ([#2068](https://github.com/ReVanced/revanced-manager/issues/2068)) ([23690a9](23690a98df))
* open browser when clicking on changelog link ([bc300d8](bc300d81d9))
* Save last patched app ([#1414](https://github.com/ReVanced/revanced-manager/issues/1414)) ([7720408](77204087bb))
* Support patching on ARMv7a ([a766352](a7663524e6))
2024-07-29 18:38:53 +00:00
d81808ad7b chore: Merge branch dev to main (#1857) 2024-07-30 01:30:51 +07:00
ea9654edec fix: Restore consistency with the app 2024-07-30 01:30:59 +07:00
ced37f7c76 chore(release): 1.21.0-dev.9 [skip ci]
# [1.21.0-dev.9](https://github.com/ReVanced/revanced-manager/compare/v1.21.0-dev.8...v1.21.0-dev.9) (2024-07-29)

### Bug Fixes

* Patching Screen draw-behind Navigation Bar ([#1945](https://github.com/ReVanced/revanced-manager/issues/1945)) ([f1b25d0](f1b25d09da))
2024-07-29 18:24:51 +00:00
4c6e214bf6 refactor: Remove empty keys 2024-07-30 01:16:48 +07:00
d10abd4829 ci: Remove redundancy 2024-07-30 01:15:23 +07:00
25f1640fd6 ci: Remove empty value 2024-07-30 01:13:05 +07:00
5690adc0de chore: Sync translations (#2005) 2024-07-30 01:02:02 +07:00
f1b25d09da fix: Patching Screen draw-behind Navigation Bar (#1945)
Co-authored-by: surya-technovert <surya.m@technovert.com>
2024-07-30 00:58:40 +07:00
96a21a5564 chore(release): 1.21.0-dev.8 [skip ci]
# [1.21.0-dev.8](https://github.com/ReVanced/revanced-manager/compare/v1.21.0-dev.7...v1.21.0-dev.8) (2024-07-29)

### Bug Fixes

* Correct architecture to armeabi-v7a ([63c6412](63c6412736))
* Unable to install application regardless of preference ([c7627ce](c7627ced8e))

### Features

* Support patching on ARMv7a ([a766352](a7663524e6))
2024-07-29 17:51:14 +00:00
0b21bb9807 build: Allocate more memory 2024-07-30 00:43:25 +07:00
557ee8d472 ci: Fix accident 2024-07-30 00:22:22 +07:00
6da3751a8a ci: Don't include Slang's analyser generated file 2024-07-30 00:17:29 +07:00
cf708de005 ci: Remove deleted strings from translated file 2024-07-30 00:12:43 +07:00
b649c4539f chore: Remove all long forever deleted translated strings 2024-07-30 00:11:09 +07:00
c63342bc21 chore: Normalise all translated strings 2024-07-30 00:03:06 +07:00
9f56b277ca ci: Use Slang's Normalisation feature 2024-07-30 00:03:06 +07:00
73c92b9c72 build: Update multiple dependencies at once
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-07-30 00:03:06 +07:00
c7627ced8e fix: Unable to install application regardless of preference
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-07-30 00:03:06 +07:00
63c6412736 fix: Correct architecture to armeabi-v7a
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-07-30 00:03:05 +07:00
a7663524e6 feat: Support patching on ARMv7a
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-07-30 00:03:05 +07:00
65feb34242 chore(release): 1.21.0-dev.7 [skip ci]
# [1.21.0-dev.7](https://github.com/ReVanced/revanced-manager/compare/v1.21.0-dev.6...v1.21.0-dev.7) (2024-07-29)

### Features

* Include primary architecture in external search ([#2068](https://github.com/ReVanced/revanced-manager/issues/2068)) ([23690a9](23690a98df))
2024-07-29 16:04:12 +00:00
23690a98df feat: Include primary architecture in external search (#2068) 2024-07-29 22:56:00 +07:00
7449d4e318 docs: Link user to the latest version of app (#2077) 2024-07-25 09:27:29 +07:00
c6f9e36f4b refactor: Migrate deprecated member 2024-07-13 17:32:34 +07:00
e9cee0abe2 ci: Prefer installing NPM dependencies from lock 2024-07-13 17:04:43 +07:00
9440f23b55 chore: Remove NDK constraint (#2016)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2024-07-13 16:55:02 +07:00
c67b4b438c build(deps): bump flutter_markdown from 0.7.1 to 0.7.3 (#2022)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-12 14:07:16 +07:00
1bdb820aed refactor: Remove unused strings (#2010) 2024-07-11 11:28:02 +07:00
a28d77bc65 chore(release): 1.21.0-dev.6 [skip ci]
# [1.21.0-dev.6](https://github.com/ReVanced/revanced-manager/compare/v1.21.0-dev.5...v1.21.0-dev.6) (2024-06-30)

### Bug Fixes

* Add missing import to patch options field ([d60f9aa](d60f9aa1d8))
* Follow system theme immediately ([#1942](https://github.com/ReVanced/revanced-manager/issues/1942)) ([694f2a9](694f2a9fae))
* Handle selecting files and folders for patch options correctly ([#1941](https://github.com/ReVanced/revanced-manager/issues/1941)) ([b26760b](b26760b216))
* Increase dashboard RefreshIndicator edge offset ([#1859](https://github.com/ReVanced/revanced-manager/issues/1859)) ([232b702](232b702789))
* Select previously applied patches when loading patch selection ([#1865](https://github.com/ReVanced/revanced-manager/issues/1865)) ([7ef8f04](7ef8f0454b))
* Unsupported patch toast says "patchItem.unsupportedPatchVersion" ([#2011](https://github.com/ReVanced/revanced-manager/issues/2011)) ([3209c0e](3209c0e430))

### Features

* Save last patched app ([#1414](https://github.com/ReVanced/revanced-manager/issues/1414)) ([7720408](77204087bb))
2024-06-30 20:45:38 +00:00
d60f9aa1d8 fix: Add missing import to patch options field
Patch options fields was missing the `patch_options_viewmodel` import, preventing building to complete successfully
2024-06-30 13:38:01 -07:00
3209c0e430 fix: Unsupported patch toast says "patchItem.unsupportedPatchVersion" (#2011) 2024-06-30 15:24:25 +07:00
7ef8f0454b fix: Select previously applied patches when loading patch selection (#1865) 2024-06-29 14:40:20 +02:00
232b702789 fix: Increase dashboard RefreshIndicator edge offset (#1859) 2024-06-29 14:40:04 +02:00
694f2a9fae fix: Follow system theme immediately (#1942)
Co-authored-by: surya-technovert <surya.m@technovert.com>
2024-06-29 14:39:00 +02:00
77204087bb feat: Save last patched app (#1414)
Co-authored-by: aAbed <39409020+TheAabedKhan@users.noreply.github.com>
Co-authored-by: Ushie <ushiekane@gmail.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: Mr. X <79870712+n30mrx@users.noreply.github.com>
Co-authored-by: festry0 <153519925+festry0@users.noreply.github.com>
2024-06-29 14:38:00 +02:00
b26760b216 fix: Handle selecting files and folders for patch options correctly (#1941) 2024-06-29 14:34:34 +02:00
3c36950aeb chore(release): 1.21.0-dev.5 [skip ci]
# [1.21.0-dev.5](https://github.com/ReVanced/revanced-manager/compare/v1.21.0-dev.4...v1.21.0-dev.5) (2024-06-26)

### Bug Fixes

* Adjust scroll from clipping children form fields in `AlertDialog` from `showSourcesDialog` ([#1782](https://github.com/ReVanced/revanced-manager/issues/1782)) ([bbeb836](bbeb836923))
2024-06-26 22:53:56 +00:00
bbeb836923 fix: Adjust scroll from clipping children form fields in AlertDialog from showSourcesDialog (#1782) 2024-06-27 05:45:55 +07:00
a99406f0a9 chore(release): 1.21.0-dev.4 [skip ci]
# [1.21.0-dev.4](https://github.com/ReVanced/revanced-manager/compare/v1.21.0-dev.3...v1.21.0-dev.4) (2024-06-24)

### Bug Fixes

* Cache external API calls  ([#1911](https://github.com/ReVanced/revanced-manager/issues/1911)) ([2c3e2e6](2c3e2e639f))
* Follow language update immediately ([#1944](https://github.com/ReVanced/revanced-manager/issues/1944)) ([c13827e](c13827e8e1))
* SecurityException when patching application ([#1856](https://github.com/ReVanced/revanced-manager/issues/1856)) ([e0a6de2](e0a6de2c2b))
* Update dialog shows dev version & loading gets stuck in certain circumstances ([#1792](https://github.com/ReVanced/revanced-manager/issues/1792)) ([fc52560](fc52560244))

### Features

* Add ability to set `null` in patch options ([#1947](https://github.com/ReVanced/revanced-manager/issues/1947)) ([5c68d51](5c68d513a3))
2024-06-24 17:03:46 +00:00
73368b58be build: Support for Flutter 3.22 (#1921)
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-06-24 23:55:37 +07:00
ca14e77ba3 chore: Sync translations (#1899) 2024-06-24 23:55:03 +07:00
cafdfcda47 ci: Don't fail validation on unimportant warnings 2024-06-24 23:52:23 +07:00
5c68d513a3 feat: Add ability to set null in patch options (#1947) 2024-06-24 23:37:16 +07:00
fc52560244 fix: Update dialog shows dev version & loading gets stuck in certain circumstances (#1792)
Signed-off-by: validcube <pun.butrach@gmail.com>
Co-authored-by: validcube <pun.butrach@gmail.com>
2024-06-19 14:44:09 +07:00
46f6a49a7a ci: Always run on dev branch only 2024-06-15 17:36:25 +07:00
c13827e8e1 fix: Follow language update immediately (#1944)
Co-authored-by: surya-technovert <surya.m@technovert.com>
2024-06-15 17:21:47 +07:00
e0a6de2c2b fix: SecurityException when patching application (#1856) 2024-05-28 11:36:31 +07:00
afdba00722 build: Fix invalid Gradle wrapper checksum (#1919) 2024-05-28 09:31:48 +07:00
9084c71aa3 build: Bump dependencies 2024-05-26 01:21:14 +02:00
8fc5fb6a80 docs: Improve issue templates 2024-05-26 00:43:38 +02:00
5f762c5442 build: Update Dart dependencies
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-05-19 19:15:35 +07:00
8b21ec1ea3 ci: Switch to Flutter instead 2024-05-19 18:54:48 +07:00
e83fbb864e ci: Run slang first before validating translation 2024-05-19 18:46:49 +07:00
f03af17f71 docs: Fix issue template mistakes (#1910) 2024-05-19 01:33:18 +07:00
2c3e2e639f fix: Cache external API calls (#1911) 2024-05-18 10:52:13 -07:00
cc85b393dc docs: Fix punctuation in issue forms (#1909) 2024-05-18 01:18:29 +07:00
fa6ad214f9 chore(release): 1.21.0-dev.3 [skip ci]
# [1.21.0-dev.3](https://github.com/ReVanced/revanced-manager/compare/v1.21.0-dev.2...v1.21.0-dev.3) (2024-05-07)

### Bug Fixes

* Change problematic translation string ([6b03f3a](6b03f3a169))
2024-05-07 14:01:16 +00:00
3ceb63be1d build: Downgrade intl to 0.18.1
1.19.0 is for beta channel atm
2024-05-07 20:53:43 +07:00
200483d733 ci: Always validate Dart file during translation 2024-05-07 20:51:34 +07:00
6b03f3a169 fix: Change problematic translation string 2024-05-07 20:51:01 +07:00
612a3bab49 build: Safely upgrade dependencies 2024-05-07 20:50:26 +07:00
3ac08512f3 chore: Sync translations (#1888)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2024-05-06 21:24:51 +07:00
1adc673c44 chore: Sync translations (#1842) 2024-04-29 10:24:25 +07:00
1aa1bd84cf chore(release): 1.21.0-dev.2 [skip ci]
# [1.21.0-dev.2](https://github.com/ReVanced/revanced-manager/compare/v1.21.0-dev.1...v1.21.0-dev.2) (2024-04-28)

### Bug Fixes

* Download latest integrations non-pre-release ([4a72267](4a72267d41))
2024-04-28 21:49:47 +00:00
4a72267d41 fix: Download latest integrations non-pre-release 2024-04-28 23:40:28 +02:00
7e0f18e3b7 build: Bump dependencies 2024-04-28 23:39:20 +02:00
aab5d4411b docs: Remove duplicate section in README (#1879)
[skip ci]
2024-04-23 18:29:06 +02:00
e5d83f424a chore(release): 1.21.0-dev.1 [skip ci]
# [1.21.0-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.20.1...v1.21.0-dev.1) (2024-04-16)

### Features

* open browser when clicking on changelog link ([bc300d8](bc300d81d9))
2024-04-16 14:39:51 +00:00
bc300d81d9 feat: open browser when clicking on changelog link 2024-04-16 20:00:46 +05:30
5e6cc86c7e build(deps-dev): bump semantic-release from 23.0.6 to 23.0.7 (#1844)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-05 14:57:28 +07:00
d91ea62903 build(deps): bump flutter_local_notifications from 16.3.2 to 17.0.0 (#1848)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-05 14:57:16 +07:00
f00dc4d3e6 build(deps): bump flutter_markdown from 0.6.20+1 to 0.6.22+1 (#1851)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-05 14:55:48 +07:00
6c0c961ca0 build(deps): bump build_runner from 2.4.8 to 2.4.9 (#1850)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-05 14:54:31 +07:00
a99de99202 build(deps): bump share_plus from 7.2.2 to 8.0.2 (#1853)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-05 14:53:59 +07:00
a31ec9d1fe build(deps): bump permission_handler from 11.3.0 to 11.3.1 (#1852)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-05 14:53:30 +07:00
a23f032fd2 chore(release): 1.20.1 [skip ci]
## [1.20.1](https://github.com/ReVanced/revanced-manager/compare/v1.20.0...v1.20.1) (2024-04-05)

### Bug Fixes

* Don't crash on patch ([a7e481c](a7e481c827))
2024-04-05 02:52:15 +00:00
95a8234e2d chore: Merge branch dev to main (#1855) 2024-04-05 05:43:17 +03:00
a7e481c827 fix: Don't crash on patch
This reverts commit 7833a0d552d05946f50434e799c46272e6eb30a3.
2024-04-05 05:35:36 +03:00
975870e254 chore(release): 1.20.0 [skip ci]
# [1.20.0](https://github.com/ReVanced/revanced-manager/compare/v1.19.3...v1.20.0) (2024-04-05)

### Bug Fixes

* disable proguard obfuscation ([401646a](401646ace4))
* Do not crash when selecting an APK from storage ([#1768](https://github.com/ReVanced/revanced-manager/issues/1768)) ([8564c1a](8564c1a72e))
* Don't translation ReVanced repository ([c265794](c265794d0e))
* Ensure safe area usage in Changelogs Modal Bottom Sheet ([#1772](https://github.com/ReVanced/revanced-manager/issues/1772)) ([c981cb4](c981cb4a41))
* Pre-releases changelog being shown ([#1767](https://github.com/ReVanced/revanced-manager/issues/1767)) ([add49e1](add49e14fb))
* Replace Spacer with Expanded to avoid overflow ([#1791](https://github.com/ReVanced/revanced-manager/issues/1791)) ([6f70a07](6f70a07970))
* Set text colour on dropdown menu for Custom Value ([966796d](966796dfec)), closes [#1584](https://github.com/ReVanced/revanced-manager/issues/1584)
* **ui:** Support free-scroll and auto-scroll for the installer logs ([#1736](https://github.com/ReVanced/revanced-manager/issues/1736)) ([#1836](https://github.com/ReVanced/revanced-manager/issues/1836)) ([025ff52](025ff527ee))

### Features

* Improve language update settings ([#1838](https://github.com/ReVanced/revanced-manager/issues/1838)) ([f9e6ef3](f9e6ef3fd3))
2024-04-05 01:19:30 +00:00
b7c838fd25 chore: Merge branch dev to main (#1763) 2024-04-05 04:11:17 +03:00
3776674eb4 chore(release): 1.20.0-dev.1 [skip ci]
# [1.20.0-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.19.4-dev.7...v1.20.0-dev.1) (2024-04-05)

### Bug Fixes

* Do not crash when selecting an APK from storage ([#1768](https://github.com/ReVanced/revanced-manager/issues/1768)) ([8564c1a](8564c1a72e))

### Features

* Improve language update settings ([#1838](https://github.com/ReVanced/revanced-manager/issues/1838)) ([f9e6ef3](f9e6ef3fd3))
2024-04-05 01:10:39 +00:00
4293e27681 chore: Sync translations (#1804) 2024-04-05 04:03:11 +03:00
8564c1a72e fix: Do not crash when selecting an APK from storage (#1768) 2024-04-05 03:58:59 +03:00
f9e6ef3fd3 feat: Improve language update settings (#1838) 2024-04-05 03:55:16 +03:00
e80ee09893 chore(release): 1.19.4-dev.7 [skip ci]
## [1.19.4-dev.7](https://github.com/ReVanced/revanced-manager/compare/v1.19.4-dev.6...v1.19.4-dev.7) (2024-04-04)

### Bug Fixes

* **ui:** Support free-scroll and auto-scroll for the installer logs ([#1736](https://github.com/ReVanced/revanced-manager/issues/1736)) ([#1836](https://github.com/ReVanced/revanced-manager/issues/1836)) ([025ff52](025ff527ee))
2024-04-04 02:35:20 +00:00
025ff527ee fix(ui): Support free-scroll and auto-scroll for the installer logs (#1736) (#1836) 2024-04-04 09:27:47 +07:00
35fdbb5988 chore(release): 1.19.4-dev.6 [skip ci]
## [1.19.4-dev.6](https://github.com/ReVanced/revanced-manager/compare/v1.19.4-dev.5...v1.19.4-dev.6) (2024-04-02)

### Bug Fixes

* disable proguard obfuscation ([401646a](401646ace4))
2024-04-02 16:19:30 +00:00
401646ace4 fix: disable proguard obfuscation 2024-04-02 18:09:38 +02:00
a62a8852e7 build(deps): bump connectivity_plus from 5.0.2 to 6.0.1 (#1808)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 11:49:03 +07:00
04a3df3642 build(deps): bump package_info_plus from 5.0.1 to 6.0.0 (#1806)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 11:48:46 +07:00
ecb041187b build(deps-dev): bump semantic-release from 23.0.2 to 23.0.6 (#1816)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 11:43:57 +07:00
1fd44e1cfc build(deps-dev): bump @droidsolutions-oss/semantic-release-update-file from 1.4.0-beta.1 to 1.4.0 (#1815)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 11:43:29 +07:00
fb29036d0a build(deps): bump flutter_lints from 3.0.1 to 3.0.2 (#1805)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 10:18:52 +07:00
aba2ed4378 ci: Don't specify bundle 2024-03-30 03:16:39 +03:00
076e17d670 chore: Don't preserve hierachy in Crowdin 2024-03-30 03:12:06 +03:00
a971cbd519 chore(release): 1.19.4-dev.5 [skip ci]
## [1.19.4-dev.5](https://github.com/ReVanced/revanced-manager/compare/v1.19.4-dev.4...v1.19.4-dev.5) (2024-03-28)

### Bug Fixes

* Don't translation ReVanced repository ([c265794](c265794d0e))
2024-03-28 06:23:34 +00:00
c265794d0e fix: Don't translation ReVanced repository
Regex:

,
    "patcherContributors":.*,
    "patchesContributors":.*,
    "integrationsContributors":.*,
    "cliContributors":.*,
    "managerContributors":.*
2024-03-28 06:13:05 +00:00
ecadb80113 chore(release): 1.19.4-dev.4 [skip ci]
## [1.19.4-dev.4](https://github.com/ReVanced/revanced-manager/compare/v1.19.4-dev.3...v1.19.4-dev.4) (2024-03-28)

### Bug Fixes

* Set text colour on dropdown menu for Custom Value ([966796d](966796dfec)), closes [#1584](https://github.com/ReVanced/revanced-manager/issues/1584)
2024-03-28 05:35:25 +00:00
9a66357f7a refactor: slight format change
This is so unreadable, there must've been a better way to do this.
2024-03-28 05:26:35 +00:00
63d83a43ad chore: Remove unused en_US file
This file contains absolulely nothing, whereabout: Unknown.
2024-03-28 05:20:32 +00:00
7833a0d552 build: Switch flutter_background to official sources
https://github.com/JulianAssmann/flutter_background/pull/79 is merged.
2024-03-28 05:17:49 +00:00
966796dfec fix: Set text colour on dropdown menu for Custom Value
This fix #1584 and a continuation of acb1e2434b28efca852297e0a0a8ffce155e67e4

Signed-off-by: Pun Butrach <pun.butrach@gmail.com>
2024-03-28 05:15:02 +00:00
2d19f36901 chore(release): 1.19.4-dev.3 [skip ci]
## [1.19.4-dev.3](https://github.com/ReVanced/revanced-manager/compare/v1.19.4-dev.2...v1.19.4-dev.3) (2024-03-28)

### Bug Fixes

* Replace Spacer with Expanded to avoid overflow ([#1791](https://github.com/ReVanced/revanced-manager/issues/1791)) ([6f70a07](6f70a07970))
2024-03-28 04:45:28 +00:00
6f70a07970 fix: Replace Spacer with Expanded to avoid overflow (#1791) 2024-03-28 11:38:07 +07:00
e85ed5a8e3 build: Bump language_code to v0.4.2
This fix #1714 regarding country name of es-AR (Argentina) does not exist.

Signed-off-by: Pun Butrach <pun.butrach@gmail.com>
2024-03-28 04:06:52 +00:00
ad416387c2 ci(Crowdin): Specify bundle to download 2024-03-28 02:02:55 +03:00
58d8e7f34f build: Bump dependencies (#1796) 2024-03-27 13:46:37 +01:00
bb105b5662 chore(Branding): Update ReVanced Logo name (#1794) 2024-03-27 17:33:42 +07:00
a71a930125 chore(release): 1.19.4-dev.2 [skip ci]
## [1.19.4-dev.2](https://github.com/ReVanced/revanced-manager/compare/v1.19.4-dev.1...v1.19.4-dev.2) (2024-03-23)

### Bug Fixes

* Ensure safe area usage in Changelogs Modal Bottom Sheet ([#1772](https://github.com/ReVanced/revanced-manager/issues/1772)) ([c981cb4](c981cb4a41))
2024-03-23 11:03:27 +00:00
af7e39b7f0 chore(i18n): Sync translations (#1719)
Co-authored-by: revanced-bot <github@revanced.app>
2024-03-23 17:55:11 +07:00
c981cb4a41 fix: Ensure safe area usage in Changelogs Modal Bottom Sheet (#1772) 2024-03-23 17:51:17 +07:00
3ea6ef0bbf ci(Dependabot): Add Gradle ecosystem to RVM Flutter 2024-03-22 04:54:57 +00:00
2772a96727 docs(Build): Shorten build_runner args 2024-03-22 04:41:34 +00:00
50b4a5f1d8 ci: Fix PR build success message 2024-03-18 11:51:07 +01:00
e52a6ce734 chore(release): 1.19.4-dev.1 [skip ci]
## [1.19.4-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.19.3...v1.19.4-dev.1) (2024-03-17)

### Bug Fixes

* Pre-releases changelog being shown ([#1767](https://github.com/ReVanced/revanced-manager/issues/1767)) ([add49e1](add49e14fb))
2024-03-17 17:22:14 +00:00
add49e14fb fix: Pre-releases changelog being shown (#1767) 2024-03-17 10:14:22 -07:00
ab13895196 docs(Security): Improve badges' accessibility
Signed-off-by: Pun Butrach <pun.butrach@gmail.com>
2024-03-11 15:05:40 +07:00
846afb420b ci(PR Sync): Use revanced-bot acount
Signed-off-by: Pun Butrach <pun.butrach@gmail.com>
2024-03-11 14:59:29 +07:00
dca8a1dab6 ci(Crowdin): Add workflow change to trigger event
Signed-off-by: Pun Butrach <pun.butrach@gmail.com>
2024-03-11 14:49:46 +07:00
d1c36c1bcc ci(Crowdin): Improve configuration of the workflow
Signed-off-by: Pun Butrach <pun.butrach@gmail.com>
2024-03-11 14:49:13 +07:00
c209c32613 chore(release): 1.19.3 [skip ci]
## [1.19.3](https://github.com/ReVanced/revanced-manager/compare/v1.19.2...v1.19.3) (2024-03-09)

### Bug Fixes

* Copy APK to working directory before trying to write to it ([5cd1cba](5cd1cba668))
2024-03-09 09:16:08 +00:00
9e5e89ac95 chore: Merge branch dev to main (#1756) 2024-03-09 10:09:13 +01:00
7cc6b88e4e chore(release): 1.19.3-dev.1 [skip ci]
## [1.19.3-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.19.2...v1.19.3-dev.1) (2024-03-09)

### Bug Fixes

* Copy APK to working directory before trying to write to it ([5cd1cba](5cd1cba668))
2024-03-09 09:09:02 +00:00
5cd1cba668 fix: Copy APK to working directory before trying to write to it 2024-03-09 10:01:36 +01:00
ca365bac6e build: Bump dependencies to fix generating keystore 2024-03-09 09:58:41 +01:00
f1fc48ce5a chore(release): 1.19.2 [skip ci]
## [1.19.2](https://github.com/ReVanced/revanced-manager/compare/v1.19.1...v1.19.2) (2024-03-09)

### Bug Fixes

* App list is empty if all apps are installed ([#1750](https://github.com/ReVanced/revanced-manager/issues/1750)) ([1f5461f](1f5461fbe5))
* Fix white-screen when trying to install conflicting apps ([4acd738](4acd738353))
2024-03-09 03:55:35 +00:00
316e440d0d chore: Merge branch dev to main (#1739) 2024-03-09 04:48:12 +01:00
95018814a7 ci: Bump dependencies to fix release workflow 2024-03-09 04:44:38 +01:00
b52e49d90a build: Bump dependencies to fix signing issues 2024-03-09 04:39:58 +01:00
4acd738353 fix: Fix white-screen when trying to install conflicting apps
When you tried to install a patched app, but it conflicted with an existing installation, the screen would go blank. This was caused by trying to use an argument on a translation with no arguments.
2024-03-09 04:39:58 +01:00
982249f974 chore(release): 1.19.2-dev.1 [skip ci]
## [1.19.2-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.19.1...v1.19.2-dev.1) (2024-03-08)

### Bug Fixes

* App list is empty if all apps are installed ([#1750](https://github.com/ReVanced/revanced-manager/issues/1750)) ([1f5461f](1f5461fbe5))
2024-03-08 14:49:35 +00:00
1f5461fbe5 fix: App list is empty if all apps are installed (#1750)
Co-authored-by: Nikita <n.petrjakov@niitp.in>
2024-03-08 21:41:16 +07:00
adb7e5663a docs: Fix small mistakes [skip ci] 2024-03-05 14:58:11 +01:00
ffc14f2146 chore: Upgrade dependencies (#1734) 2024-03-05 14:09:02 +07:00
0c57322051 chore(release): 1.19.1 [skip ci]
## [1.19.1](https://github.com/ReVanced/revanced-manager/compare/v1.19.0...v1.19.1) (2024-03-05)

### Bug Fixes

* Keep names for needed classes to fix crash at launch ([eef7016](eef701615b))
2024-03-05 02:29:37 +00:00
a8b7debf8d chore: Merge branch dev to main (#1729) 2024-03-05 03:22:11 +01:00
7ed9787b58 ci: Add workflow to open a PR to main 2024-03-05 03:21:07 +01:00
eef701615b fix: Keep names for needed classes to fix crash at launch 2024-03-05 03:19:20 +01:00
97d8519b8b chore(release): 1.19.0 [skip ci]
# [1.19.0](https://github.com/ReVanced/revanced-manager/compare/v1.18.0...v1.19.0) (2024-03-05)

### Bug Fixes

* added a trailing comma ([975180b](975180b075))
* adjust padding ([3559477](3559477247))
* Allow mounting without Magisk ([3f96608](3f96608398))
* Bump dependencies to support BCS keystore ([6ec6546](6ec6546cc5))
* Do not delete files from post-fs-data.d ([70a1086](70a1086edf))
* Fix patched APKs exports after installation ([1200360](1200360588))
* fix redundant buttons on dialog ([079c0de](079c0defaf))
* Incorrect strings and logics ([#1619](https://github.com/ReVanced/revanced-manager/issues/1619)) ([4f22e88](4f22e88e42))
* **Keystore Password:** textfield title display ([8e52abd](8e52abda9a))
* Mount script causes build to fail ([#1613](https://github.com/ReVanced/revanced-manager/issues/1613)) ([f3c78c2](f3c78c2c24))
* **Patch Option:** Set text colour on dropdown menu ([acb1e24](acb1e2434b))
* **PopScope:** User able to exit patch screen when the installer is still running ([#1663](https://github.com/ReVanced/revanced-manager/issues/1663)) ([eb6d3cd](eb6d3cd64e))
* **Release CI:** truncate the "v" from version ([8595099](85950991ab))
* Show version label correctly ([c72d10a](c72d10ac85))
* Specify that dark theme is dark ([#1699](https://github.com/ReVanced/revanced-manager/issues/1699)) ([d4b15ae](d4b15aee4d))
* Stop patch when signing fails ([#1553](https://github.com/ReVanced/revanced-manager/issues/1553)) ([5b2c551](5b2c55142e))
* **Update Confirmation Sheet:** Add top padding ([9aeb156](9aeb156d92))
* Use correct title size for bottom sheet ([#1687](https://github.com/ReVanced/revanced-manager/issues/1687)) ([3436523](34365239c1))
* Use correct version code & name ([#1647](https://github.com/ReVanced/revanced-manager/issues/1647)) ([d933997](d933997c89))
* use lowercase repo names ([#1626](https://github.com/ReVanced/revanced-manager/issues/1626)) ([edd8602](edd86024b9))

### Features

* Add a toggle for alternative sources ([#1686](https://github.com/ReVanced/revanced-manager/issues/1686)) ([f89c742](f89c742c90))
* Add API migration code ([#1615](https://github.com/ReVanced/revanced-manager/issues/1615)) ([28ae276](28ae2766f0))
* add haptic feedback ([#1459](https://github.com/ReVanced/revanced-manager/issues/1459)) ([7911459](7911459817))
* Allow changing languages ([#1488](https://github.com/ReVanced/revanced-manager/issues/1488)) ([f82c439](f82c439b26))
* Display current app language at the top of the list ([aa0575a](aa0575a637))
* Hide the Install button during installation ([#1633](https://github.com/ReVanced/revanced-manager/issues/1633)) ([3e696d6](3e696d6847))
* Improve app selector and patcher UI ([#1616](https://github.com/ReVanced/revanced-manager/issues/1616)) ([efb2d5e](efb2d5ef32))
* Improve consistency on language selector ([b2119ce](b2119ce60e))
* Improve language distinguishness and resolve language-specific issues ([#1706](https://github.com/ReVanced/revanced-manager/issues/1706)) ([6d866d4](6d866d4424))
* Improve Split APK warning readability ([#1625](https://github.com/ReVanced/revanced-manager/issues/1625)) ([6fd740f](6fd740f8c0))
* Show a dialog when an update is available ([#1654](https://github.com/ReVanced/revanced-manager/issues/1654)) ([c7d975e](c7d975e612))
* Simplify settings strings ([#1618](https://github.com/ReVanced/revanced-manager/issues/1618)) ([0d45fe4](0d45fe4a97))
* Use more appropriate progress values ([2d7026a](2d7026ac7a))
* use native TextField ([9ed43ef](9ed43efe5d))

### Performance Improvements

* Load patched apps as soon as possible ([c94eb7a](c94eb7a48e))

### Reverts

* WillPopScope migration ([3b0fed5](3b0fed55e4))
2024-03-05 01:57:21 +00:00
00210f7f0e chore: Workaround CI bug by setting the expected version 2024-03-05 02:49:42 +01:00
cea4c6c27a chore: Merge dev to main (#1611)
100 commits 
2024-03-05 07:48:42 +07:00
bc83a39b0f chore(release): 1.19.0-dev.21 [skip ci]
# [1.19.0-dev.21](https://github.com/ReVanced/revanced-manager/compare/v1.19.0-dev.20...v1.19.0-dev.21) (2024-03-05)

### Features

* Display current app language at the top of the list ([aa0575a](aa0575a637))
2024-03-05 00:12:19 +00:00
aa0575a637 feat: Display current app language at the top of the list 2024-03-05 03:04:01 +03:00
4ca7b8a7c1 chore(release): 1.19.0-dev.20 [skip ci]
# [1.19.0-dev.20](https://github.com/ReVanced/revanced-manager/compare/v1.19.0-dev.19...v1.19.0-dev.20) (2024-03-04)

### Bug Fixes

* Bump dependencies to support BCS keystore ([6ec6546](6ec6546cc5))
2024-03-04 14:56:19 +00:00
6ec6546cc5 fix: Bump dependencies to support BCS keystore 2024-03-04 15:48:13 +01:00
c5e04cc824 build: Bump dependencies 2024-03-04 13:53:33 +01:00
4fc913eae1 chore(release): 1.19.0-dev.19 [skip ci]
# [1.19.0-dev.19](https://github.com/ReVanced/revanced-manager/compare/v1.19.0-dev.18...v1.19.0-dev.19) (2024-03-04)

### Bug Fixes

* Fix patched APKs exports after installation ([1200360](1200360588))

### Features

* Use more appropriate progress values ([2d7026a](2d7026ac7a))
2024-03-04 12:51:06 +00:00
2d7026ac7a feat: Use more appropriate progress values 2024-03-04 13:43:35 +01:00
1200360588 fix: Fix patched APKs exports after installation 2024-03-04 13:43:34 +01:00
02722fc0be chore(release): 1.19.0-dev.18 [skip ci]
# [1.19.0-dev.18](https://github.com/ReVanced/revanced-manager/compare/v1.19.0-dev.17...v1.19.0-dev.18) (2024-03-04)
2024-03-04 12:25:39 +00:00
2e1de94623 chore: Fix builds 2024-03-04 13:14:30 +01:00
10bae69db6 build(Needs bump): Bump dependencies (#1717) 2024-03-04 12:47:38 +01:00
6dee3aa1b7 docs: Fix consistency issues with other repositories (#1707) 2024-03-04 12:42:46 +01:00
e76418d48d chore: Sync translations (#1701) 2024-03-04 11:28:34 +01:00
52e1020a90 chore(Dependency): Switch language_code back to Upstream version 2024-03-04 13:31:51 +07:00
c170392123 ci(Release): Use ReVanced account instead of semantic-release 2024-03-04 11:51:54 +07:00
d414a91f40 ci: Match changelog format of other ReVanced repos (#1705) 2024-02-26 03:01:30 +01:00
6d6fae1ecd chore(release): 1.19.0-dev.17 [skip ci]
## Features
* Improve language distinguishness and resolve language-specific issues (#1706) ([Ushie](6d866d4424))
2024-02-26 01:15:28 +00:00
6d866d4424 feat: Improve language distinguishness and resolve language-specific issues (#1706) 2024-02-26 08:08:14 +07:00
f4dea6e58c chore(release): 1.19.0-dev.16 [skip ci]
## Features
* Hide the Install button during installation (#1633) ([Eray Erdin (&mut self)](3e696d6847))
## Bug Fixes
* Show version label correctly ([oSumAtrIX](c72d10ac85))
2024-02-25 20:57:07 +00:00
c72d10ac85 fix: Show version label correctly 2024-02-25 21:49:38 +01:00
3e696d6847 feat: Hide the Install button during installation (#1633)
Co-authored-by: Ushie <ushiekane@gmail.com>
2024-02-25 23:48:52 +03:00
28d6ab692e chore(release): 1.19.0-dev.15 [skip ci]
## Bug Fixes
* Specify that dark theme is dark (#1699) ([Benjamin](d4b15aee4d))
## Documentation
* Switch order of note and tip [skip ci] ([oSumAtrIX](d0689555f1))
## Continuous Integration
* **Release:** Remove unnecessary upload (#1695) ([Benjamin](82d6e3f105))
## Chores
* **i18n:** Sync translations (#1694) ([github-actions[bot]](f7747809f2))
2024-02-25 02:50:24 +00:00
f7747809f2 chore(i18n): Sync translations (#1694)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: revanced-bot <github@revanced.app>
2024-02-25 09:42:15 +07:00
d4b15aee4d fix: Specify that dark theme is dark (#1699) 2024-02-25 09:37:55 +07:00
d0689555f1 docs: Switch order of note and tip [skip ci] 2024-02-25 01:01:00 +01:00
82d6e3f105 ci(Release): Remove unnecessary upload (#1695) 2024-02-20 06:41:03 +07:00
244d078b11 chore(release): 1.19.0-dev.14 [skip ci]
## Features
* Add a toggle for alternative sources (#1686) ([aAbed](f89c742c90))
2024-02-19 14:49:04 +00:00
f89c742c90 feat: Add a toggle for alternative sources (#1686)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2024-02-19 20:26:42 +05:45
ce5088ab53 chore(release): 1.19.0-dev.13 [skip ci]
## Chores
* **i18n:** Sync translations (#1688) ([github-actions[bot]](0443c8c200))
* Upgrade dependencies (#1693) ([Benjamin](8a4161753e))
2024-02-18 22:40:36 +00:00
0443c8c200 chore(i18n): Sync translations (#1688)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: revanced-bot <github@revanced.app>
2024-02-19 05:33:11 +07:00
8a4161753e chore: Upgrade dependencies (#1693) 2024-02-19 05:31:24 +07:00
f2e7071f6d chore(release): 1.19.0-dev.12 [skip ci]
## Documentation
* Add a tip for the prompt to select an APK from storage ([oSumAtrIX](bfe59ea57a))
* Adjust footnote regarding obtaining APK files ([oSumAtrIX](f5ba84d81e))
* Use consistent wording ([oSumAtrIX](933c71923e))
## Code Refactoring
* PopScope Migration (#1674) ([Pun Butrach](3b58d229da))
## Continuous Integration
* Dependabot (#1692) ([Benjamin](25d53ce9a8))
2024-02-18 22:05:57 +00:00
25d53ce9a8 ci: Dependabot (#1692)
And accept more commit.

Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2024-02-19 04:58:29 +07:00
3b58d229da refactor: PopScope Migration (#1674)
Co-authored-by: Benjamin Halko <benjaminhalko@hotmail.com>
2024-02-18 08:32:32 +07:00
f5ba84d81e docs: Adjust footnote regarding obtaining APK files 2024-02-15 19:03:35 +01:00
933c71923e docs: Use consistent wording 2024-02-15 18:55:54 +01:00
bfe59ea57a docs: Add a tip for the prompt to select an APK from storage 2024-02-15 18:54:32 +01:00
953209ca13 chore(release): 1.19.0-dev.11 [skip ci]
## Features
* Improve consistency on language selector ([Pun Butrach](b2119ce60e))
## Code Refactoring
* Check-in pubspec.lock ([Pun Butrach](5a24911fad))
## Build System
* Bump Gradle to v8.6 ([Pun Butrach](f57898a471))
* Enable ProGuard (#1650) ([kitadai31](b754a045eb))
## Continuous Integration
* **build:** Upload build artifact ([Pun Butrach](e0c750d27e))
## Chores
* Update .gitignore ([Pun Butrach](0ec6897fda))
2024-02-15 09:48:29 +00:00
b2119ce60e feat: Improve consistency on language selector 2024-02-15 16:41:09 +07:00
e0c750d27e ci(build): Upload build artifact 2024-02-15 16:31:19 +07:00
b754a045eb build: Enable ProGuard (#1650) 2024-02-15 16:24:24 +07:00
5a24911fad refactor: Check-in pubspec.lock 2024-02-15 08:17:11 +00:00
0ec6897fda chore: Update .gitignore 2024-02-15 08:17:10 +00:00
f57898a471 build: Bump Gradle to v8.6 2024-02-15 08:17:10 +00:00
3ee29c2256 chore(release): 1.19.0-dev.10 [skip ci]
## Chores
* **i18n:** Sync translations (#1685) ([github-actions[bot]](31a32eb11d))
## Reverts
* WillPopScope migration ([Pun Butrach](3b0fed55e4))
2024-02-15 08:08:25 +00:00
3b0fed55e4 revert: WillPopScope migration
This reverts commit ef9b1d5c2d72c6787246c4b6d11929044a13b54d.

Why is this so hard to implement??? Are we missing something??
2024-02-15 08:00:52 +00:00
31a32eb11d chore(i18n): Sync translations (#1685)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: revanced-bot <github@revanced.app>
2024-02-13 13:26:08 +07:00
64c10b44f5 chore(release): 1.19.0-dev.9 [skip ci]
## Bug Fixes
* Use correct title size for bottom sheet (#1687) ([oSumAtrIX](34365239c1))
2024-02-12 19:55:06 +00:00
34365239c1 fix: Use correct title size for bottom sheet (#1687)
Co-authored-by: aAbed <aabedhkhan@gmail.com>
2024-02-12 20:46:58 +01:00
bdfeda23b6 chore(release): 1.19.0-dev.8 [skip ci]
## Features
* Allow changing languages (#1488) ([Ushie](f82c439b26))
## Chores
* **i18n:** Sync translations (#1671) ([github-actions[bot]](f5578b0fc1))
* update dependencies ([Benjamin Halko](1aaeac1fe3))
2024-02-12 17:39:10 +00:00
1aaeac1fe3 chore: update dependencies 2024-02-13 09:31:23 -08:00
f5578b0fc1 chore(i18n): Sync translations (#1671) 2024-02-12 02:25:59 +03:00
f82c439b26 feat: Allow changing languages (#1488)
Co-authored-by: validcube <pun.butrach@gmail.com>
2024-02-12 02:22:25 +03:00
c498cff096 chore(release): 1.19.0-dev.7 [skip ci]
## Bug Fixes
* adjust padding ([aAbed](3559477247))
## Documentation
* **Security:** Format badges ([Pun Butrach](b9241fa1b2))
2024-02-06 15:14:29 +00:00
3559477247 fix: adjust padding 2024-02-06 20:52:19 +05:45
b9241fa1b2 docs(Security): Format badges 2024-02-03 08:16:33 +00:00
9bf5153e6b chore(release): 1.19.0-dev.6 [skip ci]
## Bug Fixes
* **PopScope:** User able to exit patch screen when the installer is still running (#1663) ([Benjamin](eb6d3cd64e))
## Code Refactoring
* Disable update for dev build (#1662) ([Benjamin](c900d09cf8))
## Continuous Integration
* Add scope to changelog (#1672) ([Benjamin](8cda2c164d))
2024-02-03 03:36:47 +00:00
eb6d3cd64e fix(PopScope): User able to exit patch screen when the installer is still running (#1663) 2024-02-03 10:29:34 +07:00
8cda2c164d ci: Add scope to changelog (#1672) 2024-02-03 09:02:53 +07:00
c900d09cf8 refactor: Disable update for dev build (#1662) 2024-02-02 21:12:10 +07:00
edc8ef4f44 chore(release): 1.19.0-dev.5 [skip ci]
## Bug Fixes
* Add top padding ([Pun Butrach](9aeb156d92))
2024-02-02 13:12:37 +00:00
9aeb156d92 fix(Update Confirmation Sheet): Add top padding
Before the "Changelog" or homeView.updateChangelogTitle was almost at the edge of the [DraggableScrollableSheet]

We fix it by adding 12px padding off the top, 12px was chosen because [bottom] padding is using 12px so it would make sense for [top] to be using 12px.
2024-02-02 13:05:15 +00:00
8be07de373 ci(release): Migrate deprecated Node version 2024-02-02 11:38:53 +07:00
93482b0041 chore(release): 1.19.0-dev.4 [skip ci]
## Bug Fixes
* Stop patch when signing fails ([#1553](https://github.com/ReVanced/revanced-manager/issues/1553)) ([kitadai31](5b2c55142e))
2024-02-02 00:02:38 +00:00
5b2c55142e fix: Stop patch when signing fails (#1553) 2024-02-02 00:55:18 +01:00
088a3b7c28 ci: Improve build workflow (#1668) 2024-02-01 08:57:15 +07:00
b115643034 chore(release): 1.19.0-dev.3 [skip ci]
# [1.19.0-dev.3](https://github.com/ReVanced/revanced-manager/compare/v1.19.0-dev.2...v1.19.0-dev.3) (2024-01-29)

### Bug Fixes

* Do not delete files from post-fs-data.d ([70a1086](70a1086edf))

### Features

* Show a dialog when an update is available ([#1654](https://github.com/ReVanced/revanced-manager/issues/1654)) ([c7d975e](c7d975e612))
2024-01-29 10:19:31 +00:00
07fc964f9c ci: Specify paths in release workflow (#1664) 2024-01-29 11:11:42 +01:00
3e51caf111 docs(Security): Avoid table shifting layout 2024-01-29 11:11:42 +01:00
deb1ba339f docs(Security): Use dynamic information on version
Meanwhile latest get the static treatment.
2024-01-29 11:11:42 +01:00
70a1086edf fix: Do not delete files from post-fs-data.d 2024-01-27 09:08:44 +05:45
c7d975e612 feat: Show a dialog when an update is available (#1654) 2024-01-27 09:08:44 +05:45
7104d6d6dd chore(release): 1.19.0-dev.2 [skip ci]
# [1.19.0-dev.2](https://github.com/ReVanced/revanced-manager/compare/v1.19.0-dev.1...v1.19.0-dev.2) (2024-01-22)

### Bug Fixes

* Use correct version code & name ([#1647](https://github.com/ReVanced/revanced-manager/issues/1647)) ([d933997](d933997c89))
2024-01-22 22:34:38 +00:00
d933997c89 fix: Use correct version code & name (#1647) 2024-01-23 05:27:33 +07:00
bea99bb4c6 chore(release): 1.19.0-dev.1 [skip ci]
# [1.19.0-dev.1](https://github.com/ReVanced/revanced-manager/compare/v1.18.0...v1.19.0-dev.1) (2024-01-22)

### Bug Fixes

* added a trailing comma ([975180b](975180b075))
* Allow mounting without Magisk ([3f96608](3f96608398))
* fix redundant buttons on dialog ([079c0de](079c0defaf))
* Incorrect strings and logics ([#1619](https://github.com/ReVanced/revanced-manager/issues/1619)) ([4f22e88](4f22e88e42))
* **Keystore Password:** textfield title display ([8e52abd](8e52abda9a))
* Mount script causes build to fail ([#1613](https://github.com/ReVanced/revanced-manager/issues/1613)) ([f3c78c2](f3c78c2c24))
* **Patch Option:** Set text colour on dropdown menu ([acb1e24](acb1e2434b))
* **Release CI:** truncate the "v" from version ([8595099](85950991ab))
* use lowercase repo names ([#1626](https://github.com/ReVanced/revanced-manager/issues/1626)) ([edd8602](edd86024b9))

### Features

* Add API migration code ([#1615](https://github.com/ReVanced/revanced-manager/issues/1615)) ([28ae276](28ae2766f0))
* add haptic feedback ([#1459](https://github.com/ReVanced/revanced-manager/issues/1459)) ([7911459](7911459817))
* Improve app selector and patcher UI ([#1616](https://github.com/ReVanced/revanced-manager/issues/1616)) ([efb2d5e](efb2d5ef32))
* Improve Split APK warning readability ([#1625](https://github.com/ReVanced/revanced-manager/issues/1625)) ([6fd740f](6fd740f8c0))
* Simplify settings strings ([#1618](https://github.com/ReVanced/revanced-manager/issues/1618)) ([0d45fe4](0d45fe4a97))
* use native TextField ([9ed43ef](9ed43efe5d))

### Performance Improvements

* Load patched apps as soon as possible ([c94eb7a](c94eb7a48e))
2024-01-22 11:41:51 +00:00
f38a593434 ci(Release Build): Use correct syntax in getting the version 2024-01-22 18:34:35 +07:00
85950991ab fix(Release CI): truncate the "v" from version
ci: cleanup

force push soon™️
2024-01-22 18:30:16 +07:00
6fd740f8c0 feat: Improve Split APK warning readability (#1625)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2024-01-22 12:20:09 +01:00
ec2c2d8ccb ci: Build pre-release versions (#1606) 2024-01-22 09:29:57 +01:00
efb2d5ef32 feat: Improve app selector and patcher UI (#1616) 2024-01-22 09:20:17 +01:00
acb1e2434b fix(Patch Option): Set text colour on dropdown menu
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-01-21 20:36:23 +07:00
8e52abda9a fix(Keystore Password): textfield title display
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-01-21 16:58:00 +07:00
9764326242 chore(patch_item): Remove import directive
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-01-21 15:15:37 +07:00
97c33d6c54 chore: Remove deprecated lint rules
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-01-21 15:10:03 +07:00
30d5f3ad3f docs(security): Prepare for v1.19.0 2024-01-21 15:02:52 +07:00
079c0defaf fix: fix redundant buttons on dialog
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-01-21 14:45:25 +07:00
cf1afddb9e chore: remove unused dependencies (#1603) 2024-01-21 14:07:30 +07:00
b07b9351c4 docs(security): init (#1599)
Co-authored-by: Ax333l <main@axelen.xyz>
2024-01-21 13:59:57 +07:00
7911459817 feat: add haptic feedback (#1459)
Co-authored-by: Ushie <ushiekane@gmail.com>
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2024-01-14 14:29:24 -08:00
ef9b1d5c2d refactor: Replace deprecated WillPopScope (#1604) 2024-01-13 19:34:04 -08:00
fd2780624a chore: remove unused import 2024-01-12 09:49:26 -08:00
4f22e88e42 fix: Incorrect strings and logics (#1619) 2024-01-12 05:15:37 +07:00
0d45fe4a97 feat: Simplify settings strings (#1618) 2024-01-09 20:23:54 +01:00
28ae2766f0 feat: Add API migration code (#1615) 2024-01-09 20:23:26 +01:00
edd86024b9 fix: use lowercase repo names (#1626) 2024-01-09 05:07:18 +07:00
f3c78c2c24 fix: Mount script causes build to fail (#1613) 2024-01-01 00:32:57 +03:00
3f96608398 fix: Allow mounting without Magisk 2023-12-31 03:32:04 +03:00
9ed43efe5d feat: use native TextField
Signed-off-by: validcube <pun.butrach@gmail.com>
2023-12-30 14:37:46 +07:00
975180b075 fix: added a trailing comma 2023-12-28 13:17:25 -08:00
c94eb7a48e perf: Load patched apps as soon as possible 2023-12-24 00:12:33 +01:00
bb1b0da749 chore: merge dev to main (#1573) 2023-12-23 22:43:10 +01:00
85c8006977 build: Bump version to v1.18.0 2023-12-23 22:20:32 +01:00
850bdc4a4d fix: Reset progress if patch was cancelled 2023-12-23 22:19:56 +01:00
ca95e32164 chore: Ignore autogenerated project file 2023-12-23 22:14:44 +01:00
a141ec85b0 fix: Correct update message 2023-12-23 22:11:38 +01:00
1298a96b0e fix: Update the progress properly until installation finishes successfully 2023-12-23 22:10:22 +01:00
1928b150ad fix: Use correct installation type labels 2023-12-23 22:10:22 +01:00
7426f5484d fix: Update home screen after installation 2023-12-23 21:58:53 +01:00
11a8f313b0 feat: Clarify non root installation button 2023-12-23 21:46:58 +01:00
69b6ef07a1 feat: Improve dialog text clarity 2023-12-23 21:36:47 +01:00
7a1ba9dabf fix: Add missing confirmation dialog 2023-12-23 21:11:24 +01:00
48a739c94e refactor: Simplify uninstallApp code 2023-12-23 20:53:51 +01:00
9680f0cf12 refactor: Remove unnecessary quotation marks 2023-12-23 20:53:31 +01:00
5b1c89a0c5 perf: Reduce amount of shell commands 2023-12-23 20:52:52 +01:00
8a1ab478a3 fix: Prevent crash by escaping string correctly 2023-12-23 20:52:52 +01:00
2ae8d49526 fix: exported logs patch selection (#1535)
Co-authored-by: validcube <pun.butrach@gmail.com>
2023-12-23 11:29:36 +07:00
a0b673c138 refactor: apply suggestions from analyser
Signed-off-by: validcube <pun.butrach@gmail.com>
2023-12-23 10:47:12 +07:00
c56c445fb7 fix: migration latest changes to native buttons
Signed-off-by: validcube <pun.butrach@gmail.com>
2023-12-23 10:36:59 +07:00
c23275f2fe feat: Improve installation robustness (#1528)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: Ushie <ushiekane@gmail.com>
Co-authored-by: Dhruvan Bhalara <53393418+dhruvanbhalara@users.noreply.github.com>
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2023-12-23 10:16:28 +07:00
8b28a33b73 ci(security): resolve arbitrary code execution 2023-12-22 20:39:21 +07:00
f8d086a743 feat: dialogs correctly follows Material 3 specifications (#1560)
Signed-off-by: validcube <pun.butrach@gmail.com>
2023-12-22 20:34:03 +07:00
c06d15de5f docs: Improve troubleshooting solutions (#1543)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-12-22 05:50:10 +01:00
dcaf1f54e4 build(gradle): bump Gradle to v8.5
Signed-off-by: validcube <pun.butrach@gmail.com>
2023-12-17 20:16:46 +07:00
aa91abb022 docs(readme): Correct grammar mistake (#1569) 2023-12-16 10:40:50 +07:00
67d204eb02 build: Bump version to v1.17.3 2023-12-12 08:22:10 +03:00
18e680b298 fix: Unable to unselect new patches 2023-12-12 10:37:35 +05:45
6ef1b072e8 build: Bump version to v1.17.2 2023-12-11 18:27:19 -08:00
ca9ef544ce chore: merge dev to main (#1545) 2023-12-11 18:23:08 -08:00
7831a3438d fix: Include new patches that are used by default 2023-12-12 02:32:57 +01:00
1fad90441c perf: Use hashset for fast comparison 2023-12-12 02:32:45 +01:00
7f26c5bd45 revert: "feat: improve predictive back (#1487)"
This reverts commit 06ff36c83628099f4214b1f129d94b7e103a3394.

Signed-off-by: validcube <pun.butrach@gmail.com>
2023-12-11 19:02:13 +07:00
637641cf54 feat: output suggested version into patch log (#1557)
Signed-off-by: validcube <pun.butrach@gmail.com>
2023-12-11 11:26:52 +07:00
d9a316abbb docs(contributing): update branding 2023-12-09 11:49:19 +07:00
093cfa5269 docs(contributing): use correct semantic 2023-12-09 11:48:20 +07:00
61a025de4d docs: correct styling issue 2023-12-09 11:47:26 +07:00
7df1ae7ed8 feat: append patch version to the APK name while sharing/exporting (#1547) 2023-12-05 12:58:32 +05:45
eb6e75b156 ci: use migrate setup-java to Node 20 (#1542) 2023-12-05 09:43:04 +07:00
8480b3ac3d chore: update repository's owner name 2023-12-02 17:39:02 +07:00
6339a31fec build(dart-dependency): bump FlutterToast to v8.2.4 2023-12-02 17:35:35 +07:00
71b5bb3f8f build(dart-dependency): prefer immutable for dependency using git 2023-12-02 17:33:42 +07:00
a9878dbbdf chore: Merge dev to main (#1544) 2023-12-02 03:46:17 +03:00
b1fb9dd7d3 build: Bump version to v1.17.1 2023-12-02 03:44:51 +03:00
de51fbd7be fix: Incorrect duplicate filename handling when exporting files (#1541) 2023-12-01 20:31:46 +05:45
c9412a97d0 build(dependency): Bump patcher to 19.1.0 2023-12-01 13:22:38 +01:00
63c29bdd75 chore: Merge dev to main (#1529) 2023-11-27 11:06:43 +03:00
bace26063d build: Bump version to v1.17.0 2023-11-27 11:05:56 +03:00
7931eb97b9 feat: updated logs (#1526) 2023-11-25 17:06:18 -08:00
9df89c7b74 fix: Disable wakelock when patching is canceled (#1514) 2023-11-22 16:21:27 +03:00
78978276c4 fix(App Selector): Unable to select APK from storage when asked to (#1513) 2023-11-22 16:20:51 +03:00
011eddbbc5 chore: Merge dev to main (#1478) 2023-11-21 17:37:40 +03:00
4e9f3fe1dc build: Bump version to v1.16.0 2023-11-21 17:36:24 +03:00
9bd48c19ff feat: ability to search query for suggested version (#1151)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: Ushie <ushiekane@gmail.com>
2023-11-21 17:33:21 +03:00
c0516c3665 ci(build): Improve pull request build (#1305)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-11-20 14:50:44 +07:00
06ff36c836 feat: improve predictive back (#1487)
Signed-off-by: validcube <pun.butrach@gmail.com>
2023-11-20 14:49:39 +07:00
98c16eb1dc docs(readme): remove redundant feature information 2023-11-20 07:34:08 +07:00
35d80840e5 fix: Remove incorrect punctuation (#1499) 2023-11-19 05:38:34 +01:00
ad3a778fb9 refactor: apply lint from analyzer 2023-11-12 10:02:40 +05:45
d25d1efe9c fix(build): allow profile variant to compile
fix backported from #1305
2023-11-12 10:02:10 +05:45
de58dff423 docs: improve readme (#1484) 2023-11-12 10:02:07 +05:45
ec26e4d8d1 feat: update color scheme (#1483) 2023-11-12 09:59:55 +05:45
c24da41505 refactor: replace deprecated command 2023-11-12 09:59:50 +05:45
f785185e1c chore: update label for template 2023-11-11 13:19:01 +07:00
5ed3ed9a2d fix: patcher logs hiding behind navigation bar (#1476) 2023-11-11 12:00:21 +07:00
a3adace60e build: Update Gradle to v8.4 2023-11-08 17:22:06 +01:00
3db4de09e2 chore: Remove unnecessary project files 2023-11-08 17:21:02 +01:00
70b2ee0a84 feat: Disable selection of un-suggested app version by default (#1471)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-11-08 03:38:54 +01:00
e7d82850c9 fix: Log saved patch option values (#1420) 2023-11-07 23:37:59 +01:00
e6a8f4e6dc build: Bump version to v1.15.1 2023-11-04 22:41:48 +01:00
494e268bc5 fix: Use correct method name for string replacement 2023-11-04 22:41:27 +01:00
e1c6f65b7e chore: Merge branch dev to main (#1446) 2023-11-04 22:10:14 +01:00
89075c5588 build: Bump version to v1.15.0 2023-11-04 22:08:20 +01:00
c7fa9b8ce7 chore: upgrade dependencies (#1404) 2023-11-04 22:08:20 +01:00
cb70082d31 docs: Remove unneeded steps to build from source 2023-11-04 22:08:19 +01:00
48b9ac8f5b docs: use correct directory path (#1440) 2023-11-04 22:08:19 +01:00
0be568bbbd fix: Hide empty patches category labels (#1439) 2023-11-04 22:08:19 +01:00
ba44fa620f build: Bump dependencies to support patch option values (#1431)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-11-04 22:08:19 +01:00
dde402afbf chore: merge dev to main (#1437) 2023-10-26 17:22:07 +03:00
bbe5142ca9 build: bump version to v1.14.2 2023-10-26 17:21:25 +03:00
e74ffac5b0 fix: unable to use custom API (#1435) 2023-10-26 17:20:34 +03:00
cfb8980e3a chore: merge dev to main (#1425) 2023-10-25 11:42:54 +03:00
e06e1bdcbe build: bump version to v1.14.1 2023-10-25 11:42:19 +03:00
d60ced2f61 fix: remove codeblock from log export
Fixes: #1416
2023-10-25 11:40:30 +03:00
e68689828e fix: Material You toggle not updating sometimes (#1421) 2023-10-25 11:37:03 +03:00
ba932758c8 build: bump version to v1.14.0 2023-10-21 23:00:51 +07:00
ee43fa6311 chore: merge dev to main (#1399) 2023-10-21 22:58:58 +07:00
ad6b164d51 feat: root status in export patch log (#1407)
Co-authored-by: Ushie <ushiekane@gmail.com>
2023-10-21 17:46:51 +07:00
4a5510acb2 refactor: remove the remaining of semantic release
Removal reason: not maintained
2023-10-21 15:37:23 +07:00
970dbc4428 refactor(social): use correct github organisation name
the correct name is "ReVanced" not "revanced", we changed for a while now.
2023-10-21 15:33:36 +07:00
f8f37325eb refactor(social): change Twitter to X 2023-10-21 15:32:13 +07:00
bb999019ef feat: show patch options in error log (#1394) 2023-10-17 13:21:59 +07:00
533b6a155a feat: clarify "Version compatibility check" description (#1397) 2023-10-17 13:20:46 +07:00
4cdc92388c refactor: fix patch log order to be consistent with settings order (#1398) 2023-10-17 13:19:18 +07:00
ccc6be1e71 refactor(accessibility): improve patch options (#1369)
Co-authored-by: Palm <palmpasuthorn@gmail.com>
2023-10-16 16:47:59 +07:00
b355778a92 chore: merge dev to main (#1388) 2023-10-15 20:19:54 +03:00
6a12e8f37a build: bump version to v1.13.1 2023-10-15 20:19:23 +03:00
59adb91f5f fix(settings): inverted version compatibility switch 2023-10-15 20:16:58 +03:00
53677e2f39 fix: typo in reset patch selection dialog (#1387) 2023-10-15 20:14:19 +03:00
1c74f43b22 build: bump version to 1.13.0 2023-10-15 18:22:42 +07:00
b4801970e8 chore: merge dev to main (#1384)
ReVanced Manager 1.13.0!!
2023-10-15 18:18:09 +07:00
7a3a6b512f docs(building): use new highlight format 2023-10-15 18:04:19 +07:00
72ea33b6de fix(patch_selector): correct popup menu style
fix: #1372
2023-10-15 17:46:44 +07:00
d97192e0ee refactor(patch_options): disable card tap (#1368) 2023-10-15 16:56:43 +07:00
196d9fe4d2 refactor: reorganize and rename settings (#1307)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
Co-authored-by: Ushie <ushiekane@gmail.com>
2023-10-15 16:56:02 +07:00
e960fcc303 feat: add user agent (#1380)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2023-10-15 16:51:31 +07:00
f334da95ff feat: Remove full external storage access (#1381) 2023-10-15 16:44:11 +07:00
5d5f311e36 fix(settings-view): list items jumping on scroll up (#1375) 2023-10-15 16:17:28 +07:00
d577e97758 fix(app-bar): title not hiding completely (#1376) 2023-10-15 16:16:55 +07:00
2dc92e26d3 fix(patches-selector): ignore punctuation marks when searching for patch (#1377) 2023-10-14 19:43:18 +07:00
f4994a36a3 chore: Merge branch dev to main (#1366) 2023-10-13 00:22:02 +02:00
7a785a8163 build: Bump version to v1.12.1 2023-10-13 00:14:14 +02:00
6ad0d860c7 fix: Load patches from older versions of ReVanced Manager correctly 2023-10-13 00:12:27 +02:00
38a2fa55df chore: Merge branch dev to main (#1350) 2023-10-12 21:58:29 +02:00
a21b170b52 build: Bump version to v1.12.0 2023-10-12 21:55:06 +02:00
44265b2362 chore: Setup Crowdin configuration file 2023-10-12 18:51:16 +02:00
069193342b docs: use correct alert syntax 2023-10-12 14:25:13 +02:00
54e9a56cda docs: Add options setting 2023-10-12 14:22:20 +02:00
39bc9227dc docs: Improve readability 2023-10-12 14:20:44 +02:00
ac636670c3 feat: Add patch options (#1354) 2023-10-12 02:00:39 +02:00
2abadc73e4 fix: selected patches order (#1345) 2023-10-10 18:54:42 +07:00
377368f6bf docs: reflect the latest changes of ReVanced Manager (#1349) 2023-10-10 01:07:58 +07:00
4085c10bfc fix: search with package name (#1344) 2023-10-09 11:22:15 +07:00
657ba11e7e chore: Merge branch dev to main (#1340) 2023-10-07 02:04:26 +02:00
a9ae45fe63 build: Bump version to v1.11.2 2023-10-07 02:01:10 +02:00
61bb39b46f build: Bump dependencies to improve merging integrations speed 2023-10-07 02:01:10 +02:00
2ad106f7d7 fix(export-settings): remove boolean workaround 2023-10-07 02:01:09 +02:00
8fd4fe0e55 feat(patcher): improve logs (#1299)
Co-authored-by: Ushie <ushiekane@gmail.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-10-07 02:01:09 +02:00
b1c9aedac3 build: Bump version to v1.11.1 2023-10-05 17:45:56 +02:00
a80415be02 build: Bump dependencies 2023-10-05 17:45:56 +02:00
d9acd0d74b chore: Merge branch dev to main (#1329) 2023-10-05 01:35:08 +02:00
7ae09159ba build: Bump version to v1.11.0 2023-10-05 01:23:48 +02:00
a709abd80c perf: Do not load patches twice (#1328) 2023-10-04 21:58:25 +02:00
cd07f39b69 fix: reset patches after patching 2023-10-04 12:16:56 -07:00
f7c11d07a8 fix(export-settings): export patches as json object 2023-10-04 11:33:13 -07:00
b07439d402 fix: Reload patches 2023-10-04 19:38:34 +02:00
d8eadc2a2d feat: Use simpler wording 2023-10-04 19:01:17 +02:00
3a88d4d3e6 build: Bump dependencies 2023-10-04 18:39:24 +02:00
012110f008 perf: Do not load patches twice 2023-10-04 18:39:24 +02:00
4de274bf62 feat: Export settings migration activity (#1308) 2023-10-01 19:12:31 +02:00
76b89baee3 build: Bump dependencies (#1311) 2023-10-01 19:03:26 +02:00
697ae92031 Apply suggestions from code review [skip ci] 2023-10-01 19:02:49 +02:00
c87f92b346 feat: Adjust install dialog labels 2023-10-01 06:21:03 +02:00
6961bb7fd0 fix: Do not delete cached downloads 2023-10-01 04:48:24 +02:00
6e26130744 chore: Add todo 2023-09-30 22:15:46 +02:00
123a375a27 refactor: Remove unused strings 2023-09-30 22:14:51 +02:00
2b4b3ca0a5 fix: Retrieve app information from patched app 2023-09-30 21:40:03 +02:00
c4a795418f fix: Move installation log to correct place 2023-09-30 21:13:32 +02:00
91837ebade feat: Remove original package name in app info view 2023-09-30 21:07:26 +02:00
0492e910ea fix: Fill the preferred action 2023-09-30 20:11:53 +02:00
36c86e22b1 fix: Load installed apps 2023-09-30 20:03:09 +02:00
6bdc0c7bb2 feat: Simplify label 2023-09-30 19:58:45 +02:00
1e8d8f749a fix: do not ask for patches consent before initializing model 2023-09-30 14:57:48 +05:45
2e8e3b0d1e fix: Do not hardcode any predefined packages 2023-09-29 20:12:39 +02:00
15b8613d3c feat: Only log relevant records 2023-09-29 19:40:22 +02:00
8ce266bc94 perf: Reduce amount of network requests 2023-09-29 18:39:07 +02:00
8661d72e45 feat: Simplify label 2023-09-29 17:00:34 +02:00
62505f2543 build: Bump dependencies 2023-09-28 17:36:10 +02:00
37986c58ec docs(building): correct path to gradle.properties 2023-09-28 21:45:41 +07:00
2968d96fe9 remove log import 2023-09-27 14:42:11 -07:00
e7c8d0e78c use same fingerprint 2023-09-27 14:36:39 -07:00
83cbb34a5b use revanced fingerprint 2023-09-27 14:27:38 -07:00
7559c7b67e verify fingerprint of calling app 2023-09-27 14:17:29 -07:00
02822f4b38 remove user interaction 2023-09-27 13:47:59 -07:00
96736afb94 make bars transparent 2023-09-27 12:32:19 -07:00
72ae132fcd make export settings activity transparent 2023-09-27 12:21:27 -07:00
2250e1bcab convert Booleans to Ints 2023-09-26 16:46:49 -07:00
d9d5b746c3 Added ExportSettingsActivity 2023-09-26 16:21:46 -07:00
f1ea306291 change booleans to numbers 2023-09-25 14:42:40 -07:00
378d62395a remove newlines from base64 output 2023-09-25 10:15:56 -07:00
99c92069b9 export saved patches and keystore 2023-09-25 08:43:10 -07:00
2a89ef797f feat: share settings 2023-09-24 16:56:25 -07:00
5838550188 build: bump version to v1.10.3 2023-09-23 13:10:46 +03:00
e0e01ae3ee chore: merge dev to main (#1300) 2023-09-23 13:10:08 +03:00
0983ba8a0f fix: search bar overflow (#1301) 2023-09-23 00:51:25 +03:00
0bfa776ce7 fix: npe when loading patch bundle on android 8 2023-09-23 00:24:17 +03:00
d2b09936d1 chore: merge dev to main (#1295) 2023-09-22 22:08:13 +07:00
68e9f0f7c1 build: bump version 1.10.2
!!
2023-09-22 22:05:31 +07:00
c3d345de80 fix: force disable material you on Android 11 and below (#1293) 2023-09-22 21:05:13 +07:00
385c0e246a build: use correct version code
The user won't notice it :shhh: we're fine, continue as normal.
2023-09-21 19:32:10 +07:00
5ead49a5b7 build: bump version to v1.10.1 2023-09-20 20:06:19 -07:00
c0760b1347 chore: merge dev to main (#1289) 2023-09-20 20:04:10 -07:00
e01b323aee fix: make entire theme item clickable 2023-09-20 20:03:47 -07:00
6f4866ef63 fix: default theme not following system (#1288) 2023-09-20 20:03:15 -07:00
1b6d72661c build: bump version to v1.10.0 2023-09-20 17:40:23 -07:00
c59d4aea81 chore: merge dev to main (#1239) 2023-09-20 17:35:01 -07:00
6260a80738 feat(settings - appearance): add system option (#1279)
Closes #1260
2023-09-21 03:25:23 +03:00
e75d3c8273 fix(install-type): update padding and enable radio list scrolling (#1287)
fix(install-type): update padding and enable radio list scrolling
2023-09-20 17:24:37 -07:00
b7acb475e9 fix: update install type dialog padding 2023-09-20 16:57:13 -07:00
42b6bbff7c fix: update youtube link (#1286) 2023-09-21 02:16:55 +03:00
4b8542b35b fix: load patches via PatchBundle (#1242) 2023-09-21 01:35:32 +03:00
9ad1d6cbfb fix(custom-sources): ignore casing when checking if default repo is being used (#1281) 2023-09-21 00:42:29 +03:00
4cdd9acd73 docs(readme): add documentation and minor fixes (#1264)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2023-09-16 17:38:06 +07:00
f4b0a695d6 fix: improve app list loading speed (#1166) 2023-09-15 22:58:15 +07:00
b525ea1ba4 ci: remove analyze workflow (#1262)
Code style is not enforced so analysis is not needed.
2023-09-12 23:40:00 +07:00
c1fc2c4766 ci: bump actions/checkout to v4 2023-09-10 14:43:58 +07:00
5c733932c7 ci(pr-build): revert "sign apk with keystore (#1231)"
This reverts commit 8bf08ff4641451db64c80fddc9a3281cdf73098b, as it fails for PRs originating from forks
2023-09-08 01:14:13 +02:00
d1218616ec docs(readme): minor improvements 2023-09-08 01:14:13 +02:00
2bf6a03d56 fix: back button closing the app from any page 2023-09-08 01:14:13 +02:00
b6ee63c1ea ci(pr-build): sign apk with keystore (#1231) 2023-09-08 01:14:13 +02:00
6d08efdcd7 chore: fix issue template 2023-09-08 01:13:42 +02:00
a0a43a5651 build: bump version to v1.9.5 2023-09-08 01:13:42 +02:00
3af2f5b032 chore: merge dev to main (#1177) 2023-09-04 04:15:12 +03:00
8f54b226b4 refactor(patches-selector): improve universal patches header 2023-09-03 21:14:09 +03:00
9f64011b26 fix: npe when patching on android 8 2023-09-03 20:54:42 +03:00
c5fc54e721 fix(installer): open the patched app after install (#1233) 2023-09-03 21:48:28 +07:00
fc8a4fc5b6 fix: Don't use 'BuildContext's across async gaps. (#1148) 2023-09-03 01:47:20 +03:00
6f9ab232ae chore: ignore the root .gradle folder (#1160) 2023-09-02 09:29:49 -07:00
8cb96f1e45 fix: permissions handling at first launch 2023-08-31 19:34:12 +05:45
5733acb77a build(dependency): update patcher to v14.2.2
https://github.com/ReVanced/revanced-patcher/releases/tag/v14.2.2
2023-08-31 20:35:24 +07:00
e49bcb2a69 chore: simplify issue templates (#1165) 2023-08-31 17:36:29 +07:00
42e41c399f fix: hide install button on error 2023-08-28 19:05:30 +03:00
166a3180d3 build: correct version code 2023-08-28 03:20:15 +03:00
3bf4982f23 chore: merge dev to main (#1163) 2023-08-28 02:47:47 +03:00
f4e1cccfac build: bump version to v1.9.4 2023-08-28 01:44:46 +02:00
7911a8f49e fix: properly log messages and progress 2023-08-28 01:44:46 +02:00
64a96fc3ce fix: close before returning 2023-08-28 01:44:46 +02:00
8e2cfbddc5 fix: ignore the root .gradle folder 2023-08-27 14:05:39 -07:00
45fae3f0fd build: bump ReVanced Patcher back to v14.2.1
This reverts the previous regression with the dependency to ReVanced Patcher.
2023-08-27 22:35:03 +02:00
e45a7824c1 build: bump version to v1.9.3 2023-08-27 13:27:53 +07:00
5d72c48a76 chore: merge dev to main (#1157) 2023-08-27 13:27:01 +07:00
d6169c6fa2 fix: broken settings page 2023-08-27 11:55:21 +05:45
9df6d52e2d build: bump version to v1.9.2 2023-08-27 05:40:50 +03:00
239de8e923 chore: merge dev to main (#1156) 2023-08-27 05:40:13 +03:00
7d553a87f3 build: revert patcher to v11.0.4 2023-08-27 05:39:24 +03:00
557b42bc56 build: bump version to 1.9.1 2023-08-27 03:58:30 +03:00
8423914748 chore: merge dev to main (#1155) 2023-08-27 03:57:57 +03:00
07dce23794 build: bump patcher to v14.2.0 2023-08-27 03:55:56 +03:00
18fd0552db build: bump version to v1.9.0 2023-08-27 02:23:36 +03:00
d537d48f8e chore: merge dev to main (#1125) 2023-08-27 02:21:48 +03:00
b456512bbb build: bump patcher to v14.1.0 (#1153)
Co-authored-by: aAbed <aabedhkhan@gmail.com>
2023-08-27 02:21:16 +03:00
227 changed files with 14613 additions and 7022 deletions

View File

@ -1,61 +0,0 @@
name: 🐞 Bug report
description: Create a new bug report.
title: 'bug: <title>'
labels: [bug]
body:
- type: markdown
attributes:
value: |
# ReVanced Manager bug report
Please check for existing issues [here](https://github.com/revanced/revanced-manager/labels/bug) before creating a new one.
- type: textarea
attributes:
label: Bug description
description: |
- Describe your bug in detail
- Add steps to reproduce the bug if possible (Step 1. Download some files. Step 2. ...)
- Add images and videos if possible
- List selected patches if applicable
validations:
required: true
- type: textarea
attributes:
label: Version of ReVanced Manager and version & name of application you tried to patch
validations:
required: true
- type: dropdown
attributes:
label: Installation type
options:
- Non-root
- Root
validations:
required: false
- type: textarea
attributes:
label: Device logs
description: Export logs in ReVanced Manager settings.
render: shell
validations:
required: true
- type: textarea
attributes:
label: Patcher logs
description: Export logs in "Patcher" screen.
render: shell
validations:
required: false
- type: checkboxes
attributes:
label: Acknowledgements
description: Your issue will be closed if you don't follow the checklist below!
options:
- label: This request is not a duplicate of an existing issue.
required: true
- label: I have chosen an appropriate title.
required: true
- label: All requested information has been provided properly.
required: true
- label: The issue is solely related to the ReVanced Manager
required: true

109
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,109 @@
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 +1,5 @@
blank_issues_enabled: false
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!

View File

@ -1,42 +0,0 @@
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

@ -0,0 +1,103 @@
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 Manager. Join us on [Discord](https://revanced.app/discord) if you want to receive a contributor role.
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) to receive a role for your contribution.

View File

@ -0,0 +1,31 @@
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
- name: Cache Gradle
uses: burrunan/gradle-cache-action@v1
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew assembleRelease --no-daemon
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: revanced-manager
path: |
app/build/outputs/apk/release/revanced-manager*.apk
app/build/outputs/apk/release/revanced-manager*.apk.asc

26
.github/workflows/open_pull_request.yml vendored Normal file
View File

@ -0,0 +1,26 @@
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

View File

@ -1,40 +0,0 @@
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: Get commit hash
id: get_commit_hash
run: echo "hash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Build with Gradle
run: ./gradlew assembleRelease --no-daemon -PnoProguard -Psuffix=${{ steps.get_commit_hash.outputs.hash }}
- name: Upload build
uses: actions/upload-artifact@v3
with:
name: revanced-manager-${{ steps.get_commit_hash.outputs.hash }}
path: app/build/outputs/apk/release/*.apk

View File

@ -1,52 +0,0 @@
name: Release Build
on:
workflow_dispatch:
push:
branches:
- main
- dev
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Set up Node.js 20
uses: actions/setup-node@v2
with:
node-version: '20'
- name: Cache Node modules
uses: actions/cache@v3
with:
path: |
node_modules
key: npm-${{ hashFiles('package-lock.json') }}
- name: Setup semantic-release
run: npm install
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
with:
cache-disabled: true
- name: Build with Gradle
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
signingKey: "keystore.jks"
keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }}
keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
run: |
echo "${{ secrets.SIGNING_KEYSTORE }}" | base64 --decode > app/keystore.jks
npx semantic-release

70
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,70 @@
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
- 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

@ -11,9 +11,9 @@ jobs:
name: Dispatch event to documentation repository
if: github.ref == 'refs/heads/main'
steps:
- uses: peter-evans/repository-dispatch@v2
- uses: peter-evans/repository-dispatch@v3
with:
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 }}"}'

137
.gitignore vendored
View File

@ -1,11 +1,132 @@
### 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
/local.properties
/.idea
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
**/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
/node_modules
.cxx

View File

@ -14,15 +14,17 @@
]
}
],
"@semantic-release/changelog",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"gradle-semantic-release-plugin",
[
"@semantic-release/git",
{
"assets": [
"gradle.properties"
]
"CHANGELOG.md",
"gradle.properties",
],
"message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
[
@ -30,17 +32,17 @@
{
"assets": [
{
"path": "app/build/outputs/apk/release/revanced-manager*.apk"
}
"path": "app/build/outputs/apk/release/revanced-manager*.apk?(.asc)"
},
],
"successComment": false
successComment: false
}
],
[
"@saithodev/semantic-release-backmerge",
{
"backmergeBranches": [{"from": "main", "to": "dev"}],
"clearWorkspace": true
backmergeBranches: [{"from": "main", "to": "dev"}],
clearWorkspace: true
}
]
]

View File

@ -13,8 +13,8 @@
<br>
<a href="https://revanced.app/">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="assets/revanced-logo/revanced-logo-round.svg" />
<img height="24px" src="assets/revanced-logo/revanced-logo-round.svg" />
<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">
@ -58,21 +58,46 @@
Continuing the legacy of Vanced
</p>
# 🔒 Security Policy
# 👋 Contribution guidelines
This document describes how to report security vulnerabilities for ReVanced Manager.
This document describes how to contribute to ReVanced Manager.
## 🚨 Reporting a Vulnerability
## 📖 Resources to help you get started
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).
* The [documentation](/docs/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
If a vulnerability is confirmed and accepted, you can join our [Discord](https://discord.gg/revanced) server to receive a special contributor role.
## 🙏 Submitting a feature request
### ⏳ Supported Versions
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+).
| Version | Branch | Supported |
| ------- | ------------|------------------- |
| v1.18.0 | main | :white_check_mark: |
| latest | dev | :white_check_mark: |
| latest | compose-dev | :white_check_mark: |
> **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+).
## 📝 How to contribute
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
## 🤚 I want to contribute but don't know how to code
Even if you don't know how to code, you can still contribute by
translating ReVanced Manager on [Crowdin](https://translate.revanced.app/).
❤️ Thank you for considering contributing to ReVanced Manager,
ReVanced

119
README.md
View File

@ -1,55 +1,104 @@
# ReVanced Manager (Compose Rewrite)
<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>
[![GitHub license](https://img.shields.io/github/license/revanced/revanced-manager)](../../blob/main/LICENSE)
[![GitHub last commit](https://img.shields.io/github/last-commit/revanced/revanced-manager/compose-dev)](https://github.com/ReVanced/revanced-manager/commits/compose-dev)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/w/revanced/revanced-manager/compose-dev)](https://github.com/ReVanced/revanced-manager/commits/compose-dev)
# 💊 ReVanced Manager
_(Yet another)_ rewrite of the ReVanced Manager using Kotlin and Jetpack Compose.
![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)
## Design system
Application to use ReVanced on Android
In this rewrite, we are adopting the latest Material Design principles and guidelines by using Material 3 and Material You.
## ❓ About
Material Design is a design system developed by Google that provides a unified visual language for building beautiful and consistent user interfaces across all platforms and devices. Material You is an extension of Material Design that provides even more customization options for users, making it possible for them to personalize their device and create a unique look and feel.
ReVanced Manager is an application that uses [ReVanced Patcher](https://github.com/revanced/revanced-patcher) to patch Android apps.
### Why Material 3?
## 💪 Features
* **Consistent design language**
* **Improved accessibility**
* **Better user experience**
Some of the features ReVanced Manager provides are:
By using Material 3 and Material You, we are ensuring that the app's user interface is consistent, customizable, accessible, and engaging for our users. This will help to improve the overall user experience and increase user satisfaction with the the manager.
## Technology stack
* Kotlin: Kotlin is a modern and concise programming language that is fully interoperable with Java and provides improved safety, readability, and maintainability compared to Java.
* Jetpack Compose: Jetpack Compose is a modern UI toolkit for Android development that allows developers to build beautiful and performant user interfaces using declarative programming. It provides a unified and efficient way of building UI that is well-integrated with the Android framework.
## Why Kotlin and Compose?
* **Improved safety:** Kotlin provides improved safety compared to Java, which reduces the likelihood of common programming mistakes that can cause security vulnerabilities or crashes.
* **Concise and readable code:** Kotlin's concise syntax and expressive type system make the code more readable, which makes it easier for developers to understand and maintain the codebase.
* **Better performance:** Jetpack Compose uses the power of the Android framework to provide smooth and fast performance, which enhances the user experience.
* **Modern and efficient UI development:** Jetpack Compose provides a modern and efficient way of building UI, which makes it easier for developers to create beautiful and performant user interfaces.
- ⬇️ **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
## 🔽 Download
You can obtain ReVanced Manager by downloading it from either [revanced.app/download](https://revanced.app/download) or [GitHub Releases](https://github.com/ReVanced/revanced-manager/releases)
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).
Learn how to use ReVanced Manager by following the [documentation](/docs).
## 📝 Prerequisites
## 📚 Everything else
For a list of prerequisites, refer to [docs/0_prerequisites.md](docs/0_prerequisites.md)
### 📙 Contributing
## 🔴 Issues
Thank you for considering contributing to ReVanced Manager.
You can find the contribution guidelines [here](CONTRIBUTING.md).
For suggestions and bug reports, open an issue [here](https://github.com/revanced/revanced-manager/issues/new/choose).
### 🛠️ Building
## 🌐 Translation
To build a ReVanced Manager, you can follow the [documentation](/docs).
[![Crowdin](https://badges.crowdin.net/revanced/localized.svg)](https://crowdin.com/project/revanced)
### 📄 Documentation
We're accepting translations on [Crowdin](https://translate.revanced.app)
You can find the documentation for ReVanced Manager [here](/docs).
## 🛠 Building Manager from source
## License
For instructions on how to build ReVanced Manager from source, refer to [docs/4_building.md](docs/4_building.md)
ReVanced Manager is licensed under the GPLv3 license. Please see the [license file](LICENSE) for more information.
[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Manager as long as you track changes/dates in source files.
Any modifications to ReVanced Manager must also be made available under the GPL, along with build & install instructions.

182
api/api/api.api Normal file
View File

@ -0,0 +1,182 @@
public abstract interface class app/revanced/manager/plugin/downloader/BaseDownloadScope : app/revanced/manager/plugin/downloader/Scope {
}
public final class app/revanced/manager/plugin/downloader/ConstantsKt {
public static final field PLUGIN_HOST_PERMISSION Ljava/lang/String;
}
public final class app/revanced/manager/plugin/downloader/DownloadUrl : android/os/Parcelable {
public static final field $stable I
public static final field CREATOR Landroid/os/Parcelable$Creator;
public fun <init> (Ljava/lang/String;Ljava/util/Map;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/util/Map;
public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lapp/revanced/manager/plugin/downloader/DownloadUrl;
public static synthetic fun copy$default (Lapp/revanced/manager/plugin/downloader/DownloadUrl;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lapp/revanced/manager/plugin/downloader/DownloadUrl;
public final fun describeContents ()I
public fun equals (Ljava/lang/Object;)Z
public final fun getHeaders ()Ljava/util/Map;
public final fun getUrl ()Ljava/lang/String;
public fun hashCode ()I
public final fun toDownloadResult ()Lkotlin/Pair;
public fun toString ()Ljava/lang/String;
public final fun writeToParcel (Landroid/os/Parcel;I)V
}
public final class app/revanced/manager/plugin/downloader/DownloadUrl$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lapp/revanced/manager/plugin/downloader/DownloadUrl;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lapp/revanced/manager/plugin/downloader/DownloadUrl;
public synthetic fun newArray (I)[Ljava/lang/Object;
}
public final class app/revanced/manager/plugin/downloader/Downloader {
public static final field $stable I
}
public final class app/revanced/manager/plugin/downloader/DownloaderBuilder {
public static final field $stable I
}
public final class app/revanced/manager/plugin/downloader/DownloaderKt {
public static final fun Downloader (Lkotlin/jvm/functions/Function1;)Lapp/revanced/manager/plugin/downloader/DownloaderBuilder;
}
public final class app/revanced/manager/plugin/downloader/DownloaderScope : app/revanced/manager/plugin/downloader/Scope {
public static final field $stable I
public final fun download (Lkotlin/jvm/functions/Function3;)V
public final fun get (Lkotlin/jvm/functions/Function4;)V
public fun getHostPackageName ()Ljava/lang/String;
public fun getPluginPackageName ()Ljava/lang/String;
public final fun useService (Landroid/content/Intent;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class app/revanced/manager/plugin/downloader/ExtensionsKt {
public static final fun download (Lapp/revanced/manager/plugin/downloader/DownloaderScope;Lkotlin/jvm/functions/Function4;)V
}
public abstract interface class app/revanced/manager/plugin/downloader/GetScope : app/revanced/manager/plugin/downloader/Scope {
public abstract fun requestStartActivity (Landroid/content/Intent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class app/revanced/manager/plugin/downloader/InputDownloadScope : app/revanced/manager/plugin/downloader/BaseDownloadScope {
}
public abstract interface class app/revanced/manager/plugin/downloader/OutputDownloadScope : app/revanced/manager/plugin/downloader/BaseDownloadScope {
public abstract fun reportSize (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class app/revanced/manager/plugin/downloader/Package : android/os/Parcelable {
public static final field $stable I
public static final field CREATOR Landroid/os/Parcelable$Creator;
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/manager/plugin/downloader/Package;
public static synthetic fun copy$default (Lapp/revanced/manager/plugin/downloader/Package;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lapp/revanced/manager/plugin/downloader/Package;
public final fun describeContents ()I
public fun equals (Ljava/lang/Object;)Z
public final fun getName ()Ljava/lang/String;
public final fun getVersion ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public final fun writeToParcel (Landroid/os/Parcel;I)V
}
public final class app/revanced/manager/plugin/downloader/Package$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lapp/revanced/manager/plugin/downloader/Package;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lapp/revanced/manager/plugin/downloader/Package;
public synthetic fun newArray (I)[Ljava/lang/Object;
}
public abstract interface annotation class app/revanced/manager/plugin/downloader/PluginHostApi : java/lang/annotation/Annotation {
}
public abstract interface class app/revanced/manager/plugin/downloader/Scope {
public abstract fun getHostPackageName ()Ljava/lang/String;
public abstract fun getPluginPackageName ()Ljava/lang/String;
}
public abstract class app/revanced/manager/plugin/downloader/UserInteractionException : java/lang/Exception {
public static final field $stable I
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public abstract class app/revanced/manager/plugin/downloader/UserInteractionException$Activity : app/revanced/manager/plugin/downloader/UserInteractionException {
public static final field $stable I
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class app/revanced/manager/plugin/downloader/UserInteractionException$Activity$Cancelled : app/revanced/manager/plugin/downloader/UserInteractionException$Activity {
public static final field $stable I
}
public final class app/revanced/manager/plugin/downloader/UserInteractionException$Activity$NotCompleted : app/revanced/manager/plugin/downloader/UserInteractionException$Activity {
public static final field $stable I
public final fun getIntent ()Landroid/content/Intent;
public final fun getResultCode ()I
}
public final class app/revanced/manager/plugin/downloader/UserInteractionException$RequestDenied : app/revanced/manager/plugin/downloader/UserInteractionException {
public static final field $stable I
}
public final class app/revanced/manager/plugin/downloader/webview/APIKt {
public static final fun WebViewDownloader (Lkotlin/jvm/functions/Function4;)Lapp/revanced/manager/plugin/downloader/DownloaderBuilder;
public static final fun runWebView (Lapp/revanced/manager/plugin/downloader/GetScope;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public class app/revanced/manager/plugin/downloader/webview/IWebView$Default : app/revanced/manager/plugin/downloader/webview/IWebView {
public fun <init> ()V
public fun asBinder ()Landroid/os/IBinder;
public fun finish ()V
public fun load (Ljava/lang/String;)V
}
public abstract class app/revanced/manager/plugin/downloader/webview/IWebView$Stub : android/os/Binder, app/revanced/manager/plugin/downloader/webview/IWebView {
public fun <init> ()V
public fun asBinder ()Landroid/os/IBinder;
public static fun asInterface (Landroid/os/IBinder;)Lapp/revanced/manager/plugin/downloader/webview/IWebView;
public fun onTransact (ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
}
public class app/revanced/manager/plugin/downloader/webview/IWebViewEvents$Default : app/revanced/manager/plugin/downloader/webview/IWebViewEvents {
public fun <init> ()V
public fun asBinder ()Landroid/os/IBinder;
public fun download (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public fun pageLoad (Ljava/lang/String;)V
public fun ready (Lapp/revanced/manager/plugin/downloader/webview/IWebView;)V
}
public abstract class app/revanced/manager/plugin/downloader/webview/IWebViewEvents$Stub : android/os/Binder, app/revanced/manager/plugin/downloader/webview/IWebViewEvents {
public fun <init> ()V
public fun asBinder ()Landroid/os/IBinder;
public static fun asInterface (Landroid/os/IBinder;)Lapp/revanced/manager/plugin/downloader/webview/IWebViewEvents;
public fun onTransact (ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
}
public final class app/revanced/manager/plugin/downloader/webview/WebViewActivity$Parameters$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lapp/revanced/manager/plugin/downloader/webview/WebViewActivity$Parameters;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lapp/revanced/manager/plugin/downloader/webview/WebViewActivity$Parameters;
public synthetic fun newArray (I)[Ljava/lang/Object;
}
public abstract interface class app/revanced/manager/plugin/downloader/webview/WebViewCallbackScope : app/revanced/manager/plugin/downloader/Scope {
public abstract fun finish (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun load (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class app/revanced/manager/plugin/downloader/webview/WebViewScope : app/revanced/manager/plugin/downloader/Scope {
public static final field $stable I
public final fun download (Lkotlin/jvm/functions/Function5;)V
public fun getHostPackageName ()Ljava/lang/String;
public fun getPluginPackageName ()Ljava/lang/String;
public final fun pageLoad (Lkotlin/jvm/functions/Function3;)V
}

150
api/build.gradle.kts Normal file
View File

@ -0,0 +1,150 @@
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

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -0,0 +1,8 @@
// IWebView.aidl
package app.revanced.manager.plugin.downloader.webview;
@JavaPassthrough(annotation="@app.revanced.manager.plugin.downloader.PluginHostApi")
oneway interface IWebView {
void load(String url);
void finish();
}

View File

@ -0,0 +1,11 @@
// IWebViewEvents.aidl
package app.revanced.manager.plugin.downloader.webview;
import app.revanced.manager.plugin.downloader.webview.IWebView;
@JavaPassthrough(annotation="@app.revanced.manager.plugin.downloader.PluginHostApi")
oneway interface IWebViewEvents {
void ready(IWebView iface);
void pageLoad(String url);
void download(String url, String mimetype, String userAgent);
}

View File

@ -0,0 +1,7 @@
package app.revanced.manager.plugin.downloader
/**
* The permission ID of the special plugin host permission. Only ReVanced Manager will have this permission.
* Plugin UI activities and internal services can be protected using this permission.
*/
const val PLUGIN_HOST_PERMISSION = "app.revanced.manager.permission.PLUGIN_HOST"

View File

@ -0,0 +1,165 @@
package app.revanced.manager.plugin.downloader
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.app.Activity
import android.os.Parcelable
import kotlinx.coroutines.withTimeout
import java.io.InputStream
import java.io.OutputStream
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@RequiresOptIn(
level = RequiresOptIn.Level.ERROR,
message = "This API is only intended for plugin hosts, don't use it in a plugin.",
)
@Retention(AnnotationRetention.BINARY)
annotation class PluginHostApi
/**
* The base interface for all DSL scopes.
*/
interface Scope {
/**
* The package name of ReVanced Manager.
*/
val hostPackageName: String
/**
* The package name of the plugin.
*/
val pluginPackageName: String
}
/**
* The scope of [DownloaderScope.get].
*/
interface GetScope : Scope {
/**
* Ask the user to perform some required interaction in the activity specified by the provided [Intent].
* This function returns normally with the resulting [Intent] when the activity finishes with code [Activity.RESULT_OK].
*
* @throws UserInteractionException.RequestDenied User decided to skip this plugin.
* @throws UserInteractionException.Activity.Cancelled The activity was cancelled.
* @throws UserInteractionException.Activity.NotCompleted The activity finished with an unknown result code.
*/
suspend fun requestStartActivity(intent: Intent): Intent?
}
interface BaseDownloadScope : Scope
/**
* The scope for [DownloaderScope.download].
*/
interface InputDownloadScope : BaseDownloadScope
typealias Size = Long
typealias DownloadResult = Pair<InputStream, Size?>
typealias Version = String
typealias GetResult<T> = Pair<T, Version?>
class DownloaderScope<T : Parcelable> internal constructor(
private val scopeImpl: Scope,
internal val context: Context
) : Scope by scopeImpl {
// Returning an InputStream is the primary way for plugins to implement the download function, but we also want to offer an OutputStream API since using InputStream might not be convenient in all cases.
// It is much easier to implement the main InputStream API on top of OutputStreams compared to doing it the other way around, which is why we are using OutputStream here. This detail is not visible to plugins.
internal var download: (suspend OutputDownloadScope.(T, OutputStream) -> Unit)? = null
internal var get: (suspend GetScope.(String, String?) -> GetResult<T>?)? = null
private val inputDownloadScopeImpl = object : InputDownloadScope, Scope by scopeImpl {}
/**
* Define the download block of the plugin.
*/
fun download(block: suspend InputDownloadScope.(data: T) -> DownloadResult) {
download = { app, outputStream ->
val (inputStream, size) = inputDownloadScopeImpl.block(app)
inputStream.use {
if (size != null) reportSize(size)
it.copyTo(outputStream)
}
}
}
/**
* Define the get block of the plugin.
* The block should return null if the app cannot be found. The version in the result must match the version argument unless it is null.
*/
fun get(block: suspend GetScope.(packageName: String, version: String?) -> GetResult<T>?) {
get = block
}
/**
* Utilize the service specified by the provided [Intent]. The service will be unbound when the scope ends.
*/
suspend fun <R : Any?> useService(intent: Intent, block: suspend (IBinder) -> R): R {
var onBind: ((IBinder) -> Unit)? = null
val serviceConn = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) =
onBind!!(service!!)
override fun onServiceDisconnected(name: ComponentName?) {}
}
return try {
val binder = withTimeout(10000L) {
suspendCoroutine { continuation ->
onBind = continuation::resume
context.bindService(intent, serviceConn, Context.BIND_AUTO_CREATE)
}
}
block(binder)
} finally {
onBind = null
context.unbindService(serviceConn)
}
}
}
class DownloaderBuilder<T : Parcelable> internal constructor(private val block: DownloaderScope<T>.() -> Unit) {
@PluginHostApi
fun build(scopeImpl: Scope, context: Context) =
with(DownloaderScope<T>(scopeImpl, context)) {
block()
Downloader(
download = download!!,
get = get!!
)
}
}
class Downloader<T : Parcelable> internal constructor(
@property:PluginHostApi val get: suspend GetScope.(packageName: String, version: String?) -> GetResult<T>?,
@property:PluginHostApi val download: suspend OutputDownloadScope.(data: T, outputStream: OutputStream) -> Unit
)
/**
* Define a downloader plugin.
*/
fun <T : Parcelable> Downloader(block: DownloaderScope<T>.() -> Unit) = DownloaderBuilder(block)
/**
* @see GetScope.requestStartActivity
*/
sealed class UserInteractionException(message: String) : Exception(message) {
class RequestDenied @PluginHostApi constructor() :
UserInteractionException("Request denied by user")
sealed class Activity(message: String) : UserInteractionException(message) {
class Cancelled @PluginHostApi constructor() : Activity("Interaction cancelled")
/**
* @param resultCode The result code of the activity.
* @param intent The [Intent] of the activity.
*/
class NotCompleted @PluginHostApi constructor(val resultCode: Int, val intent: Intent?) :
Activity("Unexpected activity result code: $resultCode")
}
}

View File

@ -0,0 +1,42 @@
package app.revanced.manager.plugin.downloader
import android.app.Activity
import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.os.Parcelable
import java.io.OutputStream
/**
* The scope of the [OutputStream] version of [DownloaderScope.download].
*/
interface OutputDownloadScope : BaseDownloadScope {
suspend fun reportSize(size: Long)
}
/**
* A replacement for [DownloaderScope.download] that uses [OutputStream].
* The provided [OutputStream] does not need to be closed manually.
*/
fun <T : Parcelable> DownloaderScope<T>.download(block: suspend OutputDownloadScope.(T, OutputStream) -> Unit) {
download = block
}
/**
* Performs [GetScope.requestStartActivity] with an [Intent] created using the type information of [ACTIVITY].
* @see [GetScope.requestStartActivity]
*/
suspend inline fun <reified ACTIVITY : Activity> GetScope.requestStartActivity() =
requestStartActivity(
Intent().apply { setClassName(pluginPackageName, ACTIVITY::class.qualifiedName!!) }
)
/**
* Performs [DownloaderScope.useService] with an [Intent] created using the type information of [SERVICE].
* @see [DownloaderScope.useService]
*/
suspend inline fun <reified SERVICE : Service, R : Any?> DownloaderScope<*>.useService(
noinline block: suspend (IBinder) -> R
) = useService(
Intent().apply { setClassName(pluginPackageName, SERVICE::class.qualifiedName!!) }, block
)

View File

@ -0,0 +1,39 @@
package app.revanced.manager.plugin.downloader
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import java.net.HttpURLConnection
import java.net.URI
/**
* A simple parcelable data class for storing a package name and version.
* This can be used as the data type for plugins that only need a name and version to implement their [DownloaderScope.download] function.
*
* @param name The package name.
* @param version The version.
*/
@Parcelize
data class Package(val name: String, val version: String) : Parcelable
/**
* A data class for storing a download URL.
*
* @param url The download URL.
* @param headers The headers to use for the request.
*/
@Parcelize
data class DownloadUrl(val url: String, val headers: Map<String, String> = emptyMap()) : Parcelable {
/**
* Converts this into a [DownloadResult].
*/
fun toDownloadResult(): DownloadResult = with(URI.create(url).toURL().openConnection() as HttpURLConnection) {
useCaches = false
allowUserInteraction = false
headers.forEach(::setRequestProperty)
connectTimeout = 10_000
connect()
inputStream to getHeaderField("Content-Length").toLong()
}
}

View File

@ -0,0 +1,176 @@
package app.revanced.manager.plugin.downloader.webview
import android.content.Intent
import app.revanced.manager.plugin.downloader.DownloadUrl
import app.revanced.manager.plugin.downloader.DownloaderScope
import app.revanced.manager.plugin.downloader.GetScope
import app.revanced.manager.plugin.downloader.Scope
import app.revanced.manager.plugin.downloader.Downloader
import app.revanced.manager.plugin.downloader.PluginHostApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.withContext
import kotlin.properties.Delegates
typealias InitialUrl = String
typealias PageLoadCallback<T> = suspend WebViewCallbackScope<T>.(url: String) -> Unit
typealias DownloadCallback<T> = suspend WebViewCallbackScope<T>.(url: String, mimeType: String, userAgent: String) -> Unit
interface WebViewCallbackScope<T> : Scope {
/**
* Finishes the activity and returns the [result].
*/
suspend fun finish(result: T)
/**
* Tells the WebView to load the specified [url].
*/
suspend fun load(url: String)
}
@OptIn(PluginHostApi::class)
class WebViewScope<T> internal constructor(
coroutineScope: CoroutineScope,
private val scopeImpl: Scope,
setResult: (T) -> Unit
) : Scope by scopeImpl {
private var onPageLoadCallback: PageLoadCallback<T> = {}
private var onDownloadCallback: DownloadCallback<T> = { _, _, _ -> }
@OptIn(ExperimentalCoroutinesApi::class)
private val dispatcher = Dispatchers.Default.limitedParallelism(1)
private lateinit var webView: IWebView
internal lateinit var initialUrl: String
internal val binder = object : IWebViewEvents.Stub() {
override fun ready(iface: IWebView?) {
coroutineScope.launch(dispatcher) {
webView = iface!!.also {
it.load(initialUrl)
}
}
}
override fun pageLoad(url: String?) {
coroutineScope.launch(dispatcher) { onPageLoadCallback(callbackScope, url!!) }
}
override fun download(url: String?, mimetype: String?, userAgent: String?) {
coroutineScope.launch(dispatcher) {
onDownloadCallback(
callbackScope,
url!!,
mimetype!!,
userAgent!!
)
}
}
}
private val callbackScope = object : WebViewCallbackScope<T>, Scope by scopeImpl {
override suspend fun finish(result: T) {
setResult(result)
// Tell the WebViewActivity to finish
webView.let { withContext(Dispatchers.IO) { it.finish() } }
}
override suspend fun load(url: String) {
webView.let { withContext(Dispatchers.IO) { it.load(url) } }
}
}
/**
* Called when the WebView attempts to download a file to disk.
*/
fun download(block: DownloadCallback<T>) {
onDownloadCallback = block
}
/**
* Called when the WebView finishes loading a page.
*/
fun pageLoad(block: PageLoadCallback<T>) {
onPageLoadCallback = block
}
}
@JvmInline
private value class Container<U>(val value: U)
/**
* Run a [android.webkit.WebView] Activity controlled by the provided code block.
* The activity will keep running until it is cancelled or an event handler calls [WebViewCallbackScope.finish].
* The [block] defines the event handlers and returns the initial URL.
*
* @param title The string displayed in the action bar.
* @param block The control block.
*/
@OptIn(PluginHostApi::class)
suspend fun <T> GetScope.runWebView(
title: String,
block: suspend WebViewScope<T>.() -> InitialUrl
) = supervisorScope {
var result by Delegates.notNull<Container<T>>()
val scope = WebViewScope<T>(this@supervisorScope, this@runWebView) { result = Container(it) }
scope.initialUrl = scope.block()
// Start the webview activity and wait until it finishes.
requestStartActivity(Intent().apply {
putExtra(
WebViewActivity.KEY,
WebViewActivity.Parameters(title, scope.binder)
)
setClassName(
hostPackageName,
WebViewActivity::class.qualifiedName!!
)
})
// Return the result and cancel any leftover coroutines.
coroutineContext.cancelChildren()
result.value
}
/**
* Implement a downloader using [runWebView] and [DownloadUrl]. This function will automatically define a handler for download events unlike [runWebView].
* Returning null inside the [block] is equivalent to returning null inside [DownloaderScope.get].
*
* @see runWebView
*/
fun WebViewDownloader(block: suspend WebViewScope<DownloadUrl>.(packageName: String, version: String?) -> InitialUrl?) =
Downloader<DownloadUrl> {
val label = context.applicationInfo.loadLabel(
context.packageManager
).toString()
get { packageName, version ->
class ReturnNull : Exception()
try {
runWebView(label) {
download { url, _, userAgent ->
finish(
DownloadUrl(
url,
mapOf("User-Agent" to userAgent)
)
)
}
block(this@runWebView, packageName, version) ?: throw ReturnNull()
} to version
} catch (_: ReturnNull) {
null
}
}
download {
it.toDownloadResult()
}
}

View File

@ -0,0 +1,161 @@
package app.revanced.manager.plugin.downloader.webview
import android.annotation.SuppressLint
import android.os.Bundle
import android.os.IBinder
import android.os.Parcelable
import android.view.MenuItem
import android.webkit.CookieManager
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.ComponentActivity
import androidx.activity.addCallback
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.viewModelScope
import app.revanced.manager.plugin.downloader.PluginHostApi
import app.revanced.manager.plugin.downloader.R
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
@OptIn(PluginHostApi::class)
@PluginHostApi
class WebViewActivity : ComponentActivity() {
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vm by viewModels<WebViewModel>()
enableEdgeToEdge()
setContentView(R.layout.activity_webview)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
val webView = findViewById<WebView>(R.id.webview)
onBackPressedDispatcher.addCallback {
if (webView.canGoBack()) webView.goBack()
else cancelActivity()
}
val params = intent.getParcelableExtra<Parameters>(KEY)!!
actionBar?.apply {
title = params.title
setHomeAsUpIndicator(android.R.drawable.ic_menu_close_clear_cancel)
setDisplayHomeAsUpEnabled(true)
}
val events = IWebViewEvents.Stub.asInterface(params.events)!!
vm.setup(events)
webView.apply {
settings.apply {
cacheMode = WebSettings.LOAD_NO_CACHE
allowContentAccess = false
domStorageEnabled = true
javaScriptEnabled = true
}
webViewClient = vm.webViewClient
setDownloadListener { url, userAgent, _, mimetype, _ ->
vm.onDownload(url, mimetype, userAgent)
}
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
vm.commands.collect {
when (it) {
is WebViewModel.Command.Finish -> {
setResult(RESULT_OK)
finish()
}
is WebViewModel.Command.Load -> webView.loadUrl(it.url)
}
}
}
}
}
private fun cancelActivity() {
setResult(RESULT_CANCELED)
finish()
}
override fun onOptionsItemSelected(item: MenuItem) = if (item.itemId == android.R.id.home) {
cancelActivity()
true
} else super.onOptionsItemSelected(item)
@Parcelize
internal class Parameters(
val title: String, val events: IBinder
) : Parcelable
internal companion object {
const val KEY = "params"
}
}
@OptIn(PluginHostApi::class)
internal class WebViewModel : ViewModel() {
init {
CookieManager.getInstance().apply {
removeAllCookies(null)
setAcceptCookie(true)
}
}
private val commandChannel = Channel<Command>()
val commands = commandChannel.receiveAsFlow()
private var eventBinder: IWebViewEvents? = null
private val ctrlBinder = object : IWebView.Stub() {
override fun load(url: String?) {
viewModelScope.launch {
commandChannel.send(Command.Load(url!!))
}
}
override fun finish() {
viewModelScope.launch {
commandChannel.send(Command.Finish)
}
}
}
val webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
eventBinder!!.pageLoad(url)
}
}
fun onDownload(url: String, mimeType: String, userAgent: String) {
eventBinder!!.download(url, mimeType, userAgent)
}
fun setup(binder: IWebViewEvents) {
if (eventBinder != null) return
eventBinder = binder
binder.ready(ctrlBinder)
}
sealed interface Command {
data class Load(val url: String) : Command
data object Finish : Command
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main">
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -0,0 +1 @@
<resources></resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.WebViewActivity" parent="Theme.AppCompat.DayNight">
<item name="android:windowActionBar">true</item>
<item name="android:windowNoTitle">false</item>
</style>
</resources>

View File

@ -1,132 +1,28 @@
import kotlin.random.Random
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.devtools)
alias(libs.plugins.about.libraries)
id("kotlin-parcelize")
kotlin("plugin.serialization") version "1.9.10"
signing
}
val (majorVersion, minorVersion, patchVersion, devVersion) = "${project.version}.0".replace("-dev","").split(".")
android {
namespace = "app.revanced.manager"
compileSdk = 34
buildToolsVersion = "34.0.0"
defaultConfig {
applicationId = "app.revanced.manager"
minSdk = 26
targetSdk = 34
versionName = project.version.toString()
versionCode = (majorVersion.toInt() * 100000000) + (minorVersion.toInt() * 100000) + (patchVersion.toInt() * 100) + devVersion.toInt()
resourceConfigurations.addAll(listOf(
"en",
))
vectorDrawables.useSupportLibrary = true
}
buildTypes {
debug {
applicationIdSuffix = ".debug"
resValue("string", "app_name", "ReVanced Manager Debug")
}
release {
if (System.getenv("signingKey") != null) {
signingConfigs {
create("release") {
storeFile = file(System.getenv("signingKey"))
storePassword = System.getenv("keyStorePassword")
keyAlias = System.getenv("keyAlias")
keyPassword = System.getenv("keyPassword")
}
}
signingConfig = signingConfigs.getByName("release")
} else {
applicationIdSuffix = ".debug"
resValue("string", "app_name", "ReVanced Manager Debug")
signingConfig = signingConfigs.getByName("debug")
}
if (!project.hasProperty("noProguard")) {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
var suffix = "v${project.version}"
if (project.hasProperty("suffix")) {
suffix = "${project.property("suffix")}"
}
applicationVariants.all {
this.outputs
.map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
.forEach { output ->
output.outputFileName = "revanced-manager-${suffix}.apk"
}
}
}
}
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
buildFeatures.aidl = true
buildFeatures.buildConfig=true
composeOptions.kotlinCompilerExtensionVersion = "1.5.3"
}
kotlin {
jvmToolchain(17)
}
tasks.register("publish") {
group = "Build"
description = "Assemble main outputs for all the variants."
dependsOn("assembleRelease")
}
val outputApkFileName = "${rootProject.name}-$version.apk"
dependencies {
// AndroidX Core
implementation(libs.androidx.ktx)
implementation(libs.runtime.ktx)
implementation(libs.runtime.compose)
implementation(libs.splash.screen)
implementation(libs.compose.activity)
implementation(libs.paging.common.ktx)
implementation(libs.activity.compose)
implementation(libs.work.runtime.ktx)
implementation(libs.preferences.datastore)
implementation(libs.appcompat)
// Compose
implementation(platform(libs.compose.bom))
@ -136,11 +32,13 @@ dependencies {
implementation(libs.compose.livedata)
implementation(libs.compose.material.icons.extended)
implementation(libs.compose.material3)
implementation(libs.navigation.compose)
// Accompanist
implementation(libs.accompanist.drawablepainter)
implementation(libs.accompanist.webview)
implementation(libs.accompanist.placeholder)
// Placeholder
implementation(libs.placeholder.material3)
// HTML Scraper
implementation(libs.skrapeit.dsl)
@ -153,6 +51,7 @@ dependencies {
// KotlinX
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.collection.immutable)
implementation(libs.kotlinx.datetime)
// Room
implementation(libs.room.runtime)
@ -164,6 +63,16 @@ dependencies {
implementation(libs.revanced.patcher)
implementation(libs.revanced.library)
// Downloader plugins
implementation(project(":api"))
// Native processes
implementation(libs.kotlin.process)
// HiddenAPI
compileOnly(libs.hidden.api.stub)
// LibSU
implementation(libs.libsu.core)
implementation(libs.libsu.service)
implementation(libs.libsu.nio)
@ -171,11 +80,9 @@ dependencies {
// Koin
implementation(libs.koin.android)
implementation(libs.koin.compose)
implementation(libs.koin.compose.navigation)
implementation(libs.koin.workmanager)
// Compose Navigation
implementation(libs.reimagined.navigation)
// Licenses
implementation(libs.about.libraries)
@ -194,4 +101,156 @@ dependencies {
// Scrollbars
implementation(libs.scrollbars)
// EnumUtil
implementation(libs.enumutil)
ksp(libs.enumutil.ksp)
// Reorderable lists
implementation(libs.reorderable)
// 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

@ -26,6 +26,10 @@
kotlinx.serialization.KSerializer serializer(...);
}
# This required for the process runtime.
-keep class app.revanced.manager.patcher.runtime.process.* {
*;
}
# Required for the patcher to function correctly
-keep class app.revanced.patcher.** {
*;
@ -45,6 +49,11 @@
-keep class com.android.** {
*;
}
-keep class app.revanced.manager.plugin.** {
*;
}
-dontwarn com.google.auto.value.**
-dontwarn java.awt.**
-dontwarn javax.**
-dontwarn org.slf4j.**

View File

@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "802fa2fda94b930bf0ebb85d195f1022",
"identityHash": "d0119047505da435972c5247181de675",
"entities": [
{
"tableName": "patch_bundles",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `name` TEXT NOT NULL, `source` TEXT NOT NULL, `auto_update` INTEGER NOT NULL, `version` TEXT, `integrations_version` TEXT, PRIMARY KEY(`uid`))",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `name` TEXT NOT NULL, `version` TEXT, `source` TEXT NOT NULL, `auto_update` INTEGER NOT NULL, PRIMARY KEY(`uid`))",
"fields": [
{
"fieldPath": "uid",
@ -20,6 +20,12 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "version",
"columnName": "version",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "source",
"columnName": "source",
@ -31,18 +37,6 @@
"columnName": "auto_update",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "versionInfo.patches",
"columnName": "version",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "versionInfo.integrations",
"columnName": "integrations_version",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
@ -51,17 +45,7 @@
"uid"
]
},
"indices": [
{
"name": "index_patch_bundles_name",
"unique": true,
"columnNames": [
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_patch_bundles_name` ON `${TABLE_NAME}` (`name`)"
}
],
"indices": [],
"foreignKeys": []
},
{
@ -160,7 +144,7 @@
},
{
"tableName": "downloaded_app",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `version` TEXT NOT NULL, `directory` TEXT NOT NULL, PRIMARY KEY(`package_name`, `version`))",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `version` TEXT NOT NULL, `directory` TEXT NOT NULL, `last_used` INTEGER NOT NULL, PRIMARY KEY(`package_name`, `version`))",
"fields": [
{
"fieldPath": "packageName",
@ -179,6 +163,12 @@
"columnName": "directory",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastUsed",
"columnName": "last_used",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
@ -231,7 +221,7 @@
},
{
"tableName": "applied_patch",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `bundle` INTEGER NOT NULL, `patch_name` TEXT NOT NULL, PRIMARY KEY(`package_name`, `bundle`, `patch_name`), FOREIGN KEY(`package_name`) REFERENCES `installed_app`(`current_package_name`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`bundle`) REFERENCES `patch_bundles`(`uid`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `bundle` INTEGER NOT NULL, `patch_name` TEXT NOT NULL, PRIMARY KEY(`package_name`, `bundle`, `patch_name`), FOREIGN KEY(`package_name`) REFERENCES `installed_app`(`current_package_name`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`bundle`) REFERENCES `patch_bundles`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "packageName",
@ -285,7 +275,7 @@
},
{
"table": "patch_bundles",
"onDelete": "NO ACTION",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"bundle"
@ -402,12 +392,38 @@
]
}
]
},
{
"tableName": "trusted_downloader_plugins",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `signature` BLOB NOT NULL, PRIMARY KEY(`package_name`))",
"fields": [
{
"fieldPath": "packageName",
"columnName": "package_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "signature",
"columnName": "signature",
"affinity": "BLOB",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"package_name"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '802fa2fda94b930bf0ebb85d195f1022')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd0119047505da435972c5247181de675')"
]
}
}

View File

@ -2,9 +2,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="ReservedSystemPermission" />
<permission
android:name="app.revanced.manager.permission.PLUGIN_HOST"
android:protectionLevel="signature"
android:label="@string/plugin_host_permission_label"
android:description="@string/plugin_host_permission_description"
/>
<uses-permission android:name="app.revanced.manager.permission.PLUGIN_HOST" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
@ -16,12 +23,7 @@
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<queries>
<intent>
<action android:name="android.intent.action.MAIN" />
</intent>
</queries>
<uses-permission android:name="android.permission.ENFORCE_UPDATE_OWNERSHIP" />
<application
android:name=".ManagerApplication"
@ -47,6 +49,8 @@
</intent-filter>
</activity>
<activity android:name=".plugin.downloader.webview.WebViewActivity" android:exported="false" android:theme="@style/Theme.WebViewActivity" />
<service android:name=".service.InstallService" />
<service android:name=".service.UninstallService" />

View File

@ -0,0 +1,11 @@
// IPatcherEvents.aidl
package app.revanced.manager.patcher.runtime.process;
// Interface for sending events back to the main app process.
oneway interface IPatcherEvents {
void log(String level, String msg);
void patchSucceeded();
void progress(String name, String state, String msg);
// The patching process has ended. The exceptionStackTrace is null if it finished successfully.
void finished(String exceptionStackTrace);
}

View File

@ -0,0 +1,14 @@
// IPatcherProcess.aidl
package app.revanced.manager.patcher.runtime.process;
import app.revanced.manager.patcher.runtime.process.Parameters;
import app.revanced.manager.patcher.runtime.process.IPatcherEvents;
interface IPatcherProcess {
// Returns BuildConfig.BUILD_ID, which is used to ensure the main app and runner process are running the same code.
long buildId();
// Makes the patcher process exit with code 0
oneway void exit();
// Starts patching.
oneway void start(in Parameters parameters, IPatcherEvents events);
}

View File

@ -0,0 +1,4 @@
// Parameters.aidl
package app.revanced.manager.patcher.runtime.process;
parcelable Parameters;

View File

@ -0,0 +1,38 @@
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)
# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("prop_override")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
# is preferred for the same purpose.
#
# In order to load a library into your app from Java/Kotlin, you must call
# System.loadLibrary() and pass the name of the library defined here;
# for GameActivity/NativeActivity derived applications, the same library name must be
# used in the AndroidManifest.xml file.
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
prop_override.cpp)
# Specifies libraries CMake should link to your target library. You
# can link libraries from various origins, such as libraries defined in this
# build script, prebuilt third-party libraries, or Android system libraries.
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
android
log)

View File

@ -0,0 +1,62 @@
// Library for overriding Android system properties via environment variables.
//
// Usage: LD_PRELOAD=prop_override.so PROP_dalvik.vm.heapsize=123M getprop dalvik.vm.heapsize
// Output: 123M
#include <string>
#include <cstring>
#include <cstdlib>
#include <dlfcn.h>
// Source: https://android.googlesource.com/platform/system/core/+/100b08a848d018eeb1caa5d5e7c7c2aaac65da15/libcutils/include/cutils/properties.h
#define PROP_VALUE_MAX 92
// This is the mangled name of "android::base::GetProperty".
#define GET_PROPERTY_MANGLED_NAME "_ZN7android4base11GetPropertyERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_"
extern "C" typedef int (*property_get_ptr)(const char *, char *, const char *);
typedef std::string (*GetProperty_ptr)(const std::string &, const std::string &);
char *GetPropOverride(const std::string &key) {
auto envKey = "PROP_" + key;
return getenv(envKey.c_str());
}
// See: https://android.googlesource.com/platform/system/core/+/100b08a848d018eeb1caa5d5e7c7c2aaac65da15/libcutils/properties.cpp
extern "C" int property_get(const char *key, char *value, const char *default_value) {
auto replacement = GetPropOverride(std::string(key));
if (replacement) {
int len = strnlen(replacement, PROP_VALUE_MAX);
strncpy(value, replacement, len);
return len;
}
static property_get_ptr original = NULL;
if (!original) {
// Get the address of the original function.
original = reinterpret_cast<property_get_ptr>(dlsym(RTLD_NEXT, "property_get"));
}
return original(key, value, default_value);
}
// Defining android::base::GetProperty ourselves won't work because std::string has a slightly different "path" in the NDK version of the C++ standard library.
// We can get around this by forcing the function to adopt a specific name using the asm keyword.
std::string GetProperty(const std::string &, const std::string &) asm(GET_PROPERTY_MANGLED_NAME);
// See: https://android.googlesource.com/platform/system/libbase/+/1a34bb67c4f3ba0a1ea6f4f20ac9fe117ba4fe64/properties.cpp
// This isn't used for the properties we want to override, but property_get is deprecated so that could change in the future.
std::string GetProperty(const std::string &key, const std::string &default_value) {
auto replacement = GetPropOverride(key);
if (replacement) {
return std::string(replacement);
}
static GetProperty_ptr original = NULL;
if (!original) {
original = reinterpret_cast<GetProperty_ptr>(dlsym(RTLD_NEXT, GET_PROPERTY_MANGLED_NAME));
}
return original(key, default_value);
}

View File

@ -1,185 +1,336 @@
package app.revanced.manager
import android.content.ActivityNotFoundException
import android.os.Bundle
import android.os.Parcelable
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
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.material.icons.Icons
import androidx.compose.material.icons.outlined.Update
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.runtime.getValue
import androidx.compose.ui.res.stringResource
import androidx.compose.runtime.remember
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import app.revanced.manager.ui.component.AutoUpdatesDialog
import app.revanced.manager.ui.destination.Destination
import app.revanced.manager.ui.destination.SettingsDestination
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
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.VersionSelectorScreen
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.screen.settings.update.UpdatesSettingsScreen
import app.revanced.manager.ui.theme.ReVancedManagerTheme
import app.revanced.manager.ui.theme.Theme
import app.revanced.manager.ui.viewmodel.MainViewModel
import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel
import dev.olshevski.navigation.reimagined.AnimatedNavHost
import dev.olshevski.navigation.reimagined.NavBackHandler
import dev.olshevski.navigation.reimagined.navigate
import dev.olshevski.navigation.reimagined.pop
import dev.olshevski.navigation.reimagined.popUpTo
import dev.olshevski.navigation.reimagined.rememberNavController
import app.revanced.manager.util.EventEffect
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
import org.koin.androidx.compose.navigation.koinNavViewModel
import org.koin.core.parameter.parametersOf
import org.koin.androidx.compose.getViewModel as getComposeViewModel
import org.koin.androidx.viewmodel.ext.android.getViewModel as getAndroidViewModel
import org.koin.androidx.viewmodel.ext.android.getViewModel as getActivityViewModel
class MainActivity : ComponentActivity() {
@ExperimentalAnimationApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
enableEdgeToEdge()
installSplashScreen()
val vm: MainViewModel = getAndroidViewModel()
vm.importLegacySettings(this)
val vm: MainViewModel = getActivityViewModel()
setContent {
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult(),
onResult = vm::applyLegacySettings
)
val theme by vm.prefs.theme.getAsState()
val dynamicColor by vm.prefs.dynamicColor.getAsState()
EventEffect(vm.legacyImportActivityFlow) {
try {
launcher.launch(it)
} catch (_: ActivityNotFoundException) {
}
}
ReVancedManagerTheme(
darkTheme = theme == Theme.SYSTEM && isSystemInDarkTheme() || theme == Theme.DARK,
dynamicColor = dynamicColor
) {
val navController =
rememberNavController<Destination>(startDestination = Destination.Dashboard)
NavBackHandler(navController)
val firstLaunch by vm.prefs.firstLaunch.getAsState()
if (firstLaunch) AutoUpdatesDialog(vm::applyAutoUpdatePrefs)
vm.updatedManagerVersion?.let {
AlertDialog(
onDismissRequest = vm::dismissUpdateDialog,
confirmButton = {
TextButton(
onClick = {
vm.dismissUpdateDialog()
navController.navigate(Destination.Settings(SettingsDestination.Update(false)))
}
) {
Text(stringResource(R.string.update))
}
},
dismissButton = {
TextButton(onClick = vm::dismissUpdateDialog) {
Text(stringResource(R.string.dismiss_temporary))
}
},
icon = { Icon(Icons.Outlined.Update, null) },
title = { Text(stringResource(R.string.update_available_dialog_title)) },
text = { Text(stringResource(R.string.update_available_dialog_description, it)) }
)
}
AnimatedNavHost(
controller = navController
) { destination ->
when (destination) {
is Destination.Dashboard -> DashboardScreen(
onSettingsClick = { navController.navigate(Destination.Settings()) },
onAppSelectorClick = { navController.navigate(Destination.AppSelector) },
onAppClick = { installedApp ->
navController.navigate(
Destination.InstalledApplicationInfo(
installedApp
)
)
}
)
is Destination.InstalledApplicationInfo -> InstalledAppInfoScreen(
onPatchClick = { packageName, patchSelection ->
navController.navigate(
Destination.VersionSelector(
packageName,
patchSelection
)
)
},
onBackClick = { navController.pop() },
viewModel = getComposeViewModel { parametersOf(destination.installedApp) }
)
is Destination.Settings -> SettingsScreen(
onBackClick = { navController.pop() },
startDestination = destination.startDestination
)
is Destination.AppSelector -> AppSelectorScreen(
onAppClick = { navController.navigate(Destination.VersionSelector(it)) },
onStorageClick = {
navController.navigate(
Destination.SelectedApplicationInfo(
it
)
)
},
onBackClick = { navController.pop() }
)
is Destination.VersionSelector -> VersionSelectorScreen(
onBackClick = { navController.pop() },
onAppClick = { selectedApp ->
navController.navigate(
Destination.SelectedApplicationInfo(
selectedApp,
destination.patchSelection,
)
)
},
viewModel = getComposeViewModel {
parametersOf(
destination.packageName,
destination.patchSelection
)
}
)
is Destination.SelectedApplicationInfo -> SelectedAppInfoScreen(
onPatchClick = { app, patches, options ->
navController.navigate(
Destination.Patcher(
app, patches, options
)
)
},
onBackClick = navController::pop,
vm = getComposeViewModel {
parametersOf(
SelectedAppInfoViewModel.Params(
destination.selectedApp,
destination.patchSelection
)
)
}
)
is Destination.Patcher -> PatcherScreen(
onBackClick = { navController.popUpTo { it is Destination.Dashboard } },
vm = getComposeViewModel { parametersOf(destination) }
)
}
}
ReVancedManager(vm)
}
}
}
}
@Composable
private fun ReVancedManager(vm: MainViewModel) {
val navController = rememberNavController()
EventEffect(vm.appSelectFlow) { app ->
navController.navigateComplex(
SelectedApplicationInfo,
SelectedApplicationInfo.ViewModelParams(app)
)
}
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(
onSettingsClick = { navController.navigate(Settings) },
onAppSelectorClick = {
navController.navigate(AppSelector)
},
onUpdateClick = {
navController.navigate(Update())
},
onDownloaderPluginClick = {
navController.navigate(Settings.Downloads)
},
onAppClick = { packageName ->
navController.navigate(InstalledApplicationInfo(packageName))
}
)
}
composable<InstalledApplicationInfo> {
val data = it.toRoute<InstalledApplicationInfo>()
InstalledAppInfoScreen(
onPatchClick = vm::selectApp,
onBackClick = navController::popBackStack,
viewModel = koinViewModel { parametersOf(data.packageName) }
)
}
composable<AppSelector> {
AppSelectorScreen(
onSelect = vm::selectApp,
onStorageSelect = vm::selectApp,
onBackClick = navController::popBackStack
)
}
composable<Patcher> {
PatcherScreen(
onBackClick = {
navController.navigate(route = Dashboard) {
launchSingleTop = true
popUpTo<Dashboard> {
inclusive = false
}
}
},
viewModel = koinViewModel { parametersOf(it.getComplexArg<Patcher.ViewModelParams>()) }
)
}
composable<Update> {
val data = it.toRoute<Update>()
UpdateScreen(
onBackClick = navController::popBackStack,
vm = koinViewModel { parametersOf(data.downloadOnScreenEntry) }
)
}
navigation<SelectedApplicationInfo>(startDestination = SelectedApplicationInfo.Main) {
composable<SelectedApplicationInfo.Main> {
val parentBackStackEntry = navController.navGraphEntry(it)
val data =
parentBackStackEntry.getComplexArg<SelectedApplicationInfo.ViewModelParams>()
val viewModel =
koinNavViewModel<SelectedAppInfoViewModel>(viewModelStoreOwner = parentBackStackEntry) {
parametersOf(data)
}
SelectedAppInfoScreen(
onBackClick = navController::popBackStack,
onPatchClick = {
it.lifecycleScope.launch {
navController.navigateComplex(
Patcher,
viewModel.getPatcherParams()
)
}
},
onPatchSelectorClick = { app, patches, options ->
navController.navigateComplex(
SelectedApplicationInfo.PatchesSelector,
SelectedApplicationInfo.PatchesSelector.ViewModelParams(
app,
patches,
options
)
)
},
onRequiredOptions = { app, patches, options ->
navController.navigateComplex(
SelectedApplicationInfo.RequiredOptions,
SelectedApplicationInfo.PatchesSelector.ViewModelParams(
app,
patches,
options
)
)
},
vm = viewModel
)
}
composable<SelectedApplicationInfo.PatchesSelector> {
val data =
it.getComplexArg<SelectedApplicationInfo.PatchesSelector.ViewModelParams>()
val selectedAppInfoVm = koinNavViewModel<SelectedAppInfoViewModel>(
viewModelStoreOwner = navController.navGraphEntry(it)
)
PatchesSelectorScreen(
onBackClick = navController::popBackStack,
onSave = { patches, options ->
selectedAppInfoVm.updateConfiguration(patches, options)
navController.popBackStack()
},
viewModel = koinViewModel { parametersOf(data) }
)
}
composable<SelectedApplicationInfo.RequiredOptions> {
val data =
it.getComplexArg<SelectedApplicationInfo.PatchesSelector.ViewModelParams>()
val selectedAppInfoVm = koinNavViewModel<SelectedAppInfoViewModel>(
viewModelStoreOwner = navController.navGraphEntry(it)
)
RequiredOptionsScreen(
onBackClick = navController::popBackStack,
onContinue = { patches, options ->
selectedAppInfoVm.updateConfiguration(patches, options)
it.lifecycleScope.launch {
navController.navigateComplex(
Patcher,
selectedAppInfoVm.getPatcherParams()
)
}
},
vm = koinViewModel { parametersOf(data) }
)
}
}
navigation<Settings>(startDestination = Settings.Main) {
composable<Settings.Main> {
SettingsScreen(
onBackClick = navController::popBackStack,
navigate = navController::navigate
)
}
composable<Settings.General> {
GeneralSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.Advanced> {
AdvancedSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.Developer> {
DeveloperSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.Updates> {
UpdatesSettingsScreen(
onBackClick = navController::popBackStack,
onChangelogClick = { navController.navigate(Settings.Changelogs) },
onUpdateClick = { navController.navigate(Update()) }
)
}
composable<Settings.Downloads> {
DownloadsSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.ImportExport> {
ImportExportSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.About> {
AboutSettingsScreen(
onBackClick = navController::popBackStack,
navigate = navController::navigate
)
}
composable<Settings.Changelogs> {
ChangelogsSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.Contributors> {
ContributorSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.Licenses> {
LicensesSettingsScreen(onBackClick = navController::popBackStack)
}
}
}
}
@Composable
private fun NavController.navGraphEntry(entry: NavBackStackEntry) =
remember(entry) { getBackStackEntry(entry.destination.parent!!.id) }
// Androidx Navigation does not support storing complex types in route objects, so we have to store them inside the saved state handle of the back stack entry instead.
private fun <T : Parcelable, R : ComplexParameter<T>> NavController.navigateComplex(
route: R,
data: T
) {
navigate(route)
getBackStackEntry(route).savedStateHandle["args"] = data
}
private fun <T : Parcelable> NavBackStackEntry.getComplexArg() = savedStateHandle.get<T>("args")!!

View File

@ -1,23 +1,24 @@
package app.revanced.manager
import android.app.Activity
import android.app.Application
import android.content.Intent
import android.os.Bundle
import android.util.Log
import app.revanced.manager.data.platform.Filesystem
import app.revanced.manager.di.*
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.DownloaderPluginRepository
import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.service.ManagerRootService
import app.revanced.manager.service.RootConnection
import app.revanced.manager.util.tag
import kotlinx.coroutines.Dispatchers
import coil.Coil
import coil.ImageLoader
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.BuilderImpl
import com.topjohnwu.superuser.ipc.RootService
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
@ -28,6 +29,9 @@ class ManagerApplication : Application() {
private val scope = MainScope()
private val prefs: PreferencesManager by inject()
private val patchBundleRepository: PatchBundleRepository by inject()
private val downloaderPluginRepository: DownloaderPluginRepository by inject()
private val fs: Filesystem by inject()
override fun onCreate() {
super.onCreate()
@ -61,17 +65,46 @@ class ManagerApplication : Application() {
val shellBuilder = BuilderImpl.create().setFlags(Shell.FLAG_MOUNT_MASTER)
Shell.setDefaultBuilder(shellBuilder)
val intent = Intent(this, ManagerRootService::class.java)
RootService.bind(intent, get<RootConnection>())
scope.launch {
prefs.preload()
}
scope.launch(Dispatchers.Default) {
downloaderPluginRepository.reload()
}
scope.launch(Dispatchers.Default) {
with(patchBundleRepository) {
reload()
updateCheck()
}
}
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
private var firstActivityCreated = false
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
if (firstActivityCreated) return
firstActivityCreated = true
// We do not want to call onFreshProcessStart() if there is state to restore.
// This can happen on system-initiated process death.
if (savedInstanceState == null) {
Log.d(tag, "Fresh process created")
onFreshProcessStart()
} else Log.d(tag, "System-initiated process death detected")
}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {}
})
}
private fun onFreshProcessStart() {
fs.uiTempDir.apply {
deleteRecursively()
mkdirs()
}
}
}

View File

@ -1,13 +1,16 @@
package app.revanced.manager.data.platform
import android.Manifest
import android.app.Application
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Environment
import android.Manifest
import android.content.pm.PackageManager
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts
import app.revanced.manager.util.RequestManageStorageContract
import java.io.File
import java.nio.file.Path
class Filesystem(private val app: Application) {
val contentResolver = app.contentResolver // TODO: move Content Resolver operations to here.
@ -16,21 +19,33 @@ class Filesystem(private val app: Application) {
* A directory that gets cleared when the app restarts.
* Do not store paths to this directory in a parcel.
*/
val tempDir = app.cacheDir.resolve("ephemeral").apply {
val tempDir: File = app.getDir("ephemeral", Context.MODE_PRIVATE).apply {
deleteRecursively()
mkdirs()
}
fun externalFilesDir() = Environment.getExternalStorageDirectory().toPath()
/**
* A directory for storing temporary files related to UI.
* This is the same as [tempDir], but does not get cleared on system-initiated process death.
* Paths to this directory can be safely stored in parcels.
*/
val uiTempDir: File = app.getDir("ui_ephemeral", Context.MODE_PRIVATE)
fun externalFilesDir(): Path = Environment.getExternalStorageDirectory().toPath()
private fun usesManagePermission() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
private val storagePermissionName = if (usesManagePermission()) Manifest.permission.MANAGE_EXTERNAL_STORAGE else Manifest.permission.READ_EXTERNAL_STORAGE
private val storagePermissionName =
if (usesManagePermission()) Manifest.permission.MANAGE_EXTERNAL_STORAGE else Manifest.permission.READ_EXTERNAL_STORAGE
fun permissionContract(): Pair<ActivityResultContract<String, Boolean>, String> {
val contract = if (usesManagePermission()) RequestManageStorageContract() else ActivityResultContracts.RequestPermission()
val contract =
if (usesManagePermission()) RequestManageStorageContract() else ActivityResultContracts.RequestPermission()
return contract to storagePermissionName
}
fun hasStoragePermission() = if (usesManagePermission()) Environment.isExternalStorageManager() else app.checkSelfPermission(storagePermissionName) == PackageManager.PERMISSION_GRANTED
fun hasStoragePermission() =
if (usesManagePermission()) Environment.isExternalStorageManager() else app.checkSelfPermission(
storagePermissionName
) == PackageManager.PERMISSION_GRANTED
}

View File

@ -16,9 +16,14 @@ import app.revanced.manager.data.room.bundles.PatchBundleEntity
import app.revanced.manager.data.room.options.Option
import app.revanced.manager.data.room.options.OptionDao
import app.revanced.manager.data.room.options.OptionGroup
import app.revanced.manager.data.room.plugins.TrustedDownloaderPlugin
import app.revanced.manager.data.room.plugins.TrustedDownloaderPluginDao
import kotlin.random.Random
@Database(entities = [PatchBundleEntity::class, PatchSelection::class, SelectedPatch::class, DownloadedApp::class, InstalledApp::class, AppliedPatch::class, OptionGroup::class, Option::class], version = 1)
@Database(
entities = [PatchBundleEntity::class, PatchSelection::class, SelectedPatch::class, DownloadedApp::class, InstalledApp::class, AppliedPatch::class, OptionGroup::class, Option::class, TrustedDownloaderPlugin::class],
version = 1
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun patchBundleDao(): PatchBundleDao
@ -26,6 +31,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun downloadedAppDao(): DownloadedAppDao
abstract fun installedAppDao(): InstalledAppDao
abstract fun optionDao(): OptionDao
abstract fun trustedDownloaderPluginDao(): TrustedDownloaderPluginDao
companion object {
fun generateUid() = Random.Default.nextInt()

View File

@ -2,7 +2,7 @@ package app.revanced.manager.data.room
import androidx.room.TypeConverter
import app.revanced.manager.data.room.bundles.Source
import io.ktor.http.*
import app.revanced.manager.data.room.options.Option.SerializedValue
import java.io.File
class Converters {
@ -17,4 +17,10 @@ class Converters {
@TypeConverter
fun fileToString(file: File): String = file.path
@TypeConverter
fun serializedOptionFromString(value: String) = SerializedValue.fromJsonString(value)
@TypeConverter
fun serializedOptionToString(value: SerializedValue) = value.toJsonString()
}

View File

@ -12,4 +12,5 @@ data class DownloadedApp(
@ColumnInfo(name = "package_name") val packageName: String,
@ColumnInfo(name = "version") val version: String,
@ColumnInfo(name = "directory") val directory: File,
@ColumnInfo(name = "last_used") val lastUsed: Long = System.currentTimeMillis()
)

View File

@ -4,6 +4,7 @@ import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Upsert
import kotlinx.coroutines.flow.Flow
@Dao
@ -14,8 +15,11 @@ interface DownloadedAppDao {
@Query("SELECT * FROM downloaded_app WHERE package_name = :packageName AND version = :version")
suspend fun get(packageName: String, version: String): DownloadedApp?
@Insert
suspend fun insert(downloadedApp: DownloadedApp)
@Upsert
suspend fun upsert(downloadedApp: DownloadedApp)
@Query("UPDATE downloaded_app SET last_used = :newValue WHERE package_name = :packageName AND version = :version")
suspend fun markUsed(packageName: String, version: String, newValue: Long = System.currentTimeMillis())
@Delete
suspend fun delete(downloadedApps: Collection<DownloadedApp>)

View File

@ -22,7 +22,8 @@ import kotlinx.parcelize.Parcelize
ForeignKey(
PatchBundleEntity::class,
parentColumns = ["uid"],
childColumns = ["bundle"]
childColumns = ["bundle"],
onDelete = ForeignKey.CASCADE
)
],
indices = [Index(value = ["bundle"], unique = false)]

View File

@ -1,18 +1,15 @@
package app.revanced.manager.data.room.apps.installed
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import app.revanced.manager.R
import kotlinx.parcelize.Parcelize
enum class InstallType(val stringResource: Int) {
DEFAULT(R.string.default_install),
ROOT(R.string.root_install)
MOUNT(R.string.mount_install)
}
@Parcelize
@Entity(tableName = "installed_app")
data class InstalledApp(
@PrimaryKey
@ -20,4 +17,4 @@ data class InstalledApp(
@ColumnInfo(name = "original_package_name") val originalPackageName: String,
@ColumnInfo(name = "version") val version: String,
@ColumnInfo(name = "install_type") val installType: InstallType
) : Parcelable
)

View File

@ -3,7 +3,7 @@ package app.revanced.manager.data.room.apps.installed
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.MapInfo
import androidx.room.MapColumn
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Upsert
@ -17,12 +17,13 @@ interface InstalledAppDao {
@Query("SELECT * FROM installed_app WHERE current_package_name = :packageName")
suspend fun get(packageName: String): InstalledApp?
@MapInfo(keyColumn = "bundle", valueColumn = "patch_name")
@Query(
"SELECT bundle, patch_name FROM applied_patch" +
" WHERE package_name = :packageName"
)
suspend fun getPatchesSelection(packageName: String): Map<Int, List<String>>
suspend fun getPatchesSelection(packageName: String): Map<@MapColumn("bundle") Int, List<@MapColumn(
"patch_name"
) String>>
@Transaction
suspend fun upsertApp(installedApp: InstalledApp, appliedPatches: List<AppliedPatch>) {

View File

@ -8,22 +8,25 @@ interface PatchBundleDao {
@Query("SELECT * FROM patch_bundles")
suspend fun all(): List<PatchBundleEntity>
@Query("SELECT version, integrations_version, auto_update FROM patch_bundles WHERE uid = :uid")
fun getPropsById(uid: Int): Flow<BundleProperties>
@Query("SELECT version, auto_update FROM patch_bundles WHERE uid = :uid")
fun getPropsById(uid: Int): Flow<BundleProperties?>
@Query("UPDATE patch_bundles SET version = :patches, integrations_version = :integrations WHERE uid = :uid")
suspend fun updateVersion(uid: Int, patches: String?, integrations: String?)
@Query("UPDATE patch_bundles SET version = :patches WHERE uid = :uid")
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)
@Query("UPDATE patch_bundles SET name = :value WHERE uid = :uid")
suspend fun setName(uid: Int, value: String)
@Query("DELETE FROM patch_bundles WHERE uid != 0")
suspend fun purgeCustomBundles()
@Transaction
suspend fun reset() {
purgeCustomBundles()
updateVersion(0, null, null) // Reset the main source
updateVersion(0, null) // Reset the main source
}
@Query("DELETE FROM patch_bundles WHERE uid = :uid")

View File

@ -21,7 +21,7 @@ sealed class Source {
}
companion object {
fun from(value: String) = when(value) {
fun from(value: String) = when (value) {
Local.SENTINEL -> Local
API.SENTINEL -> API
else -> Remote(Url(value))
@ -29,21 +29,16 @@ sealed class Source {
}
}
data class VersionInfo(
@ColumnInfo(name = "version") val patches: String? = null,
@ColumnInfo(name = "integrations_version") val integrations: String? = null,
)
@Entity(tableName = "patch_bundles", indices = [Index(value = ["name"], unique = true)])
@Entity(tableName = "patch_bundles")
data class PatchBundleEntity(
@PrimaryKey val uid: Int,
@ColumnInfo(name = "name") val name: String,
@Embedded val versionInfo: VersionInfo,
@ColumnInfo(name = "version") val version: String? = null,
@ColumnInfo(name = "source") val source: Source,
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
)
data class BundleProperties(
@Embedded val versionInfo: VersionInfo,
@ColumnInfo(name = "version") val version: String? = null,
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
)

View File

@ -3,6 +3,25 @@ package app.revanced.manager.data.room.options
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import app.revanced.manager.patcher.patch.Option
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.add
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.float
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.long
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.typeOf
@Entity(
tableName = "options",
@ -19,5 +38,79 @@ data class Option(
@ColumnInfo(name = "patch_name") val patchName: String,
@ColumnInfo(name = "key") val key: String,
// Encoded as Json.
@ColumnInfo(name = "value") val value: String,
)
@ColumnInfo(name = "value") val value: SerializedValue,
) {
@Serializable
data class SerializedValue(val raw: JsonElement) {
fun toJsonString() = json.encodeToString(raw)
fun deserializeFor(option: Option<*>): Any? {
if (raw is JsonNull) return null
val errorMessage = "Cannot deserialize value as ${option.type}"
try {
if (option.type.classifier == List::class) {
val elementType = option.type.arguments.first().type!!
return raw.jsonArray.map { deserializeBasicType(elementType, it.jsonPrimitive) }
}
return deserializeBasicType(option.type, raw.jsonPrimitive)
} catch (e: IllegalArgumentException) {
throw SerializationException(errorMessage, e)
} catch (e: IllegalStateException) {
throw SerializationException(errorMessage, e)
} catch (e: kotlinx.serialization.SerializationException) {
throw SerializationException(errorMessage, e)
}
}
companion object {
private val json = Json {
// Patcher does not forbid the use of these values, so we should support them.
allowSpecialFloatingPointValues = true
}
private fun deserializeBasicType(type: KType, value: JsonPrimitive) = when (type) {
typeOf<Boolean>() -> value.boolean
typeOf<Int>() -> value.int
typeOf<Long>() -> value.long
typeOf<Float>() -> value.float
typeOf<String>() -> value.content.also {
if (!value.isString) throw SerializationException(
"Expected value to be a string: $value"
)
}
else -> throw SerializationException("Unknown type: $type")
}
fun fromJsonString(value: String) = SerializedValue(json.decodeFromString(value))
fun fromValue(value: Any?) = SerializedValue(when (value) {
null -> JsonNull
is Number -> JsonPrimitive(value)
is Boolean -> JsonPrimitive(value)
is String -> JsonPrimitive(value)
is List<*> -> buildJsonArray {
var elementClass: KClass<out Any>? = null
value.forEach {
when (it) {
null -> throw SerializationException("List elements must not be null")
is Number -> add(it)
is Boolean -> add(it)
is String -> add(it)
else -> throw SerializationException("Unknown element type: ${it::class.simpleName}")
}
if (elementClass == null) elementClass = it::class
else if (elementClass != it::class) throw SerializationException("List elements must have the same type")
}
}
else -> throw SerializationException("Unknown type: ${value::class.simpleName}")
})
}
}
class SerializationException(message: String, cause: Throwable? = null) :
Exception(message, cause)
}

View File

@ -2,7 +2,7 @@ package app.revanced.manager.data.room.options
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.MapInfo
import androidx.room.MapColumn
import androidx.room.Query
import androidx.room.Transaction
import kotlinx.coroutines.flow.Flow
@ -10,13 +10,12 @@ import kotlinx.coroutines.flow.Flow
@Dao
abstract class OptionDao {
@Transaction
@MapInfo(keyColumn = "patch_bundle")
@Query(
"SELECT patch_bundle, `group`, patch_name, `key`, value FROM option_groups" +
" LEFT JOIN options ON uid = options.`group`" +
" WHERE package_name = :packageName"
)
abstract suspend fun getOptions(packageName: String): Map<Int, List<Option>>
abstract suspend fun getOptions(packageName: String): Map<@MapColumn("patch_bundle") Int, List<Option>>
@Query("SELECT uid FROM option_groups WHERE patch_bundle = :bundleUid AND package_name = :packageName")
abstract suspend fun getGroupId(bundleUid: Int, packageName: String): Int?
@ -28,10 +27,10 @@ abstract class OptionDao {
abstract suspend fun createOptionGroup(group: OptionGroup)
@Query("DELETE FROM option_groups WHERE patch_bundle = :uid")
abstract suspend fun clearForPatchBundle(uid: Int)
abstract suspend fun resetOptionsForPatchBundle(uid: Int)
@Query("DELETE FROM option_groups WHERE package_name = :packageName")
abstract suspend fun clearForPackage(packageName: String)
abstract suspend fun resetOptionsForPackage(packageName: String)
@Query("DELETE FROM option_groups")
abstract suspend fun reset()

View File

@ -0,0 +1,11 @@
package app.revanced.manager.data.room.plugins
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "trusted_downloader_plugins")
class TrustedDownloaderPlugin(
@PrimaryKey @ColumnInfo(name = "package_name") val packageName: String,
@ColumnInfo(name = "signature") val signature: ByteArray
)

View File

@ -0,0 +1,22 @@
package app.revanced.manager.data.room.plugins
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Upsert
@Dao
interface TrustedDownloaderPluginDao {
@Query("SELECT signature FROM trusted_downloader_plugins WHERE package_name = :packageName")
suspend fun getTrustedSignature(packageName: String): ByteArray?
@Upsert
suspend fun upsertTrust(plugin: TrustedDownloaderPlugin)
@Query("DELETE FROM trusted_downloader_plugins WHERE package_name = :packageName")
suspend fun remove(packageName: String)
@Transaction
@Query("DELETE FROM trusted_downloader_plugins WHERE package_name IN (:packageNames)")
suspend fun removeAll(packageNames: Set<String>)
}

View File

@ -2,29 +2,32 @@ package app.revanced.manager.data.room.selection
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.MapInfo
import androidx.room.MapColumn
import androidx.room.Query
import androidx.room.Transaction
import kotlinx.coroutines.flow.Flow
@Dao
abstract class SelectionDao {
@Transaction
@MapInfo(keyColumn = "patch_bundle", valueColumn = "patch_name")
@Query(
"SELECT patch_bundle, patch_name FROM patch_selections" +
" LEFT JOIN selected_patches ON uid = selected_patches.selection" +
" WHERE package_name = :packageName"
)
abstract suspend fun getSelectedPatches(packageName: String): Map<Int, List<String>>
abstract suspend fun getSelectedPatches(packageName: String): Map<@MapColumn("patch_bundle") Int, List<@MapColumn(
"patch_name"
) String>>
@Transaction
@MapInfo(keyColumn = "package_name", valueColumn = "patch_name")
@Query(
"SELECT package_name, patch_name FROM patch_selections" +
" LEFT JOIN selected_patches ON uid = selected_patches.selection" +
" WHERE patch_bundle = :bundleUid"
)
abstract suspend fun exportSelection(bundleUid: Int): Map<String, List<String>>
abstract suspend fun exportSelection(bundleUid: Int): Map<@MapColumn("package_name") String, List<@MapColumn(
"patch_name"
) String>>
@Query("SELECT uid FROM patch_selections WHERE patch_bundle = :bundleUid AND package_name = :packageName")
abstract suspend fun getSelectionId(bundleUid: Int, packageName: String): Int?
@ -32,11 +35,14 @@ 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 clearForPatchBundle(uid: Int)
abstract suspend fun resetForPatchBundle(uid: Int)
@Query("DELETE FROM patch_selections WHERE package_name = :packageName")
abstract suspend fun clearForPackage(packageName: String)
abstract suspend fun resetForPackage(packageName: String)
@Query("DELETE FROM patch_selections")
abstract suspend fun reset()

View File

@ -22,6 +22,7 @@ val repositoryModule = module {
// It is best to load patch bundles ASAP
createdAtStart()
}
singleOf(::DownloaderPluginRepository)
singleOf(::WorkerRepository)
singleOf(::DownloadedAppRepository)
singleOf(::InstalledAppRepository)

View File

@ -1,11 +1,9 @@
package app.revanced.manager.di
import app.revanced.manager.domain.installer.RootInstaller
import app.revanced.manager.service.RootConnection
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
val rootModule = module {
singleOf(::RootConnection)
singleOf(::RootInstaller)
}

View File

@ -1,11 +1,9 @@
package app.revanced.manager.di
import app.revanced.manager.network.service.HttpService
import app.revanced.manager.network.service.ReVancedService
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
val serviceModule = module {
singleOf(::ReVancedService)
singleOf(::HttpService)
}

View File

@ -9,14 +9,15 @@ val viewModelModule = module {
viewModelOf(::DashboardViewModel)
viewModelOf(::SelectedAppInfoViewModel)
viewModelOf(::PatchesSelectorViewModel)
viewModelOf(::SettingsViewModel)
viewModelOf(::GeneralSettingsViewModel)
viewModelOf(::AdvancedSettingsViewModel)
viewModelOf(::AppSelectorViewModel)
viewModelOf(::VersionSelectorViewModel)
viewModelOf(::PatcherViewModel)
viewModelOf(::UpdateViewModel)
viewModelOf(::ChangelogsViewModel)
viewModelOf(::ImportExportViewModel)
viewModelOf(::AboutViewModel)
viewModelOf(::DeveloperOptionsViewModel)
viewModelOf(::ContributorViewModel)
viewModelOf(::DownloadsViewModel)
viewModelOf(::InstalledAppsViewModel)

View File

@ -4,22 +4,18 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.io.InputStream
import java.nio.file.Files
import java.nio.file.StandardCopyOption
class LocalPatchBundle(name: String, id: Int, directory: File) : PatchBundleSource(name, id, directory) {
suspend fun replace(patches: InputStream? = null, integrations: InputStream? = null) {
class LocalPatchBundle(name: String, id: Int, directory: File) :
PatchBundleSource(name, id, directory) {
suspend fun replace(patches: InputStream) {
withContext(Dispatchers.IO) {
patches?.let { inputStream ->
patchBundleOutputStream().use { outputStream ->
inputStream.copyTo(outputStream)
}
}
integrations?.let {
Files.copy(it, this@LocalPatchBundle.integrationsFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
patchBundleOutputStream().use { outputStream ->
patches.copyTo(outputStream)
}
}
reload()
reload()?.also {
saveVersion(it.readManifestAttribute("Version"))
}
}
}

View File

@ -1,12 +1,22 @@
package app.revanced.manager.domain.bundles
import android.app.Application
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R
import app.revanced.manager.domain.repository.PatchBundlePersistenceRepository
import app.revanced.manager.patcher.patch.PatchBundle
import app.revanced.manager.util.tag
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.io.File
import java.io.OutputStream
@ -14,13 +24,20 @@ import java.io.OutputStream
* A [PatchBundle] source.
*/
@Stable
sealed class PatchBundleSource(val name: String, val uid: Int, directory: File) {
sealed class PatchBundleSource(initialName: String, val uid: Int, directory: File) : KoinComponent {
protected val configRepository: PatchBundlePersistenceRepository by inject()
private val app: Application by inject()
protected val patchesFile = directory.resolve("patches.jar")
protected val integrationsFile = directory.resolve("integrations.apk")
private val _state = MutableStateFlow(load())
val state = _state.asStateFlow()
private val _nameFlow = MutableStateFlow(initialName)
val nameFlow =
_nameFlow.map { it.ifEmpty { app.getString(if (isDefault) R.string.bundle_name_default else R.string.bundle_name_fallback) } }
suspend fun getName() = nameFlow.first()
/**
* Returns true if the bundle has been downloaded to local storage.
*/
@ -40,15 +57,40 @@ sealed class PatchBundleSource(val name: String, val uid: Int, directory: File)
if (!hasInstalled()) return State.Missing
return try {
State.Loaded(PatchBundle(patchesFile, integrationsFile.takeIf(File::exists)))
State.Loaded(PatchBundle(patchesFile))
} catch (t: Throwable) {
Log.e(tag, "Failed to load patch bundle $name", t)
Log.e(tag, "Failed to load patch bundle with UID $uid", t)
State.Failed(t)
}
}
fun reload() {
_state.value = load()
suspend fun reload(): PatchBundle? {
val newState = load()
_state.value = newState
val bundle = newState.patchBundleOrNull()
// Try to read the name from the patch bundle manifest if the bundle does not have a name.
if (bundle != null && _nameFlow.value.isEmpty()) {
bundle.readManifestAttribute("Name")?.let { setName(it) }
}
return bundle
}
/**
* Create a flow that emits the [app.revanced.manager.data.room.bundles.BundleProperties] of this [PatchBundleSource].
* The flow will emit null if the associated [PatchBundleSource] is deleted.
*/
fun propsFlow() = configRepository.getProps(uid).flowOn(Dispatchers.Default)
suspend fun getProps() = propsFlow().first()!!
suspend fun currentVersion() = getProps().version
protected suspend fun saveVersion(version: String?) =
configRepository.updateVersion(uid, version)
suspend fun setName(name: String) {
configRepository.setName(uid, name)
_nameFlow.value = name
}
sealed interface State {
@ -61,9 +103,12 @@ sealed class PatchBundleSource(val name: String, val uid: Int, directory: File)
}
}
companion object {
val PatchBundleSource.isDefault get() = uid == 0
val PatchBundleSource.asRemoteOrNull get() = this as? RemotePatchBundle
fun PatchBundleSource.propsOrNullFlow() = asRemoteOrNull?.propsFlow() ?: flowOf(null)
companion object Extensions {
val PatchBundleSource.isDefault inline get() = uid == 0
val PatchBundleSource.asRemoteOrNull inline get() = this as? RemotePatchBundle
val PatchBundleSource.nameState
@Composable inline get() = nameFlow.collectAsStateWithLifecycle(
""
)
}
}

View File

@ -1,54 +1,31 @@
package app.revanced.manager.domain.bundles
import androidx.compose.runtime.Stable
import app.revanced.manager.data.room.bundles.VersionInfo
import app.revanced.manager.domain.repository.PatchBundlePersistenceRepository
import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.network.api.ReVancedAPI.Extensions.findAssetByType
import app.revanced.manager.network.dto.BundleAsset
import app.revanced.manager.network.dto.BundleInfo
import app.revanced.manager.network.dto.ReVancedAsset
import app.revanced.manager.network.service.HttpService
import app.revanced.manager.network.utils.getOrThrow
import app.revanced.manager.util.APK_MIMETYPE
import app.revanced.manager.util.JAR_MIMETYPE
import io.ktor.client.request.url
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.io.File
@Stable
sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpoint: String) :
PatchBundleSource(name, id, directory), KoinComponent {
private val configRepository: PatchBundlePersistenceRepository by inject()
PatchBundleSource(name, id, directory) {
protected val http: HttpService by inject()
protected abstract suspend fun getLatestInfo(): BundleInfo
protected abstract suspend fun getLatestInfo(): ReVancedAsset
private suspend fun download(info: BundleInfo) = withContext(Dispatchers.IO) {
val (patches, integrations) = info
coroutineScope {
launch {
patchBundleOutputStream().use {
http.streamTo(it) {
url(patches.url)
}
}
}
launch {
http.download(integrationsFile) {
url(integrations.url)
}
private suspend fun download(info: ReVancedAsset) = withContext(Dispatchers.IO) {
patchBundleOutputStream().use {
http.streamTo(it) {
url(info.downloadUrl)
}
}
saveVersion(patches.version, integrations.version)
saveVersion(info.version)
reload()
}
@ -58,29 +35,18 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo
suspend fun update(): Boolean = withContext(Dispatchers.IO) {
val info = getLatestInfo()
if (hasInstalled() && VersionInfo(
info.patches.version,
info.integrations.version
) == currentVersion()
) {
if (hasInstalled() && info.version == currentVersion())
return@withContext false
}
download(info)
true
}
private suspend fun currentVersion() = configRepository.getProps(uid).first().versionInfo
private suspend fun saveVersion(patches: String, integrations: String) =
configRepository.updateVersion(uid, patches, integrations)
suspend fun deleteLocalFiles() = withContext(Dispatchers.Default) {
arrayOf(patchesFile, integrationsFile).forEach(File::delete)
patchesFile.delete()
reload()
}
fun propsFlow() = configRepository.getProps(uid)
suspend fun setAutoUpdate(value: Boolean) = configRepository.setAutoUpdate(uid, value)
companion object {
@ -91,7 +57,7 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo
class JsonPatchBundle(name: String, id: Int, directory: File, endpoint: String) :
RemotePatchBundle(name, id, directory, endpoint) {
override suspend fun getLatestInfo() = withContext(Dispatchers.IO) {
http.request<BundleInfo> {
http.request<ReVancedAsset> {
url(endpoint)
}.getOrThrow()
}
@ -101,22 +67,5 @@ class APIPatchBundle(name: String, id: Int, directory: File, endpoint: String) :
RemotePatchBundle(name, id, directory, endpoint) {
private val api: ReVancedAPI by inject()
override suspend fun getLatestInfo() = coroutineScope {
fun getAssetAsync(repo: String, mime: String) = async(Dispatchers.IO) {
api
.getLatestRelease(repo)
.getOrThrow()
.let {
BundleAsset(it.version, it.findAssetByType(mime).downloadUrl)
}
}
val patches = getAssetAsync("revanced-patches", JAR_MIMETYPE)
val integrations = getAssetAsync("revanced-integrations", APK_MIMETYPE)
BundleInfo(
patches.await(),
integrations.await()
)
}
override suspend fun getLatestInfo() = api.getPatchesUpdate().getOrThrow()
}

View File

@ -1,49 +1,97 @@
package app.revanced.manager.domain.installer
import android.app.Application
import app.revanced.manager.service.RootConnection
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import app.revanced.manager.IRootSystemService
import app.revanced.manager.service.ManagerRootService
import app.revanced.manager.util.PM
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ipc.RootService
import com.topjohnwu.superuser.nio.FileSystemManager
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.time.withTimeoutOrNull
import kotlinx.coroutines.withContext
import java.io.File
import java.time.Duration
class RootInstaller(
private val app: Application,
private val rootConnection: RootConnection,
private val pm: PM
) {
) : ServiceConnection {
private var remoteFS = CompletableDeferred<FileSystemManager>()
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val ipc = IRootSystemService.Stub.asInterface(service)
val binder = ipc.fileSystemService
remoteFS.complete(FileSystemManager.getRemote(binder))
}
override fun onServiceDisconnected(name: ComponentName?) {
remoteFS = CompletableDeferred()
}
private suspend fun awaitRemoteFS(): FileSystemManager {
if (remoteFS.isActive) {
withContext(Dispatchers.Main) {
val intent = Intent(app, ManagerRootService::class.java)
RootService.bind(intent, this@RootInstaller)
}
}
return withTimeoutOrNull(Duration.ofSeconds(20L)) {
remoteFS.await()
} ?: throw RootServiceException()
}
private suspend fun getShell() = with(CompletableDeferred<Shell>()) {
Shell.getShell(::complete)
await()
}
suspend fun execute(vararg commands: String) = getShell().newJob().add(*commands).exec()
fun hasRootAccess() = Shell.isAppGrantedRoot() ?: false
fun isAppInstalled(packageName: String) =
rootConnection.remoteFS?.getFile("$modulesPath/$packageName-revanced")
?.exists() ?: throw RootServiceException()
fun isDeviceRooted() = System.getenv("PATH")?.split(":")?.any { path ->
File(path, "su").canExecute()
} ?: false
fun isAppMounted(packageName: String): Boolean {
return pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir?.let {
Shell.cmd("mount | grep \"$it\"").exec().isSuccess
suspend fun isAppInstalled(packageName: String) =
awaitRemoteFS().getFile("$modulesPath/$packageName-revanced").exists()
suspend fun isAppMounted(packageName: String) = withContext(Dispatchers.IO) {
pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir?.let {
execute("mount | grep \"$it\"").isSuccess
} ?: false
}
fun mount(packageName: String) {
suspend fun mount(packageName: String) {
if (isAppMounted(packageName)) return
val stockAPK = pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir
?: throw Exception("Failed to load application info")
val patchedAPK = "$modulesPath/$packageName-revanced/$packageName.apk"
withContext(Dispatchers.IO) {
val stockAPK = pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir
?: throw Exception("Failed to load application info")
val patchedAPK = "$modulesPath/$packageName-revanced/$packageName.apk"
Shell.cmd("mount -o bind \"$patchedAPK\" \"$stockAPK\"").exec()
.also { if (!it.isSuccess) throw Exception("Failed to mount APK") }
execute("mount -o bind \"$patchedAPK\" \"$stockAPK\"").assertSuccess("Failed to mount APK")
}
}
fun unmount(packageName: String) {
suspend fun unmount(packageName: String) {
if (!isAppMounted(packageName)) return
val stockAPK = pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir
?: throw Exception("Failed to load application info")
withContext(Dispatchers.IO) {
val stockAPK = pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir
?: throw Exception("Failed to load application info")
Shell.cmd("umount -l \"$stockAPK\"").exec()
.also { if (!it.isSuccess) throw Exception("Failed to unmount APK") }
execute("umount -l \"$stockAPK\"").assertSuccess("Failed to unmount APK")
}
}
suspend fun install(
@ -52,80 +100,82 @@ class RootInstaller(
packageName: String,
version: String,
label: String
) {
withContext(Dispatchers.IO) {
rootConnection.remoteFS?.let { remoteFS ->
val assets = app.assets
val modulePath = "$modulesPath/$packageName-revanced"
) = withContext(Dispatchers.IO) {
val remoteFS = awaitRemoteFS()
val assets = app.assets
val modulePath = "$modulesPath/$packageName-revanced"
unmount(packageName)
unmount(packageName)
stockAPK?.let { stockApp ->
pm.getPackageInfo(packageName)?.let { packageInfo ->
if (packageInfo.versionName <= version)
Shell.cmd("pm uninstall -k --user 0 $packageName").exec()
.also { if (!it.isSuccess) throw Exception("Failed to uninstall stock app") }
stockAPK?.let { stockApp ->
pm.getPackageInfo(packageName)?.let { packageInfo ->
// TODO: get user id programmatically
if (pm.getVersionCode(packageInfo) <= pm.getVersionCode(
pm.getPackageInfo(patchedAPK)
?: error("Failed to get package info for patched app")
)
)
execute("pm uninstall -k --user 0 $packageName").assertSuccess("Failed to uninstall stock app")
}
execute("pm install \"${stockApp.absolutePath}\"").assertSuccess("Failed to install stock app")
}
remoteFS.getFile(modulePath).mkdir()
listOf(
"service.sh",
"module.prop",
).forEach { file ->
assets.open("root/$file").use { inputStream ->
remoteFS.getFile("$modulePath/$file").newOutputStream()
.use { outputStream ->
val content = String(inputStream.readBytes())
.replace("__PKG_NAME__", packageName)
.replace("__VERSION__", version)
.replace("__LABEL__", label)
.toByteArray()
outputStream.write(content)
}
}
}
Shell.cmd("pm install \"${stockApp.absolutePath}\"").exec()
.also { if (!it.isSuccess) throw Exception("Failed to install stock app") }
}
"$modulePath/$packageName.apk".let { apkPath ->
remoteFS.getFile(modulePath).mkdir()
listOf(
"service.sh",
"module.prop",
).forEach { file ->
assets.open("root/$file").use { inputStream ->
remoteFS.getFile("$modulePath/$file").newOutputStream()
.use { outputStream ->
val content = String(inputStream.readBytes())
.replace("__PKG_NAME__", packageName)
.replace("__VERSION__", version)
.replace("__LABEL__", label)
.toByteArray()
outputStream.write(content)
}
remoteFS.getFile(patchedAPK.absolutePath)
.also { if (!it.exists()) throw Exception("File doesn't exist") }
.newInputStream().use { inputStream ->
remoteFS.getFile(apkPath).newOutputStream().use { outputStream ->
inputStream.copyTo(outputStream)
}
}
"$modulePath/$packageName.apk".let { apkPath ->
remoteFS.getFile(patchedAPK.absolutePath)
.also { if (!it.exists()) throw Exception("File doesn't exist") }
.newInputStream().use { inputStream ->
remoteFS.getFile(apkPath).newOutputStream().use { outputStream ->
inputStream.copyTo(outputStream)
}
}
Shell.cmd(
"chmod 644 $apkPath",
"chown system:system $apkPath",
"chcon u:object_r:apk_data_file:s0 $apkPath",
"chmod +x $modulePath/service.sh"
).exec()
.let { if (!it.isSuccess) throw Exception("Failed to set file permissions") }
}
} ?: throw RootServiceException()
execute(
"chmod 644 $apkPath",
"chown system:system $apkPath",
"chcon u:object_r:apk_data_file:s0 $apkPath",
"chmod +x $modulePath/service.sh"
).assertSuccess("Failed to set file permissions")
}
}
fun uninstall(packageName: String) {
rootConnection.remoteFS?.let { remoteFS ->
if (isAppMounted(packageName))
unmount(packageName)
suspend fun uninstall(packageName: String) {
val remoteFS = awaitRemoteFS()
if (isAppMounted(packageName))
unmount(packageName)
remoteFS.getFile("$modulesPath/$packageName-revanced").deleteRecursively()
.also { if (!it) throw Exception("Failed to delete files") }
} ?: throw RootServiceException()
remoteFS.getFile("$modulesPath/$packageName-revanced").deleteRecursively()
.also { if (!it) throw Exception("Failed to delete files") }
}
companion object {
const val modulesPath = "/data/adb/modules"
private fun Shell.Result.assertSuccess(errorMessage: String) {
if (!isSuccess) throw Exception(errorMessage)
}
}
}
class RootServiceException: Exception("Root not available")
class RootServiceException : Exception("Root not available")

View File

@ -12,6 +12,8 @@ import java.io.InputStream
import java.io.OutputStream
import java.nio.file.Files
import java.security.UnrecoverableKeyException
import java.util.Date
import kotlin.time.Duration.Companion.days
class KeystoreManager(app: Application, private val prefs: PreferencesManager) {
companion object Constants {
@ -19,6 +21,7 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) {
* Default alias and password for the keystore.
*/
const val DEFAULT = "ReVanced"
private val eightYearsFromNow get() = Date(System.currentTimeMillis() + (365.days * 8).inWholeMilliseconds * 24)
}
private val keystorePath =
@ -29,40 +32,45 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) {
prefs.keystorePass.value = pass
}
private suspend fun signingOptions(path: File = keystorePath) = ApkUtils.SigningOptions(
private suspend fun signingDetails(path: File = keystorePath) = ApkUtils.KeyStoreDetails(
keyStore = path,
keyStorePassword = null,
alias = prefs.keystoreCommonName.get(),
signer = prefs.keystoreCommonName.get(),
password = prefs.keystorePass.get()
)
suspend fun sign(input: File, output: File) = withContext(Dispatchers.Default) {
ApkUtils.sign(input, output, signingOptions())
ApkUtils.signApk(input, output, prefs.keystoreCommonName.get(), signingDetails())
}
suspend fun regenerate() = withContext(Dispatchers.Default) {
val keyCertPair = ApkSigner.newPrivateKeyCertificatePair(
prefs.keystoreCommonName.get(),
eightYearsFromNow
)
val ks = ApkSigner.newKeyStore(
listOf(
setOf(
ApkSigner.KeyStoreEntry(
DEFAULT, DEFAULT
DEFAULT, DEFAULT, keyCertPair
)
)
)
keystorePath.outputStream().use {
ks.store(it, null)
withContext(Dispatchers.IO) {
keystorePath.outputStream().use {
ks.store(it, null)
}
}
updatePrefs(DEFAULT, DEFAULT)
}
suspend fun import(cn: String, pass: String, keystore: InputStream): Boolean {
val keystoreData = keystore.readBytes()
val keystoreData = withContext(Dispatchers.IO) { keystore.readBytes() }
try {
val ks = ApkSigner.readKeyStore(ByteArrayInputStream(keystoreData), null)
ApkSigner.readKeyCertificatePair(ks, cn, pass)
ApkSigner.readPrivateKeyCertificatePair(ks, cn, pass)
} catch (_: UnrecoverableKeyException) {
return false
} catch (_: IllegalArgumentException) {

View File

@ -3,6 +3,7 @@ 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
@ -12,17 +13,22 @@ class PreferencesManager(
val api = stringPreference("api_url", "https://api.revanced.app")
val multithreadingDexFileWriter = booleanPreference("multithreading_dex_file_writer", true)
val disablePatchVersionCompatCheck = booleanPreference("disable_patch_version_compatibility_check", false)
val useProcessRuntime = booleanPreference("use_process_runtime", false)
val patcherProcessMemoryLimit = intPreference("process_runtime_memory_limit", 700)
val keystoreCommonName = stringPreference("keystore_cn", KeystoreManager.DEFAULT)
val keystorePass = stringPreference("keystore_pass", KeystoreManager.DEFAULT)
val preferSplits = booleanPreference("prefer_splits", false)
val firstLaunch = booleanPreference("first_launch", true)
val managerAutoUpdates = booleanPreference("manager_auto_updates", false)
val showManagerUpdateDialogOnLaunch = booleanPreference("show_manager_update_dialog_on_launch", true)
val disablePatchVersionCompatCheck = booleanPreference("disable_patch_version_compatibility_check", false)
val disableSelectionWarning = booleanPreference("disable_selection_warning", false)
val enableSelectionWarningCountdown = booleanPreference("enable_selection_warning_countdown", true)
val disableUniversalPatchCheck = booleanPreference("disable_patch_universal_check", 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

@ -26,6 +26,9 @@ abstract class BasePreferencesManager(private val context: Context, name: String
protected fun stringPreference(key: String, default: String) =
StringPreference(dataStore, key, default)
protected fun stringSetPreference(key: String, default: Set<String>) =
StringSetPreference(dataStore, key, default)
protected fun booleanPreference(key: String, default: Boolean) =
BooleanPreference(dataStore, key, default)
@ -52,11 +55,15 @@ class EditorContext(private val prefs: MutablePreferences) {
var <T> Preference<T>.value
get() = prefs.run { read() }
set(value) = prefs.run { write(value) }
operator fun Preference<Set<String>>.plusAssign(value: String) = prefs.run {
write(read() + value)
}
}
abstract class Preference<T>(
private val dataStore: DataStore<Preferences>,
protected val default: T
val default: T
) {
internal abstract fun Preferences.read(): T
internal abstract fun MutablePreferences.write(value: T)
@ -65,10 +72,12 @@ abstract class Preference<T>(
suspend fun get() = flow.first()
fun getBlocking() = runBlocking { get() }
@Composable
fun getAsState() = flow.collectAsStateWithLifecycle(initialValue = remember {
getBlocking()
})
suspend fun update(value: T) = dataStore.editor {
this@Preference.value = value
}
@ -108,6 +117,14 @@ class StringPreference(
override val key = stringPreferencesKey(key)
}
class StringSetPreference(
dataStore: DataStore<Preferences>,
key: String,
default: Set<String>
) : BasePreference<Set<String>>(dataStore, default) {
override val key = stringSetPreferencesKey(key)
}
class BooleanPreference(
dataStore: DataStore<Preferences>,
key: String,

View File

@ -2,56 +2,126 @@ package app.revanced.manager.domain.repository
import android.app.Application
import android.content.Context
import android.os.Parcelable
import app.revanced.manager.data.room.AppDatabase
import app.revanced.manager.data.room.AppDatabase.Companion.generateUid
import app.revanced.manager.data.room.apps.downloaded.DownloadedApp
import app.revanced.manager.network.downloader.AppDownloader
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
import app.revanced.manager.plugin.downloader.OutputDownloadScope
import app.revanced.manager.util.PM
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import java.io.File
import java.io.FilterOutputStream
import java.nio.file.StandardOpenOption
import java.util.concurrent.atomic.AtomicLong
import kotlin.io.path.outputStream
class DownloadedAppRepository(
app: Application,
db: AppDatabase
private val app: Application,
db: AppDatabase,
private val pm: PM
) {
private val dir = app.getDir("downloaded-apps", Context.MODE_PRIVATE)
private val dao = db.downloadedAppDao()
fun getAll() = dao.getAllApps().distinctUntilChanged()
fun getApkFileForApp(app: DownloadedApp): File = getApkFileForDir(dir.resolve(app.directory))
fun getApkFileForApp(app: DownloadedApp): File =
getApkFileForDir(dir.resolve(app.directory))
private fun getApkFileForDir(directory: File) = directory.listFiles()!!.first()
suspend fun download(
app: AppDownloader.App,
preferSplits: Boolean,
onDownload: suspend (downloadProgress: Pair<Float, Float>?) -> Unit = {},
plugin: LoadedDownloaderPlugin,
data: Parcelable,
expectedPackageName: String,
expectedVersion: String?,
onDownload: suspend (downloadProgress: Pair<Long, Long?>) -> Unit,
): File {
this.get(app.packageName, app.version)?.let { downloaded ->
return getApkFileForApp(downloaded)
}
// Converted integers cannot contain / or .. unlike the package name or version, so they are safer to use here.
val relativePath = File(generateUid().toString())
val savePath = dir.resolve(relativePath).also { it.mkdirs() }
val saveDir = dir.resolve(relativePath).also { it.mkdirs() }
val targetFile = saveDir.resolve("base.apk").toPath()
try {
app.download(savePath, preferSplits, onDownload)
val downloadSize = AtomicLong(0)
val downloadedBytes = AtomicLong(0)
dao.insert(DownloadedApp(
packageName = app.packageName,
version = app.version,
directory = relativePath,
))
channelFlow {
val scope = object : OutputDownloadScope {
override val pluginPackageName = plugin.packageName
override val hostPackageName = app.packageName
override suspend fun reportSize(size: Long) {
require(size > 0) { "Size must be greater than zero" }
require(
downloadSize.compareAndSet(
0,
size
)
) { "Download size has already been set" }
send(downloadedBytes.get() to size)
}
}
fun emitProgress(bytes: Long) {
val newValue = downloadedBytes.addAndGet(bytes)
val totalSize = downloadSize.get()
if (totalSize < 1) return
trySend(newValue to totalSize).getOrThrow()
}
targetFile.outputStream(StandardOpenOption.CREATE_NEW).buffered().use {
val stream = object : FilterOutputStream(it) {
override fun write(b: Int) = out.write(b).also { emitProgress(1) }
override fun write(b: ByteArray?, off: Int, len: Int) =
out.write(b, off, len).also {
emitProgress(
(len - off).toLong()
)
}
}
plugin.download(scope, data, stream)
}
}
.conflate()
.flowOn(Dispatchers.IO)
.collect { (downloaded, size) -> onDownload(downloaded to size) }
if (downloadedBytes.get() < 1) error("Downloader did not download anything.")
val pkgInfo =
pm.getPackageInfo(targetFile.toFile()) ?: error("Downloaded APK file is invalid")
if (pkgInfo.packageName != expectedPackageName) error("Downloaded APK has the wrong package name. Expected: $expectedPackageName, Actual: ${pkgInfo.packageName}")
if (expectedVersion != null && pkgInfo.versionName != expectedVersion) error("Downloaded APK has the wrong version. Expected: $expectedVersion, Actual: ${pkgInfo.versionName}")
// Delete the previous copy (if present).
dao.get(pkgInfo.packageName, pkgInfo.versionName!!)?.directory?.let {
if (!dir.resolve(it).deleteRecursively()) throw Exception("Failed to delete existing directory")
}
dao.upsert(
DownloadedApp(
packageName = pkgInfo.packageName,
version = pkgInfo.versionName!!,
directory = relativePath,
)
)
} catch (e: Exception) {
savePath.deleteRecursively()
saveDir.deleteRecursively()
throw e
}
// Return the Apk file.
return getApkFileForDir(savePath)
return getApkFileForDir(saveDir)
}
suspend fun get(packageName: String, version: String) = dao.get(packageName, version)
suspend fun get(packageName: String, version: String, markUsed: Boolean = false) =
dao.get(packageName, version)?.also {
if (markUsed) dao.markUsed(packageName, version)
}
suspend fun delete(downloadedApps: Collection<DownloadedApp>) {
downloadedApps.forEach {

View File

@ -0,0 +1,168 @@
package app.revanced.manager.domain.repository
import android.app.Application
import android.content.pm.PackageManager
import android.os.Parcelable
import android.util.Log
import app.revanced.manager.data.room.AppDatabase
import app.revanced.manager.data.room.plugins.TrustedDownloaderPlugin
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.network.downloader.DownloaderPluginState
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
import app.revanced.manager.network.downloader.ParceledDownloaderData
import app.revanced.manager.plugin.downloader.DownloaderBuilder
import app.revanced.manager.plugin.downloader.PluginHostApi
import app.revanced.manager.plugin.downloader.Scope
import app.revanced.manager.util.PM
import app.revanced.manager.util.tag
import dalvik.system.PathClassLoader
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import java.lang.reflect.Modifier
@OptIn(PluginHostApi::class)
class DownloaderPluginRepository(
private val pm: PM,
private val prefs: PreferencesManager,
private val app: Application,
db: AppDatabase
) {
private val trustDao = db.trustedDownloaderPluginDao()
private val _pluginStates = MutableStateFlow(emptyMap<String, DownloaderPluginState>())
val pluginStates = _pluginStates.asStateFlow()
val loadedPluginsFlow = pluginStates.map { states ->
states.values.filterIsInstance<DownloaderPluginState.Loaded>().map { it.plugin }
}
private val acknowledgedDownloaderPlugins = prefs.acknowledgedDownloaderPlugins
private val installedPluginPackageNames = MutableStateFlow(emptySet<String>())
val newPluginPackageNames = combine(
installedPluginPackageNames,
acknowledgedDownloaderPlugins.flow
) { installed, acknowledged ->
installed subtract acknowledged
}
suspend fun reload() {
val plugins =
withContext(Dispatchers.IO) {
pm.getPackagesWithFeature(PLUGIN_FEATURE)
.associate { it.packageName to loadPlugin(it.packageName) }
}
_pluginStates.value = plugins
installedPluginPackageNames.value = plugins.keys
val acknowledgedPlugins = acknowledgedDownloaderPlugins.get()
val uninstalledPlugins = acknowledgedPlugins subtract installedPluginPackageNames.value
if (uninstalledPlugins.isNotEmpty()) {
Log.d(tag, "Uninstalled plugins: ${uninstalledPlugins.joinToString(", ")}")
acknowledgedDownloaderPlugins.update(acknowledgedPlugins subtract uninstalledPlugins)
trustDao.removeAll(uninstalledPlugins)
}
}
fun unwrapParceledData(data: ParceledDownloaderData): Pair<LoadedDownloaderPlugin, Parcelable> {
val plugin =
(_pluginStates.value[data.pluginPackageName] as? DownloaderPluginState.Loaded)?.plugin
?: throw Exception("Downloader plugin with name ${data.pluginPackageName} is not available")
return plugin to data.unwrapWith(plugin)
}
private suspend fun loadPlugin(packageName: String): DownloaderPluginState {
try {
if (!verify(packageName)) return DownloaderPluginState.Untrusted
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Log.e(tag, "Got exception while verifying plugin $packageName", e)
return DownloaderPluginState.Failed(e)
}
return try {
val packageInfo = pm.getPackageInfo(packageName, flags = PackageManager.GET_META_DATA)!!
val className = packageInfo.applicationInfo!!.metaData.getString(METADATA_PLUGIN_CLASS)
?: throw Exception("Missing metadata attribute $METADATA_PLUGIN_CLASS")
val classLoader =
PathClassLoader(packageInfo.applicationInfo!!.sourceDir, app.classLoader)
val pluginContext = app.createPackageContext(packageName, 0)
val downloader = classLoader
.loadClass(className)
.getDownloaderBuilder()
.build(
scopeImpl = object : Scope {
override val hostPackageName = app.packageName
override val pluginPackageName = pluginContext.packageName
},
context = pluginContext
)
DownloaderPluginState.Loaded(
LoadedDownloaderPlugin(
packageName,
with(pm) { packageInfo.label() },
packageInfo.versionName!!,
downloader.get,
downloader.download,
classLoader
)
)
} catch (e: CancellationException) {
throw e
} catch (t: Throwable) {
Log.e(tag, "Failed to load plugin $packageName", t)
DownloaderPluginState.Failed(t)
}
}
suspend fun trustPackage(packageName: String) {
trustDao.upsertTrust(
TrustedDownloaderPlugin(
packageName,
pm.getSignature(packageName).toByteArray()
)
)
reload()
prefs.edit {
acknowledgedDownloaderPlugins += packageName
}
}
suspend fun revokeTrustForPackage(packageName: String) =
trustDao.remove(packageName).also { reload() }
suspend fun acknowledgeAllNewPlugins() =
acknowledgedDownloaderPlugins.update(installedPluginPackageNames.value)
private suspend fun verify(packageName: String): Boolean {
val expectedSignature =
trustDao.getTrustedSignature(packageName) ?: return false
return pm.hasSignature(packageName, expectedSignature)
}
private companion object {
const val PLUGIN_FEATURE = "app.revanced.manager.plugin.downloader"
const val METADATA_PLUGIN_CLASS = "app.revanced.manager.plugin.downloader.class"
const val PUBLIC_STATIC = Modifier.PUBLIC or Modifier.STATIC
val Int.isPublicStatic get() = (this and PUBLIC_STATIC) == PUBLIC_STATIC
val Class<*>.isDownloaderBuilder get() = DownloaderBuilder::class.java.isAssignableFrom(this)
@Suppress("UNCHECKED_CAST")
fun Class<*>.getDownloaderBuilder() =
declaredMethods
.firstOrNull { it.modifiers.isPublicStatic && it.returnType.isDownloaderBuilder && it.parameterTypes.isEmpty() }
?.let { it(null) as DownloaderBuilder<Parcelable> }
?: throw Exception("Could not find a valid downloader implementation in class $canonicalName")
}
}

View File

@ -4,8 +4,6 @@ import app.revanced.manager.data.room.AppDatabase
import app.revanced.manager.data.room.AppDatabase.Companion.generateUid
import app.revanced.manager.data.room.bundles.PatchBundleEntity
import app.revanced.manager.data.room.bundles.Source
import app.revanced.manager.data.room.bundles.VersionInfo
import io.ktor.http.*
import kotlinx.coroutines.flow.distinctUntilChanged
class PatchBundlePersistenceRepository(db: AppDatabase) {
@ -23,12 +21,11 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
suspend fun reset() = dao.reset()
suspend fun create(name: String, source: Source, autoUpdate: Boolean = false) =
PatchBundleEntity(
uid = generateUid(),
name = name,
versionInfo = VersionInfo(),
version = null,
source = source,
autoUpdate = autoUpdate
).also {
@ -37,18 +34,20 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
suspend fun delete(uid: Int) = dao.remove(uid)
suspend fun updateVersion(uid: Int, patches: String, integrations: String) =
dao.updateVersion(uid, patches, integrations)
suspend fun updateVersion(uid: Int, version: String?) =
dao.updateVersion(uid, version)
suspend fun setAutoUpdate(uid: Int, value: Boolean) = dao.setAutoUpdate(uid, value)
suspend fun setName(uid: Int, name: String) = dao.setName(uid, name)
fun getProps(id: Int) = dao.getPropsById(id).distinctUntilChanged()
private companion object {
val defaultSource = PatchBundleEntity(
uid = 0,
name = "Main",
versionInfo = VersionInfo(),
name = "",
version = null,
source = Source.API,
autoUpdate = false
)

View File

@ -3,6 +3,7 @@ package app.revanced.manager.domain.repository
import android.app.Application
import android.content.Context
import android.util.Log
import app.revanced.library.mostCommonCompatibleVersions
import app.revanced.manager.R
import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.data.room.bundles.PatchBundleEntity
@ -12,6 +13,8 @@ import app.revanced.manager.data.room.bundles.Source as SourceInfo
import app.revanced.manager.domain.bundles.LocalPatchBundle
import app.revanced.manager.domain.bundles.RemotePatchBundle
import app.revanced.manager.domain.bundles.PatchBundleSource
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.patcher.patch.PatchInfo
import app.revanced.manager.util.flatMapLatestAndCombine
import app.revanced.manager.util.tag
import app.revanced.manager.util.uiSafe
@ -29,6 +32,7 @@ class PatchBundleRepository(
private val app: Application,
private val persistenceRepo: PatchBundlePersistenceRepository,
private val networkInfo: NetworkInfo,
private val prefs: PreferencesManager,
) {
private val bundlesDir = app.getDir("patch_bundles", Context.MODE_PRIVATE)
@ -47,6 +51,37 @@ class PatchBundleRepository(
it.state.map { state -> it.uid to state }
}
val suggestedVersions = bundles.map {
val allPatches =
it.values.flatMap { bundle -> bundle.patches.map(PatchInfo::toPatcherPatch) }.toSet()
allPatches.mostCommonCompatibleVersions(countUnusedPatches = true)
.mapValues { (_, versions) ->
if (versions.keys.size < 2)
return@mapValues versions.keys.firstOrNull()
// The entries are ordered from most compatible to least compatible.
// If there are entries with the same number of compatible patches, older versions will be first, which is undesirable.
// This means we have to pick the last entry we find that has the highest patch count.
// The order may change in future versions of ReVanced Library.
var currentHighestPatchCount = -1
versions.entries.last { (_, patchCount) ->
if (patchCount >= currentHighestPatchCount) {
currentHighestPatchCount = patchCount
true
} else false
}.key
}
}
suspend fun isVersionAllowed(packageName: String, version: String) =
withContext(Dispatchers.Default) {
if (!prefs.suggestedVersionSafeguard.get()) return@withContext true
val suggestedVersion = suggestedVersions.first()[packageName] ?: return@withContext true
suggestedVersion == version
}
/**
* Get the directory of the [PatchBundleSource] with the specified [uid], creating it if needed.
*/
@ -102,16 +137,16 @@ class PatchBundleRepository(
private fun addBundle(patchBundle: PatchBundleSource) =
_sources.update { it.toMutableMap().apply { put(patchBundle.uid, patchBundle) } }
suspend fun createLocal(name: String, patches: InputStream, integrations: InputStream?) {
val id = persistenceRepo.create(name, SourceInfo.Local).uid
val bundle = LocalPatchBundle(name, id, directoryOf(id))
suspend fun createLocal(patches: InputStream) = withContext(Dispatchers.Default) {
val uid = persistenceRepo.create("", SourceInfo.Local).uid
val bundle = LocalPatchBundle("", uid, directoryOf(uid))
bundle.replace(patches, integrations)
bundle.replace(patches)
addBundle(bundle)
}
suspend fun createRemote(name: String, url: String, autoUpdate: Boolean) {
val entity = persistenceRepo.create(name, SourceInfo.from(url), autoUpdate)
suspend fun createRemote(url: String, autoUpdate: Boolean) = withContext(Dispatchers.Default) {
val entity = persistenceRepo.create("", SourceInfo.from(url), autoUpdate)
addBundle(entity.load())
}
@ -139,8 +174,8 @@ class PatchBundleRepository(
getBundlesByType<RemotePatchBundle>().forEach {
launch {
if (!it.propsFlow().first().autoUpdate) return@launch
Log.d(tag, "Updating patch bundle: ${it.name}")
if (!it.getProps().autoUpdate) return@launch
Log.d(tag, "Updating patch bundle: ${it.getName()}")
it.update()
}
}

View File

@ -1,18 +1,14 @@
package app.revanced.manager.domain.repository
import android.util.Log
import app.revanced.manager.data.room.AppDatabase
import app.revanced.manager.data.room.options.Option
import app.revanced.manager.data.room.options.OptionGroup
import app.revanced.manager.patcher.patch.PatchInfo
import app.revanced.manager.util.Options
import app.revanced.manager.util.tag
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.floatOrNull
import kotlinx.serialization.json.intOrNull
class PatchOptionsRepository(db: AppDatabase) {
private val dao = db.optionDao()
@ -24,19 +20,37 @@ class PatchOptionsRepository(db: AppDatabase) {
packageName = packageName
).also { dao.createOptionGroup(it) }.uid
suspend fun getOptions(packageName: String): Options {
suspend fun getOptions(
packageName: String,
bundlePatches: Map<Int, Map<String, PatchInfo>>
): Options {
val options = dao.getOptions(packageName)
// Bundle -> Patches
return buildMap<Int, MutableMap<String, MutableMap<String, Any?>>>(options.size) {
options.forEach { (sourceUid, bundlePatchOptionsList) ->
// Patches -> Patch options
this[sourceUid] = bundlePatchOptionsList.fold(mutableMapOf()) { bundlePatchOptions, option ->
val patchOptions = bundlePatchOptions.getOrPut(option.patchName, ::mutableMapOf)
this[sourceUid] =
bundlePatchOptionsList.fold(mutableMapOf()) { bundlePatchOptions, dbOption ->
val deserializedPatchOptions =
bundlePatchOptions.getOrPut(dbOption.patchName, ::mutableMapOf)
patchOptions[option.key] = deserialize(option.value)
val option =
bundlePatches[sourceUid]?.get(dbOption.patchName)?.options?.find { it.key == dbOption.key }
if (option != null) {
try {
deserializedPatchOptions[option.key] =
dbOption.value.deserializeFor(option)
} catch (e: Option.SerializationException) {
Log.w(
tag,
"Option ${dbOption.patchName}:${option.key} could not be deserialized",
e
)
}
}
bundlePatchOptions
}
bundlePatchOptions
}
}
}
}
@ -47,8 +61,12 @@ class PatchOptionsRepository(db: AppDatabase) {
groupId to bundlePatchOptions.flatMap { (patchName, patchOptions) ->
patchOptions.mapNotNull { (key, value) ->
val serialized = serialize(value)
?: return@mapNotNull null // Don't save options that we can't serialize.
val serialized = try {
Option.SerializedValue.fromValue(value)
} catch (e: Option.SerializationException) {
Log.e(tag, "Option $patchName:$key could not be serialized", e)
return@mapNotNull null
}
Option(groupId, patchName, key, serialized)
}
@ -58,32 +76,7 @@ class PatchOptionsRepository(db: AppDatabase) {
fun getPackagesWithSavedOptions() =
dao.getPackagesWithOptions().map(Iterable<String>::toSet).distinctUntilChanged()
suspend fun clearOptionsForPackage(packageName: String) = dao.clearForPackage(packageName)
suspend fun clearOptionsForPatchBundle(uid: Int) = dao.clearForPatchBundle(uid)
suspend fun resetOptionsForPackage(packageName: String) = dao.resetOptionsForPackage(packageName)
suspend fun resetOptionsForPatchBundle(uid: Int) = dao.resetOptionsForPatchBundle(uid)
suspend fun reset() = dao.reset()
private companion object {
fun deserialize(value: String): Any? {
val primitive = Json.decodeFromString<JsonPrimitive>(value)
return when {
primitive.isString -> primitive.content
primitive is JsonNull -> null
else -> primitive.booleanOrNull ?: primitive.intOrNull ?: primitive.floatOrNull
}
}
fun serialize(value: Any?): String? {
val primitive = when (value) {
null -> JsonNull
is String -> JsonPrimitive(value)
is Int -> JsonPrimitive(value)
is Float -> JsonPrimitive(value)
is Boolean -> JsonPrimitive(value)
else -> return null
}
return Json.encodeToString(primitive)
}
}
}

View File

@ -3,6 +3,8 @@ 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()
@ -25,8 +27,15 @@ class PatchSelectionRepository(db: AppDatabase) {
)
})
suspend fun clearSelection(packageName: String) {
dao.clearForPackage(packageName)
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 reset() = dao.reset()
@ -34,7 +43,7 @@ class PatchSelectionRepository(db: AppDatabase) {
suspend fun export(bundleUid: Int): SerializedSelection = dao.exportSelection(bundleUid)
suspend fun import(bundleUid: Int, selection: SerializedSelection) {
dao.clearForPatchBundle(bundleUid)
dao.resetForPatchBundle(bundleUid)
dao.updateSelections(selection.entries.associate { (packageName, patches) ->
getOrCreateSelection(bundleUid, packageName) to patches.toSet()
})

View File

@ -2,34 +2,41 @@ package app.revanced.manager.network.api
import android.os.Build
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.network.dto.ReVancedRelease
import app.revanced.manager.network.service.ReVancedService
import app.revanced.manager.network.dto.ReVancedAsset
import app.revanced.manager.network.dto.ReVancedGitRepository
import app.revanced.manager.network.dto.ReVancedInfo
import app.revanced.manager.network.service.HttpService
import app.revanced.manager.network.utils.APIResponse
import app.revanced.manager.network.utils.getOrThrow
import app.revanced.manager.network.utils.transform
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import io.ktor.client.request.url
class ReVancedAPI(
private val service: ReVancedService,
private val client: HttpService,
private val prefs: PreferencesManager
) {
private suspend fun apiUrl() = prefs.api.get()
suspend fun getContributors() = service.getContributors(apiUrl()).transform { it.repositories }
private suspend inline fun <reified T> request(api: String, route: String): APIResponse<T> =
withContext(
Dispatchers.IO
) {
client.request {
url("$api/v4/$route")
}
}
suspend fun getLatestRelease(name: String) =
service.getLatestRelease(apiUrl(), name).transform { it.release }
suspend fun getReleases(name: String) =
service.getReleases(apiUrl(), name).transform { it.releases }
private suspend inline fun <reified T> request(route: String) = request<T>(apiUrl(), route)
suspend fun getAppUpdate() =
getLatestRelease("revanced-manager")
.getOrThrow()
.takeIf { it.version != Build.VERSION.RELEASE }
getLatestAppInfo().getOrThrow().takeIf { it.version != Build.VERSION.RELEASE }
companion object Extensions {
fun ReVancedRelease.findAssetByType(mime: String) =
assets.singleOrNull { it.contentType == mime } ?: throw MissingAssetException(mime)
}
}
suspend fun getLatestAppInfo() = request<ReVancedAsset>("manager")
class MissingAssetException(type: String) : Exception("No asset with type $type")
suspend fun getPatchesUpdate() = request<ReVancedAsset>("patches")
suspend fun getContributors() = request<List<ReVancedGitRepository>>("contributors")
suspend fun getInfo(api: String? = null) = request<ReVancedInfo>(api ?: apiUrl(), "about")
}

View File

@ -1,276 +0,0 @@
package app.revanced.manager.network.downloader
import android.os.Build.SUPPORTED_ABIS
import app.revanced.manager.network.service.HttpService
import io.ktor.client.plugins.onDownload
import io.ktor.client.request.parameter
import io.ktor.client.request.url
import it.skrape.selects.html5.a
import it.skrape.selects.html5.div
import it.skrape.selects.html5.form
import it.skrape.selects.html5.h5
import it.skrape.selects.html5.input
import it.skrape.selects.html5.p
import it.skrape.selects.html5.span
import kotlinx.coroutines.flow.flow
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.core.component.inject
import java.io.File
class APKMirror : AppDownloader, KoinComponent {
private val httpClient: HttpService = get()
enum class APKType {
APK,
BUNDLE
}
data class Variant(
val apkType: APKType,
val arch: String,
val link: String
)
private suspend fun getAppLink(packageName: String): String {
val searchResults = httpClient.getHtml { url("$apkMirror/?post_type=app_release&searchtype=app&s=$packageName") }
.div {
withId = "content"
findFirst {
div {
withClass = "listWidget"
findAll {
find {
it.children.first().text.contains(packageName)
}!!.children.mapNotNull {
if (it.classNames.isEmpty()) {
it.h5 {
withClass = "appRowTitle"
findFirst {
a {
findFirst {
attribute("href")
}
}
}
}
} else null
}
}
}
}
}
return searchResults.find { url ->
httpClient.getHtml { url(apkMirror + url) }
.div {
withId = "primary"
findFirst {
div {
withClass = "tab-buttons"
findFirst {
div {
withClass = "tab-button-positioning"
findFirst {
children.any {
it.attribute("href") == "https://play.google.com/store/apps/details?id=$packageName"
}
}
}
}
}
}
}
} ?: throw Exception("App isn't available for download")
}
override fun getAvailableVersions(packageName: String, versionFilter: Set<String>) = flow<AppDownloader.App> {
// Vanced music uses the same package name so we have to hardcode...
val appCategory = if (packageName == "com.google.android.apps.youtube.music")
"youtube-music"
else
getAppLink(packageName).split("/")[3]
var page = 1
val versions = mutableListOf<String>()
while (
if (versionFilter.isNotEmpty())
versions.size < versionFilter.size && page <= 7
else
page <= 1
) {
httpClient.getHtml {
url("$apkMirror/uploads/page/$page/")
parameter("appcategory", appCategory)
}.div {
withClass = "widget_appmanager_recentpostswidget"
findFirst {
div {
withClass = "listWidget"
findFirst {
children.mapNotNull { element ->
if (element.className.isEmpty()) {
APKMirrorApp(
packageName = packageName,
version = element.div {
withClass = "infoSlide"
findFirst {
p {
findFirst {
span {
withClass = "infoSlide-value"
findFirst {
text
}
}
}
}
}
}.also {
if (it in versionFilter)
versions.add(it)
},
downloadLink = element.findFirst {
a {
withClass = "downloadLink"
findFirst {
attribute("href")
}
}
}
)
} else null
}
}
}
}
}.onEach { version -> emit(version) }
page++
}
}
@Parcelize
private class APKMirrorApp(
override val packageName: String,
override val version: String,
private val downloadLink: String,
) : AppDownloader.App, KoinComponent {
@IgnoredOnParcel private val httpClient: HttpService by inject()
override suspend fun download(
saveDirectory: File,
preferSplit: Boolean,
onDownload: suspend (downloadProgress: Pair<Float, Float>?) -> Unit
) {
val variants = httpClient.getHtml { url(apkMirror + downloadLink) }
.div {
withClass = "variants-table"
findFirst { // list of variants
children.drop(1).map {
Variant(
apkType = it.div {
findFirst {
span {
findFirst {
enumValueOf(text)
}
}
}
},
arch = it.div {
findSecond {
text
}
},
link = it.div {
findFirst {
a {
findFirst {
attribute("href")
}
}
}
}
)
}
}
}
val orderedAPKTypes = mutableListOf(APKType.APK, APKType.BUNDLE)
.also { if (preferSplit) it.reverse() }
val variant = orderedAPKTypes.firstNotNullOfOrNull { apkType ->
supportedArches.firstNotNullOfOrNull { arch ->
variants.find { it.arch == arch && it.apkType == apkType }
}
} ?: throw Exception("No compatible variant found")
if (variant.apkType == APKType.BUNDLE) throw Exception("Split apks are not supported yet") // TODO
val downloadPage = httpClient.getHtml { url(apkMirror + variant.link) }
.a {
withClass = "downloadButton"
findFirst {
attribute("href")
}
}
val downloadLink = httpClient.getHtml { url(apkMirror + downloadPage) }
.form {
withId = "filedownload"
findFirst {
val apkLink = attribute("action")
val id = input {
withAttribute = "name" to "id"
findFirst {
attribute("value")
}
}
val key = input {
withAttribute = "name" to "key"
findFirst {
attribute("value")
}
}
"$apkLink?id=$id&key=$key"
}
}
val targetFile = saveDirectory.resolve("base.apk")
try {
httpClient.download(targetFile) {
url(apkMirror + downloadLink)
onDownload { bytesSentTotal, contentLength ->
onDownload(bytesSentTotal.div(100000).toFloat().div(10) to contentLength.div(100000).toFloat().div(10))
}
}
if (variant.apkType == APKType.BUNDLE) {
// TODO: Extract temp.zip
targetFile.delete()
}
} finally {
onDownload(null)
}
}
}
companion object {
const val apkMirror = "https://www.apkmirror.com"
val supportedArches = listOf("universal", "noarch") + SUPPORTED_ABIS
}
}

View File

@ -1,27 +0,0 @@
package app.revanced.manager.network.downloader
import android.os.Parcelable
import kotlinx.coroutines.flow.Flow
import java.io.File
interface AppDownloader {
/**
* Returns all downloadable apps.
*
* @param packageName The package name of the app.
* @param versionFilter A set of versions to filter.
*/
fun getAvailableVersions(packageName: String, versionFilter: Set<String>): Flow<App>
interface App : Parcelable {
val packageName: String
val version: String
suspend fun download(
saveDirectory: File,
preferSplit: Boolean,
onDownload: suspend (downloadProgress: Pair<Float, Float>?) -> Unit = {}
)
}
}

View File

@ -0,0 +1,9 @@
package app.revanced.manager.network.downloader
sealed interface DownloaderPluginState {
data object Untrusted : DownloaderPluginState
data class Loaded(val plugin: LoadedDownloaderPlugin) : DownloaderPluginState
data class Failed(val throwable: Throwable) : DownloaderPluginState
}

View File

@ -0,0 +1,15 @@
package app.revanced.manager.network.downloader
import android.os.Parcelable
import app.revanced.manager.plugin.downloader.OutputDownloadScope
import app.revanced.manager.plugin.downloader.GetScope
import java.io.OutputStream
class LoadedDownloaderPlugin(
val packageName: String,
val name: String,
val version: String,
val get: suspend GetScope.(packageName: String, version: String?) -> Pair<Parcelable, String?>?,
val download: suspend OutputDownloadScope.(data: Parcelable, outputStream: OutputStream) -> Unit,
val classLoader: ClassLoader
)

View File

@ -0,0 +1,45 @@
package app.revanced.manager.network.downloader
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
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].
*/
class ParceledDownloaderData private constructor(
val pluginPackageName: String,
private val bundle: Bundle
) : Parcelable {
constructor(plugin: LoadedDownloaderPlugin, data: Parcelable) : this(
plugin.packageName,
createBundle(data)
)
fun unwrapWith(plugin: LoadedDownloaderPlugin): Parcelable {
bundle.classLoader = plugin.classLoader
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val className = bundle.getString(CLASS_NAME_KEY)!!
val clazz = plugin.classLoader.loadClass(className)
bundle.getParcelable(DATA_KEY, clazz)!! as Parcelable
} else @Suppress("Deprecation") bundle.getParcelable(DATA_KEY)!!
}
private companion object {
const val CLASS_NAME_KEY = "class"
const val DATA_KEY = "data"
fun createBundle(data: Parcelable) = Bundle().apply {
putParcelable(DATA_KEY, data)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) putString(
CLASS_NAME_KEY,
data::class.java.canonicalName
)
}
}
}

View File

@ -1,9 +0,0 @@
package app.revanced.manager.network.dto
import kotlinx.serialization.Serializable
@Serializable
data class BundleInfo(val patches: BundleAsset, val integrations: BundleAsset)
@Serializable
data class BundleAsset(val version: String, val url: String)

View File

@ -1,16 +0,0 @@
package app.revanced.manager.network.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GithubChangelog(
@SerialName("tag_name") val version: String,
@SerialName("body") val body: String,
@SerialName("assets") val assets: List<GithubAsset>
)
@Serializable
data class GithubAsset(
@SerialName("download_count") val downloadCount: Int,
)

View File

@ -0,0 +1,18 @@
package app.revanced.manager.network.dto
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ReVancedAsset (
@SerialName("download_url")
val downloadUrl: String,
@SerialName("created_at")
val createdAt: LocalDateTime,
@SerialName("signature_download_url")
val signatureDownloadUrl: String? = null,
val description: String,
val version: String,
)

View File

@ -3,19 +3,15 @@ package app.revanced.manager.network.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ReVancedGitRepositories(
val repositories: List<ReVancedGitRepository>,
)
@Serializable
data class ReVancedGitRepository(
val name: String,
val url: String,
val contributors: List<ReVancedContributor>,
)
@Serializable
data class ReVancedContributor(
@SerialName("login") val username: String,
@SerialName("name") val username: String,
@SerialName("avatar_url") val avatarUrl: String,
)
)

View File

@ -0,0 +1,53 @@
package app.revanced.manager.network.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ReVancedInfo(
val name: String,
val about: String,
val branding: ReVancedBranding,
val contact: ReVancedContact,
val socials: List<ReVancedSocial>,
val donations: ReVancedDonation,
)
@Serializable
data class ReVancedBranding(
val logo: String,
)
@Serializable
data class ReVancedContact(
val email: String,
)
@Serializable
data class ReVancedSocial(
val name: String,
val url: String,
val preferred: Boolean,
)
@Serializable
data class ReVancedDonation(
val wallets: List<ReVancedWallet>,
val links: List<ReVancedDonationLink>,
)
@Serializable
data class ReVancedWallet(
val network: String,
@SerialName("currency_code")
val currencyCode: String,
val address: String,
val preferred: Boolean
)
@Serializable
data class ReVancedDonationLink(
val name: String,
val url: String,
val preferred: Boolean,
)

View File

@ -1,41 +0,0 @@
package app.revanced.manager.network.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ReVancedLatestRelease(
val release: ReVancedRelease,
)
@Serializable
data class ReVancedReleases(
val releases: List<ReVancedRelease>
)
@Serializable
data class ReVancedRelease(
val metadata: ReVancedReleaseMeta,
val assets: List<Asset>
) {
val version get() = metadata.tag
}
@Serializable
data class ReVancedReleaseMeta(
@SerialName("tag_name") val tag: String,
val name: String,
val draft: Boolean,
val prerelease: Boolean,
@SerialName("created_at") val createdAt: String,
@SerialName("published_at") val publishedAt: String,
val body: String,
)
@Serializable
data class Asset(
val name: String,
@SerialName("download_count") val downloadCount: Int,
@SerialName("browser_download_url") val downloadUrl: String,
@SerialName("content_type") val contentType: String
)

View File

@ -1,34 +0,0 @@
package app.revanced.manager.network.service
import app.revanced.manager.network.dto.ReVancedLatestRelease
import app.revanced.manager.network.dto.ReVancedGitRepositories
import app.revanced.manager.network.dto.ReVancedReleases
import app.revanced.manager.network.utils.APIResponse
import io.ktor.client.request.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class ReVancedService(
private val client: HttpService,
) {
suspend fun getLatestRelease(api: String, repo: String): APIResponse<ReVancedLatestRelease> =
withContext(Dispatchers.IO) {
client.request {
url("$api/v2/$repo/releases/latest")
}
}
suspend fun getReleases(api: String, repo: String): APIResponse<ReVancedReleases> =
withContext(Dispatchers.IO) {
client.request {
url("$api/v2/$repo/releases")
}
}
suspend fun getContributors(api: String): APIResponse<ReVancedGitRepositories> =
withContext(Dispatchers.IO) {
client.request {
url("$api/contributors")
}
}
}

View File

@ -0,0 +1,10 @@
package app.revanced.manager.patcher
import android.content.Context
import java.io.File
abstract class LibraryResolver {
protected fun findLibrary(context: Context, searchTerm: String): File? = File(context.applicationInfo.nativeLibraryDir).run {
list { _, f -> !File(f).isDirectory && f.contains(searchTerm) }?.firstOrNull()?.let { resolve(it) }
}
}

View File

@ -1,22 +1,20 @@
package app.revanced.manager.patcher
import android.content.Context
import app.revanced.library.ApkUtils
import app.revanced.library.ApkUtils.applyTo
import app.revanced.manager.R
import app.revanced.manager.patcher.logger.ManagerLogger
import app.revanced.manager.patcher.logger.Logger
import app.revanced.manager.ui.model.State
import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherOptions
import app.revanced.patcher.PatcherConfig
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.withContext
import java.io.Closeable
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.util.logging.Logger
internal typealias PatchList = List<Patch<*>>
@ -24,11 +22,10 @@ class Session(
cacheDir: String,
frameworkDir: String,
aaptPath: String,
multithreadingDexFileWriter: Boolean,
private val androidContext: Context,
private val logger: ManagerLogger,
private val logger: Logger,
private val input: File,
private val patchesProgress: MutableStateFlow<Pair<Int, Int>>,
private val onPatchCompleted: suspend () -> Unit,
private val onProgress: (name: String?, state: State?, message: String?) -> Unit
) : Closeable {
private fun updateProgress(name: String? = null, state: State? = null, message: String? = null) =
@ -36,12 +33,11 @@ class Session(
private val tempDir = File(cacheDir).resolve("patcher").also { it.mkdirs() }
private val patcher = Patcher(
PatcherOptions(
inputFile = input,
resourceCachePath = tempDir.resolve("aapt-resources"),
PatcherConfig(
apkFile = input,
temporaryFilesPath = tempDir,
frameworkFileDirectory = frameworkDir,
aaptBinaryPath = aaptPath,
multithreadingDexFileWriter = multithreadingDexFileWriter,
aaptBinaryPath = aaptPath
)
)
@ -49,16 +45,16 @@ class Session(
var nextPatchIndex = 0
updateProgress(
name = androidContext.getString(R.string.applying_patch, selectedPatches[nextPatchIndex]),
name = androidContext.getString(R.string.executing_patch, selectedPatches[nextPatchIndex]),
state = State.RUNNING
)
this.apply(true).collect { (patch, exception) ->
this().collect { (patch, exception) ->
if (patch !in selectedPatches) return@collect
if (exception != null) {
updateProgress(
name = androidContext.getString(R.string.failed_to_apply_patch, patch.name),
name = androidContext.getString(R.string.failed_to_execute_patch, patch.name),
state = State.FAILED,
message = exception.stackTraceToString()
)
@ -70,13 +66,11 @@ class Session(
nextPatchIndex++
patchesProgress.value.let {
patchesProgress.emit(it.copy(it.first + 1))
}
onPatchCompleted()
selectedPatches.getOrNull(nextPatchIndex)?.let { nextPatch ->
updateProgress(
name = androidContext.getString(R.string.applying_patch, nextPatch.name)
name = androidContext.getString(R.string.executing_patch, nextPatch.name)
)
}
@ -86,28 +80,28 @@ class Session(
updateProgress(
state = State.COMPLETED,
name = androidContext.resources.getQuantityString(
R.plurals.patches_applied,
R.plurals.patches_executed,
selectedPatches.size,
selectedPatches.size
)
)
}
suspend fun run(output: File, selectedPatches: PatchList, integrations: List<File>) {
suspend fun run(output: File, selectedPatches: PatchList) {
updateProgress(state = State.COMPLETED) // Unpacking
Logger.getLogger("").apply {
java.util.logging.Logger.getLogger("").apply {
handlers.forEach {
it.close()
removeHandler(it)
}
addHandler(logger)
addHandler(logger.handler)
}
with(patcher) {
logger.info("Merging integrations")
acceptIntegrations(integrations)
acceptPatches(selectedPatches)
updateProgress(state = State.COMPLETED) // Merging
this += selectedPatches.toSet()
logger.info("Applying patches...")
applyPatchesVerbose(selectedPatches.sortedBy { it.name })
@ -116,13 +110,16 @@ class Session(
logger.info("Writing patched files...")
val result = patcher.get()
val aligned = tempDir.resolve("aligned.apk")
ApkUtils.copyAligned(input, aligned, result)
val patched = tempDir.resolve("result.apk")
withContext(Dispatchers.IO) {
Files.copy(input.toPath(), patched.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
result.applyTo(patched)
logger.info("Patched apk saved to $aligned")
logger.info("Patched apk saved to $patched")
withContext(Dispatchers.IO) {
Files.move(aligned.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING)
Files.move(patched.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
updateProgress(state = State.COMPLETED) // Saving
}

View File

@ -1,18 +1,12 @@
package app.revanced.manager.patcher.aapt
import android.content.Context
import app.revanced.manager.patcher.LibraryResolver
import android.os.Build.SUPPORTED_ABIS as DEVICE_ABIS
import java.io.File
object Aapt {
private val WORKING_ABIS = setOf("arm64-v8a", "x86", "x86_64")
object Aapt : LibraryResolver() {
private val WORKING_ABIS = setOf("arm64-v8a", "x86", "x86_64", "armeabi-v7a")
fun supportsDevice() = (DEVICE_ABIS intersect WORKING_ABIS).isNotEmpty()
fun binary(context: Context): File? {
return File(context.applicationInfo.nativeLibraryDir).resolveAapt()
}
fun binary(context: Context) = findLibrary(context, "aapt")
}
private fun File.resolveAapt() =
list { _, f -> !File(f).isDirectory && f.contains("aapt") }?.firstOrNull()?.let { resolve(it) }

View File

@ -0,0 +1,37 @@
package app.revanced.manager.patcher.logger
import java.util.logging.Handler
import java.util.logging.Level
import java.util.logging.LogRecord
abstract class Logger {
abstract fun log(level: LogLevel, message: String)
fun trace(msg: String) = log(LogLevel.TRACE, msg)
fun info(msg: String) = log(LogLevel.INFO, msg)
fun warn(msg: String) = log(LogLevel.WARN, msg)
fun error(msg: String) = log(LogLevel.ERROR, msg)
val handler = object : Handler() {
override fun publish(record: LogRecord) {
val msg = record.message
when (record.level) {
Level.INFO -> info(msg)
Level.SEVERE -> error(msg)
Level.WARNING -> warn(msg)
else -> trace(msg)
}
}
override fun flush() = Unit
override fun close() = Unit
}
}
enum class LogLevel {
TRACE,
INFO,
WARN,
ERROR,
}

View File

@ -1,59 +0,0 @@
package app.revanced.manager.patcher.logger
import android.util.Log
import java.util.logging.Handler
import java.util.logging.Level
import java.util.logging.LogRecord
class ManagerLogger : Handler() {
private val logs = mutableListOf<Pair<LogLevel, String>>()
private fun log(level: LogLevel, msg: String) {
level.androidLog(msg)
if (level == LogLevel.TRACE) return
logs.add(level to msg)
}
fun export() =
logs.asSequence().map { (level, msg) -> "[${level.name}]: $msg" }.joinToString("\n")
fun trace(msg: String) = log(LogLevel.TRACE, msg)
fun info(msg: String) = log(LogLevel.INFO, msg)
fun warn(msg: String) = log(LogLevel.WARN, msg)
fun error(msg: String) = log(LogLevel.ERROR, msg)
override fun publish(record: LogRecord) {
val msg = record.message
val fn = when (record.level) {
Level.INFO -> ::info
Level.SEVERE -> ::error
Level.WARNING -> ::warn
else -> ::trace
}
fn(msg)
}
override fun flush() = Unit
override fun close() = Unit
}
enum class LogLevel {
TRACE {
override fun androidLog(msg: String) = Log.v(androidTag, msg)
},
INFO {
override fun androidLog(msg: String) = Log.i(androidTag, msg)
},
WARN {
override fun androidLog(msg: String) = Log.w(androidTag, msg)
},
ERROR {
override fun androidLog(msg: String) = Log.e(androidTag, msg)
};
abstract fun androidLog(msg: String): Int
private companion object {
const val androidTag = "ReVanced Patcher"
}
}

View File

@ -2,23 +2,24 @@ package app.revanced.manager.patcher.patch
import android.util.Log
import app.revanced.manager.util.tag
import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchLoader
import java.io.File
import java.io.IOException
import java.util.jar.JarFile
class PatchBundle(private val loader: Iterable<Patch<*>>, val integrations: File?) {
constructor(bundleJar: File, integrations: File?) : this(
object : Iterable<Patch<*>> {
private fun load(): Iterable<Patch<*>> {
bundleJar.setReadOnly()
return PatchBundleLoader.Dex(bundleJar, optimizedDexDirectory = null)
}
class PatchBundle(val patchesJar: File) {
private val loader = object : Iterable<Patch<*>> {
private fun load(): Iterable<Patch<*>> {
patchesJar.setReadOnly()
return PatchLoader.Dex(setOf(patchesJar))
}
override fun iterator(): Iterator<Patch<*>> = load().iterator()
},
integrations
) {
Log.d(tag, "Loaded patch bundle: $bundleJar")
override fun iterator(): Iterator<Patch<*>> = load().iterator()
}
init {
Log.d(tag, "Loaded patch bundle: $patchesJar")
}
/**
@ -26,15 +27,26 @@ class PatchBundle(private val loader: Iterable<Patch<*>>, val integrations: File
*/
val patches = loader.map(::PatchInfo)
/**
* The [java.util.jar.Manifest] of [patchesJar].
*/
private val manifest = try {
JarFile(patchesJar).use { it.manifest }
} catch (_: IOException) {
null
}
fun readManifestAttribute(name: String) = manifest?.mainAttributes?.getValue(name)
/**
* Load all patches compatible with the specified package.
*/
fun patchClasses(packageName: String) = loader.filter { patch ->
fun patches(packageName: String) = loader.filter { patch ->
val compatiblePackages = patch.compatiblePackages
?: // The patch has no compatibility constraints, which means it is universal.
return@filter true
if (!compatiblePackages.any { it.name == packageName }) {
if (!compatiblePackages.any { (name, _) -> name == packageName }) {
// Patch is not compatible with this package.
return@filter false
}

View File

@ -2,69 +2,86 @@ package app.revanced.manager.patcher.patch
import androidx.compose.runtime.Immutable
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.options.PatchOption
import app.revanced.patcher.patch.Option as PatchOption
import app.revanced.patcher.patch.resourcePatch
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet
import kotlin.reflect.KType
data class PatchInfo(
val name: String,
val description: String?,
val include: Boolean,
val compatiblePackages: ImmutableList<CompatiblePackage>?,
val options: ImmutableList<Option>?
val options: ImmutableList<Option<*>>?
) {
constructor(patch: Patch<*>) : this(
patch.name.orEmpty(),
patch.description,
patch.use,
patch.compatiblePackages?.map { CompatiblePackage(it) }?.toImmutableList(),
patch.compatiblePackages?.map { (pkgName, versions) ->
CompatiblePackage(
pkgName,
versions?.toImmutableSet()
)
}?.toImmutableList(),
patch.options.map { (_, option) -> Option(option) }.ifEmpty { null }?.toImmutableList()
)
fun compatibleWith(packageName: String) =
compatiblePackages?.any { it.packageName == packageName } ?: true
fun supportsVersion(packageName: String, versionName: String): Boolean {
fun supports(packageName: String, versionName: String?): Boolean {
val packages = compatiblePackages ?: return true // Universal patch
return packages.any { pkg ->
if (pkg.packageName != packageName) {
return@any false
}
if (pkg.packageName != packageName) return@any false
if (pkg.versions == null) return@any true
pkg.versions == null || pkg.versions.contains(versionName)
versionName != null && versionName in pkg.versions
}
}
/**
* Create a fake [Patch] with the same metadata as the [PatchInfo] instance.
* The resulting patch cannot be executed.
* This is necessary because some functions in ReVanced Library only accept full [Patch] objects.
*/
fun toPatcherPatch(): Patch<*> =
resourcePatch(name = name, description = description, use = include) {
compatiblePackages?.let { pkgs ->
compatibleWith(*pkgs.map { it.packageName to it.versions }.toTypedArray())
}
}
}
@Immutable
data class CompatiblePackage(
val packageName: String,
val versions: ImmutableSet<String>?
) {
constructor(pkg: Patch.CompatiblePackage) : this(
pkg.name,
pkg.versions?.toImmutableSet()
)
}
)
@Immutable
data class Option(
data class Option<T>(
val title: String,
val key: String,
val description: String,
val required: Boolean,
val type: String,
val default: Any?
val type: KType,
val default: T?,
val presets: Map<String, T?>?,
val validator: (T?) -> Boolean,
) {
constructor(option: PatchOption<*>) : this(
constructor(option: PatchOption<T>) : this(
option.title ?: option.key,
option.key,
option.description.orEmpty(),
option.required,
option.valueType,
option.type,
option.default,
option.values,
{ option.validator(option, it) },
)
}

View File

@ -0,0 +1,66 @@
package app.revanced.manager.patcher.runtime
import android.content.Context
import app.revanced.manager.patcher.Session
import app.revanced.manager.patcher.logger.Logger
import app.revanced.manager.patcher.worker.ProgressEventHandler
import app.revanced.manager.ui.model.State
import app.revanced.manager.util.Options
import app.revanced.manager.util.PatchSelection
import java.io.File
/**
* Simple [Runtime] implementation that runs the patcher using coroutines.
*/
class CoroutineRuntime(private val context: Context) : Runtime(context) {
override suspend fun execute(
inputFile: String,
outputFile: String,
packageName: String,
selectedPatches: PatchSelection,
options: Options,
logger: Logger,
onPatchCompleted: suspend () -> Unit,
onProgress: ProgressEventHandler,
) {
val bundles = bundles()
val selectedBundles = selectedPatches.keys
val allPatches = bundles.filterKeys { selectedBundles.contains(it) }
.mapValues { (_, bundle) -> bundle.patches(packageName) }
val patchList = selectedPatches.flatMap { (bundle, selected) ->
allPatches[bundle]?.filter { selected.contains(it.name) }
?: throw IllegalArgumentException("Patch bundle $bundle does not exist")
}
// Set all patch options.
options.forEach { (bundle, bundlePatchOptions) ->
val patches = allPatches[bundle] ?: return@forEach
bundlePatchOptions.forEach { (patchName, configuredPatchOptions) ->
val patchOptions = patches.single { it.name == patchName }.options
configuredPatchOptions.forEach { (key, value) ->
patchOptions[key] = value
}
}
}
onProgress(null, State.COMPLETED, null) // Loading patches
Session(
cacheDir,
frameworkPath,
aaptPath,
context,
logger,
File(inputFile),
onPatchCompleted = onPatchCompleted,
onProgress
).use { session ->
session.run(
File(outputFile),
patchList
)
}
}
}

View File

@ -0,0 +1,189 @@
package app.revanced.manager.patcher.runtime
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
import androidx.core.content.ContextCompat
import app.revanced.manager.BuildConfig
import app.revanced.manager.patcher.runtime.process.IPatcherEvents
import app.revanced.manager.patcher.runtime.process.IPatcherProcess
import app.revanced.manager.patcher.LibraryResolver
import app.revanced.manager.patcher.logger.Logger
import app.revanced.manager.patcher.runtime.process.Parameters
import app.revanced.manager.patcher.runtime.process.PatchConfiguration
import app.revanced.manager.patcher.runtime.process.PatcherProcess
import app.revanced.manager.patcher.worker.ProgressEventHandler
import app.revanced.manager.ui.model.State
import app.revanced.manager.util.Options
import app.revanced.manager.util.PM
import app.revanced.manager.util.PatchSelection
import app.revanced.manager.util.tag
import com.github.pgreze.process.Redirect
import com.github.pgreze.process.process
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import org.koin.core.component.inject
/**
* Runs the patcher in another process by using the app_process binary and IPC.
*/
class ProcessRuntime(private val context: Context) : Runtime(context) {
private val pm: PM by inject()
private suspend fun awaitBinderConnection(): IPatcherProcess {
val binderFuture = CompletableDeferred<IPatcherProcess>()
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val binder =
intent.getBundleExtra(INTENT_BUNDLE_KEY)?.getBinder(BUNDLE_BINDER_KEY)!!
binderFuture.complete(IPatcherProcess.Stub.asInterface(binder))
}
}
ContextCompat.registerReceiver(context, receiver, IntentFilter().apply {
addAction(CONNECT_TO_APP_ACTION)
}, ContextCompat.RECEIVER_NOT_EXPORTED)
return try {
withTimeout(10000L) {
binderFuture.await()
}
} finally {
context.unregisterReceiver(receiver)
}
}
override suspend fun execute(
inputFile: String,
outputFile: String,
packageName: String,
selectedPatches: PatchSelection,
options: Options,
logger: Logger,
onPatchCompleted: suspend () -> Unit,
onProgress: ProgressEventHandler,
) = coroutineScope {
// Get the location of our own Apk.
val managerBaseApk = pm.getPackageInfo(context.packageName)!!.applicationInfo!!.sourceDir
val limit = "${prefs.patcherProcessMemoryLimit.get()}M"
val propOverride = resolvePropOverride(context)?.absolutePath
?: throw Exception("Couldn't find prop override library")
val env =
System.getenv().toMutableMap().apply {
putAll(
mapOf(
"CLASSPATH" to managerBaseApk,
// Override the props used by ART to set the memory limit.
"LD_PRELOAD" to propOverride,
"PROP_dalvik.vm.heapgrowthlimit" to limit,
"PROP_dalvik.vm.heapsize" to limit,
)
)
}
launch(Dispatchers.IO) {
val result = process(
APP_PROCESS_BIN_PATH,
"-Djava.io.tmpdir=$cacheDir", // The process will use /tmp if this isn't set, which is a problem because that folder is not accessible on Android.
"/", // The unused cmd-dir parameter
"--nice-name=${context.packageName}:Patcher",
PatcherProcess::class.java.name, // The class with the main function.
context.packageName,
env = env,
stdout = Redirect.CAPTURE,
stderr = Redirect.CAPTURE,
) { line ->
// The process shouldn't generally be writing to stdio. Log any lines we get as warnings.
logger.warn("[STDIO]: $line")
}
Log.d(tag, "Process finished with exit code ${result.resultCode}")
if (result.resultCode != 0) throw Exception("Process exited with nonzero exit code ${result.resultCode}")
}
val patching = CompletableDeferred<Unit>()
val scope = this
launch(Dispatchers.IO) {
val binder = awaitBinderConnection()
// Android Studio's fast deployment feature causes an issue where the other process will be running older code compared to the main process.
// The patcher process is running outdated code if the randomly generated BUILD_ID numbers don't match.
// To fix it, clear the cache in the Android settings or disable fast deployment (Run configurations -> Edit Configurations -> app -> Enable "always deploy with package manager").
if (binder.buildId() != BuildConfig.BUILD_ID) throw Exception("app_process is running outdated code. Clear the app cache or disable disable Android 11 deployment optimizations in your IDE")
val eventHandler = object : IPatcherEvents.Stub() {
override fun log(level: String, msg: String) = logger.log(enumValueOf(level), msg)
override fun patchSucceeded() {
scope.launch { onPatchCompleted() }
}
override fun progress(name: String?, state: String?, msg: String?) =
onProgress(name, state?.let { enumValueOf<State>(it) }, msg)
override fun finished(exceptionStackTrace: String?) {
binder.exit()
exceptionStackTrace?.let {
patching.completeExceptionally(RemoteFailureException(it))
return
}
patching.complete(Unit)
}
}
val bundles = bundles()
val parameters = Parameters(
aaptPath = aaptPath,
frameworkDir = frameworkPath,
cacheDir = cacheDir,
packageName = packageName,
inputFile = inputFile,
outputFile = outputFile,
configurations = selectedPatches.map { (id, patches) ->
val bundle = bundles[id]!!
PatchConfiguration(
bundle.patchesJar.absolutePath,
patches,
options[id].orEmpty()
)
}
)
binder.start(parameters, eventHandler)
}
// Wait until patching finishes.
patching.await()
}
companion object : LibraryResolver() {
private const val APP_PROCESS_BIN_PATH = "/system/bin/app_process"
const val CONNECT_TO_APP_ACTION = "CONNECT_TO_APP_ACTION"
const val INTENT_BUNDLE_KEY = "BUNDLE"
const val BUNDLE_BINDER_KEY = "BINDER"
private fun resolvePropOverride(context: Context) = findLibrary(context, "prop_override")
}
/**
* An [Exception] occurred in the remote process while patching.
*
* @param originalStackTrace The stack trace of the original [Exception].
*/
class RemoteFailureException(val originalStackTrace: String) : Exception()
}

View File

@ -0,0 +1,40 @@
package app.revanced.manager.patcher.runtime
import android.content.Context
import app.revanced.manager.data.platform.Filesystem
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.patcher.aapt.Aapt
import app.revanced.manager.patcher.logger.Logger
import app.revanced.manager.patcher.worker.ProgressEventHandler
import app.revanced.manager.util.Options
import app.revanced.manager.util.PatchSelection
import kotlinx.coroutines.flow.first
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.io.FileNotFoundException
sealed class Runtime(context: Context) : KoinComponent {
private val fs: Filesystem by inject()
private val patchBundlesRepo: PatchBundleRepository by inject()
protected val prefs: PreferencesManager by inject()
protected val cacheDir: String = fs.tempDir.absolutePath
protected val aaptPath = Aapt.binary(context)?.absolutePath
?: throw FileNotFoundException("Could not resolve aapt.")
protected val frameworkPath: String =
context.cacheDir.resolve("framework").also { it.mkdirs() }.absolutePath
protected suspend fun bundles() = patchBundlesRepo.bundles.first()
abstract suspend fun execute(
inputFile: String,
outputFile: String,
packageName: String,
selectedPatches: PatchSelection,
options: Options,
logger: Logger,
onPatchCompleted: suspend () -> Unit,
onProgress: ProgressEventHandler,
)
}

View File

@ -0,0 +1,23 @@
package app.revanced.manager.patcher.runtime.process
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.RawValue
@Parcelize
data class Parameters(
val cacheDir: String,
val aaptPath: String,
val frameworkDir: String,
val packageName: String,
val inputFile: String,
val outputFile: String,
val configurations: List<PatchConfiguration>,
) : Parcelable
@Parcelize
data class PatchConfiguration(
val bundlePath: String,
val patches: Set<String>,
val options: @RawValue Map<String, Map<String, Any?>>
) : Parcelable

View File

@ -0,0 +1,138 @@
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
import app.revanced.manager.patcher.Session
import app.revanced.manager.patcher.logger.LogLevel
import app.revanced.manager.patcher.logger.Logger
import app.revanced.manager.patcher.patch.PatchBundle
import app.revanced.manager.patcher.runtime.ProcessRuntime
import app.revanced.manager.ui.model.State
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import kotlin.system.exitProcess
/**
* The main class that runs inside the runner process launched by [ProcessRuntime].
*/
class PatcherProcess(private val context: Context) : IPatcherProcess.Stub() {
private var eventBinder: IPatcherEvents? = null
private val scope =
CoroutineScope(Dispatchers.Default + CoroutineExceptionHandler { _, throwable ->
// Try to send the exception information to the main app.
eventBinder?.let {
try {
it.finished(throwable.stackTraceToString())
return@CoroutineExceptionHandler
} catch (_: Exception) {
}
}
throwable.printStackTrace()
exitProcess(1)
})
override fun buildId() = BuildConfig.BUILD_ID
override fun exit() = exitProcess(0)
override fun start(parameters: Parameters, events: IPatcherEvents) {
eventBinder = events
scope.launch {
val logger = object : Logger() {
override fun log(level: LogLevel, message: String) =
events.log(level.name, message)
}
logger.info("Memory limit: ${Runtime.getRuntime().maxMemory() / (1024 * 1024)}MB")
val patchList = parameters.configurations.flatMap { config ->
val bundle = PatchBundle(File(config.bundlePath))
val patches =
bundle.patches(parameters.packageName).filter { it.name in config.patches }
.associateBy { it.name }
config.options.forEach { (patchName, opts) ->
val patchOptions = patches[patchName]?.options
?: throw Exception("Patch with name $patchName does not exist.")
opts.forEach { (key, value) ->
patchOptions[key] = value
}
}
patches.values
}
events.progress(null, State.COMPLETED.name, null) // Loading patches
Session(
cacheDir = parameters.cacheDir,
aaptPath = parameters.aaptPath,
frameworkDir = parameters.frameworkDir,
androidContext = context,
logger = logger,
input = File(parameters.inputFile),
onPatchCompleted = { events.patchSucceeded() },
onProgress = { name, state, message ->
events.progress(name, state?.name, message)
}
).use {
it.run(File(parameters.outputFile), patchList)
}
events.finished(null)
}
}
companion object {
private val longArrayClass = LongArray::class.java
private val emptyLongArray = LongArray(0)
@SuppressLint("PrivateApi")
@JvmStatic
fun main(args: Array<String>) {
Looper.prepare()
val managerPackageName = args[0]
// Abuse hidden APIs to get a context.
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 {
action = ProcessRuntime.CONNECT_TO_APP_ACTION
`package` = managerPackageName
putExtra(ProcessRuntime.INTENT_BUNDLE_KEY, Bundle().apply {
putBinder(ProcessRuntime.BUNDLE_BINDER_KEY, ipcInterface.asBinder())
})
})
Looper.loop()
exitProcess(1) // Shouldn't happen
}
}
}

View File

@ -1,5 +1,6 @@
package app.revanced.manager.patcher.worker
import android.app.Activity
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
@ -9,9 +10,10 @@ import android.content.Intent
import android.content.pm.ServiceInfo
import android.graphics.drawable.Icon
import android.os.Build
import android.os.Parcelable
import android.os.PowerManager
import android.util.Log
import android.view.WindowManager
import androidx.activity.result.ActivityResult
import androidx.core.content.ContextCompat
import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
@ -22,50 +24,58 @@ import app.revanced.manager.domain.installer.RootInstaller
import app.revanced.manager.domain.manager.KeystoreManager
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.DownloadedAppRepository
import app.revanced.manager.domain.repository.DownloaderPluginRepository
import app.revanced.manager.domain.repository.InstalledAppRepository
import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.domain.worker.Worker
import app.revanced.manager.domain.worker.WorkerRepository
import app.revanced.manager.patcher.Session
import app.revanced.manager.patcher.aapt.Aapt
import app.revanced.manager.patcher.logger.ManagerLogger
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
import app.revanced.manager.patcher.logger.Logger
import app.revanced.manager.patcher.runtime.CoroutineRuntime
import app.revanced.manager.patcher.runtime.ProcessRuntime
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.SelectedApp
import app.revanced.manager.ui.model.State
import app.revanced.manager.util.Options
import app.revanced.manager.util.PM
import app.revanced.manager.util.PatchSelection
import app.revanced.manager.util.tag
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.io.File
import java.io.FileNotFoundException
typealias ProgressEventHandler = (name: String?, state: State?, message: String?) -> Unit
@OptIn(PluginHostApi::class)
class PatcherWorker(
context: Context,
parameters: WorkerParameters
) : Worker<PatcherWorker.Args>(context, parameters), KoinComponent {
private val patchBundleRepository: PatchBundleRepository by inject()
private val workerRepository: WorkerRepository by inject()
private val prefs: PreferencesManager by inject()
private val keystoreManager: KeystoreManager by inject()
private val downloaderPluginRepository: DownloaderPluginRepository by inject()
private val downloadedAppRepository: DownloadedAppRepository by inject()
private val pm: PM by inject()
private val fs: Filesystem by inject()
private val installedAppRepository: InstalledAppRepository by inject()
private val rootInstaller: RootInstaller by inject()
data class Args(
class Args(
val input: SelectedApp,
val output: String,
val selectedPatches: PatchSelection,
val options: Options,
val logger: ManagerLogger,
val downloadProgress: MutableStateFlow<Pair<Float, Float>?>,
val patchesProgress: MutableStateFlow<Pair<Int, Int>>,
val setInputFile: (File) -> Unit,
val onProgress: (name: String?, state: State?, message: String?) -> Unit
val logger: Logger,
val onDownloadProgress: suspend (Pair<Long, Long?>?) -> Unit,
val onPatchCompleted: suspend () -> Unit,
val handleStartActivityRequest: suspend (LoadedDownloaderPlugin, Intent) -> ActivityResult,
val setInputFile: suspend (File) -> Unit,
val onProgress: ProgressEventHandler
) {
val packageName get() = input.packageName
}
@ -111,7 +121,8 @@ class PatcherWorker(
val wakeLock: PowerManager.WakeLock =
(applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager)
.newWakeLock(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, "$tag::Patcher").apply {
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$tag::Patcher")
.apply {
acquire(10 * 60 * 1000L)
Log.d(tag, "Acquired wakelock.")
}
@ -133,97 +144,114 @@ class PatcherWorker(
val patchedApk = fs.tempDir.resolve("patched.apk")
return try {
val bundles = patchBundleRepository.bundles.first()
// TODO: consider passing all the classes directly now that the input no longer needs to be serializable.
val selectedBundles = args.selectedPatches.keys
val allPatches = bundles.filterKeys { selectedBundles.contains(it) }
.mapValues { (_, bundle) -> bundle.patchClasses(args.packageName) }
val selectedPatches = args.selectedPatches.flatMap { (bundle, selected) ->
allPatches[bundle]?.filter { selected.contains(it.name) }
?: throw IllegalArgumentException("Patch bundle $bundle does not exist")
}
val aaptPath = Aapt.binary(applicationContext)?.absolutePath
?: throw FileNotFoundException("Could not resolve aapt.")
val frameworkPath =
applicationContext.cacheDir.resolve("framework").also { it.mkdirs() }.absolutePath
val integrations = bundles.mapNotNull { (_, bundle) -> bundle.integrations }
if (args.input is SelectedApp.Installed) {
installedAppRepository.get(args.packageName)?.let {
if (it.installType == InstallType.ROOT) {
if (it.installType == InstallType.MOUNT) {
rootInstaller.unmount(args.packageName)
}
}
}
// Set all patch options.
args.options.forEach { (bundle, bundlePatchOptions) ->
val patches = allPatches[bundle] ?: return@forEach
bundlePatchOptions.forEach { (patchName, configuredPatchOptions) ->
val patchOptions = patches.single { it.name == patchName }.options
configuredPatchOptions.forEach { (key, value) ->
patchOptions[key] = value
}
suspend fun download(plugin: LoadedDownloaderPlugin, data: Parcelable) =
downloadedAppRepository.download(
plugin,
data,
args.packageName,
args.input.version,
onDownload = args.onDownloadProgress
).also {
args.setInputFile(it)
updateProgress(state = State.COMPLETED) // Download APK
}
}
updateProgress(state = State.COMPLETED) // Loading patches
val inputFile = when (val selectedApp = args.input) {
is SelectedApp.Download -> {
downloadedAppRepository.download(
selectedApp.app,
prefs.preferSplits.get(),
onDownload = { args.downloadProgress.emit(it) }
).also {
args.setInputFile(it)
updateProgress(state = State.COMPLETED) // Download APK
}
val (plugin, data) = downloaderPluginRepository.unwrapParceledData(selectedApp.data)
download(plugin, data)
}
is SelectedApp.Search -> {
downloaderPluginRepository.loadedPluginsFlow.first()
.firstNotNullOfOrNull { plugin ->
try {
val getScope = object : GetScope {
override val pluginPackageName = plugin.packageName
override val hostPackageName = applicationContext.packageName
override suspend fun requestStartActivity(intent: Intent): Intent? {
val result = args.handleStartActivityRequest(plugin, intent)
return when (result.resultCode) {
Activity.RESULT_OK -> result.data
Activity.RESULT_CANCELED -> throw UserInteractionException.Activity.Cancelled()
else -> throw UserInteractionException.Activity.NotCompleted(
result.resultCode,
result.data
)
}
}
}
withContext(Dispatchers.IO) {
plugin.get(
getScope,
selectedApp.packageName,
selectedApp.version
)
}?.takeIf { (_, version) -> selectedApp.version == null || version == selectedApp.version }
} catch (e: UserInteractionException.Activity.NotCompleted) {
throw e
} catch (_: UserInteractionException) {
null
}?.let { (data, _) -> download(plugin, data) }
} ?: throw Exception("App is not available.")
}
is SelectedApp.Local -> selectedApp.file.also { args.setInputFile(it) }
is SelectedApp.Installed -> File(pm.getPackageInfo(selectedApp.packageName)!!.applicationInfo.sourceDir)
is SelectedApp.Installed -> File(pm.getPackageInfo(selectedApp.packageName)!!.applicationInfo!!.sourceDir)
}
Session(
fs.tempDir.absolutePath,
frameworkPath,
aaptPath,
prefs.multithreadingDexFileWriter.get(),
applicationContext,
args.logger,
inputFile,
args.patchesProgress,
args.onProgress
).use { session ->
session.run(
patchedApk,
selectedPatches,
integrations
)
val runtime = if (prefs.useProcessRuntime.get()) {
ProcessRuntime(applicationContext)
} else {
CoroutineRuntime(applicationContext)
}
runtime.execute(
inputFile.absolutePath,
patchedApk.absolutePath,
args.packageName,
args.selectedPatches,
args.options,
args.logger,
args.onPatchCompleted,
args.onProgress
)
keystoreManager.sign(patchedApk, File(args.output))
updateProgress(state = State.COMPLETED) // Signing
Log.i(tag, "Patching succeeded".logFmt())
Result.success()
} catch (e: ProcessRuntime.RemoteFailureException) {
Log.e(
tag,
"An exception occurred in the remote process while patching. ${e.originalStackTrace}".logFmt()
)
updateProgress(state = State.FAILED, message = e.originalStackTrace)
Result.failure()
} catch (e: Exception) {
Log.e(tag, "Exception while patching".logFmt(), e)
Log.e(tag, "An exception occurred while patching".logFmt(), e)
updateProgress(state = State.FAILED, message = e.stackTraceToString())
Result.failure()
} finally {
patchedApk.delete()
if (args.input is SelectedApp.Local && args.input.temporary) {
args.input.file.delete()
}
}
}
companion object {
private const val logPrefix = "[Worker]:"
private fun String.logFmt() = "$logPrefix $this"
private const val LOG_PREFIX = "[Worker]"
private fun String.logFmt() = "$LOG_PREFIX $this"
}
}

View File

@ -1,8 +1,6 @@
package app.revanced.manager.service
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import app.revanced.manager.IRootSystemService
import com.topjohnwu.superuser.ipc.RootService
@ -14,23 +12,5 @@ class ManagerRootService : RootService() {
FileSystemManager.getService()
}
override fun onBind(intent: Intent): IBinder {
return RootSystemService()
}
}
class RootConnection : ServiceConnection {
var remoteFS: FileSystemManager? = null
private set
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val ipc = IRootSystemService.Stub.asInterface(service)
val binder = ipc.fileSystemService
remoteFS = FileSystemManager.getRemote(binder)
}
override fun onServiceDisconnected(name: ComponentName?) {
remoteFS = null
}
override fun onBind(intent: Intent): IBinder = RootSystemService()
}

Some files were not shown because too many files have changed in this diff Show More