Compare commits

..

22 Commits

Author SHA1 Message Date
4f8fd23a87 use snackbar instead because toasts are dumb 2025-05-15 20:50:35 +02:00
98c0262d10 add strings 2025-05-15 19:39:38 +02:00
f412c99c60 rename 2025-05-15 19:10:49 +02:00
62d5863846 Merge branch 'compose-dev' into feat/hide-dev-options 2025-05-15 19:01:01 +02:00
e71e6fb797 fix: handle edge-to-edge properly in fullscreen dialogs 2025-05-14 20:23:23 +02:00
111e8808a0 feat(Compose): Add confirmation dialog on multiple operations (#2529) 2025-05-14 19:55:09 +02:00
a30ff45111 refactor: Rename settings screens for consistency (#2547) 2025-05-14 19:34:05 +02:00
eb03daaebd feat(Compose): Improve patches selector tab by adding the bundle version (#2545) 2025-05-14 19:33:05 +02:00
9e2a7917ba ci: Adjust and modernize workflow files to match other repos 2025-05-12 18:32:30 +02:00
88fc639751 docs: Adjust issue templates to match other repos 2025-05-12 18:32:30 +02:00
2fc099568e docs: Add contribution guidelines and adjust README 2025-05-12 18:32:29 +02:00
e641b91e57 build: Update Gradle Wrapper 2025-05-12 18:32:29 +02:00
d09e9b79cb fix: patch count remaining at zero when using process runtime (#2542) 2025-05-11 19:30:12 +02:00
3f5d04a513 feat: Order bundles by number of patches 2025-05-11 13:10:11 +02:00
3ad027007f style: Apply formatting 2025-05-10 23:07:46 +02:00
813ec4dbe2 build: Sign releases using keystore if available 2025-05-10 20:00:01 +02:00
9fac5126df feat: Use "Debug" and "Debug signed" for build names respectively 2025-05-10 20:00:00 +02:00
5e03cf2307 feat: Move safeguards above patcher preference group 2025-05-10 20:00:00 +02:00
4d522664b6 fix: Reset cached theme on theme change to avoid broken colors (#2527) 2025-05-08 15:31:34 +02:00
0058b5f504 feat: hide developer options 2025-05-07 18:57:11 +02:00
8fdd5074a0 feat(Compose): Move developer options to top level (#2528) 2025-05-07 18:37:30 +02:00
5555373deb build: Remove repos that are not required 2025-05-05 19:37:10 +02:00
52 changed files with 921 additions and 455 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,25 @@
name: Build pull request
on:
workflow_dispatch:
pull_request:
branches:
- dev
jobs:
release:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cache Gradle
uses: burrunan/gradle-cache-action@v1
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew build --no-daemon

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,44 +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: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build with Gradle
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew assembleRelease --no-daemon -PnoProguard -PsignAsDebug
- name: Set env
run: echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Add hash to APK
run: mv app/build/outputs/apk/release/app-release.apk revanced-manager-${{ env.COMMIT_HASH }}.apk
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: revanced-manager
path: revanced-manager-${{ env.COMMIT_HASH }}.apk

View File

@ -1,57 +0,0 @@
name: Release Build
on:
push:
tags:
- "v*"
jobs:
build:
name: Build
runs-on: ubuntu-latest
permissions:
id-token: write
attestations: write
steps:
- uses: actions/checkout@v4
- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build with Gradle
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew assembleRelease --no-daemon
- name: Sign APK
id: sign_apk
uses: ilharp/sign-android-release@v1
with:
releaseDir: ./app/build/outputs/apk/release/
signingKey: ${{ secrets.SIGNING_KEYSTORE }}
keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }}
keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
- name: Add version to APK
run: mv ${{ steps.sign_apk.outputs.signedFile }} revanced-manager-${{ env.RELEASE_VERSION }}.apk
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-path: revanced-manager-${{ env.RELEASE_VERSION }}.apk
- name: Publish release APK
uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: false
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk

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

@ -0,0 +1,64 @@
name: Release
on:
workflow_dispatch:
push:
branches:
- main
- dev
jobs:
release:
name: Release
permissions:
contents: write
id-token: write
attestations: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Cache Gradle
uses: burrunan/gradle-cache-action@v1
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew assembleRelease
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*"
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: 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-path: build/app/outputs/apk/release/revanced-manager-*.apk

View File

@ -16,4 +16,4 @@ jobs:
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
repository: revanced/revanced-documentation
event-type: update-documentation
client-payload: '{"repo": "${{ github.event.repository.name }}", "ref": "${{ github.ref }}"}'
client-payload: '{"repo": "${{ github.event.repository.name }}", "ref": "${{ github.ref }}"}'

View File

@ -58,20 +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 |
| --------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------------------ |
| ![Latest stable release](https://img.shields.io/github/v/release/ReVanced/revanced-manager?style=for-the-badge "Latest stable release") | main | :white_check_mark: |
| ![Latest version](https://img.shields.io/badge/version-latest-brightgreen?style=for-the-badge "Latest version") | dev | :white_check_mark: |
| ![Latest version](https://img.shields.io/badge/version-latest-brightgreen?style=for-the-badge "Latest version") | 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.

View File

@ -27,7 +27,7 @@ android {
buildTypes {
debug {
applicationIdSuffix = ".debug"
resValue("string", "app_name", "ReVanced Manager (dev)")
resValue("string", "app_name", "ReVanced Manager (Debug)")
isPseudoLocalesEnabled = true
buildConfigField("long", "BUILD_ID", "${Random.nextLong()}L")
@ -40,12 +40,21 @@ android {
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
if (project.hasProperty("signAsDebug")) {
applicationIdSuffix = ".debug"
resValue("string", "app_name", "ReVanced Manager Debug")
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")
@ -63,15 +72,17 @@ android {
}
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",
))
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
}

View File

@ -44,13 +44,13 @@ import app.revanced.manager.ui.screen.SettingsScreen
import app.revanced.manager.ui.screen.UpdateScreen
import app.revanced.manager.ui.screen.settings.AboutSettingsScreen
import app.revanced.manager.ui.screen.settings.AdvancedSettingsScreen
import app.revanced.manager.ui.screen.settings.ContributorScreen
import app.revanced.manager.ui.screen.settings.DeveloperOptionsScreen
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.LicensesScreen
import app.revanced.manager.ui.screen.settings.update.ChangelogsScreen
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
@ -164,7 +164,7 @@ private fun ReVancedManager(vm: MainViewModel) {
}
}
},
vm = koinViewModel { parametersOf(it.getComplexArg<Patcher.ViewModelParams>()) }
viewModel = koinViewModel { parametersOf(it.getComplexArg<Patcher.ViewModelParams>()) }
)
}
@ -277,6 +277,10 @@ private fun ReVancedManager(vm: MainViewModel) {
AdvancedSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.Developer> {
DeveloperSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.Updates> {
UpdatesSettingsScreen(
onBackClick = navController::popBackStack,
@ -301,20 +305,17 @@ private fun ReVancedManager(vm: MainViewModel) {
}
composable<Settings.Changelogs> {
ChangelogsScreen(onBackClick = navController::popBackStack)
ChangelogsSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.Contributors> {
ContributorScreen(onBackClick = navController::popBackStack)
ContributorSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.Licenses> {
LicensesScreen(onBackClick = navController::popBackStack)
LicensesSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.DeveloperOptions> {
DeveloperOptionsScreen(onBackClick = navController::popBackStack)
}
}
}
}

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
@ -28,4 +29,6 @@ class PreferencesManager(
val suggestedVersionSafeguard = booleanPreference("suggested_version_safeguard", true)
val acknowledgedDownloaderPlugins = stringSetPreference("acknowledged_downloader_plugins", emptySet())
val showDeveloperSettings = booleanPreference("show_developer_settings", context.isDebuggable)
}

View File

@ -111,6 +111,7 @@ class ProcessRuntime(private val context: Context) : Runtime(context) {
}
val patching = CompletableDeferred<Unit>()
val scope = this
launch(Dispatchers.IO) {
val binder = awaitBinderConnection()
@ -124,7 +125,7 @@ class ProcessRuntime(private val context: Context) : Runtime(context) {
override fun log(level: String, msg: String) = logger.log(enumValueOf(level), msg)
override fun patchSucceeded() {
launch { onPatchCompleted() }
scope.launch { onPatchCompleted() }
}
override fun progress(name: String?, state: String?, msg: String?) =
@ -179,7 +180,7 @@ class ProcessRuntime(private val context: Context) : Runtime(context) {
}
/**
* An [Exception] occured in the remote process while patching.
* An [Exception] occurred in the remote process while patching.
*
* @param originalStackTrace The stack trace of the original [Exception].
*/

View File

@ -1,8 +1,10 @@
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
@ -95,6 +97,10 @@ class PatcherProcess(private val context: Context) : IPatcherProcess.Stub() {
}
companion object {
private val longArrayClass = LongArray::class.java
private val emptyLongArray = LongArray(0)
@SuppressLint("PrivateApi")
@JvmStatic
fun main(args: Array<String>) {
Looper.prepare()
@ -105,6 +111,15 @@ class PatcherProcess(private val context: Context) : IPatcherProcess.Stub() {
val systemContext = ActivityThread.systemMain().systemContext as Context
val appContext = systemContext.createPackageContext(managerPackageName, 0)
// Avoid annoying logs. See https://github.com/robolectric/robolectric/blob/ad0484c6b32c7d11176c711abeb3cb4a900f9258/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java#L376-L388
Class.forName("android.app.AppCompatCallbacks").apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
getDeclaredMethod("install", longArrayClass, longArrayClass).also { it.isAccessible = true }(null, emptyLongArray, emptyLongArray)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
getDeclaredMethod("install", longArrayClass).also { it.isAccessible = true }(null, emptyLongArray)
}
}
val ipcInterface = PatcherProcess(appContext)
appContext.sendBroadcast(Intent().apply {

View File

@ -0,0 +1,41 @@
package app.revanced.manager.ui.component
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import app.revanced.manager.R
@Composable
fun ConfirmDialog(
onDismiss: () -> Unit,
onConfirm: () -> Unit,
title: String,
description: String,
icon: ImageVector
) {
AlertDialog(
onDismissRequest = onDismiss,
dismissButton = {
TextButton(onDismiss) {
Text(stringResource(R.string.cancel))
}
},
confirmButton = {
TextButton(
onClick = {
onConfirm()
onDismiss()
}
) {
Text(stringResource(R.string.confirm))
}
},
title = { Text(title) },
icon = { Icon(icon, null) },
text = { Text(description) }
)
}

View File

@ -16,8 +16,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import app.revanced.manager.R
import app.revanced.manager.ui.component.bundle.BundleTopBar
@ -26,12 +24,8 @@ import app.revanced.manager.ui.component.bundle.BundleTopBar
fun ExceptionViewerDialog(text: String, onDismiss: () -> Unit) {
val context = LocalContext.current
Dialog(
FullscreenDialog(
onDismissRequest = onDismiss,
properties = DialogProperties(
usePlatformDefaultWidth = false,
dismissOnBackPress = true
)
) {
Scaffold(
topBar = {

View File

@ -0,0 +1,34 @@
package app.revanced.manager.ui.component
import android.view.WindowManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider
private val properties = DialogProperties(
usePlatformDefaultWidth = false,
dismissOnBackPress = true,
decorFitsSystemWindows = false,
)
@Composable
fun FullscreenDialog(onDismissRequest: () -> Unit, content: @Composable () -> Unit) {
Dialog(
onDismissRequest = onDismissRequest,
properties = properties
) {
val window = (LocalView.current.parent as DialogWindowProvider).window
LaunchedEffect(Unit) {
window.statusBarColor = Color.Transparent.toArgb()
window.navigationBarColor = Color.Transparent.toArgb()
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
}
content()
}
}

View File

@ -12,8 +12,6 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R
import app.revanced.manager.data.platform.NetworkInfo
@ -23,6 +21,7 @@ import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.asRemote
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.isDefault
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState
import app.revanced.manager.ui.component.ExceptionViewerDialog
import app.revanced.manager.ui.component.FullscreenDialog
import kotlinx.coroutines.launch
import org.koin.compose.koinInject
@ -56,12 +55,8 @@ fun BundleInformationDialog(
)
}
Dialog(
FullscreenDialog(
onDismissRequest = onDismissRequest,
properties = DialogProperties(
usePlatformDefaultWidth = false,
dismissOnBackPress = true
)
) {
val bundleName by bundle.nameState

View File

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.ErrorOutline
import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material3.Icon
@ -26,8 +27,9 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R
import app.revanced.manager.domain.bundles.PatchBundleSource
import app.revanced.manager.ui.component.haptics.HapticCheckbox
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState
import app.revanced.manager.ui.component.ConfirmDialog
import app.revanced.manager.ui.component.haptics.HapticCheckbox
import kotlinx.coroutines.flow.map
@OptIn(ExperimentalFoundationApi::class)
@ -42,6 +44,7 @@ fun BundleItem(
toggleSelection: (Boolean) -> Unit,
) {
var viewBundleDialogPage by rememberSaveable { mutableStateOf(false) }
var showDeleteConfirmationDialog by rememberSaveable { mutableStateOf(false) }
val state by bundle.state.collectAsStateWithLifecycle()
val version by remember(bundle) {
@ -52,15 +55,25 @@ fun BundleItem(
if (viewBundleDialogPage) {
BundleInformationDialog(
onDismissRequest = { viewBundleDialogPage = false },
onDeleteRequest = {
viewBundleDialogPage = false
onDelete()
},
onDeleteRequest = { showDeleteConfirmationDialog = true },
bundle = bundle,
onUpdate = onUpdate,
)
}
if (showDeleteConfirmationDialog) {
ConfirmDialog(
onDismiss = { showDeleteConfirmationDialog = false },
onConfirm = {
onDelete()
viewBundleDialogPage = false
},
title = stringResource(R.string.bundle_delete_single_dialog_title),
description = stringResource(R.string.bundle_delete_single_dialog_description, name),
icon = Icons.Outlined.Delete
)
}
ListItem(
modifier = Modifier
.height(64.dp)

View File

@ -22,13 +22,12 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R
import app.revanced.manager.domain.bundles.PatchBundleSource
import app.revanced.manager.patcher.patch.PatchInfo
import app.revanced.manager.ui.component.ArrowButton
import app.revanced.manager.ui.component.FullscreenDialog
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
@OptIn(ExperimentalMaterial3Api::class)
@ -41,12 +40,8 @@ fun BundlePatchesDialog(
var showOptions by rememberSaveable { mutableStateOf(false) }
val state by bundle.state.collectAsStateWithLifecycle()
Dialog(
FullscreenDialog(
onDismissRequest = onDismissRequest,
properties = DialogProperties(
usePlatformDefaultWidth = false,
dismissOnBackPress = true
)
) {
Scaffold(
topBar = {

View File

@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListItemInfo
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
@ -55,13 +56,13 @@ import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import app.revanced.manager.R
import app.revanced.manager.data.platform.Filesystem
import app.revanced.manager.patcher.patch.Option
import app.revanced.manager.ui.component.AlertDialogExtended
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.FloatInputDialog
import app.revanced.manager.ui.component.FullscreenDialog
import app.revanced.manager.ui.component.IntInputDialog
import app.revanced.manager.ui.component.LongInputDialog
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
@ -73,16 +74,17 @@ import app.revanced.manager.util.saver.snapshotStateListSaver
import app.revanced.manager.util.saver.snapshotStateSetSaver
import app.revanced.manager.util.toast
import app.revanced.manager.util.transparentListItemColors
import kotlinx.coroutines.CoroutineScope
import kotlinx.parcelize.Parcelize
import org.koin.compose.koinInject
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyColumnState
import sh.calvin.reorderable.rememberReorderableLazyListState
import java.io.Serializable
import kotlin.random.Random
import kotlin.reflect.typeOf
import androidx.compose.ui.window.Dialog as ComposeDialog
private class OptionEditorScope<T : Any>(
private val editor: OptionEditor<T>,
@ -497,7 +499,8 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
val lazyListState = rememberLazyListState()
val reorderableLazyColumnState =
rememberReorderableLazyColumnState(lazyListState) { from, to ->
// Update the list
rememberReorderableLazyListState(lazyListState) { from, to ->
// Update the list
items.add(to.index, items.removeAt(from.index))
}
@ -524,12 +527,8 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
scope.submitDialog(items.mapNotNull { it.value })
}
ComposeDialog(
FullscreenDialog(
onDismissRequest = back,
properties = DialogProperties(
usePlatformDefaultWidth = false,
dismissOnBackPress = true
),
) {
Scaffold(
topBar = {

View File

@ -23,10 +23,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import app.revanced.manager.R
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.FullscreenDialog
import app.revanced.manager.ui.component.GroupHeader
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
import app.revanced.manager.util.saver.PathSaver
@ -48,12 +47,8 @@ fun PathSelectorDialog(root: Path, onSelect: (Path?) -> Unit) {
currentDirectory.listDirectoryEntries().filter(Path::isReadable).partition(Path::isDirectory)
}
Dialog(
FullscreenDialog(
onDismissRequest = { onSelect(null) },
properties = DialogProperties(
usePlatformDefaultWidth = false,
dismissOnBackPress = true
)
) {
Scaffold(
topBar = {

View File

@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.map
*/
data class BundleInfo(
val name: String,
val version: String?,
val uid: Int,
val compatible: List<PatchInfo>,
val incompatible: List<PatchInfo>,
@ -78,7 +79,7 @@ data class BundleInfo(
targetList.add(it)
}
BundleInfo(source.getName(), source.uid, compatible, incompatible, universal)
BundleInfo(source.getName(), source.currentVersion(), source.uid, compatible, incompatible, universal)
}
}

View File

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

View File

@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -19,8 +20,10 @@ fun BundleListScreen(
selectedSources: SnapshotStateList<PatchBundleSource>,
bundlesSelectable: Boolean,
) {
val sortedSources = sources.sortedBy {
it.state.value.patchBundleOrNull()?.patches?.size
val sortedSources = remember(sources) {
sources.sortedByDescending { source ->
source.state.value.patchBundleOrNull()?.patches?.size ?: 0
}
}
LazyColumnWithScrollbar(

View File

@ -21,6 +21,7 @@ import androidx.compose.material.icons.filled.BatteryAlert
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.outlined.Apps
import androidx.compose.material.icons.outlined.BugReport
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.DeleteOutline
import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.Refresh
@ -62,6 +63,7 @@ import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.AutoUpdatesDialog
import app.revanced.manager.ui.component.AvailableUpdateDialog
import app.revanced.manager.ui.component.NotificationCard
import app.revanced.manager.ui.component.ConfirmDialog
import app.revanced.manager.ui.component.bundle.BundleTopBar
import app.revanced.manager.ui.component.bundle.ImportPatchBundleDialog
import app.revanced.manager.ui.component.haptics.HapticFloatingActionButton
@ -154,6 +156,20 @@ fun DashboardScreen(
}
)
var showDeleteConfirmationDialog by rememberSaveable { mutableStateOf(false) }
if (showDeleteConfirmationDialog) {
ConfirmDialog(
onDismiss = { showDeleteConfirmationDialog = false },
onConfirm = {
vm.selectedSources.forEach { if (!it.isDefault) vm.delete(it) }
vm.cancelSourceSelection()
},
title = stringResource(R.string.bundle_delete_multiple_dialog_title),
description = stringResource(R.string.bundle_delete_multiple_dialog_description),
icon = Icons.Outlined.Delete
)
}
Scaffold(
topBar = {
if (bundlesSelectable) {
@ -169,8 +185,7 @@ fun DashboardScreen(
actions = {
IconButton(
onClick = {
vm.selectedSources.forEach { if (!it.isDefault) vm.delete(it) }
vm.cancelSourceSelection()
showDeleteConfirmationDialog = true
}
) {
Icon(

View File

@ -17,6 +17,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.OpenInNew
import androidx.compose.material.icons.outlined.Cancel
import androidx.compose.material.icons.outlined.FileDownload
import androidx.compose.material.icons.outlined.PostAdd
import androidx.compose.material.icons.outlined.Save
@ -45,6 +46,7 @@ import app.revanced.manager.R
import app.revanced.manager.data.room.apps.installed.InstallType
import app.revanced.manager.ui.component.AppScaffold
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ConfirmDialog
import app.revanced.manager.ui.component.InstallerStatusDialog
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.component.patcher.InstallPickerDialog
@ -58,25 +60,23 @@ import app.revanced.manager.util.EventEffect
@Composable
fun PatcherScreen(
onBackClick: () -> Unit,
vm: PatcherViewModel
viewModel: PatcherViewModel
) {
fun leaveScreen() {
vm.onBack()
onBackClick()
}
BackHandler(onBack = ::leaveScreen)
val context = LocalContext.current
val exportApkLauncher =
rememberLauncherForActivityResult(CreateDocument(APK_MIMETYPE), vm::export)
rememberLauncherForActivityResult(CreateDocument(APK_MIMETYPE), viewModel::export)
val patcherSucceeded by vm.patcherSucceeded.observeAsState(null)
val canInstall by remember { derivedStateOf { patcherSucceeded == true && (vm.installedPackageName != null || !vm.isInstalling) } }
val patcherSucceeded by viewModel.patcherSucceeded.observeAsState(null)
val canInstall by remember { derivedStateOf { patcherSucceeded == true && (viewModel.installedPackageName != null || !viewModel.isInstalling) } }
var showInstallPicker by rememberSaveable { mutableStateOf(false) }
var showDismissConfirmationDialog by rememberSaveable { mutableStateOf(false) }
BackHandler(onBack = { showDismissConfirmationDialog = true })
val steps by remember {
derivedStateOf {
vm.steps.groupBy { it.category }
viewModel.steps.groupBy { it.category }
}
}
@ -93,34 +93,47 @@ fun PatcherScreen(
if (showInstallPicker)
InstallPickerDialog(
onDismiss = { showInstallPicker = false },
onConfirm = vm::install
onConfirm = viewModel::install
)
vm.packageInstallerStatus?.let {
InstallerStatusDialog(it, vm, vm::dismissPackageInstallerDialog)
if (showDismissConfirmationDialog) {
ConfirmDialog(
onDismiss = { showDismissConfirmationDialog = false },
onConfirm = {
viewModel.onBack()
onBackClick()
},
title = stringResource(R.string.patcher_stop_confirm_title),
description = stringResource(R.string.patcher_stop_confirm_description),
icon = Icons.Outlined.Cancel
)
}
viewModel.packageInstallerStatus?.let {
InstallerStatusDialog(it, viewModel, viewModel::dismissPackageInstallerDialog)
}
val activityLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
onResult = vm::handleActivityResult
onResult = viewModel::handleActivityResult
)
EventEffect(flow = vm.launchActivityFlow) { intent ->
EventEffect(flow = viewModel.launchActivityFlow) { intent ->
activityLauncher.launch(intent)
}
vm.activityPromptDialog?.let { title ->
viewModel.activityPromptDialog?.let { title ->
AlertDialog(
onDismissRequest = vm::rejectInteraction,
onDismissRequest = viewModel::rejectInteraction,
confirmButton = {
TextButton(
onClick = vm::allowInteraction
onClick = viewModel::allowInteraction
) {
Text(stringResource(R.string.continue_))
}
},
dismissButton = {
TextButton(
onClick = vm::rejectInteraction
onClick = viewModel::rejectInteraction
) {
Text(stringResource(R.string.cancel))
}
@ -137,20 +150,20 @@ fun PatcherScreen(
AppTopBar(
title = stringResource(R.string.patcher),
scrollBehavior = scrollBehavior,
onBackClick = ::leaveScreen
onBackClick = { showDismissConfirmationDialog = true }
)
},
bottomBar = {
BottomAppBar(
actions = {
IconButton(
onClick = { exportApkLauncher.launch("${vm.packageName}_${vm.version}_revanced_patched.apk") },
onClick = { exportApkLauncher.launch("${viewModel.packageName}_${viewModel.version}_revanced_patched.apk") },
enabled = patcherSucceeded == true
) {
Icon(Icons.Outlined.Save, stringResource(id = R.string.save_apk))
}
IconButton(
onClick = { vm.exportLogs(context) },
onClick = { viewModel.exportLogs(context) },
enabled = patcherSucceeded != null
) {
Icon(Icons.Outlined.PostAdd, stringResource(id = R.string.save_logs))
@ -161,11 +174,11 @@ fun PatcherScreen(
HapticExtendedFloatingActionButton(
text = {
Text(
stringResource(if (vm.installedPackageName == null) R.string.install_app else R.string.open_app)
stringResource(if (viewModel.installedPackageName == null) R.string.install_app else R.string.open_app)
)
},
icon = {
vm.installedPackageName?.let {
viewModel.installedPackageName?.let {
Icon(
Icons.AutoMirrored.Outlined.OpenInNew,
stringResource(R.string.open_app)
@ -176,10 +189,10 @@ fun PatcherScreen(
)
},
onClick = {
if (vm.installedPackageName == null)
if (vm.isDeviceRooted()) showInstallPicker = true
else vm.install(InstallType.DEFAULT)
else vm.open()
if (viewModel.installedPackageName == null)
if (viewModel.isDeviceRooted()) showInstallPicker = true
else viewModel.install(InstallType.DEFAULT)
else viewModel.open()
}
)
}
@ -193,7 +206,7 @@ fun PatcherScreen(
.fillMaxSize()
) {
LinearProgressIndicator(
progress = { vm.progress },
progress = { viewModel.progress },
modifier = Modifier.fillMaxWidth()
)
@ -209,8 +222,8 @@ fun PatcherScreen(
Steps(
category = category,
steps = steps,
stepCount = if (category == StepCategory.PATCHING) vm.patchesProgress else null,
stepProgressProvider = vm
stepCount = if (category == StepCategory.PATCHING) viewModel.patchesProgress else null,
stepProgressProvider = viewModel
)
}
}

View File

@ -60,14 +60,13 @@ import androidx.compose.ui.draw.rotate
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R
import app.revanced.manager.patcher.patch.Option
import app.revanced.manager.patcher.patch.PatchInfo
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.CheckedFilterChip
import app.revanced.manager.ui.component.FullscreenDialog
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
import app.revanced.manager.ui.component.SafeguardDialog
import app.revanced.manager.ui.component.SearchBar
@ -369,14 +368,22 @@ fun PatchesSelectorScreen(
Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
}
HapticExtendedFloatingActionButton(
text = { Text(stringResource(R.string.save_with_count, selectedPatchCount)) },
text = {
Text(
stringResource(
R.string.save_with_count,
selectedPatchCount
)
)
},
icon = {
Icon(
imageVector = Icons.Outlined.Save,
contentDescription = stringResource(R.string.save)
)
},
expanded = patchLazyListStates.getOrNull(pagerState.currentPage)?.isScrollingUp ?: true,
expanded = patchLazyListStates.getOrNull(pagerState.currentPage)?.isScrollingUp
?: true,
onClick = {
onSave(vm.getCustomSelection(), vm.getOptions())
}
@ -405,7 +412,19 @@ fun PatchesSelectorScreen(
)
}
},
text = { Text(bundle.name) },
text = {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = bundle.name,
style = MaterialTheme.typography.bodyMedium
)
Text(
text = bundle.version!!,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
},
selectedContentColor = MaterialTheme.colorScheme.primary,
unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant
)
@ -618,13 +637,7 @@ private fun OptionsDialog(
reset: () -> Unit,
set: (String, Any?) -> Unit,
onDismissRequest: () -> Unit,
) = Dialog(
onDismissRequest = onDismissRequest,
properties = DialogProperties(
usePlatformDefaultWidth = false,
dismissOnBackPress = true
)
) {
) = FullscreenDialog(onDismissRequest = onDismissRequest) {
Scaffold(
topBar = {
AppTopBar(

View File

@ -1,5 +1,6 @@
package app.revanced.manager.ui.screen
import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
@ -7,50 +8,79 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import app.revanced.manager.R
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.settings.SettingsListItem
import app.revanced.manager.ui.model.navigation.Settings
import org.koin.compose.koinInject
private val settingsSections = listOf(
Triple(
R.string.general,
R.string.general_description,
Icons.Outlined.Settings
) to Settings.General,
Triple(
R.string.updates,
R.string.updates_description,
Icons.Outlined.Update
) to Settings.Updates,
Triple(
R.string.downloads,
R.string.downloads_description,
Icons.Outlined.Download
) to Settings.Downloads,
Triple(
R.string.import_export,
R.string.import_export_description,
Icons.Outlined.SwapVert
) to Settings.ImportExport,
Triple(
R.string.advanced,
R.string.advanced_description,
Icons.Outlined.Tune
) to Settings.Advanced,
Triple(
R.string.about,
R.string.app_name,
Icons.Outlined.Info
) to Settings.About,
private data class Section(
@StringRes val name: Int,
@StringRes val description: Int,
val image: ImageVector,
val destination: Settings.Destination,
)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(onBackClick: () -> Unit, navigate: (Settings.Destination) -> Unit) {
val prefs: PreferencesManager = koinInject()
val showDeveloperSettings by prefs.showDeveloperSettings.getAsState()
val settingsSections = remember(showDeveloperSettings) {
listOfNotNull(
Section(
R.string.general,
R.string.general_description,
Icons.Outlined.Settings,
Settings.General
),
Section(
R.string.updates,
R.string.updates_description,
Icons.Outlined.Update,
Settings.Updates
),
Section(
R.string.downloads,
R.string.downloads_description,
Icons.Outlined.Download,
Settings.Downloads
),
Section(
R.string.import_export,
R.string.import_export_description,
Icons.Outlined.SwapVert,
Settings.ImportExport
),
Section(
R.string.advanced,
R.string.advanced_description,
Icons.Outlined.Tune,
Settings.Advanced
),
Section(
R.string.about,
R.string.app_name,
Icons.Outlined.Info,
Settings.About
),
Section(
R.string.developer_options,
R.string.developer_options_description,
Icons.Outlined.Code,
Settings.Developer
).takeIf { showDeveloperSettings }
)
}
Scaffold(
topBar = {
AppTopBar(
@ -64,12 +94,12 @@ fun SettingsScreen(onBackClick: () -> Unit, navigate: (Settings.Destination) ->
.padding(paddingValues)
.fillMaxSize()
) {
settingsSections.forEach { (titleDescIcon, destination) ->
settingsSections.forEach { (name, description, icon, destination) ->
SettingsListItem(
modifier = Modifier.clickable { navigate(destination) },
headlineContent = stringResource(titleDescIcon.first),
supportingContent = stringResource(titleDescIcon.second),
leadingContent = { Icon(titleDescIcon.third, null) }
headlineContent = stringResource(name),
supportingContent = stringResource(description),
leadingContent = { Icon(icon, null) }
)
}
}

View File

@ -23,16 +23,26 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.hideFromAccessibility
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import app.revanced.manager.BuildConfig
import app.revanced.manager.R
@ -42,6 +52,7 @@ import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.settings.SettingsListItem
import app.revanced.manager.ui.model.navigation.Settings
import app.revanced.manager.ui.viewmodel.AboutViewModel
import app.revanced.manager.ui.viewmodel.AboutViewModel.Companion.DEVELOPER_OPTIONS_TAPS
import app.revanced.manager.ui.viewmodel.AboutViewModel.Companion.getSocialIcon
import app.revanced.manager.util.openUrl
import app.revanced.manager.util.toast
@ -109,7 +120,8 @@ fun AboutSettingsScreen(
}
val listItems = listOfNotNull(
Triple(stringResource(R.string.submit_feedback),
Triple(
stringResource(R.string.submit_feedback),
stringResource(R.string.submit_feedback_description),
third = {
context.openUrl("https://github.com/ReVanced/revanced-manager/issues/new/choose")
@ -126,11 +138,6 @@ fun AboutSettingsScreen(
navigate(Settings.Contributors)
}
),
Triple(
stringResource(R.string.developer_options),
stringResource(R.string.developer_options_description),
third = { navigate(Settings.DeveloperOptions) }
),
Triple(
stringResource(R.string.opensource_licenses),
stringResource(R.string.opensource_licenses_description),
@ -139,6 +146,35 @@ fun AboutSettingsScreen(
)
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val snackbarHostState = remember { SnackbarHostState() }
val showDeveloperSettings by viewModel.showDeveloperSettings.getAsState()
var developerTaps by rememberSaveable { mutableIntStateOf(0) }
LaunchedEffect(developerTaps) {
if (developerTaps == 0) return@LaunchedEffect
if (showDeveloperSettings) {
snackbarHostState.showSnackbar(context.getString(R.string.developer_options_already_enabled))
developerTaps = 0
return@LaunchedEffect
}
val remaining = DEVELOPER_OPTIONS_TAPS - developerTaps
if (remaining > 0) {
snackbarHostState.showSnackbar(
context.getString(
R.string.developer_options_taps,
remaining
),
duration = SnackbarDuration.Long
)
} else if (remaining == 0) {
viewModel.showDeveloperSettings.update(true)
snackbarHostState.showSnackbar(context.getString(R.string.developer_options_enabled))
}
// Reset the counter
developerTaps = 0
}
Scaffold(
topBar = {
@ -148,6 +184,9 @@ fun AboutSettingsScreen(
onBackClick = onBackClick
)
},
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { paddingValues ->
ColumnWithScrollbar(
@ -158,9 +197,11 @@ fun AboutSettingsScreen(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Image(
modifier = Modifier.padding(top = 16.dp),
modifier = Modifier
.padding(top = 16.dp)
.clickable { developerTaps += 1 },
painter = icon,
contentDescription = null
contentDescription = stringResource(R.string.app_name)
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@ -168,7 +209,11 @@ fun AboutSettingsScreen(
) {
Text(
stringResource(R.string.app_name),
style = MaterialTheme.typography.headlineSmall
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.semantics {
// Icon already has this information for the purpose of being clickable.
hideFromAccessibility()
}
)
Text(
text = stringResource(R.string.version) + " " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")",

View File

@ -112,20 +112,6 @@ fun AdvancedSettingsScreen(
}
)
GroupHeader(stringResource(R.string.patcher))
BooleanItem(
preference = vm.prefs.useProcessRuntime,
coroutineScope = vm.viewModelScope,
headline = R.string.process_runtime,
description = R.string.process_runtime_description,
)
IntegerItem(
preference = vm.prefs.patcherProcessMemoryLimit,
coroutineScope = vm.viewModelScope,
headline = R.string.process_runtime_memory_limit,
description = R.string.process_runtime_memory_limit_description,
)
GroupHeader(stringResource(R.string.safeguards))
SafeguardBooleanItem(
preference = vm.prefs.disablePatchVersionCompatCheck,
@ -156,6 +142,20 @@ fun AdvancedSettingsScreen(
confirmationText = R.string.patch_selection_safeguard_confirmation
)
GroupHeader(stringResource(R.string.patcher))
BooleanItem(
preference = vm.prefs.useProcessRuntime,
coroutineScope = vm.viewModelScope,
headline = R.string.process_runtime,
description = R.string.process_runtime_description,
)
IntegerItem(
preference = vm.prefs.patcherProcessMemoryLimit,
coroutineScope = vm.viewModelScope,
headline = R.string.process_runtime_memory_limit,
description = R.string.process_runtime_memory_limit_description,
)
GroupHeader(stringResource(R.string.debugging))
val exportDebugLogsLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) {

View File

@ -51,7 +51,7 @@ import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ContributorScreen(
fun ContributorSettingsScreen(
onBackClick: () -> Unit,
viewModel: ContributorViewModel = koinViewModel()
) {

View File

@ -12,19 +12,23 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import app.revanced.manager.R
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.GroupHeader
import app.revanced.manager.ui.component.settings.BooleanItem
import app.revanced.manager.ui.component.settings.SettingsListItem
import app.revanced.manager.ui.viewmodel.DeveloperOptionsViewModel
import org.koin.androidx.compose.koinViewModel
import org.koin.compose.koinInject
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DeveloperOptionsScreen(
fun DeveloperSettingsScreen(
onBackClick: () -> Unit,
vm: DeveloperOptionsViewModel = koinViewModel()
) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val prefs: PreferencesManager = koinInject()
Scaffold(
topBar = {
@ -37,6 +41,13 @@ fun DeveloperOptionsScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { paddingValues ->
Column(modifier = Modifier.padding(paddingValues)) {
GroupHeader(stringResource(R.string.manager))
BooleanItem(
preference = prefs.showDeveloperSettings,
headline = R.string.developer_options,
description = R.string.developer_options_description,
)
GroupHeader(stringResource(R.string.patch_bundles_section))
SettingsListItem(
headlineContent = stringResource(R.string.patch_bundles_force_download),

View File

@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
@ -43,6 +44,7 @@ import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ExceptionViewerDialog
import app.revanced.manager.ui.component.GroupHeader
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
import app.revanced.manager.ui.component.ConfirmDialog
import app.revanced.manager.ui.component.haptics.HapticCheckbox
import app.revanced.manager.ui.component.settings.SettingsListItem
import app.revanced.manager.ui.viewmodel.DownloadsViewModel
@ -59,6 +61,17 @@ fun DownloadsSettingsScreen(
val downloadedApps by viewModel.downloadedApps.collectAsStateWithLifecycle(emptyList())
val pluginStates by viewModel.downloaderPluginStates.collectAsStateWithLifecycle()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
var showDeleteConfirmationDialog by rememberSaveable { mutableStateOf(false) }
if (showDeleteConfirmationDialog) {
ConfirmDialog(
onDismiss = { showDeleteConfirmationDialog = false },
onConfirm = { viewModel.deleteApps() },
title = stringResource(R.string.downloader_plugin_delete_apps_title),
description = stringResource(R.string.downloader_plugin_delete_apps_description),
icon = Icons.Outlined.Delete
)
}
Scaffold(
topBar = {
@ -68,7 +81,7 @@ fun DownloadsSettingsScreen(
onBackClick = onBackClick,
actions = {
if (viewModel.appSelection.isNotEmpty()) {
IconButton(onClick = { viewModel.deleteApps() }) {
IconButton(onClick = { showDeleteConfirmationDialog = true }) {
Icon(Icons.Default.Delete, stringResource(R.string.delete))
}
}

View File

@ -18,7 +18,7 @@ import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LicensesScreen(
fun LicensesSettingsScreen(
onBackClick: () -> Unit,
) {
AppScaffold(

View File

@ -27,7 +27,7 @@ import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ChangelogsScreen(
fun ChangelogsSettingsScreen(
onBackClick: () -> Unit,
vm: ChangelogsViewModel = koinViewModel()
) {

View File

@ -8,6 +8,7 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.network.dto.ReVancedDonationLink
import app.revanced.manager.network.dto.ReVancedSocial
@ -27,6 +28,7 @@ import kotlinx.coroutines.withContext
class AboutViewModel(
private val reVancedAPI: ReVancedAPI,
private val network: NetworkInfo,
prefs: PreferencesManager,
) : ViewModel() {
var socials by mutableStateOf(emptyList<ReVancedSocial>())
private set
@ -37,6 +39,8 @@ class AboutViewModel(
val isConnected: Boolean
get() = network.isConnected()
val showDeveloperSettings = prefs.showDeveloperSettings
init {
viewModelScope.launch {
if (!isConnected) {
@ -53,6 +57,8 @@ class AboutViewModel(
}
companion object {
const val DEVELOPER_OPTIONS_TAPS = 5
private val socialIcons = mapOf(
"Discord" to FontAwesomeIcons.Brands.Discord,
"GitHub" to FontAwesomeIcons.Brands.Github,

View File

@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.ui.theme.Theme
import app.revanced.manager.util.resetListItemColorsCached
import kotlinx.coroutines.launch
class GeneralSettingsViewModel(
@ -11,5 +12,6 @@ class GeneralSettingsViewModel(
) : ViewModel() {
fun setTheme(theme: Theme) = viewModelScope.launch {
prefs.theme.update(theme)
resetListItemColorsCached()
}
}

View File

@ -180,6 +180,10 @@ fun LocalDateTime.relativeTime(context: Context): String {
private var transparentListItemColorsCached: ListItemColors? = null
fun resetListItemColorsCached() {
transparentListItemColorsCached = null
}
/**
* The default ListItem colors, but with [ListItemColors.containerColor] set to [Color.Transparent].
*/

View File

@ -143,6 +143,8 @@
<string name="downloader_plugin_trust_dialog_title">Trust plugin?</string>
<string name="downloader_plugin_revoke_trust_dialog_title">Revoke trust?</string>
<string name="downloader_plugin_trust_dialog_body">Package name: %1$s\nSignature (SHA-256): %2$s</string>
<string name="downloader_plugin_delete_apps_title">Delete selected apps</string>
<string name="downloader_plugin_delete_apps_description">Are you sure you want to delete the selected apps?</string>
<string name="downloader_settings_no_apps">No downloaded apps found</string>
<string name="search_apps">Search apps…</string>
@ -301,6 +303,8 @@
<string name="patcher_step_write_patched">Write patched APK file</string>
<string name="patcher_step_sign_apk">Sign patched APK file</string>
<string name="patcher_notification_message">Patching in progress…</string>
<string name="patcher_stop_confirm_title">Stop patcher</string>
<string name="patcher_stop_confirm_description">Are you sure you want to stop the patching process?</string>
<string name="execute_patches">Execute patches</string>
<string name="executing_patch">Execute %s</string>
<string name="failed_to_execute_patch">Failed to execute %s</string>
@ -336,9 +340,17 @@
<string name="bundle_view_patches">View patches</string>
<string name="bundle_view_patches_any_version">Any version</string>
<string name="bundle_view_patches_any_package">Any package</string>
<string name="bundle_delete_single_dialog_title">Delete bundle</string>
<string name="bundle_delete_multiple_dialog_title">Delete bundles</string>
<string name="bundle_delete_single_dialog_description">Are you sure you want to delete the bundle \"%s\"?</string>
<string name="bundle_delete_multiple_dialog_description">Are you sure you want to delete the selected bundles?</string>
<string name="about_revanced_manager">About ReVanced Manager</string>
<string name="revanced_manager_description">ReVanced Manager is an application designed to work with ReVanced Patcher, which allows for long-lasting patches to be created for Android apps. The patching system is designed to automatically work with new versions of apps with minimal maintenance.</string>
<string name="developer_options_taps">%d taps remaining</string>
<string name="developer_options_enabled">Developer options enabled</string>
<string name="developer_options_already_enabled">Developer options are already enabled</string>
<string name="update_available">An update is available</string>
<string name="current_version">Current version: %s</string>
<string name="new_version">New version: %s</string>
@ -425,4 +437,5 @@
<string name="show_manager_update_dialog_on_launch_description">Shows a popup notification whenever there is a new update available on launch.</string>
<string name="failed_to_import_keystore">Failed to import keystore</string>
<string name="export">Export</string>
<string name="confirm">Confirm</string>
</resources>

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1,22 +1,22 @@
[versions]
ktx = "1.15.0"
material3 = "1.3.1"
ui-tooling = "1.7.7"
viewmodel-lifecycle = "2.8.7"
ktx = "1.16.0"
material3 = "1.3.2"
ui-tooling = "1.8.1"
viewmodel-lifecycle = "2.9.0"
splash-screen = "1.0.1"
activity = "1.10.0"
activity = "1.10.1"
appcompat = "1.7.0"
preferences-datastore = "1.1.2"
work-runtime = "2.10.0"
compose-bom = "2025.01.01"
navigation = "2.8.6"
work-runtime = "2.10.1"
compose-bom = "2025.05.00"
navigation = "2.9.0"
accompanist = "0.37.0"
placeholder = "1.1.2"
reorderable = "2.4.3"
serialization = "1.8.0"
collection = "0.3.8"
datetime = "0.6.1"
room-version = "2.6.1"
room-version = "2.7.1"
revanced-patcher = "21.0.0"
revanced-library = "3.0.2"
plugin-api = "1.0.0"
@ -25,7 +25,7 @@ ktor = "2.3.9"
markdown-renderer = "0.30.0"
fading-edges = "1.0.4"
kotlin = "2.1.10"
android-gradle-plugin = "8.8.0"
android-gradle-plugin = "8.9.1"
dev-tools-gradle-plugin = "2.1.10-1.0.29"
about-libraries-gradle-plugin = "11.5.0"
coil = "2.7.0"

Binary file not shown.

View File

@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=8d97a97984f6cbd2b85fe4c60a743440a347544bf18818048e611f5288d46c94
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
distributionSha256Sum=61ad310d3c7d3e5da131b76bbf22b5a4c0786e9d892dae8c1658d4b484de3caa
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

6
gradlew vendored
View File

@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@ -205,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.

4
gradlew.bat vendored
View File

@ -70,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View File

@ -1,19 +1,16 @@
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
maven("https://jitpack.io")
mavenLocal()
google()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
google()
maven("https://jitpack.io")
mavenLocal()
maven {
// A repository must be specified for some reason. "registry" is a dummy.
url = uri("https://maven.pkg.github.com/revanced/registry")
@ -24,5 +21,6 @@ dependencyResolutionManagement {
}
}
}
rootProject.name = "ReVanced Manager"
include(":app")