mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2026-01-22 18:53:57 +00:00
Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ef1b072e8 | ||
|
|
ca9ef544ce | ||
|
|
7831a3438d | ||
|
|
1fad90441c | ||
|
|
7f26c5bd45 | ||
|
|
637641cf54 | ||
|
|
d9a316abbb | ||
|
|
093cfa5269 | ||
|
|
61a025de4d | ||
|
|
7df1ae7ed8 | ||
|
|
eb6e75b156 | ||
|
|
8480b3ac3d | ||
|
|
6339a31fec | ||
|
|
71b5bb3f8f | ||
|
|
a9878dbbdf | ||
|
|
b1fb9dd7d3 | ||
|
|
de51fbd7be | ||
|
|
c9412a97d0 | ||
|
|
63c29bdd75 | ||
|
|
bace26063d | ||
|
|
7931eb97b9 | ||
|
|
9df89c7b74 | ||
|
|
78978276c4 | ||
|
|
011eddbbc5 | ||
|
|
4e9f3fe1dc | ||
|
|
9bd48c19ff | ||
|
|
c0516c3665 | ||
|
|
06ff36c836 | ||
|
|
98c16eb1dc | ||
|
|
35d80840e5 | ||
|
|
ad3a778fb9 | ||
|
|
d25d1efe9c | ||
|
|
de58dff423 | ||
|
|
ec26e4d8d1 | ||
|
|
c24da41505 | ||
|
|
f785185e1c | ||
|
|
5ed3ed9a2d | ||
|
|
a3adace60e | ||
|
|
3db4de09e2 | ||
|
|
70b2ee0a84 | ||
|
|
e7d82850c9 | ||
|
|
e6a8f4e6dc | ||
|
|
494e268bc5 | ||
|
|
e1c6f65b7e | ||
|
|
89075c5588 | ||
|
|
c7fa9b8ce7 | ||
|
|
cb70082d31 | ||
|
|
48b9ac8f5b | ||
|
|
0be568bbbd | ||
|
|
ba44fa620f | ||
|
|
dde402afbf | ||
|
|
bbe5142ca9 | ||
|
|
e74ffac5b0 | ||
|
|
cfb8980e3a | ||
|
|
e06e1bdcbe | ||
|
|
d60ced2f61 | ||
|
|
e68689828e | ||
|
|
ba932758c8 | ||
|
|
ee43fa6311 | ||
|
|
ad6b164d51 | ||
|
|
4a5510acb2 | ||
|
|
970dbc4428 | ||
|
|
f8f37325eb | ||
|
|
bb999019ef | ||
|
|
533b6a155a | ||
|
|
4cdc92388c | ||
|
|
ccc6be1e71 | ||
|
|
b355778a92 | ||
|
|
6a12e8f37a | ||
|
|
59adb91f5f | ||
|
|
53677e2f39 | ||
|
|
1c74f43b22 | ||
|
|
b4801970e8 | ||
|
|
7a3a6b512f | ||
|
|
72ea33b6de | ||
|
|
d97192e0ee | ||
|
|
196d9fe4d2 | ||
|
|
e960fcc303 | ||
|
|
f334da95ff | ||
|
|
5d5f311e36 | ||
|
|
d577e97758 | ||
|
|
2dc92e26d3 | ||
|
|
f4994a36a3 | ||
|
|
7a785a8163 | ||
|
|
6ad0d860c7 | ||
|
|
38a2fa55df |
2
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
2
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
name: ⭐ Feature request
|
name: ⭐ Feature request
|
||||||
description: Create a new feature request.
|
description: Create a new feature request.
|
||||||
title: 'feat: <title>'
|
title: 'feat: <title>'
|
||||||
labels: [feature-request]
|
labels: 'feature request'
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
116
.github/workflows/pr-build.yml
vendored
116
.github/workflows/pr-build.yml
vendored
@@ -1,45 +1,121 @@
|
|||||||
name: PR Build
|
name: PR Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
workflow_dispatch:
|
||||||
paths:
|
inputs:
|
||||||
- ".github/workflows/pr-build.yml"
|
# Flutter
|
||||||
- "android/**"
|
flutter-branch:
|
||||||
- "assets/**"
|
description: Flutter branch
|
||||||
- "lib/**"
|
type: choice
|
||||||
|
default: 'stable'
|
||||||
|
options:
|
||||||
|
- stable
|
||||||
|
- beta
|
||||||
|
- dev
|
||||||
|
- master
|
||||||
|
flutter-cache:
|
||||||
|
description: Cache
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
# Application configuration
|
||||||
|
app-flavour:
|
||||||
|
description: App flavour
|
||||||
|
default: 'release'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- release
|
||||||
|
- debug
|
||||||
|
- profile
|
||||||
|
# Pull Request
|
||||||
|
pr-number:
|
||||||
|
description: PR number (No hashtag)
|
||||||
|
required: true
|
||||||
|
|
||||||
|
run-name: "Build PR ${{ inputs.pr-number }}"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build the application
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
|
- name: Setup
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
gh repo clone ${{ github.repository }}
|
||||||
|
cd revanced-manager
|
||||||
|
gh repo set-default ${{ github.repository }}
|
||||||
|
gh pr checkout ${{ inputs.pr-number }}
|
||||||
|
|
||||||
|
echo "DATETIME=$( TZ='UTC+0' date --rfc-email )" >> $GITHUB_ENV
|
||||||
|
echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Make sure the release step uses its own credentials:
|
|
||||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup JDK
|
- name: Setup JDK
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: '11'
|
java-version: '17'
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
|
cache: gradle
|
||||||
|
|
||||||
- name: Setup Flutter
|
- name: Setup Flutter
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
channel: ${{ inputs.flutter-branch }}
|
||||||
cache: true
|
cache: ${{ inputs.flutter-cache }}
|
||||||
|
|
||||||
- name: Install Flutter dependencies
|
- name: Install Flutter dependencies
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
|
|
||||||
- name: Generate files with Builder
|
- name: Generate files with Builder
|
||||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
run: dart run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
- name: Build with Flutter
|
- name: Build with Flutter
|
||||||
env:
|
continue-on-error: true
|
||||||
|
id: flutter-build
|
||||||
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: flutter build apk --debug
|
run: |
|
||||||
|
flutter build apk --${{ inputs.app-flavour }};
|
||||||
|
|
||||||
|
- name: Prepare to comment
|
||||||
|
run: |
|
||||||
|
if [[ "${{ steps.flutter-build.outcome }}" == "success" ]]; then
|
||||||
|
echo "MESSAGE=✅ ReVanced Manager ${{ env.COMMIT_HASH }} build succeeded." >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "MESSAGE=🚫 ReVanced Manager ${{ env.COMMIT_HASH }} build failed." >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: "Comment to Pull Request #${{ inputs.pr-number }}"
|
||||||
|
uses: thollander/actions-comment-pull-request@v2
|
||||||
|
with:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
pr_number: ${{ inputs.pr-number }}
|
||||||
|
mode: recreate
|
||||||
|
message: |
|
||||||
|
## ⚒️ ReVanced PR Build workflow
|
||||||
|
|
||||||
|
${{ env.MESSAGE }}
|
||||||
|
|
||||||
|
You can see more details in run [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})!
|
||||||
|
|
||||||
|
### ⚙️ Overview
|
||||||
|
- App flavor: ${{ inputs.app-flavour }}
|
||||||
|
- Branch: ${{ inputs.flutter-branch }}
|
||||||
|
- Start time: ${{ env.DATETIME }}
|
||||||
|
|
||||||
- name: Upload build
|
- name: Upload build
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: revanced-manager
|
if-no-files-found: error
|
||||||
path: build/app/outputs/flutter-apk/app-debug.apk
|
name: revanced-manager-(${{ env.COMMIT_HASH }}-${{ inputs.pr-number }}-${{ inputs.app-flavour }})-${{ inputs.flutter-branch }}
|
||||||
|
path: |
|
||||||
|
build/app/outputs/flutter-apk/app-${{ inputs.app-flavour }}.apk
|
||||||
|
build/app/outputs/flutter-apk/app-${{ inputs.app-flavour }}.apk.sha1
|
||||||
|
|||||||
10
.github/workflows/release-build.yml
vendored
10
.github/workflows/release-build.yml
vendored
@@ -12,10 +12,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Set env
|
- name: Set env
|
||||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: "11"
|
java-version: "17"
|
||||||
distribution: "zulu"
|
distribution: "zulu"
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
- name: Set up Flutter
|
- name: Set up Flutter
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
- name: Generate files with Builder
|
- name: Generate files with Builder
|
||||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
run: dart run build_runner build --delete-conflicting-outputs
|
||||||
- name: Build with Flutter
|
- name: Build with Flutter
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -47,4 +47,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
prerelease: false
|
prerelease: false
|
||||||
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||||
|
|||||||
75
.releaserc
75
.releaserc
@@ -1,75 +0,0 @@
|
|||||||
{
|
|
||||||
"branches": [
|
|
||||||
"main",
|
|
||||||
{
|
|
||||||
"name": "dev",
|
|
||||||
"prerelease": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"semantic-release-export-data",
|
|
||||||
"@semantic-release/commit-analyzer",
|
|
||||||
[
|
|
||||||
"@semantic-release/release-notes-generator",
|
|
||||||
{
|
|
||||||
"presetConfig": {
|
|
||||||
"types": [
|
|
||||||
{
|
|
||||||
"type": "build",
|
|
||||||
"section": "Dependency Updates"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "chore",
|
|
||||||
"section": "Other Changes",
|
|
||||||
"hidden": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "perf",
|
|
||||||
"section": "Performance Improvements",
|
|
||||||
"hidden": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "refactor",
|
|
||||||
"section": "Code Improvements",
|
|
||||||
"hidden": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@semantic-release/changelog",
|
|
||||||
"semantic-release-flutter-plugin",
|
|
||||||
[
|
|
||||||
"@semantic-release/git",
|
|
||||||
{
|
|
||||||
"assets": [
|
|
||||||
"CHANGELOG.md",
|
|
||||||
"pubspec.yaml"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"@semantic-release/github",
|
|
||||||
{
|
|
||||||
"assets": [
|
|
||||||
{
|
|
||||||
"path": "build/app/outputs/apk/release/revanced-manager-*.apk"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"successComment": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"@saithodev/semantic-release-backmerge",
|
|
||||||
{
|
|
||||||
"backmergeBranches": [
|
|
||||||
{
|
|
||||||
"from": "main",
|
|
||||||
"to": "dev"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"clearWorkspace": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
|
|
||||||
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
103
CONTRIBUTING.md
Normal file
103
CONTRIBUTING.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<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-round.svg" />
|
||||||
|
<img height="24px" src="assets/revanced-logo/revanced-logo-round.svg" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||||
|
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="http://revanced.app/discord">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://reddit.com/r/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://t.me/app_revanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://x.com/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.youtube.com/@ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Continuing the legacy of Vanced
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# 👋 Contribution guidelines
|
||||||
|
|
||||||
|
This document describes how to contribute to ReVanced Manager.
|
||||||
|
|
||||||
|
## 📖 Resources to help you get started
|
||||||
|
|
||||||
|
* 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
|
||||||
|
|
||||||
|
## 🙏 Submitting a feature request
|
||||||
|
|
||||||
|
Features can be requested by opening an issue using the
|
||||||
|
[feature request issue template](https://github.com/ReVanced/revanced-manager/issues/new?assignees=&labels=feature-request&projects=&template=feature-issue.yml&title=feat%3A+%3Ctitle%3E).
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> We may reject your request at the discretion of ReVanced Manager's maintainers,
|
||||||
|
> please provide good motivation for a request to be accepted.
|
||||||
|
|
||||||
|
## 🐞 Submitting a bug report
|
||||||
|
|
||||||
|
If you encounter a bug while using the ReVanced Manager app, open an issue using the
|
||||||
|
[bug report issue template](https://github.com/ReVanced/revanced-manager/issues/new?assignees=&labels=bug&projects=&template=bug-issue.yml&title=bug%3A+%3Ctitle%3E).
|
||||||
|
|
||||||
|
## 📝 How to contribute
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> We recommend that you discuss your changes with
|
||||||
|
> the maintainers of ReVanced Manager before contributing.
|
||||||
|
> This will help you determine whether your change is acceptable.
|
||||||
|
|
||||||
|
1. Fork the repository and create a new branch based off the `dev` branch
|
||||||
|
2. Commit your changes
|
||||||
|
3. Open a pull request to the `dev` branch and reference issues that your pull request closes
|
||||||
|
4. The maintainers of ReVanced Manager will review and provide suggestions.
|
||||||
|
Once your pull request is approved and merged, it 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.
|
||||||
104
README.md
104
README.md
@@ -1,35 +1,97 @@
|
|||||||
|
<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-round.svg" />
|
||||||
|
<img height="24px" src="assets/revanced-logo/revanced-logo-round.svg" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||||
|
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="http://revanced.app/discord">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://reddit.com/r/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://t.me/app_revanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://x.com/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.youtube.com/@ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Continuing the legacy of Vanced
|
||||||
|
</p>
|
||||||
|
|
||||||
# 💊 ReVanced Manager
|
# 💊 ReVanced Manager
|
||||||
|
|
||||||
The official ReVanced Manager based on Flutter.
|
[](https://github.com/ReVanced/revanced-manager/commits "")
|
||||||
|
[](https://github.com/ReVanced/revanced-manager/commits "")
|
||||||
|
|
||||||
|
ReVanced Manager is an Android application that uses ReVanced Patcher to add, remove, and modify existing functionalities in Android applications.
|
||||||
|
|
||||||
|
## 💪 Features
|
||||||
|
|
||||||
|
We provide the some of the features are:
|
||||||
|
|
||||||
|
* 📱 **Portable**: ReVanced Patcher that fit in your pocket;
|
||||||
|
* 🤗 **Intuitive UI**: Help you manage your patched applications with easy-to-use interface;
|
||||||
|
* 🛠️ **Customization**: Patch with third-party sources;
|
||||||
|
* ✨ And a **lot more!**
|
||||||
|
|
||||||
## 🔽 Download
|
## 🔽 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 get ReVanced Manager by downloading from [ReVanced site](https://revanced.app/download) or [GitHub releases](https://github.com/ReVanced/revanced-manager/releases).
|
||||||
|
|
||||||
## 📝 Prerequisites
|
## 📚 Everything else
|
||||||
|
|
||||||
1. Android 8 or higher
|
### 📄 Documentation
|
||||||
2. Incompatible with certain ARMv7 devices
|
|
||||||
|
|
||||||
## 📃 Documentation
|
Documentation on how to use the application is available [here](/docs/README.md).
|
||||||
The documentation can be found [here](https://github.com/revanced/revanced-manager/tree/main/docs).
|
|
||||||
|
|
||||||
## 🔴 Issues
|
### 👋 Contributing
|
||||||
|
|
||||||
For suggestions and bug reports, open an issue [here](https://github.com/revanced/revanced-manager/issues/new/choose).
|
Thank you for considering contributing to ReVanced Manager, you can find the contribution guidelines [here](/CONTRIBUTING.md).
|
||||||
|
|
||||||
## 🌐 Translation
|
### 🔴 Issues
|
||||||
|
|
||||||
[](https://crowdin.com/project/revanced)
|
For suggestions and bug reports, open an issue [here](https://github.com/ReVanced/revanced-manager/issues/choose).
|
||||||
|
|
||||||
We're accepting translations on [Crowdin](https://translate.revanced.app).
|
## ⚖️ License
|
||||||
|
|
||||||
## 🛠️ Building Manager from source
|
ReVanced Manager adopts the [GNU General Public License 3.0](/LICENSE), [tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3): You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build & install instructions.
|
||||||
|
|
||||||
1. Setup flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
|
|
||||||
2. Clone the repository locally
|
|
||||||
3. Add your GitHub token in gradle.properties like [this](/docs/4_building.md)
|
|
||||||
4. Open the project in terminal
|
|
||||||
5. Run `flutter pub get` in terminal
|
|
||||||
6. Then `flutter packages pub run build_runner build --delete-conflicting-outputs` (Must be done on each git pull)
|
|
||||||
7. To build release APK run `flutter build apk`
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
plugins {
|
||||||
|
id "com.android.application"
|
||||||
|
id "kotlin-android"
|
||||||
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
|
}
|
||||||
|
|
||||||
def localProperties = new Properties()
|
def localProperties = new Properties()
|
||||||
def localPropertiesFile = rootProject.file('local.properties')
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
if (localPropertiesFile.exists()) {
|
if (localPropertiesFile.exists()) {
|
||||||
@@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
|
||||||
if (flutterRoot == null) {
|
|
||||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
|
||||||
}
|
|
||||||
|
|
||||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
if (flutterVersionCode == null) {
|
if (flutterVersionCode == null) {
|
||||||
flutterVersionCode = '1'
|
flutterVersionCode = '1'
|
||||||
@@ -21,35 +22,27 @@ if (flutterVersionName == null) {
|
|||||||
flutterVersionName = '1.0'
|
flutterVersionName = '1.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion flutter.compileSdkVersion
|
compileSdk 34
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_11
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
targetCompatibility JavaVersion.VERSION_11
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '11'
|
jvmTarget = '17'
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main.java.srcDirs += 'src/main/kotlin'
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "app.revanced.manager.flutter"
|
applicationId "app.revanced.manager.flutter"
|
||||||
minSdkVersion 26
|
minSdk 26
|
||||||
targetSdkVersion 33
|
targetSdk 34
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
shrinkResources false
|
shrinkResources false
|
||||||
@@ -70,11 +63,32 @@ android {
|
|||||||
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64'
|
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
profile {
|
||||||
|
shrinkResources false
|
||||||
|
minifyEnabled false
|
||||||
|
resValue "string", "app_name", "ReVanced Manager Profile"
|
||||||
|
applicationIdSuffix ".profile"
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
ndk {
|
||||||
|
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
packagingOptions {
|
||||||
|
jniLibs {
|
||||||
|
useLegacyPackaging true
|
||||||
|
excludes += ['/prebuilt/**']
|
||||||
|
}
|
||||||
|
resources {
|
||||||
|
excludes += ['/prebuilt/**']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
packagingOptions {
|
namespace 'app.revanced.manager.flutter'
|
||||||
exclude '/prebuilt/**'
|
}
|
||||||
}
|
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(17)
|
||||||
}
|
}
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
@@ -85,7 +99,7 @@ dependencies {
|
|||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
// ReVanced
|
// ReVanced
|
||||||
implementation "app.revanced:revanced-patcher:17.0.0"
|
implementation "app.revanced:revanced-patcher:19.1.0"
|
||||||
|
|
||||||
// Signing & aligning
|
// Signing & aligning
|
||||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="app.revanced.manager.flutter">
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="app.revanced.manager.flutter">
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
@@ -24,8 +22,7 @@
|
|||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true">
|
||||||
android:extractNativeLibs="true">
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package app.revanced.manager.flutter
|
package app.revanced.manager.flutter
|
||||||
|
|
||||||
|
import android.app.SearchManager
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import app.revanced.manager.flutter.utils.Aapt
|
import app.revanced.manager.flutter.utils.Aapt
|
||||||
@@ -15,7 +17,9 @@ import app.revanced.patcher.patch.PatchResult
|
|||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import kotlinx.coroutines.InternalCoroutinesApi
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.flow.FlowCollector
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
@@ -25,6 +29,7 @@ import java.io.StringWriter
|
|||||||
import java.util.logging.LogRecord
|
import java.util.logging.LogRecord
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : FlutterActivity() {
|
class MainActivity : FlutterActivity() {
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
private lateinit var installerChannel: MethodChannel
|
private lateinit var installerChannel: MethodChannel
|
||||||
@@ -38,6 +43,17 @@ class MainActivity : FlutterActivity() {
|
|||||||
|
|
||||||
val patcherChannel = "app.revanced.manager.flutter/patcher"
|
val patcherChannel = "app.revanced.manager.flutter/patcher"
|
||||||
val installerChannel = "app.revanced.manager.flutter/installer"
|
val installerChannel = "app.revanced.manager.flutter/installer"
|
||||||
|
val openBrowserChannel = "app.revanced.manager.flutter/browser"
|
||||||
|
|
||||||
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, openBrowserChannel).setMethodCallHandler { call, result ->
|
||||||
|
if (call.method == "openBrowser") {
|
||||||
|
val searchQuery = call.argument<String>("query")
|
||||||
|
openBrowser(searchQuery)
|
||||||
|
result.success(null)
|
||||||
|
} else {
|
||||||
|
result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val mainChannel =
|
val mainChannel =
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, patcherChannel)
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, patcherChannel)
|
||||||
@@ -98,8 +114,10 @@ class MainActivity : FlutterActivity() {
|
|||||||
val cacheDirPath = call.argument<String>("cacheDirPath")!!
|
val cacheDirPath = call.argument<String>("cacheDirPath")!!
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
val patchBundleFile = File(patchBundleFilePath)
|
||||||
|
patchBundleFile.setWritable(false)
|
||||||
patches = PatchBundleLoader.Dex(
|
patches = PatchBundleLoader.Dex(
|
||||||
File(patchBundleFilePath),
|
patchBundleFile,
|
||||||
optimizedDexDirectory = File(cacheDirPath)
|
optimizedDexDirectory = File(cacheDirPath)
|
||||||
)
|
)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
@@ -131,24 +149,34 @@ class MainActivity : FlutterActivity() {
|
|||||||
})
|
})
|
||||||
put("options", JSONArray().apply {
|
put("options", JSONArray().apply {
|
||||||
it.options.values.forEach { option ->
|
it.options.values.forEach { option ->
|
||||||
val optionJson = JSONObject().apply option@{
|
JSONObject().apply {
|
||||||
put("key", option.key)
|
put("key", option.key)
|
||||||
put("title", option.title)
|
put("title", option.title)
|
||||||
put("description", option.description)
|
put("description", option.description)
|
||||||
put("required", option.required)
|
put("required", option.required)
|
||||||
|
|
||||||
when (val value = option.value) {
|
fun JSONObject.putValue(
|
||||||
null -> put("value", null)
|
value: Any?,
|
||||||
is Array<*> -> put("value", JSONArray().apply {
|
key: String = "value"
|
||||||
|
) = if (value is Array<*>) put(
|
||||||
|
key,
|
||||||
|
JSONArray().apply {
|
||||||
value.forEach { put(it) }
|
value.forEach { put(it) }
|
||||||
})
|
})
|
||||||
else -> put("value", option.value)
|
else put(key, value)
|
||||||
}
|
|
||||||
|
|
||||||
put("optionClassType", option::class.simpleName)
|
putValue(option.default)
|
||||||
}
|
|
||||||
put(optionJson)
|
option.values?.let { values ->
|
||||||
|
put("values",
|
||||||
|
JSONObject().apply {
|
||||||
|
values.forEach { (key, value) ->
|
||||||
|
putValue(value, key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} ?: put("values", null)
|
||||||
|
put("valueType", option.valueType)
|
||||||
|
}.let(::put)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}.let(::put)
|
}.let(::put)
|
||||||
@@ -161,6 +189,16 @@ class MainActivity : FlutterActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun openBrowser(query: String?) {
|
||||||
|
val intent = Intent(Intent.ACTION_WEB_SEARCH).apply {
|
||||||
|
putExtra(SearchManager.QUERY, query)
|
||||||
|
}
|
||||||
|
if (intent.resolveActivity(packageManager) != null) {
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(InternalCoroutinesApi::class)
|
||||||
private fun runPatcher(
|
private fun runPatcher(
|
||||||
result: MethodChannel.Result,
|
result: MethodChannel.Result,
|
||||||
originalFilePath: String,
|
originalFilePath: String,
|
||||||
@@ -283,12 +321,12 @@ class MainActivity : FlutterActivity() {
|
|||||||
acceptPatches(patches)
|
acceptPatches(patches)
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
apply(false).collect { patchResult: PatchResult ->
|
apply(false).collect(FlowCollector { patchResult: PatchResult ->
|
||||||
if (cancel) {
|
if (cancel) {
|
||||||
handler.post { stopResult!!.success(null) }
|
handler.post { stopResult!!.success(null) }
|
||||||
this.cancel()
|
this.cancel()
|
||||||
this@apply.close()
|
this@apply.close()
|
||||||
return@collect
|
return@FlowCollector
|
||||||
}
|
}
|
||||||
|
|
||||||
val msg = patchResult.exception?.let {
|
val msg = patchResult.exception?.let {
|
||||||
@@ -301,7 +339,7 @@ class MainActivity : FlutterActivity() {
|
|||||||
|
|
||||||
updateProgress(progress, "", msg)
|
updateProgress(progress, "", msg)
|
||||||
progress += progressStep
|
progress += progressStep
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="app.revanced.manager.flutter">
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.9.0'
|
ext.kotlin_version = '1.9.10'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.1.3'
|
classpath 'com.android.tools.build:gradle:8.1.2'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,8 @@ allprojects {
|
|||||||
rootProject.buildDir = '../build'
|
rootProject.buildDir = '../build'
|
||||||
subprojects {
|
subprojects {
|
||||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
project.evaluationDependsOn(':app')
|
project.evaluationDependsOn(':app')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,3 +4,6 @@ org.gradle.daemon=true
|
|||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
|
android.nonTransitiveRClass=false
|
||||||
|
android.nonFinalResIds=false
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
include ':app'
|
pluginManagement {
|
||||||
|
def flutterSdkPath = {
|
||||||
|
def properties = new Properties()
|
||||||
|
file("local.properties").withInputStream { properties.load(it) }
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
return flutterSdkPath
|
||||||
|
}
|
||||||
|
settings.ext.flutterSdkPath = flutterSdkPath()
|
||||||
|
|
||||||
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
|
||||||
def properties = new Properties()
|
|
||||||
|
|
||||||
assert localPropertiesFile.exists()
|
plugins {
|
||||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
include ":app"
|
||||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
|
||||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"okButton": "OK",
|
"okButton": "OK",
|
||||||
"cancelButton": "Cancel",
|
"cancelButton": "Cancel",
|
||||||
|
"dismissButton": "Dismiss",
|
||||||
"quitButton": "Quit",
|
"quitButton": "Quit",
|
||||||
"updateButton": "Update",
|
"updateButton": "Update",
|
||||||
"enabledLabel": "Enabled",
|
"enabledLabel": "Enabled",
|
||||||
@@ -110,6 +111,8 @@
|
|||||||
|
|
||||||
"downloadToast": "Download function is not available yet",
|
"downloadToast": "Download function is not available yet",
|
||||||
|
|
||||||
|
"requireSuggestedAppVersionDialogText": "The version of the app you have selected does not match the suggested version. Please select the app that matches the suggested version.\n\nSelected version: v{selected}\nSuggested version: v{suggested}\n\nTo proceed anyway, disable \"Require suggested app version\" in the settings.",
|
||||||
|
|
||||||
"featureNotAvailable": "Feature not implemented",
|
"featureNotAvailable": "Feature not implemented",
|
||||||
"featureNotAvailableText": "This application is a split APK and cannot be selected. Unfortunately, this feature is only available for rooted users at the moment. However, you can still install the application by selecting its APK files from your device's storage instead"
|
"featureNotAvailableText": "This application is a split APK and cannot be selected. Unfortunately, this feature is only available for rooted users at the moment. However, you can still install the application by selecting its APK files from your device's storage instead"
|
||||||
},
|
},
|
||||||
@@ -128,14 +131,14 @@
|
|||||||
"none": "None",
|
"none": "None",
|
||||||
"noneTooltip": "Deselect all patches",
|
"noneTooltip": "Deselect all patches",
|
||||||
|
|
||||||
"loadPatchesSelection": "Load patches selection",
|
"loadPatchesSelection": "Load patch selection",
|
||||||
"noSavedPatches": "No saved patches for the selected app.\nPress Done to save current selection.",
|
"noSavedPatches": "No saved patch selection for the selected app.\nPress Done to save the current selection.",
|
||||||
"noPatchesFound": "No patches found for the selected app",
|
"noPatchesFound": "No patches found for the selected app",
|
||||||
"setRequiredOption": "Some patches require options to be set:\n\n{patches}\n\nPlease set them before continuing.",
|
"setRequiredOption": "Some patches require options to be set:\n\n{patches}\n\nPlease set them before continuing."
|
||||||
|
|
||||||
"selectAllPatchesWarningContent": "You are about to select all patches, that includes non-suggested patches and can cause unwanted behavior."
|
|
||||||
},
|
},
|
||||||
"patchOptionsView": {
|
"patchOptionsView": {
|
||||||
|
"customValue": "Custom value",
|
||||||
|
"resetOptionsTooltip": "Reset patch options",
|
||||||
"viewTitle": "Patch options",
|
"viewTitle": "Patch options",
|
||||||
"saveOptions": "Save",
|
"saveOptions": "Save",
|
||||||
|
|
||||||
@@ -152,10 +155,10 @@
|
|||||||
},
|
},
|
||||||
"patchItem": {
|
"patchItem": {
|
||||||
"unsupportedDialogText": "Selecting this patch may result in patching errors.\n\nApp version: {packageVersion}\nSupported versions:\n{supportedVersions}",
|
"unsupportedDialogText": "Selecting this patch may result in patching errors.\n\nApp version: {packageVersion}\nSupported versions:\n{supportedVersions}",
|
||||||
"unsupportedPatchVersion": "Patch is not supported for this app version. Enable the experimental toggle in settings to proceed.",
|
"unsupportedPatchVersion": "Patch is not supported for this app version.",
|
||||||
"unsupportedRequiredOption": "This patch contains a required option that is not supported by this app",
|
"unsupportedRequiredOption": "This patch contains a required option that is not supported by this app",
|
||||||
|
|
||||||
"patchesChangeWarningDialogText": "It is recommended to use the default selection of patches because changing it may cause unexpected issues.\n\nIf you know what you are doing, you can enable \"Enable changing selection\" in the settings.",
|
"patchesChangeWarningDialogText": "It is recommended to use the default patch selection and options. Changing them may result in unexpected issues.\n\nYou'll need to turn on \"Allow changing patch selection\" in settings before changing any patch selection.",
|
||||||
"patchesChangeWarningDialogButton": "Use default selection"
|
"patchesChangeWarningDialogButton": "Use default selection"
|
||||||
},
|
},
|
||||||
"installerView": {
|
"installerView": {
|
||||||
@@ -187,10 +190,9 @@
|
|||||||
|
|
||||||
"appearanceSectionTitle": "Appearance",
|
"appearanceSectionTitle": "Appearance",
|
||||||
"teamSectionTitle": "Team",
|
"teamSectionTitle": "Team",
|
||||||
"infoSectionTitle": "Info",
|
"debugSectionTitle": "Debugging",
|
||||||
"advancedSectionTitle": "Advanced",
|
"advancedSectionTitle": "Advanced",
|
||||||
"exportSectionTitle": "Import & export",
|
"exportSectionTitle": "Import & export",
|
||||||
"logsSectionTitle": "Logs",
|
|
||||||
|
|
||||||
"themeModeLabel": "App theme",
|
"themeModeLabel": "App theme",
|
||||||
"systemThemeLabel": "System",
|
"systemThemeLabel": "System",
|
||||||
@@ -204,15 +206,15 @@
|
|||||||
"englishOption": "English",
|
"englishOption": "English",
|
||||||
|
|
||||||
"sourcesLabel": "Sources",
|
"sourcesLabel": "Sources",
|
||||||
"sourcesLabelHint": "Configure your custom sources",
|
"sourcesLabelHint": "Configure your sources",
|
||||||
"sourcesIntegrationsLabel": "Integrations source",
|
"sourcesIntegrationsLabel": "Integrations source",
|
||||||
"sourcesResetDialogTitle": "Reset",
|
"sourcesResetDialogTitle": "Reset",
|
||||||
"sourcesResetDialogText": "Are you sure you want to reset custom sources to their default values?",
|
"sourcesResetDialogText": "Are you sure you want to reset your sources to their default values?",
|
||||||
"apiURLResetDialogText": "Are you sure you want to reset API URL to its default value?",
|
"apiURLResetDialogText": "Are you sure you want to reset your API URL to its default value?",
|
||||||
"sourcesUpdateNote": "Note: ReVanced Patches will be updated to the latest version automatically.\n\nThis will reveal your IP address to the server.",
|
"sourcesUpdateNote": "Note: Patches will be updated to the latest version automatically.\n\nThis will reveal your IP address to the server.",
|
||||||
|
|
||||||
"apiURLLabel": "API URL",
|
"apiURLLabel": "API URL",
|
||||||
"apiURLHint": "Configure your custom API URL",
|
"apiURLHint": "Configure your API URL",
|
||||||
"selectApiURL": "API URL",
|
"selectApiURL": "API URL",
|
||||||
"hostRepositoryLabel": "Repository API",
|
"hostRepositoryLabel": "Repository API",
|
||||||
"orgPatchesLabel": "Patches organization",
|
"orgPatchesLabel": "Patches organization",
|
||||||
@@ -222,21 +224,24 @@
|
|||||||
"contributorsLabel": "Contributors",
|
"contributorsLabel": "Contributors",
|
||||||
"contributorsHint": "A list of contributors of ReVanced",
|
"contributorsHint": "A list of contributors of ReVanced",
|
||||||
|
|
||||||
"logsLabel": "Logs",
|
"logsLabel": "Share logs",
|
||||||
"logsHint": "Share Manager's logs",
|
"logsHint": "Share ReVanced Manager logs",
|
||||||
|
|
||||||
"enablePatchesSelectionLabel": "Enable changing selection",
|
"enablePatchesSelectionLabel": "Allow changing patch selection",
|
||||||
"enablePatchesSelectionHint": "Enable changing the selection of patches.",
|
"enablePatchesSelectionHint": "Allow changing the selection of patches",
|
||||||
"enablePatchesSelectionWarningText": "Changing the default selection of patches may cause unexpected issues.\n\nEnable anyways?",
|
"enablePatchesSelectionWarningText": "Changing the selection of patches may cause unexpected issues.\n\nEnable anyways?",
|
||||||
"disablePatchesSelectionWarningText": "You are about to disable changing the selection of patches.\nThe default selection of patches will be restored.\n\nDisable anyways?",
|
"disablePatchesSelectionWarningText": "You are about to disable changing the selection of patches.\nThe default selection of patches will be restored.\n\nDisable anyways?",
|
||||||
|
|
||||||
"autoUpdatePatchesLabel": "Auto update patches",
|
"autoUpdatePatchesLabel": "Auto update patches",
|
||||||
"autoUpdatePatchesHint": "Automatically update ReVanced Patches to the latest version",
|
"autoUpdatePatchesHint": "Automatically update patches to the latest version",
|
||||||
"experimentalUniversalPatchesLabel": "Experimental universal patches support",
|
"universalPatchesLabel": "Show universal patches",
|
||||||
"experimentalUniversalPatchesHint": "Display all applications to use with universal patches, loading list of apps may be slower",
|
"universalPatchesHint": "Display all apps and universal patches (may slow down the app list)",
|
||||||
"experimentalPatchesLabel": "Experimental patches support",
|
|
||||||
"experimentalPatchesHint": "Enable usage of unsupported patches in any app version",
|
"versionCompatibilityCheckLabel": "Version compatibility check",
|
||||||
"enabledExperimentalPatches": "Experimental patches support enabled",
|
"versionCompatibilityCheckHint": "Restricts patches to supported app versions",
|
||||||
|
"requireSuggestedAppVersionLabel": "Require suggested app version",
|
||||||
|
"requireSuggestedAppVersionHint": "Enforce selection of suggested app version",
|
||||||
|
"requireSuggestedAppVersionDialogText": "Selecting an app that is not the suggested version may cause unexpected issues.\n\nDo you want to proceed anyways?",
|
||||||
|
|
||||||
"aboutLabel": "About",
|
"aboutLabel": "About",
|
||||||
"snackbarMessage": "Copied to clipboard",
|
"snackbarMessage": "Copied to clipboard",
|
||||||
@@ -246,54 +251,52 @@
|
|||||||
"deleteTempDirHint": "Delete unused temporary files",
|
"deleteTempDirHint": "Delete unused temporary files",
|
||||||
"deletedTempDir": "Temporary files deleted",
|
"deletedTempDir": "Temporary files deleted",
|
||||||
|
|
||||||
"exportPatchesLabel": "Export patches selection",
|
"exportPatchesLabel": "Export patch selection",
|
||||||
"exportPatchesHint": "Export patches selection to a JSON file",
|
"exportPatchesHint": "Export patch selection to a JSON file",
|
||||||
"exportedPatches": "Patches selection exported",
|
"exportedPatches": "Patch selection exported",
|
||||||
"noExportFileFound": "No patches selection to export",
|
"noExportFileFound": "No patch selection to export",
|
||||||
|
|
||||||
"importPatchesLabel": "Import patches selection",
|
"importPatchesLabel": "Import patch selection",
|
||||||
"importPatchesHint": "Import patches selection from a JSON file",
|
"importPatchesHint": "Import patch selection from a JSON file",
|
||||||
"importedPatches": "Patches selection imported",
|
"importedPatches": "Patch selection imported",
|
||||||
|
|
||||||
"resetStoredPatchesLabel": "Reset patches",
|
"resetStoredPatchesLabel": "Reset patch selection",
|
||||||
"resetStoredPatchesHint": "Reset the stored patches selection",
|
"resetStoredPatchesHint": "Reset the stored patch selection",
|
||||||
|
"resetStoredPatchesDialogTitle": "Reset patch selection?",
|
||||||
|
"resetStoredPatchesDialogText": "The default selection of patches will be restored.",
|
||||||
|
"resetStoredPatches": "Patch selection has been reset",
|
||||||
|
|
||||||
"resetStoredOptionsLabel": "Reset options",
|
"resetStoredOptionsLabel": "Reset patch options",
|
||||||
"resetStoredOptionsHint": "Reset all patch options",
|
"resetStoredOptionsHint": "Reset all patch options",
|
||||||
|
"resetStoredOptionsDialogTitle": "Reset patch options?",
|
||||||
"resetStoredPatchesDialogTitle": "Reset patches selection?",
|
"resetStoredOptionsDialogText": "Resetting patch options will remove all saved options.",
|
||||||
"resetStoredPatchesDialogText": "Resetting patches selection will remove all selected patches.",
|
|
||||||
"resetStoredPatches": "Patches selection has been reset",
|
|
||||||
|
|
||||||
"resetStoredOptionsDialogTitle": "Reset options?",
|
|
||||||
"resetStoredOptionsDialogText": "Resetting options will remove all saved options.",
|
|
||||||
"resetStoredOptions": "Options have been reset",
|
"resetStoredOptions": "Options have been reset",
|
||||||
|
|
||||||
"deleteLogsLabel": "Delete logs",
|
"deleteLogsLabel": "Clear logs",
|
||||||
"deleteLogsHint": "Delete collected manager logs",
|
"deleteLogsHint": "Delete collected ReVanced Manager logs",
|
||||||
"deletedLogs": "Logs deleted",
|
"deletedLogs": "Logs deleted",
|
||||||
|
|
||||||
"regenerateKeystoreLabel": "Regenerate keystore",
|
"regenerateKeystoreLabel": "Regenerate keystore",
|
||||||
"regenerateKeystoreHint": "Regenerate the keystore used to sign the app",
|
"regenerateKeystoreHint": "Regenerate the keystore used to sign apps",
|
||||||
|
|
||||||
"regenerateKeystoreDialogTitle": "Regenerate keystore?",
|
"regenerateKeystoreDialogTitle": "Regenerate keystore?",
|
||||||
"regenerateKeystoreDialogText": "Patched apps signed with the old keystore will no longer be able to update.",
|
"regenerateKeystoreDialogText": "Patched apps signed with the old keystore will no longer be able to be updated.",
|
||||||
"regeneratedKeystore": "Keystore regenerated",
|
"regeneratedKeystore": "Keystore regenerated",
|
||||||
|
|
||||||
"exportKeystoreLabel": "Export keystore",
|
"exportKeystoreLabel": "Export keystore",
|
||||||
"exportKeystoreHint": "Export keystore used to sign apps",
|
"exportKeystoreHint": "Export the keystore used to sign apps",
|
||||||
"exportedKeystore": "Keystore exported",
|
"exportedKeystore": "Keystore exported",
|
||||||
"noKeystoreExportFileFound": "No keystore to export",
|
"noKeystoreExportFileFound": "No keystore to export",
|
||||||
|
|
||||||
"importKeystoreLabel": "Import keystore",
|
"importKeystoreLabel": "Import keystore",
|
||||||
"importKeystoreHint": "Import keystore used to sign apps",
|
"importKeystoreHint": "Import a keystore used to sign apps",
|
||||||
"importedKeystore": "Keystore imported",
|
"importedKeystore": "Keystore imported",
|
||||||
|
|
||||||
"selectKeystorePassword": "Keystore Password",
|
"selectKeystorePassword": "Keystore password",
|
||||||
"selectKeystorePasswordHint": "Select keystore password used to sign the apk",
|
"selectKeystorePasswordHint": "Select keystore password used to sign apps",
|
||||||
|
|
||||||
"jsonSelectorErrorMessage": "Unable to use selected JSON file",
|
"jsonSelectorErrorMessage": "Unable to use selected JSON file",
|
||||||
"keystoreSelectorErrorMessage": "Unable to use selected KEYSTORE file"
|
"keystoreSelectorErrorMessage": "Unable to use selected keystore file"
|
||||||
},
|
},
|
||||||
"appInfoView": {
|
"appInfoView": {
|
||||||
"widgetTitle": "App info",
|
"widgetTitle": "App info",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
1
assets/revanced-logo/revanced-logo-round.svg
Normal file
1
assets/revanced-logo/revanced-logo-round.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="Logo"><g id="Ring"><circle id="Ring-Background" serif:id="Ring Background" cx="400" cy="400" r="400" style="fill:#1b1b1b;"/><path id="Ring1" serif:id="Ring" d="M400,0c220.766,0 400,179.234 400,400c-0,220.766 -179.234,400 -400,400c-220.766,-0 -400,-179.234 -400,-400c0,-220.766 179.234,-400 400,-400Zm-0,36c200.897,-0 364,163.103 364,364c0,200.897 -163.103,364 -364,364c-200.897,0 -364,-163.103 -364,-364c-0,-200.897 163.103,-364 364,-364Z" style="fill:url(#_Linear1);"/></g><g id="Shape"><path id="V-Shape" serif:id="V Shape" d="M538.74,269.872c1.481,-3.382 1.157,-7.283 -0.863,-10.373c-2.021,-3.091 -5.464,-4.954 -9.156,-4.954c-5.148,0 -10.435,0 -14.165,0c-3.1,0 -5.907,1.834 -7.153,4.672c-12.468,28.396 -78.273,178.273 -100.25,228.328c-1.246,2.838 -4.053,4.671 -7.154,4.671c-3.1,0 -5.907,-1.833 -7.153,-4.671c-21.977,-50.055 -87.782,-199.932 -100.25,-228.328c-1.246,-2.838 -4.053,-4.672 -7.153,-4.672c-3.73,0 -9.017,0 -14.164,0c-3.693,0 -7.135,1.863 -9.156,4.954c-2.02,3.09 -2.344,6.991 -0.863,10.373c23.557,53.766 101.872,232.519 117.871,269.034c1.743,3.979 5.674,6.549 10.018,6.549c6.293,-0 15.408,-0 21.701,-0c4.344,-0 8.275,-2.57 10.018,-6.549c15.999,-36.515 94.315,-215.268 117.872,-269.034Z" style="fill:#fff;"/><path id="Diamond" d="M408.119,395.312c-1.675,2.901 -4.77,4.688 -8.119,4.688c-3.349,-0 -6.444,-1.787 -8.119,-4.688c-16.997,-29.44 -56.156,-97.264 -73.153,-126.704c-1.675,-2.901 -1.675,-6.474 0,-9.375c1.675,-2.901 4.77,-4.688 8.119,-4.688c33.995,0 112.311,0 146.306,0c3.349,0 6.444,1.787 8.119,4.688c1.675,2.901 1.675,6.474 -0,9.375c-16.997,29.44 -56.156,97.264 -73.153,126.704Z" style="fill:url(#_Linear2);"/></g></g><defs><linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(4.89859e-14,800,-800,4.89859e-14,400.001,3.31681e-10)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient><linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.77155e-14,289.317,-282.535,1.73003e-14,400,254.545)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient></defs></svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -2,37 +2,41 @@
|
|||||||
|
|
||||||
ReVanced Manager has settings that can be configured to your liking.
|
ReVanced Manager has settings that can be configured to your liking.
|
||||||
|
|
||||||
## ⭐ Essential settings
|
## 🎛️ Essential settings
|
||||||
|
|
||||||
- ### 🔗 API URL
|
- ### 🪛 Allow changing patch selection
|
||||||
|
|
||||||
API to use to fetch updates and ReVanced Patches from.
|
Allows the user to change the patch selection from the default selection.
|
||||||
|
|
||||||
|
- ### 🔍 Version compatibility check
|
||||||
|
|
||||||
|
Constrains patches to supported app versions. Disable this to patch any version of an app.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Disabling this may cause issues if the patches are not compatible with the app version.
|
||||||
|
|
||||||
|
- ### 🧑🔬 Show universal patches
|
||||||
|
|
||||||
|
Reveals patches which can be applied to any app.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> These patches may not work on all apps.
|
||||||
|
|
||||||
- ### 🧬 Sources
|
- ### 🧬 Sources
|
||||||
|
|
||||||
Override the API and download ReVanced Patches from a different source.
|
Override the API and download patches from a different source.
|
||||||
|
|
||||||
- ### 🧪 Experimental ReVanced Patches support
|
- ### 🔗 API URL
|
||||||
|
|
||||||
Disable checking for the version of the app when applying ReVanced Patches.
|
API to use to fetch updates and patches from.
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
> This may cause issues if the ReVanced Patches are not compatible with the app version.
|
|
||||||
|
|
||||||
- ### 🧑🔬 Experimental universal support
|
|
||||||
|
|
||||||
This will show or hide ReVanced Patches, which are not meant for any app in particular but apply to all apps
|
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
> Because the patches generalize the app, they may not work on all apps.
|
|
||||||
|
|
||||||
- ### 💾 Imports & Exports
|
- ### 💾 Imports & Exports
|
||||||
|
|
||||||
You can import, export or reset the following settings:
|
You can import, export or reset the following settings:
|
||||||
|
|
||||||
- 🔑 Keystore
|
- 🔑 Keystore
|
||||||
- 📄 ReVanced Patches selection
|
- 📄 Patch selection
|
||||||
- ⚙️ Options
|
- ⚙️ Patch options
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> This is particularly useful if you want to backup or reset your settings.
|
> This is particularly useful if you want to backup or reset your settings.
|
||||||
@@ -45,4 +49,4 @@ ReVanced Manager has settings that can be configured to your liking.
|
|||||||
|
|
||||||
The next page will bring you back to the usage page.
|
The next page will bring you back to the usage page.
|
||||||
|
|
||||||
Continue: [🛠️ Usage](2_usage.md)
|
Continue: [🛠️ Usage](2_usage.md)
|
||||||
@@ -2,38 +2,31 @@
|
|||||||
|
|
||||||
This page will guide you through building ReVanced Manager from source.
|
This page will guide you through building ReVanced Manager from source.
|
||||||
|
|
||||||
1. Setup the Flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
|
1\. Setup the Flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
|
||||||
|
|
||||||
2. Clone the repository
|
2\. Clone the repository
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/revanced/revanced-manager.git && cd revanced-manager
|
git clone https://github.com/revanced/revanced-manager.git && cd revanced-manager
|
||||||
```
|
```
|
||||||
|
3\. Get dependencies
|
||||||
3. Create a GitHub personal access token with the `read:packages` scope [here](https://github.com/settings/tokens/new?scopes=read:packages&description=ReVanced)
|
|
||||||
|
|
||||||
4. Add your GitHub username and the token to `~/android/gradle.properties`
|
|
||||||
|
|
||||||
```properties
|
|
||||||
gpr.user = YourUsername
|
|
||||||
gpr.key = ghp_longrandomkey
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Get dependencies
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
flutter pub get
|
flutter pub get
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Delete conflicting outputs
|
4\. Delete conflicting outputs
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> Must be run every time you sync your local repository with the remote repository.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note**: Must be run every time you sync your local repository with the remote repository.
|
|
||||||
|
|
||||||
7. Build the APK
|
|
||||||
|
5\. Build the APK
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
flutter build apk
|
flutter build apk
|
||||||
|
|||||||
BIN
fonts/custom-icons.ttf
Normal file
BIN
fonts/custom-icons.ttf
Normal file
Binary file not shown.
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +0,0 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
|
||||||
distributionPath=wrapper/dists
|
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
|
||||||
240
gradlew
vendored
240
gradlew
vendored
@@ -1,240 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
#
|
|
||||||
# Copyright © 2015-2021 the original authors.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Gradle start up script for POSIX generated by Gradle.
|
|
||||||
#
|
|
||||||
# Important for running:
|
|
||||||
#
|
|
||||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
|
||||||
# noncompliant, but you have some other compliant shell such as ksh or
|
|
||||||
# bash, then to run this script, type that shell name before the whole
|
|
||||||
# command line, like:
|
|
||||||
#
|
|
||||||
# ksh Gradle
|
|
||||||
#
|
|
||||||
# Busybox and similar reduced shells will NOT work, because this script
|
|
||||||
# requires all of these POSIX shell features:
|
|
||||||
# * functions;
|
|
||||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
|
||||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
|
||||||
# * compound commands having a testable exit status, especially «case»;
|
|
||||||
# * various built-in commands including «command», «set», and «ulimit».
|
|
||||||
#
|
|
||||||
# Important for patching:
|
|
||||||
#
|
|
||||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
|
||||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
|
||||||
#
|
|
||||||
# The "traditional" practice of packing multiple parameters into a
|
|
||||||
# space-separated string is a well documented source of bugs and security
|
|
||||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
|
||||||
# options in "$@", and eventually passing that to Java.
|
|
||||||
#
|
|
||||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
|
||||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
|
||||||
# see the in-line comments for details.
|
|
||||||
#
|
|
||||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
|
||||||
# Darwin, MinGW, and NonStop.
|
|
||||||
#
|
|
||||||
# (3) This script is generated from the Groovy template
|
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
|
||||||
# within the Gradle project.
|
|
||||||
#
|
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
|
||||||
app_path=$0
|
|
||||||
|
|
||||||
# Need this for daisy-chained symlinks.
|
|
||||||
while
|
|
||||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
|
||||||
[ -h "$app_path" ]
|
|
||||||
do
|
|
||||||
ls=$( ls -ld "$app_path" )
|
|
||||||
link=${ls#*' -> '}
|
|
||||||
case $link in #(
|
|
||||||
/*) app_path=$link ;; #(
|
|
||||||
*) app_path=$APP_HOME$link ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
|
||||||
MAX_FD=maximum
|
|
||||||
|
|
||||||
warn () {
|
|
||||||
echo "$*"
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
die () {
|
|
||||||
echo
|
|
||||||
echo "$*"
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
|
||||||
cygwin=false
|
|
||||||
msys=false
|
|
||||||
darwin=false
|
|
||||||
nonstop=false
|
|
||||||
case "$( uname )" in #(
|
|
||||||
CYGWIN* ) cygwin=true ;; #(
|
|
||||||
Darwin* ) darwin=true ;; #(
|
|
||||||
MSYS* | MINGW* ) msys=true ;; #(
|
|
||||||
NONSTOP* ) nonstop=true ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
|
||||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
|
||||||
else
|
|
||||||
JAVACMD=$JAVA_HOME/bin/java
|
|
||||||
fi
|
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
JAVACMD=java
|
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|
||||||
case $MAX_FD in #(
|
|
||||||
max*)
|
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
|
||||||
warn "Could not query maximum file descriptor limit"
|
|
||||||
esac
|
|
||||||
case $MAX_FD in #(
|
|
||||||
'' | soft) :;; #(
|
|
||||||
*)
|
|
||||||
ulimit -n "$MAX_FD" ||
|
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Collect all arguments for the java command, stacking in reverse order:
|
|
||||||
# * args from the command line
|
|
||||||
# * the main class name
|
|
||||||
# * -classpath
|
|
||||||
# * -D...appname settings
|
|
||||||
# * --module-path (only if needed)
|
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if "$cygwin" || "$msys" ; then
|
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
|
||||||
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
for arg do
|
|
||||||
if
|
|
||||||
case $arg in #(
|
|
||||||
-*) false ;; # don't mess with options #(
|
|
||||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
|
||||||
[ -e "$t" ] ;; #(
|
|
||||||
*) false ;;
|
|
||||||
esac
|
|
||||||
then
|
|
||||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
|
||||||
fi
|
|
||||||
# Roll the args list around exactly as many times as the number of
|
|
||||||
# args, so each arg winds up back in the position where it started, but
|
|
||||||
# possibly modified.
|
|
||||||
#
|
|
||||||
# NB: a `for` loop captures its iteration list before it begins, so
|
|
||||||
# changing the positional parameters here affects neither the number of
|
|
||||||
# iterations, nor the values presented in `arg`.
|
|
||||||
shift # remove old arg
|
|
||||||
set -- "$@" "$arg" # push replacement arg
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
|
||||||
# double quotes to make sure that they get re-expanded; and
|
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
|
||||||
|
|
||||||
set -- \
|
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
|
||||||
-classpath "$CLASSPATH" \
|
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
|
||||||
"$@"
|
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
|
||||||
if ! command -v xargs >/dev/null 2>&1
|
|
||||||
then
|
|
||||||
die "xargs is not available"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
|
||||||
#
|
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
|
||||||
#
|
|
||||||
# In Bash we could simply go:
|
|
||||||
#
|
|
||||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
|
||||||
# set -- "${ARGS[@]}" "$@"
|
|
||||||
#
|
|
||||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
|
||||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
|
||||||
# character that might be a shell metacharacter, then use eval to reverse
|
|
||||||
# that process (while maintaining the separation between arguments), and wrap
|
|
||||||
# the whole thing up as a single "set" statement.
|
|
||||||
#
|
|
||||||
# This will of course break if any of these variables contains a newline or
|
|
||||||
# an unmatched quote.
|
|
||||||
#
|
|
||||||
|
|
||||||
eval "set -- $(
|
|
||||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
|
||||||
xargs -n1 |
|
|
||||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
|
||||||
tr '\n' ' '
|
|
||||||
)" '"$@"'
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
|
||||||
91
gradlew.bat
vendored
91
gradlew.bat
vendored
@@ -1,91 +0,0 @@
|
|||||||
@rem
|
|
||||||
@rem Copyright 2015 the original author or authors.
|
|
||||||
@rem
|
|
||||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
@rem you may not use this file except in compliance with the License.
|
|
||||||
@rem You may obtain a copy of the License at
|
|
||||||
@rem
|
|
||||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
@rem
|
|
||||||
@rem Unless required by applicable law or agreed to in writing, software
|
|
||||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
@rem See the License for the specific language governing permissions and
|
|
||||||
@rem limitations under the License.
|
|
||||||
@rem
|
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
|
||||||
@rem ##########################################################################
|
|
||||||
@rem
|
|
||||||
@rem Gradle startup script for Windows
|
|
||||||
@rem
|
|
||||||
@rem ##########################################################################
|
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
|
||||||
set APP_BASE_NAME=%~n0
|
|
||||||
set APP_HOME=%DIRNAME%
|
|
||||||
|
|
||||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
|
||||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
|
||||||
|
|
||||||
@rem Find java.exe
|
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:execute
|
|
||||||
@rem Setup the command line
|
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
|
||||||
|
|
||||||
:end
|
|
||||||
@rem End local scope for the variables with windows NT shell
|
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
set EXIT_CODE=%ERRORLEVEL%
|
|
||||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
|
||||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
|
||||||
exit /b %EXIT_CODE%
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:revanced_manager/services/download_manager.dart';
|
||||||
import 'package:revanced_manager/services/github_api.dart';
|
import 'package:revanced_manager/services/github_api.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/patcher_api.dart';
|
import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
@@ -41,6 +42,7 @@ import 'package:stacked_services/stacked_services.dart';
|
|||||||
LazySingleton(classType: PatcherAPI),
|
LazySingleton(classType: PatcherAPI),
|
||||||
LazySingleton(classType: RevancedAPI),
|
LazySingleton(classType: RevancedAPI),
|
||||||
LazySingleton(classType: GithubAPI),
|
LazySingleton(classType: GithubAPI),
|
||||||
|
LazySingleton(classType: DownloadManager),
|
||||||
LazySingleton(classType: Toast),
|
LazySingleton(classType: Toast),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,21 +4,21 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
|
import 'package:revanced_manager/services/download_manager.dart';
|
||||||
import 'package:revanced_manager/services/github_api.dart';
|
import 'package:revanced_manager/services/github_api.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/revanced_api.dart';
|
import 'package:revanced_manager/services/revanced_api.dart';
|
||||||
import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart';
|
import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart';
|
||||||
import 'package:revanced_manager/ui/views/navigation/navigation_view.dart';
|
import 'package:revanced_manager/ui/views/navigation/navigation_view.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:stacked_themes/stacked_themes.dart';
|
|
||||||
import 'package:timezone/data/latest.dart' as tz;
|
import 'package:timezone/data/latest.dart' as tz;
|
||||||
|
|
||||||
late SharedPreferences prefs;
|
late SharedPreferences prefs;
|
||||||
Future main() async {
|
Future main() async {
|
||||||
await ThemeManager.initialise();
|
|
||||||
await setupLocator();
|
await setupLocator();
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await locator<ManagerAPI>().initialize();
|
await locator<ManagerAPI>().initialize();
|
||||||
|
await locator<DownloadManager>().initialize();
|
||||||
final String apiUrl = locator<ManagerAPI>().getApiUrl();
|
final String apiUrl = locator<ManagerAPI>().getApiUrl();
|
||||||
await locator<RevancedAPI>().initialize(apiUrl);
|
await locator<RevancedAPI>().initialize(apiUrl);
|
||||||
final String repoUrl = locator<ManagerAPI>().getRepoUrl();
|
final String repoUrl = locator<ManagerAPI>().getRepoUrl();
|
||||||
@@ -30,7 +30,7 @@ Future main() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp({Key? key}) : super(key: key);
|
const MyApp({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -12,7 +12,18 @@ class Patch {
|
|||||||
required this.options,
|
required this.options,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Patch.fromJson(Map<String, dynamic> json) => _$PatchFromJson(json);
|
factory Patch.fromJson(Map<String, dynamic> json) {
|
||||||
|
_migrateV16ToV17(json);
|
||||||
|
|
||||||
|
return _$PatchFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _migrateV16ToV17(Map<String, dynamic> json) {
|
||||||
|
if (json['options'] == null) {
|
||||||
|
json['options'] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final String? description;
|
final String? description;
|
||||||
final bool excluded;
|
final bool excluded;
|
||||||
@@ -49,18 +60,37 @@ class Option {
|
|||||||
required this.title,
|
required this.title,
|
||||||
required this.description,
|
required this.description,
|
||||||
required this.value,
|
required this.value,
|
||||||
|
required this.values,
|
||||||
required this.required,
|
required this.required,
|
||||||
required this.optionClassType,
|
required this.valueType,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Option.fromJson(Map<String, dynamic> json) => _$OptionFromJson(json);
|
factory Option.fromJson(Map<String, dynamic> json) {
|
||||||
|
_migrateV17ToV19(json);
|
||||||
|
|
||||||
|
return _$OptionFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _migrateV17ToV19(Map<String, dynamic> json) {
|
||||||
|
if (json['valueType'] == null) {
|
||||||
|
final type = json['optionClassType'];
|
||||||
|
if (type is String) {
|
||||||
|
json['valueType'] = type
|
||||||
|
.replaceAll('PatchOption', '')
|
||||||
|
.replaceAll('List', 'Array');
|
||||||
|
|
||||||
|
json['optionClassType'] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final String key;
|
final String key;
|
||||||
final String title;
|
final String title;
|
||||||
final String description;
|
final String description;
|
||||||
dynamic value;
|
final dynamic value;
|
||||||
|
final Map<String, dynamic>? values;
|
||||||
final bool required;
|
final bool required;
|
||||||
final String optionClassType;
|
final String valueType;
|
||||||
|
|
||||||
Map toJson() => _$OptionToJson(this);
|
Map toJson() => _$OptionToJson(this);
|
||||||
}
|
}
|
||||||
|
|||||||
75
lib/services/download_manager.dart
Normal file
75
lib/services/download_manager.dart
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_cache_manager/file.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
|
|
||||||
|
@lazySingleton
|
||||||
|
class DownloadManager {
|
||||||
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
|
late final String _userAgent;
|
||||||
|
|
||||||
|
final _cacheOptions = CacheOptions(
|
||||||
|
store: MemCacheStore(),
|
||||||
|
maxStale: const Duration(days: 1),
|
||||||
|
priority: CachePriority.high,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> initialize() async {
|
||||||
|
_userAgent = 'ReVanced-Manager/${await _managerAPI.getCurrentManagerVersion()}';
|
||||||
|
}
|
||||||
|
|
||||||
|
Dio initDio(String url) {
|
||||||
|
var dio = Dio();
|
||||||
|
try {
|
||||||
|
dio = Dio(
|
||||||
|
BaseOptions(
|
||||||
|
baseUrl: url,
|
||||||
|
headers: {
|
||||||
|
'User-Agent': _userAgent,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
|
||||||
|
return dio;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> clearAllCache() async {
|
||||||
|
try {
|
||||||
|
await _cacheOptions.store!.clean();
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<File> getSingleFile(String url) async {
|
||||||
|
return DefaultCacheManager().getSingleFile(
|
||||||
|
url,
|
||||||
|
headers: {
|
||||||
|
'User-Agent': _userAgent,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<FileResponse> getFileStream(String url) {
|
||||||
|
return DefaultCacheManager().getFileStream(
|
||||||
|
url,
|
||||||
|
withProgress: true,
|
||||||
|
headers: {
|
||||||
|
'User-Agent': _userAgent,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,48 +1,24 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
|
import 'package:revanced_manager/services/download_manager.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
class GithubAPI {
|
class GithubAPI {
|
||||||
late Dio _dio = Dio();
|
late final Dio _dio;
|
||||||
late final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
late final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
|
late final DownloadManager _downloadManager = locator<DownloadManager>();
|
||||||
final _cacheOptions = CacheOptions(
|
|
||||||
store: MemCacheStore(),
|
|
||||||
maxStale: const Duration(days: 1),
|
|
||||||
priority: CachePriority.high,
|
|
||||||
);
|
|
||||||
|
|
||||||
Future<void> initialize(String repoUrl) async {
|
Future<void> initialize(String repoUrl) async {
|
||||||
try {
|
_dio = _downloadManager.initDio(repoUrl);
|
||||||
_dio = Dio(
|
|
||||||
BaseOptions(
|
|
||||||
baseUrl: repoUrl,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
|
|
||||||
} on Exception catch (e) {
|
|
||||||
if (kDebugMode) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> clearAllCache() async {
|
Future<void> clearAllCache() async {
|
||||||
try {
|
await _downloadManager.clearAllCache();
|
||||||
await _cacheOptions.store!.clean();
|
|
||||||
} on Exception catch (e) {
|
|
||||||
if (kDebugMode) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>?> getLatestRelease(
|
Future<Map<String, dynamic>?> getLatestRelease(
|
||||||
@@ -104,7 +80,7 @@ class GithubAPI {
|
|||||||
final Map<String, dynamic> releases = response.data[0];
|
final Map<String, dynamic> releases = response.data[0];
|
||||||
int updates = 0;
|
int updates = 0;
|
||||||
final String currentVersion =
|
final String currentVersion =
|
||||||
await ManagerAPI().getCurrentManagerVersion();
|
await _managerAPI.getCurrentManagerVersion();
|
||||||
while (response.data[updates]['tag_name'] != 'v$currentVersion') {
|
while (response.data[updates]['tag_name'] != 'v$currentVersion') {
|
||||||
updates++;
|
updates++;
|
||||||
}
|
}
|
||||||
@@ -141,7 +117,7 @@ class GithubAPI {
|
|||||||
(asset) => (asset['name'] as String).endsWith(extension),
|
(asset) => (asset['name'] as String).endsWith(extension),
|
||||||
);
|
);
|
||||||
if (asset != null) {
|
if (asset != null) {
|
||||||
return await DefaultCacheManager().getSingleFile(
|
return await _downloadManager.getSingleFile(
|
||||||
asset['browser_download_url'],
|
asset['browser_download_url'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -162,7 +138,7 @@ class GithubAPI {
|
|||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
if (url.isNotEmpty) {
|
if (url.isNotEmpty) {
|
||||||
return await DefaultCacheManager().getSingleFile(
|
return await _downloadManager.getSingleFile(
|
||||||
url,
|
url,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -180,7 +156,7 @@ class GithubAPI {
|
|||||||
} else {
|
} else {
|
||||||
_managerAPI.setPatchesDownloadURL(downloadUrl);
|
_managerAPI.setPatchesDownloadURL(downloadUrl);
|
||||||
}
|
}
|
||||||
return await DefaultCacheManager().getSingleFile(
|
return await _downloadManager.getSingleFile(
|
||||||
downloadUrl,
|
downloadUrl,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||||
@@ -33,6 +34,8 @@ class ManagerAPI {
|
|||||||
Patch? selectedPatch;
|
Patch? selectedPatch;
|
||||||
BuildContext? ctx;
|
BuildContext? ctx;
|
||||||
bool isRooted = false;
|
bool isRooted = false;
|
||||||
|
bool suggestedAppVersionSelected = true;
|
||||||
|
bool isDynamicThemeAvailable = false;
|
||||||
String storedPatchesFile = '/selected-patches.json';
|
String storedPatchesFile = '/selected-patches.json';
|
||||||
String keystoreFile =
|
String keystoreFile =
|
||||||
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore';
|
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore';
|
||||||
@@ -59,10 +62,16 @@ class ManagerAPI {
|
|||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
_prefs = await SharedPreferences.getInstance();
|
_prefs = await SharedPreferences.getInstance();
|
||||||
isRooted = await _rootAPI.isRooted();
|
isRooted = await _rootAPI.isRooted();
|
||||||
|
isDynamicThemeAvailable = (await getSdkVersion()) >= 31; // ANDROID_12_SDK_VERSION = 31
|
||||||
storedPatchesFile =
|
storedPatchesFile =
|
||||||
(await getApplicationDocumentsDirectory()).path + storedPatchesFile;
|
(await getApplicationDocumentsDirectory()).path + storedPatchesFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<int> getSdkVersion() async {
|
||||||
|
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
|
||||||
|
return info.version.sdkInt;
|
||||||
|
}
|
||||||
|
|
||||||
String getApiUrl() {
|
String getApiUrl() {
|
||||||
return _prefs.getString('apiUrl') ?? defaultApiUrl;
|
return _prefs.getString('apiUrl') ?? defaultApiUrl;
|
||||||
}
|
}
|
||||||
@@ -71,7 +80,6 @@ class ManagerAPI {
|
|||||||
if (url.isEmpty || url == ' ') {
|
if (url.isEmpty || url == ' ') {
|
||||||
url = defaultApiUrl;
|
url = defaultApiUrl;
|
||||||
}
|
}
|
||||||
await _revancedAPI.initialize(url);
|
|
||||||
await _revancedAPI.clearAllCache();
|
await _revancedAPI.clearAllCache();
|
||||||
await _prefs.setString('apiUrl', url);
|
await _prefs.setString('apiUrl', url);
|
||||||
}
|
}
|
||||||
@@ -244,12 +252,20 @@ class ManagerAPI {
|
|||||||
await _prefs.setBool('universalPatchesEnabled', value);
|
await _prefs.setBool('universalPatchesEnabled', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool areExperimentalPatchesEnabled() {
|
bool isVersionCompatibilityCheckEnabled() {
|
||||||
return _prefs.getBool('experimentalPatchesEnabled') ?? false;
|
return _prefs.getBool('versionCompatibilityCheckEnabled') ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> enableExperimentalPatchesStatus(bool value) async {
|
Future<void> enableVersionCompatibilityCheckStatus(bool value) async {
|
||||||
await _prefs.setBool('experimentalPatchesEnabled', value);
|
await _prefs.setBool('versionCompatibilityCheckEnabled', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isRequireSuggestedAppVersionEnabled() {
|
||||||
|
return _prefs.getBool('requireSuggestedAppVersionEnabled') ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> enableRequireSuggestedAppVersionStatus(bool value) async {
|
||||||
|
await _prefs.setBool('requireSuggestedAppVersionEnabled', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setKeystorePassword(String password) async {
|
Future<void> setKeystorePassword(String password) async {
|
||||||
@@ -677,7 +693,7 @@ class ManagerAPI {
|
|||||||
Future<List<String>> getDefaultPatches() async {
|
Future<List<String>> getDefaultPatches() async {
|
||||||
final List<Patch> patches = await getPatches();
|
final List<Patch> patches = await getPatches();
|
||||||
final List<String> defaultPatches = [];
|
final List<String> defaultPatches = [];
|
||||||
if (areExperimentalPatchesEnabled() == false) {
|
if (isVersionCompatibilityCheckEnabled() == true) {
|
||||||
defaultPatches.addAll(
|
defaultPatches.addAll(
|
||||||
patches
|
patches
|
||||||
.where(
|
.where(
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:cr_file_saver/file_saver.dart';
|
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:install_plugin/install_plugin.dart';
|
import 'package:install_plugin/install_plugin.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@@ -13,7 +13,7 @@ import 'package:revanced_manager/models/patch.dart';
|
|||||||
import 'package:revanced_manager/models/patched_application.dart';
|
import 'package:revanced_manager/models/patched_application.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/root_api.dart';
|
import 'package:revanced_manager/services/root_api.dart';
|
||||||
import 'package:share_extend/share_extend.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
class PatcherAPI {
|
class PatcherAPI {
|
||||||
@@ -236,10 +236,11 @@ void exportPatchedFile(String appName, String version) {
|
|||||||
try {
|
try {
|
||||||
if (outFile != null) {
|
if (outFile != null) {
|
||||||
final String newName = _getFileName(appName, version);
|
final String newName = _getFileName(appName, version);
|
||||||
CRFileSaver.saveFileWithDialog(
|
FlutterFileDialog.saveFile(
|
||||||
SaveFileDialogParams(
|
params: SaveFileDialogParams(
|
||||||
sourceFilePath: outFile!.path,
|
sourceFilePath: outFile!.path,
|
||||||
destinationFileName: newName,
|
fileName: newName,
|
||||||
|
mimeTypesFilter: ['application/vnd.android.package-archive'],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -258,7 +259,7 @@ void sharePatchedFile(String appName, String version) {
|
|||||||
final String newPath =
|
final String newPath =
|
||||||
outFile!.path.substring(0, lastSeparator + 1) + newName;
|
outFile!.path.substring(0, lastSeparator + 1) + newName;
|
||||||
final File shareFile = outFile!.copySync(newPath);
|
final File shareFile = outFile!.copySync(newPath);
|
||||||
ShareExtend.share(shareFile.path, 'file');
|
Share.shareXFiles([XFile(shareFile.path)]);
|
||||||
}
|
}
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
@@ -268,8 +269,9 @@ void sharePatchedFile(String appName, String version) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _getFileName(String appName, String version) {
|
String _getFileName(String appName, String version) {
|
||||||
|
final String patchVersion = _managerAPI.patchesVersion!;
|
||||||
final String prefix = appName.toLowerCase().replaceAll(' ', '-');
|
final String prefix = appName.toLowerCase().replaceAll(' ', '-');
|
||||||
final String newName = '$prefix-revanced_v$version.apk';
|
final String newName = '$prefix-revanced_v$version-patches_$patchVersion.apk';
|
||||||
return newName;
|
return newName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,10 +288,10 @@ Future<void> exportPatcherLog(String logs) async {
|
|||||||
final String fileName = 'revanced-manager_patcher_$dateTime.txt';
|
final String fileName = 'revanced-manager_patcher_$dateTime.txt';
|
||||||
final File log = File('${logDir.path}/$fileName');
|
final File log = File('${logDir.path}/$fileName');
|
||||||
log.writeAsStringSync(logs);
|
log.writeAsStringSync(logs);
|
||||||
CRFileSaver.saveFileWithDialog(
|
FlutterFileDialog.saveFile(
|
||||||
SaveFileDialogParams(
|
params:SaveFileDialogParams(
|
||||||
sourceFilePath: log.path,
|
sourceFilePath: log.path,
|
||||||
destinationFileName: fileName,
|
fileName: fileName,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,49 +3,27 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
|
import 'package:revanced_manager/services/download_manager.dart';
|
||||||
import 'package:synchronized/synchronized.dart';
|
import 'package:synchronized/synchronized.dart';
|
||||||
import 'package:timeago/timeago.dart';
|
import 'package:timeago/timeago.dart';
|
||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
class RevancedAPI {
|
class RevancedAPI {
|
||||||
late Dio _dio = Dio();
|
late final Dio _dio;
|
||||||
|
late final DownloadManager _downloadManager = locator<DownloadManager>();
|
||||||
|
|
||||||
final Lock getToolsLock = Lock();
|
final Lock getToolsLock = Lock();
|
||||||
|
|
||||||
final _cacheOptions = CacheOptions(
|
Future<void> initialize(String repoUrl) async {
|
||||||
store: MemCacheStore(),
|
_dio = _downloadManager.initDio(repoUrl);
|
||||||
maxStale: const Duration(days: 1),
|
|
||||||
priority: CachePriority.high,
|
|
||||||
);
|
|
||||||
|
|
||||||
Future<void> initialize(String apiUrl) async {
|
|
||||||
try {
|
|
||||||
_dio = Dio(
|
|
||||||
BaseOptions(
|
|
||||||
baseUrl: apiUrl,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
|
|
||||||
} on Exception catch (e) {
|
|
||||||
if (kDebugMode) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> clearAllCache() async {
|
Future<void> clearAllCache() async {
|
||||||
try {
|
await _downloadManager.clearAllCache();
|
||||||
await _cacheOptions.store!.clean();
|
|
||||||
} on Exception catch (e) {
|
|
||||||
if (kDebugMode) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, List<dynamic>>> getContributors() async {
|
Future<Map<String, List<dynamic>>> getContributors() async {
|
||||||
@@ -120,7 +98,7 @@ class RevancedAPI {
|
|||||||
);
|
);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
final String url = release['browser_download_url'];
|
final String url = release['browser_download_url'];
|
||||||
return await DefaultCacheManager().getSingleFile(url);
|
return await _downloadManager.getSingleFile(url);
|
||||||
}
|
}
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
@@ -152,9 +130,8 @@ class RevancedAPI {
|
|||||||
'revanced/revanced-manager',
|
'revanced/revanced-manager',
|
||||||
);
|
);
|
||||||
File? outputFile;
|
File? outputFile;
|
||||||
await for (final result in DefaultCacheManager().getFileStream(
|
await for (final result in _downloadManager.getFileStream(
|
||||||
release!['browser_download_url'] as String,
|
release!['browser_download_url'] as String,
|
||||||
withProgress: true,
|
|
||||||
)) {
|
)) {
|
||||||
if (result is DownloadProgress) {
|
if (result is DownloadProgress) {
|
||||||
final totalSize = result.totalSize ?? 10000000;
|
final totalSize = result.totalSize ?? 10000000;
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
var lightCustomColorScheme = ColorScheme.fromSeed(
|
var lightCustomColorScheme = ColorScheme.fromSeed(
|
||||||
seedColor: Colors.blue,
|
seedColor: Colors.purple,
|
||||||
primary: const Color(0xff1B73E8),
|
primary: const Color(0xFF4C51C0),
|
||||||
);
|
);
|
||||||
|
|
||||||
var lightCustomTheme = ThemeData(
|
var lightCustomTheme = ThemeData(
|
||||||
@@ -21,10 +21,10 @@ var lightCustomTheme = ThemeData(
|
|||||||
);
|
);
|
||||||
|
|
||||||
var darkCustomColorScheme = ColorScheme.fromSeed(
|
var darkCustomColorScheme = ColorScheme.fromSeed(
|
||||||
seedColor: Colors.blue,
|
seedColor: Colors.purple,
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
primary: const Color(0xffA5CAFF),
|
primary: const Color(0xFFBFC1FF),
|
||||||
surface: const Color(0xff1B1A1D),
|
surface: const Color(0xFF131316),
|
||||||
);
|
);
|
||||||
|
|
||||||
var darkCustomTheme = ThemeData(
|
var darkCustomTheme = ThemeData(
|
||||||
@@ -38,7 +38,7 @@ var darkCustomTheme = ThemeData(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
canvasColor: const Color(0xff1B1A1D),
|
canvasColor: const Color(0xFF131316),
|
||||||
scaffoldBackgroundColor: const Color(0xff1B1A1D),
|
scaffoldBackgroundColor: const Color(0xFF131316),
|
||||||
textTheme: GoogleFonts.robotoTextTheme(ThemeData.dark().textTheme),
|
textTheme: GoogleFonts.robotoTextTheme(ThemeData.dark().textTheme),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ import 'package:stacked_services/stacked_services.dart';
|
|||||||
|
|
||||||
class DynamicThemeBuilder extends StatefulWidget {
|
class DynamicThemeBuilder extends StatefulWidget {
|
||||||
const DynamicThemeBuilder({
|
const DynamicThemeBuilder({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.home,
|
required this.home,
|
||||||
required this.localizationsDelegates,
|
required this.localizationsDelegates,
|
||||||
}) : super(key: key);
|
});
|
||||||
final String title;
|
final String title;
|
||||||
final Widget home;
|
final Widget home;
|
||||||
final Iterable<LocalizationsDelegate> localizationsDelegates;
|
final Iterable<LocalizationsDelegate> localizationsDelegates;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
|||||||
import 'package:stacked/stacked.dart' hide SkeletonLoader;
|
import 'package:stacked/stacked.dart' hide SkeletonLoader;
|
||||||
|
|
||||||
class AppSelectorView extends StatefulWidget {
|
class AppSelectorView extends StatefulWidget {
|
||||||
const AppSelectorView({Key? key}) : super(key: key);
|
const AppSelectorView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AppSelectorView> createState() => _AppSelectorViewState();
|
State<AppSelectorView> createState() => _AppSelectorViewState();
|
||||||
@@ -28,7 +28,6 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
icon: const Icon(Icons.sd_storage),
|
icon: const Icon(Icons.sd_storage),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
model.selectAppFromStorage(context);
|
model.selectAppFromStorage(context);
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
@@ -38,13 +37,11 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
floating: true,
|
floating: true,
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'appSelectorView.viewTitle',
|
'appSelectorView.viewTitle',
|
||||||
child: Text(
|
),
|
||||||
'',
|
titleTextStyle: TextStyle(
|
||||||
style: TextStyle(
|
fontSize: 22.0,
|
||||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.arrow_back,
|
Icons.arrow_back,
|
||||||
@@ -115,9 +112,13 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
context,
|
context,
|
||||||
app.packageName,
|
app.packageName,
|
||||||
),
|
),
|
||||||
|
onLinkTap: () =>
|
||||||
|
model.searchSuggestedVersionOnWeb(
|
||||||
|
packageName: app.packageName,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
,
|
||||||
...model
|
...model
|
||||||
.getFilteredAppsNames(_query)
|
.getFilteredAppsNames(_query)
|
||||||
.map(
|
.map(
|
||||||
@@ -129,9 +130,13 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
model.showDownloadToast();
|
model.showDownloadToast();
|
||||||
},
|
},
|
||||||
|
onLinkTap: () =>
|
||||||
|
model.searchSuggestedVersionOnWeb(
|
||||||
|
packageName: app,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
,
|
||||||
const SizedBox(height: 70.0),
|
const SizedBox(height: 70.0),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
@@ -70,7 +71,53 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> selectApp(ApplicationWithIcon application) async {
|
Future<void> searchSuggestedVersionOnWeb({
|
||||||
|
required String packageName,
|
||||||
|
}) async {
|
||||||
|
final String suggestedVersion = getSuggestedVersion(packageName);
|
||||||
|
|
||||||
|
if (suggestedVersion.isNotEmpty) {
|
||||||
|
await openDefaultBrowser('$packageName apk version v$suggestedVersion');
|
||||||
|
} else {
|
||||||
|
await openDefaultBrowser('$packageName apk');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> openDefaultBrowser(String query) async {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
try {
|
||||||
|
const platform = MethodChannel('app.revanced.manager.flutter/browser');
|
||||||
|
await platform.invokeMethod('openBrowser', {'query': query});
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw 'Platform not supported';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> selectApp(
|
||||||
|
BuildContext context,
|
||||||
|
ApplicationWithIcon application, [
|
||||||
|
bool isFromStorage = false,
|
||||||
|
]) async {
|
||||||
|
final String suggestedVersion =
|
||||||
|
getSuggestedVersion(application.packageName);
|
||||||
|
if (application.versionName != suggestedVersion && suggestedVersion.isNotEmpty) {
|
||||||
|
_managerAPI.suggestedAppVersionSelected = false;
|
||||||
|
if (_managerAPI.isRequireSuggestedAppVersionEnabled() &&
|
||||||
|
context.mounted) {
|
||||||
|
return showRequireSuggestedAppVersionDialog(
|
||||||
|
context,
|
||||||
|
application.versionName!,
|
||||||
|
suggestedVersion,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_managerAPI.suggestedAppVersionSelected = true;
|
||||||
|
}
|
||||||
locator<PatcherViewModel>().selectedApp = PatchedApplication(
|
locator<PatcherViewModel>().selectedApp = PatchedApplication(
|
||||||
name: application.appName,
|
name: application.appName,
|
||||||
packageName: application.packageName,
|
packageName: application.packageName,
|
||||||
@@ -78,8 +125,12 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
apkFilePath: application.apkFilePath,
|
apkFilePath: application.apkFilePath,
|
||||||
icon: application.icon,
|
icon: application.icon,
|
||||||
patchDate: DateTime.now(),
|
patchDate: DateTime.now(),
|
||||||
|
isFromStorage: isFromStorage,
|
||||||
);
|
);
|
||||||
await locator<PatcherViewModel>().loadLastSelectedPatches();
|
await locator<PatcherViewModel>().loadLastSelectedPatches();
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> canSelectInstalled(
|
Future<void> canSelectInstalled(
|
||||||
@@ -89,27 +140,64 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
final app =
|
final app =
|
||||||
await DeviceApps.getApp(packageName, true) as ApplicationWithIcon?;
|
await DeviceApps.getApp(packageName, true) as ApplicationWithIcon?;
|
||||||
if (app != null) {
|
if (app != null) {
|
||||||
if (await checkSplitApk(packageName) && !isRooted) {
|
final bool isSplitApk = await checkSplitApk(packageName);
|
||||||
|
if (isRooted || !isSplitApk) {
|
||||||
|
if (context.mounted) {
|
||||||
|
await selectApp(context, app);
|
||||||
|
}
|
||||||
|
final List<Option> requiredNullOptions = getNullRequiredOptions(
|
||||||
|
locator<PatcherViewModel>().selectedPatches,
|
||||||
|
packageName,
|
||||||
|
);
|
||||||
|
if (requiredNullOptions.isNotEmpty) {
|
||||||
|
locator<PatcherViewModel>().showRequiredOptionDialog();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
return showSelectFromStorageDialog(context);
|
return showSelectFromStorageDialog(context);
|
||||||
}
|
}
|
||||||
} else if (!await checkSplitApk(packageName) || isRooted) {
|
|
||||||
await selectApp(app);
|
|
||||||
if (context.mounted) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
final List<Option> requiredNullOptions = getNullRequiredOptions(locator<PatcherViewModel>().selectedPatches, packageName);
|
|
||||||
if(requiredNullOptions.isNotEmpty){
|
|
||||||
locator<PatcherViewModel>().showRequiredOptionDialog();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future showRequireSuggestedAppVersionDialog(
|
||||||
|
BuildContext context,
|
||||||
|
String selectedVersion,
|
||||||
|
String suggestedVersion,
|
||||||
|
) async {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
title: I18nText('warning'),
|
||||||
|
content: I18nText(
|
||||||
|
'appSelectorView.requireSuggestedAppVersionDialogText',
|
||||||
|
translationParams: {
|
||||||
|
'suggested': suggestedVersion,
|
||||||
|
'selected': selectedVersion,
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
CustomMaterialButton(
|
||||||
|
label: I18nText('okButton'),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future showSelectFromStorageDialog(BuildContext context) async {
|
Future showSelectFromStorageDialog(BuildContext context) async {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => SimpleDialog(
|
builder: (innerContext) => SimpleDialog(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
contentPadding:
|
contentPadding:
|
||||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
|
const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
|
||||||
@@ -118,7 +206,7 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
Icon(
|
Icon(
|
||||||
Icons.block,
|
Icons.block,
|
||||||
size: 28,
|
size: 28,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(innerContext).colorScheme.primary,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
I18nText(
|
I18nText(
|
||||||
@@ -145,12 +233,10 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
CustomMaterialButton(
|
CustomMaterialButton(
|
||||||
onPressed: () => selectAppFromStorage(context).then(
|
onPressed: () async {
|
||||||
(_) {
|
Navigator.pop(innerContext);
|
||||||
Navigator.pop(context);
|
await selectAppFromStorage(context);
|
||||||
Navigator.pop(context);
|
},
|
||||||
},
|
|
||||||
),
|
|
||||||
label: Row(
|
label: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@@ -164,7 +250,7 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
CustomMaterialButton(
|
CustomMaterialButton(
|
||||||
isFilled: false,
|
isFilled: false,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(innerContext);
|
||||||
},
|
},
|
||||||
label: Row(
|
label: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -181,13 +267,14 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
|
|
||||||
Future<void> selectAppFromStorage(BuildContext context) async {
|
Future<void> selectAppFromStorage(BuildContext context) async {
|
||||||
try {
|
try {
|
||||||
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
final String? result = await FlutterFileDialog.pickFile(
|
||||||
type: FileType.custom,
|
params: const OpenFileDialogParams(
|
||||||
allowedExtensions: ['apk'],
|
fileExtensionsFilter: ['apk'],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (result != null && result.files.single.path != null) {
|
if (result != null) {
|
||||||
final File apkFile = File(result.files.single.path!);
|
final File apkFile = File(result);
|
||||||
final List<String> pathSplit = result.files.single.path!.split('/');
|
final List<String> pathSplit = result.split('/');
|
||||||
pathSplit.removeLast();
|
pathSplit.removeLast();
|
||||||
final Directory filePickerCacheDir = Directory(pathSplit.join('/'));
|
final Directory filePickerCacheDir = Directory(pathSplit.join('/'));
|
||||||
final Iterable<File> deletableFiles =
|
final Iterable<File> deletableFiles =
|
||||||
@@ -202,17 +289,8 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
apkFile.path,
|
apkFile.path,
|
||||||
true,
|
true,
|
||||||
) as ApplicationWithIcon?;
|
) as ApplicationWithIcon?;
|
||||||
if (application != null) {
|
if (application != null && context.mounted) {
|
||||||
locator<PatcherViewModel>().selectedApp = PatchedApplication(
|
await selectApp(context, application, true);
|
||||||
name: application.appName,
|
|
||||||
packageName: application.packageName,
|
|
||||||
version: application.versionName!,
|
|
||||||
apkFilePath: result.files.single.path!,
|
|
||||||
icon: application.icon,
|
|
||||||
patchDate: DateTime.now(),
|
|
||||||
isFromStorage: true,
|
|
||||||
);
|
|
||||||
locator<PatcherViewModel>().loadLastSelectedPatches();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
|||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class ContributorsView extends StatelessWidget {
|
class ContributorsView extends StatelessWidget {
|
||||||
const ContributorsView({Key? key}) : super(key: key);
|
const ContributorsView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
|||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class HomeView extends StatelessWidget {
|
class HomeView extends StatelessWidget {
|
||||||
const HomeView({Key? key}) : super(key: key);
|
const HomeView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:cross_connectivity/cross_connectivity.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -64,8 +64,9 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
flutterLocalNotificationsPlugin
|
flutterLocalNotificationsPlugin
|
||||||
.resolvePlatformSpecificImplementation<
|
.resolvePlatformSpecificImplementation<
|
||||||
AndroidFlutterLocalNotificationsPlugin>()
|
AndroidFlutterLocalNotificationsPlugin>()
|
||||||
?.requestPermission();
|
?.requestNotificationsPermission();
|
||||||
final bool isConnected = await Connectivity().checkConnection();
|
final bool isConnected = await Connectivity().checkConnectivity() !=
|
||||||
|
ConnectivityResult.none;
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
_toast.showBottom('homeView.noConnection');
|
_toast.showBottom('homeView.noConnection');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
|||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class InstallerView extends StatelessWidget {
|
class InstallerView extends StatelessWidget {
|
||||||
const InstallerView({Key? key}) : super(key: key);
|
const InstallerView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -18,7 +18,7 @@ class InstallerView extends StatelessWidget {
|
|||||||
builder: (context, model, child) => WillPopScope(
|
builder: (context, model, child) => WillPopScope(
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
bottom: false,
|
bottom: model.isPatching,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
floatingActionButton: Visibility(
|
floatingActionButton: Visibility(
|
||||||
visible: !model.isPatching && !model.hasErrors,
|
visible: !model.isPatching && !model.hasErrors,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|||||||
import 'package:revanced_manager/utils/about_info.dart';
|
import 'package:revanced_manager/utils/about_info.dart';
|
||||||
import 'package:screenshot_callback/screenshot_callback.dart';
|
import 'package:screenshot_callback/screenshot_callback.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:wakelock/wakelock.dart';
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
|
|
||||||
class InstallerViewModel extends BaseViewModel {
|
class InstallerViewModel extends BaseViewModel {
|
||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
@@ -74,7 +74,7 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
screenshotDetected(context);
|
screenshotDetected(context);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await Wakelock.enable();
|
await WakelockPlus.enable();
|
||||||
await handlePlatformChannelMethods();
|
await handlePlatformChannelMethods();
|
||||||
await runPatcher();
|
await runPatcher();
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,6 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> runPatcher() async {
|
Future<void> runPatcher() async {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await _patcherAPI.runPatcher(
|
await _patcherAPI.runPatcher(
|
||||||
_app.packageName,
|
_app.packageName,
|
||||||
@@ -159,7 +158,7 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
|
|
||||||
// Necessary to reset the state of patches so that they
|
// Necessary to reset the state of patches so that they
|
||||||
// can be reloaded again.
|
// can be reloaded again.
|
||||||
_managerAPI.patches.clear();
|
_managerAPI.patches.clear();
|
||||||
await _patcherAPI.loadPatches();
|
await _patcherAPI.loadPatches();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -172,7 +171,7 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
} // ignore
|
} // ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Wakelock.disable();
|
await WakelockPlus.disable();
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(e);
|
print(e);
|
||||||
@@ -180,33 +179,101 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _trimLogs(List<String> logLines, String keyword, String? newString) {
|
||||||
|
final lineCount = logLines.where((line) => line.endsWith(keyword)).length;
|
||||||
|
final index = logLines.indexWhere((line) => line.endsWith(keyword));
|
||||||
|
if (newString != null && lineCount > 0) {
|
||||||
|
logLines.insert(
|
||||||
|
index, newString.replaceAll('{lineCount}', lineCount.toString()));
|
||||||
|
}
|
||||||
|
logLines.removeWhere((lines) => lines.endsWith(keyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic _getPatchOptionValue(String patchName, Option option) {
|
||||||
|
final Option? savedOption =
|
||||||
|
_managerAPI.getPatchOption(_app.packageName, patchName, option.key);
|
||||||
|
if (savedOption != null) {
|
||||||
|
return savedOption.value;
|
||||||
|
} else {
|
||||||
|
return option.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatPatches(List<Patch> patches) {
|
||||||
|
if (patches.isEmpty) {
|
||||||
|
return 'None';
|
||||||
|
}
|
||||||
|
return patches
|
||||||
|
.map((p) =>
|
||||||
|
p.name +
|
||||||
|
(p.options.isEmpty
|
||||||
|
? ''
|
||||||
|
: ' [${p.options.map((o) => '${o.title}: ${_getPatchOptionValue(p.name, o)}').join(", ")}]'))
|
||||||
|
.toList()
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getSuggestedVersion(String packageName) {
|
||||||
|
String suggestedVersion = _patcherAPI.getSuggestedVersion(_app.packageName);
|
||||||
|
if (suggestedVersion.isEmpty) {
|
||||||
|
suggestedVersion = 'Any';
|
||||||
|
} else {
|
||||||
|
suggestedVersion = 'v$suggestedVersion';
|
||||||
|
}
|
||||||
|
return suggestedVersion;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> copyLogs() async {
|
Future<void> copyLogs() async {
|
||||||
final info = await AboutInfo.getInfo();
|
final info = await AboutInfo.getInfo();
|
||||||
|
|
||||||
|
// Trim out extra lines
|
||||||
|
final logsTrimmed = logs.split('\n');
|
||||||
|
_trimLogs(logsTrimmed, 'succeeded', 'Applied {lineCount} patches');
|
||||||
|
_trimLogs(logsTrimmed, '.dex', 'Compiled {lineCount} dex files');
|
||||||
|
|
||||||
|
// Get patches added / removed
|
||||||
|
final defaultPatches = _patcherAPI
|
||||||
|
.getFilteredPatches(_app.packageName)
|
||||||
|
.where((p) => !p.excluded)
|
||||||
|
.toList();
|
||||||
|
final patchesAdded =
|
||||||
|
_patches.where((p) => !defaultPatches.contains(p)).toList();
|
||||||
|
final patchesRemoved =
|
||||||
|
defaultPatches.where((p) => !_patches.contains(p)).toList();
|
||||||
|
|
||||||
|
// Options changed
|
||||||
|
final patchesChanged = defaultPatches
|
||||||
|
.where((p) =>
|
||||||
|
_patches.contains(p) &&
|
||||||
|
p.options.any((o) => _getPatchOptionValue(p.name, o) != o.value))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Add Info
|
||||||
final formattedLogs = [
|
final formattedLogs = [
|
||||||
'```',
|
'- Device Info',
|
||||||
'~ Device Info',
|
|
||||||
'ReVanced Manager: ${info['version']}',
|
'ReVanced Manager: ${info['version']}',
|
||||||
'Build: ${info['flavor']}',
|
'Build: ${info['flavor']}',
|
||||||
'Model: ${info['model']}',
|
'Model: ${info['model']}',
|
||||||
'Android version: ${info['androidVersion']}',
|
'Android version: ${info['androidVersion']}',
|
||||||
'Supported architectures: ${info['supportedArch'].join(", ")}',
|
'Supported architectures: ${info['supportedArch'].join(", ")}',
|
||||||
|
'Root permissions: ${isRooted ? 'Yes' : 'No'}', //
|
||||||
|
|
||||||
'\n~ Patch Info',
|
'\n- Patch Info',
|
||||||
'App: ${_app.packageName} v${_app.version}',
|
'App: ${_app.packageName} v${_app.version} (Suggested: ${_getSuggestedVersion(_app.packageName)})',
|
||||||
'Patches version: ${_managerAPI.patchesVersion}',
|
'Patches version: ${_managerAPI.patchesVersion}',
|
||||||
'Patches: ${_patches.map((p) => p.name).toList().join(", ")}',
|
'Patches added: ${_formatPatches(patchesAdded)}',
|
||||||
|
'Patches removed: ${_formatPatches(patchesRemoved)}',
|
||||||
|
'Options changed: ${_formatPatches(patchesChanged)}', //
|
||||||
|
|
||||||
'\n~ Settings',
|
'\n- Settings',
|
||||||
'Enabled changing patches: ${_managerAPI.isPatchesChangeEnabled()}',
|
'Allow changing patch selection: ${_managerAPI.isPatchesChangeEnabled()}',
|
||||||
'Enabled universal patches: ${_managerAPI.areUniversalPatchesEnabled()}',
|
'Version compatibility check: ${_managerAPI.isVersionCompatibilityCheckEnabled()}',
|
||||||
'Enabled experimental patches: ${_managerAPI.areExperimentalPatchesEnabled()}',
|
'Show universal patches: ${_managerAPI.areUniversalPatchesEnabled()}',
|
||||||
'Patches source: ${_managerAPI.getPatchesRepo()}',
|
'Patches source: ${_managerAPI.getPatchesRepo()}',
|
||||||
'Integration source: ${_managerAPI.getIntegrationsRepo()}',
|
'Integration source: ${_managerAPI.getIntegrationsRepo()}', //
|
||||||
|
|
||||||
'\n~ Logs',
|
'\n- Logs',
|
||||||
logs,
|
logsTrimmed.join('\n'),
|
||||||
'```',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
Clipboard.setData(ClipboardData(text: formattedLogs.join('\n')));
|
Clipboard.setData(ClipboardData(text: formattedLogs.join('\n')));
|
||||||
@@ -337,6 +404,7 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
isCanceled = true;
|
isCanceled = true;
|
||||||
update(0.5, 'Canceling...', 'Canceling patching process');
|
update(0.5, 'Canceling...', 'Canceling patching process');
|
||||||
await _patcherAPI.stopPatcher();
|
await _patcherAPI.stopPatcher();
|
||||||
|
await WakelockPlus.disable();
|
||||||
update(-100.0, 'Canceled...', 'Press back to exit');
|
update(-100.0, 'Canceled...', 'Press back to exit');
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
|
|||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class NavigationView extends StatelessWidget {
|
class NavigationView extends StatelessWidget {
|
||||||
const NavigationView({Key? key}) : super(key: key);
|
const NavigationView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ class NavigationViewModel extends IndexTrackingViewModel {
|
|||||||
Future<void> initialize(BuildContext context) async {
|
Future<void> initialize(BuildContext context) async {
|
||||||
locator<Toast>().initialize(context);
|
locator<Toast>().initialize(context);
|
||||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
await requestManageExternalStorage();
|
|
||||||
|
|
||||||
if (prefs.getBool('permissionsRequested') == null) {
|
if (prefs.getBool('permissionsRequested') == null) {
|
||||||
await Permission.storage.request();
|
await Permission.storage.request();
|
||||||
@@ -60,17 +59,6 @@ class NavigationViewModel extends IndexTrackingViewModel {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> requestManageExternalStorage() async {
|
|
||||||
final manageExternalStorageStatus =
|
|
||||||
await Permission.manageExternalStorage.status;
|
|
||||||
if (manageExternalStorageStatus.isDenied) {
|
|
||||||
await Permission.manageExternalStorage.request();
|
|
||||||
}
|
|
||||||
if (manageExternalStorageStatus.isPermanentlyDenied) {
|
|
||||||
await openAppSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget getViewForIndex(int index) {
|
Widget getViewForIndex(int index) {
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 0:
|
case 0:
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ class PatchOptionsView extends StatelessWidget {
|
|||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.history,
|
Icons.history,
|
||||||
),
|
),
|
||||||
|
tooltip: FlutterI18n.translate(
|
||||||
|
context,
|
||||||
|
'patchOptionsView.resetOptionsTooltip',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -57,8 +61,8 @@ class PatchOptionsView extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
for (final Option option in model.visibleOptions)
|
for (final Option option in model.visibleOptions)
|
||||||
if (option.optionClassType == 'StringPatchOption' ||
|
if (option.valueType == 'String' ||
|
||||||
option.optionClassType == 'IntPatchOption')
|
option.valueType == 'Int')
|
||||||
IntAndStringPatchOption(
|
IntAndStringPatchOption(
|
||||||
patchOption: option,
|
patchOption: option,
|
||||||
removeOption: (option) {
|
removeOption: (option) {
|
||||||
@@ -68,7 +72,7 @@ class PatchOptionsView extends StatelessWidget {
|
|||||||
model.modifyOptions(value, option);
|
model.modifyOptions(value, option);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
else if (option.optionClassType == 'BooleanPatchOption')
|
else if (option.valueType == 'Boolean')
|
||||||
BooleanPatchOption(
|
BooleanPatchOption(
|
||||||
patchOption: option,
|
patchOption: option,
|
||||||
removeOption: (option) {
|
removeOption: (option) {
|
||||||
@@ -78,10 +82,10 @@ class PatchOptionsView extends StatelessWidget {
|
|||||||
model.modifyOptions(value, option);
|
model.modifyOptions(value, option);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
else if (option.optionClassType ==
|
else if (option.valueType ==
|
||||||
'StringListPatchOption' ||
|
'StringArray' ||
|
||||||
option.optionClassType == 'IntListPatchOption' ||
|
option.valueType == 'IntArray' ||
|
||||||
option.optionClassType == 'LongListPatchOption')
|
option.valueType == 'LongArray')
|
||||||
IntStringLongListPatchOption(
|
IntStringLongListPatchOption(
|
||||||
patchOption: option,
|
patchOption: option,
|
||||||
removeOption: (option) {
|
removeOption: (option) {
|
||||||
|
|||||||
@@ -38,11 +38,11 @@ class PatchOptionsViewModel extends BaseViewModel {
|
|||||||
option.required &&
|
option.required &&
|
||||||
!savedOptions.any((sOption) => sOption.key == option.key),
|
!savedOptions.any((sOption) => sOption.key == option.key),
|
||||||
)
|
)
|
||||||
.toList(),
|
,
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
visibleOptions = [
|
visibleOptions = [
|
||||||
...options.where((option) => option.required).toList(),
|
...options.where((option) => option.required),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,10 @@ class PatchOptionsViewModel extends BaseViewModel {
|
|||||||
for (final Option option in options) {
|
for (final Option option in options) {
|
||||||
if (!visibleOptions.any((vOption) => vOption.key == option.key)) {
|
if (!visibleOptions.any((vOption) => vOption.key == option.key)) {
|
||||||
_managerAPI.clearPatchOption(
|
_managerAPI.clearPatchOption(
|
||||||
selectedApp, _managerAPI.selectedPatch!.name, option.key);
|
selectedApp,
|
||||||
|
_managerAPI.selectedPatch!.name,
|
||||||
|
option.key,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (final Option option in visibleOptions) {
|
for (final Option option in visibleOptions) {
|
||||||
@@ -70,7 +73,10 @@ class PatchOptionsViewModel extends BaseViewModel {
|
|||||||
requiredNullOptions.add(option);
|
requiredNullOptions.add(option);
|
||||||
} else {
|
} else {
|
||||||
_managerAPI.setPatchOption(
|
_managerAPI.setPatchOption(
|
||||||
option, _managerAPI.selectedPatch!.name, selectedApp);
|
option,
|
||||||
|
_managerAPI.selectedPatch!.name,
|
||||||
|
selectedApp,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (requiredNullOptions.isNotEmpty) {
|
if (requiredNullOptions.isNotEmpty) {
|
||||||
@@ -89,7 +95,8 @@ class PatchOptionsViewModel extends BaseViewModel {
|
|||||||
final Option modifiedOption = Option(
|
final Option modifiedOption = Option(
|
||||||
title: option.title,
|
title: option.title,
|
||||||
description: option.description,
|
description: option.description,
|
||||||
optionClassType: option.optionClassType,
|
values: option.values,
|
||||||
|
valueType: option.valueType,
|
||||||
value: value,
|
value: value,
|
||||||
required: option.required,
|
required: option.required,
|
||||||
key: option.key,
|
key: option.key,
|
||||||
@@ -107,7 +114,8 @@ class PatchOptionsViewModel extends BaseViewModel {
|
|||||||
final Option defaultOption = Option(
|
final Option defaultOption = Option(
|
||||||
title: option.title,
|
title: option.title,
|
||||||
description: option.description,
|
description: option.description,
|
||||||
optionClassType: option.optionClassType,
|
values: option.values,
|
||||||
|
valueType: option.valueType,
|
||||||
value: option.value is List ? option.value.toList() : option.value,
|
value: option.value is List ? option.value.toList() : option.value,
|
||||||
required: option.required,
|
required: option.required,
|
||||||
key: option.key,
|
key: option.key,
|
||||||
@@ -147,7 +155,7 @@ class PatchOptionsViewModel extends BaseViewModel {
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
CustomMaterialButton(
|
CustomMaterialButton(
|
||||||
label: I18nText('okButton'),
|
label: I18nText('cancelButton'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
@@ -165,30 +173,36 @@ class PatchOptionsViewModel extends BaseViewModel {
|
|||||||
.map((e) {
|
.map((e) {
|
||||||
return CustomCard(
|
return CustomCard(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
addOption(e);
|
addOption(e);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Column(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Expanded(
|
||||||
e.title,
|
child: Column(
|
||||||
style: const TextStyle(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
fontSize: 16,
|
children: [
|
||||||
|
Text(
|
||||||
|
e.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
e.description,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
e.description,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -229,7 +243,10 @@ Future<void> showRequiredOptionNullDialog(
|
|||||||
locator<PatcherViewModel>().notifyListeners();
|
locator<PatcherViewModel>().notifyListeners();
|
||||||
for (final option in options) {
|
for (final option in options) {
|
||||||
managerAPI.clearPatchOption(
|
managerAPI.clearPatchOption(
|
||||||
selectedApp, managerAPI.selectedPatch!.name, option.key);
|
selectedApp,
|
||||||
|
managerAPI.selectedPatch!.name,
|
||||||
|
option.key,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
..pop()
|
..pop()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
|||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class PatcherView extends StatelessWidget {
|
class PatcherView extends StatelessWidget {
|
||||||
const PatcherView({Key? key}) : super(key: key);
|
const PatcherView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
@@ -80,7 +84,8 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool checkRequiredPatchOption(BuildContext context) {
|
bool checkRequiredPatchOption(BuildContext context) {
|
||||||
if (getNullRequiredOptions(selectedPatches, selectedApp!.packageName).isNotEmpty) {
|
if (getNullRequiredOptions(selectedPatches, selectedApp!.packageName)
|
||||||
|
.isNotEmpty) {
|
||||||
showRequiredOptionDialog(context);
|
showRequiredOptionDialog(context);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -156,6 +161,29 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getCurrentVersionString(BuildContext context) {
|
||||||
|
return '${FlutterI18n.translate(
|
||||||
|
context,
|
||||||
|
'appSelectorCard.currentVersion',
|
||||||
|
)}: v${selectedApp!.version}';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> searchSuggestedVersionOnWeb() async {
|
||||||
|
final String suggestedVersion =
|
||||||
|
_patcherAPI.getSuggestedVersion(selectedApp!.packageName);
|
||||||
|
|
||||||
|
if (suggestedVersion.isNotEmpty) {
|
||||||
|
await openDefaultBrowser(
|
||||||
|
'${selectedApp!.packageName} apk version v$suggestedVersion');
|
||||||
|
} else {
|
||||||
|
await openDefaultBrowser('${selectedApp!.packageName} apk');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getSuggestedVersion() {
|
||||||
|
return _patcherAPI.getSuggestedVersion(selectedApp!.packageName);
|
||||||
|
}
|
||||||
|
|
||||||
String getSuggestedVersionString(BuildContext context) {
|
String getSuggestedVersionString(BuildContext context) {
|
||||||
String suggestedVersion =
|
String suggestedVersion =
|
||||||
_patcherAPI.getSuggestedVersion(selectedApp!.packageName);
|
_patcherAPI.getSuggestedVersion(selectedApp!.packageName);
|
||||||
@@ -168,14 +196,26 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
suggestedVersion = 'v$suggestedVersion';
|
suggestedVersion = 'v$suggestedVersion';
|
||||||
}
|
}
|
||||||
return '${FlutterI18n.translate(
|
return '${FlutterI18n.translate(
|
||||||
context,
|
|
||||||
'appSelectorCard.currentVersion',
|
|
||||||
)}: v${selectedApp!.version}\n${FlutterI18n.translate(
|
|
||||||
context,
|
context,
|
||||||
'appSelectorCard.suggestedVersion',
|
'appSelectorCard.suggestedVersion',
|
||||||
)}: $suggestedVersion';
|
)}: $suggestedVersion';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> openDefaultBrowser(String query) async {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
try {
|
||||||
|
const platform = MethodChannel('app.revanced.manager.flutter/browser');
|
||||||
|
await platform.invokeMethod('openBrowser', {'query': query});
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw 'Platform not supported';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> loadLastSelectedPatches() async {
|
Future<void> loadLastSelectedPatches() async {
|
||||||
this.selectedPatches.clear();
|
this.selectedPatches.clear();
|
||||||
removedPatches.clear();
|
removedPatches.clear();
|
||||||
@@ -190,7 +230,7 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
this.selectedPatches.clear();
|
this.selectedPatches.clear();
|
||||||
this.selectedPatches.addAll(patches.where((patch) => !patch.excluded));
|
this.selectedPatches.addAll(patches.where((patch) => !patch.excluded));
|
||||||
}
|
}
|
||||||
if (!_managerAPI.areExperimentalPatchesEnabled()) {
|
if (_managerAPI.isVersionCompatibilityCheckEnabled()) {
|
||||||
this.selectedPatches.removeWhere((patch) => !isPatchSupported(patch));
|
this.selectedPatches.removeWhere((patch) => !isPatchSupported(patch));
|
||||||
}
|
}
|
||||||
if (!_managerAPI.areUniversalPatchesEnabled()) {
|
if (!_managerAPI.areUniversalPatchesEnabled()) {
|
||||||
@@ -199,11 +239,12 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
.removeWhere((patch) => patch.compatiblePackages.isEmpty);
|
.removeWhere((patch) => patch.compatiblePackages.isEmpty);
|
||||||
}
|
}
|
||||||
final usedPatches = _managerAPI.getUsedPatches(selectedApp!.packageName);
|
final usedPatches = _managerAPI.getUsedPatches(selectedApp!.packageName);
|
||||||
for (final patch in usedPatches){
|
for (final patch in usedPatches) {
|
||||||
if (!patches.any((p) => p.name == patch.name)){
|
if (!patches.any((p) => p.name == patch.name)) {
|
||||||
removedPatches.add('• ${patch.name}');
|
removedPatches.add('• ${patch.name}');
|
||||||
for (final option in patch.options) {
|
for (final option in patch.options) {
|
||||||
_managerAPI.clearPatchOption(selectedApp!.packageName, patch.name, option.key);
|
_managerAPI.clearPatchOption(
|
||||||
|
selectedApp!.packageName, patch.name, option.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,11 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
|||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
||||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class PatchesSelectorView extends StatefulWidget {
|
class PatchesSelectorView extends StatefulWidget {
|
||||||
const PatchesSelectorView({Key? key}) : super(key: key);
|
const PatchesSelectorView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PatchesSelectorView> createState() => _PatchesSelectorViewState();
|
State<PatchesSelectorView> createState() => _PatchesSelectorViewState();
|
||||||
@@ -62,12 +59,10 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
floating: true,
|
floating: true,
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'patchesSelectorView.viewTitle',
|
'patchesSelectorView.viewTitle',
|
||||||
child: Text(
|
),
|
||||||
'',
|
titleTextStyle: TextStyle(
|
||||||
style: TextStyle(
|
fontSize: 22.0,
|
||||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
@@ -80,41 +75,34 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
FittedBox(
|
Container(
|
||||||
fit: BoxFit.scaleDown,
|
margin: const EdgeInsets.symmetric(vertical: 12),
|
||||||
child: Container(
|
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
margin: const EdgeInsets.only(top: 12, bottom: 12),
|
decoration: BoxDecoration(
|
||||||
padding:
|
color:
|
||||||
const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
|
Theme.of(context).colorScheme.tertiary.withOpacity(0.5),
|
||||||
decoration: BoxDecoration(
|
borderRadius: BorderRadius.circular(6),
|
||||||
color: Theme.of(context)
|
),
|
||||||
.colorScheme
|
alignment: Alignment.center,
|
||||||
.tertiary
|
child: Text(
|
||||||
.withOpacity(0.5),
|
model.patchesVersion!,
|
||||||
borderRadius: BorderRadius.circular(6),
|
style: TextStyle(
|
||||||
),
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
child: Text(
|
|
||||||
model.patchesVersion!,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
CustomPopupMenu(
|
PopupMenuButton(
|
||||||
onSelected: (value) =>
|
onSelected: (value) {
|
||||||
{model.onMenuSelection(value, context)},
|
model.onMenuSelection(value, context);
|
||||||
children: {
|
},
|
||||||
0: I18nText(
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
'patchesSelectorView.loadPatchesSelection',
|
PopupMenuItem(
|
||||||
child: const Text(
|
value: 0,
|
||||||
'',
|
child: I18nText(
|
||||||
style: TextStyle(
|
'patchesSelectorView.loadPatchesSelection',
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
@@ -192,185 +180,39 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (model.newPatchExists())
|
if (model.getQueriedPatches(_query).any((patch) => model.isPatchNew(patch)))
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
model.getPatchCategory(context, 'patchesSelectorView.newPatches'),
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 10.0,
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 10.0,
|
|
||||||
bottom: 10.0,
|
|
||||||
left: 5.0,
|
|
||||||
),
|
|
||||||
child: I18nText(
|
|
||||||
'patchesSelectorView.newPatches',
|
|
||||||
child: Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
...model.getQueriedPatches(_query).map((patch) {
|
...model.getQueriedPatches(_query).map((patch) {
|
||||||
if (model.isPatchNew(patch)) {
|
if (model.isPatchNew(patch)) {
|
||||||
return PatchItem(
|
return model.getPatchItem(context, patch);
|
||||||
name: patch.name,
|
|
||||||
simpleName: patch.getSimpleName(),
|
|
||||||
description: patch.description ?? '',
|
|
||||||
packageVersion:
|
|
||||||
model.getAppInfo().version,
|
|
||||||
supportedPackageVersions:
|
|
||||||
model.getSupportedVersions(patch),
|
|
||||||
isUnsupported: !isPatchSupported(patch),
|
|
||||||
isChangeEnabled:
|
|
||||||
_managerAPI.isPatchesChangeEnabled(),
|
|
||||||
hasUnsupportedPatchOption:
|
|
||||||
hasUnsupportedRequiredOption(
|
|
||||||
patch.options,
|
|
||||||
patch,
|
|
||||||
),
|
|
||||||
options: patch.options,
|
|
||||||
isSelected: model.isSelected(patch),
|
|
||||||
navigateToOptions: (options) =>
|
|
||||||
model.navigateToPatchOptions(
|
|
||||||
options,
|
|
||||||
patch,
|
|
||||||
),
|
|
||||||
onChanged: (value) => model.selectPatch(
|
|
||||||
patch,
|
|
||||||
value,
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Padding(
|
if (model.getQueriedPatches(_query).any((patch) => !model.isPatchNew(patch) && patch.compatiblePackages.isNotEmpty))
|
||||||
padding: const EdgeInsets.symmetric(
|
model.getPatchCategory(context, 'patchesSelectorView.patches'),
|
||||||
vertical: 10.0,
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 10.0,
|
|
||||||
bottom: 10.0,
|
|
||||||
left: 5.0,
|
|
||||||
),
|
|
||||||
child: I18nText(
|
|
||||||
'patchesSelectorView.patches',
|
|
||||||
child: Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
...model.getQueriedPatches(_query).map(
|
...model.getQueriedPatches(_query).map(
|
||||||
(patch) {
|
(patch) {
|
||||||
if (patch.compatiblePackages.isNotEmpty) {
|
if (patch.compatiblePackages.isNotEmpty && !model.isPatchNew(patch)) {
|
||||||
return PatchItem(
|
return model.getPatchItem(context, patch);
|
||||||
name: patch.name,
|
|
||||||
simpleName: patch.getSimpleName(),
|
|
||||||
description: patch.description ?? '',
|
|
||||||
packageVersion: model.getAppInfo().version,
|
|
||||||
supportedPackageVersions:
|
|
||||||
model.getSupportedVersions(patch),
|
|
||||||
isUnsupported: !isPatchSupported(patch),
|
|
||||||
isChangeEnabled:
|
|
||||||
_managerAPI.isPatchesChangeEnabled(),
|
|
||||||
hasUnsupportedPatchOption:
|
|
||||||
hasUnsupportedRequiredOption(
|
|
||||||
patch.options, patch),
|
|
||||||
options: patch.options,
|
|
||||||
isSelected: model.isSelected(patch),
|
|
||||||
navigateToOptions: (options) =>
|
|
||||||
model.navigateToPatchOptions(
|
|
||||||
options,
|
|
||||||
patch,
|
|
||||||
),
|
|
||||||
onChanged: (value) => model.selectPatch(
|
|
||||||
patch,
|
|
||||||
value,
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (_managerAPI.areUniversalPatchesEnabled())
|
if (model.getQueriedPatches(_query).any((patch) => patch.compatiblePackages.isEmpty))
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
model.getPatchCategory(context, 'patchesSelectorView.universalPatches'),
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 10.0,
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 10.0,
|
|
||||||
bottom: 10.0,
|
|
||||||
left: 5.0,
|
|
||||||
),
|
|
||||||
child: I18nText(
|
|
||||||
'patchesSelectorView.universalPatches',
|
|
||||||
child: Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
...model.getQueriedPatches(_query).map((patch) {
|
...model.getQueriedPatches(_query).map((patch) {
|
||||||
if (patch.compatiblePackages.isEmpty) {
|
if (patch.compatiblePackages.isEmpty && !model.isPatchNew(patch)) {
|
||||||
return PatchItem(
|
return model.getPatchItem(context, patch);
|
||||||
name: patch.name,
|
|
||||||
simpleName: patch.getSimpleName(),
|
|
||||||
description: patch.description ?? '',
|
|
||||||
packageVersion:
|
|
||||||
model.getAppInfo().version,
|
|
||||||
supportedPackageVersions:
|
|
||||||
model.getSupportedVersions(patch),
|
|
||||||
isUnsupported: !isPatchSupported(patch),
|
|
||||||
isChangeEnabled:
|
|
||||||
_managerAPI.isPatchesChangeEnabled(),
|
|
||||||
hasUnsupportedPatchOption:
|
|
||||||
hasUnsupportedRequiredOption(
|
|
||||||
patch.options,
|
|
||||||
patch,
|
|
||||||
),
|
|
||||||
options: patch.options,
|
|
||||||
isSelected: model.isSelected(patch),
|
|
||||||
navigateToOptions: (options) =>
|
|
||||||
model.navigateToPatchOptions(
|
|
||||||
options,
|
|
||||||
patch,
|
|
||||||
),
|
|
||||||
onChanged: (value) => model.selectPatch(
|
|
||||||
patch,
|
|
||||||
value,
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:revanced_manager/services/manager_api.dart';
|
|||||||
import 'package:revanced_manager/services/patcher_api.dart';
|
import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
@@ -24,6 +25,9 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
locator<PatcherViewModel>().selectedPatches;
|
locator<PatcherViewModel>().selectedPatches;
|
||||||
PatchedApplication? selectedApp = locator<PatcherViewModel>().selectedApp;
|
PatchedApplication? selectedApp = locator<PatcherViewModel>().selectedApp;
|
||||||
String? patchesVersion = '';
|
String? patchesVersion = '';
|
||||||
|
|
||||||
|
Set<String> savedPatchNames = {};
|
||||||
|
|
||||||
bool isDefaultPatchesRepo() {
|
bool isDefaultPatchesRepo() {
|
||||||
return _managerAPI.getPatchesRepo() == 'revanced/revanced-patches';
|
return _managerAPI.getPatchesRepo() == 'revanced/revanced-patches';
|
||||||
}
|
}
|
||||||
@@ -47,13 +51,17 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
});
|
});
|
||||||
currentSelection.clear();
|
currentSelection.clear();
|
||||||
currentSelection.addAll(selectedPatches);
|
currentSelection.addAll(selectedPatches);
|
||||||
|
|
||||||
|
savedPatchNames = _managerAPI.getSavedPatches(selectedApp!.packageName).map((p) => p.name).toSet();
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSelected(Patch patch) {
|
bool isSelected(Patch patch) {
|
||||||
return selectedPatches.any(
|
return selectedPatches.any(
|
||||||
(element) => element.name == patch.name,
|
(element) => element.name == patch.name,
|
||||||
);
|
) ||
|
||||||
|
(isPatchNew(patch) && !patch.excluded);
|
||||||
}
|
}
|
||||||
|
|
||||||
void navigateToPatchOptions(List<Option> setOptions, Patch patch) {
|
void navigateToPatchOptions(List<Option> setOptions, Patch patch) {
|
||||||
@@ -169,7 +177,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
.where(
|
.where(
|
||||||
(element) =>
|
(element) =>
|
||||||
!element.excluded &&
|
!element.excluded &&
|
||||||
(_managerAPI.areExperimentalPatchesEnabled() ||
|
(!_managerAPI.isVersionCompatibilityCheckEnabled() ||
|
||||||
isPatchSupported(element)),
|
isPatchSupported(element)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -209,7 +217,10 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
query.isEmpty ||
|
query.isEmpty ||
|
||||||
query.length < 2 ||
|
query.length < 2 ||
|
||||||
patch.name.toLowerCase().contains(query.toLowerCase()) ||
|
patch.name.toLowerCase().contains(query.toLowerCase()) ||
|
||||||
patch.getSimpleName().toLowerCase().contains(query.toLowerCase()),
|
patch.name
|
||||||
|
.replaceAll(RegExp(r'[^\w\s]+'), '')
|
||||||
|
.toLowerCase()
|
||||||
|
.contains(query.toLowerCase()),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
if (_managerAPI.areUniversalPatchesEnabled()) {
|
if (_managerAPI.areUniversalPatchesEnabled()) {
|
||||||
@@ -221,27 +232,69 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget getPatchItem(BuildContext context, Patch patch) {
|
||||||
|
return PatchItem(
|
||||||
|
name: patch.name,
|
||||||
|
simpleName: patch.getSimpleName(),
|
||||||
|
description: patch.description ?? '',
|
||||||
|
packageVersion: getAppInfo().version,
|
||||||
|
supportedPackageVersions: getSupportedVersions(patch),
|
||||||
|
isUnsupported: !isPatchSupported(patch),
|
||||||
|
isChangeEnabled: _managerAPI.isPatchesChangeEnabled(),
|
||||||
|
hasUnsupportedPatchOption: hasUnsupportedRequiredOption(
|
||||||
|
patch.options,
|
||||||
|
patch,
|
||||||
|
),
|
||||||
|
options: patch.options,
|
||||||
|
isSelected: isSelected(patch),
|
||||||
|
navigateToOptions: (options) => navigateToPatchOptions(
|
||||||
|
options,
|
||||||
|
patch,
|
||||||
|
),
|
||||||
|
onChanged: (value) => selectPatch(
|
||||||
|
patch,
|
||||||
|
value,
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getPatchCategory(BuildContext context, String category) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 10.0,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 10.0,
|
||||||
|
bottom: 10.0,
|
||||||
|
left: 5.0,
|
||||||
|
),
|
||||||
|
child: I18nText(
|
||||||
|
category,
|
||||||
|
child: Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
PatchedApplication getAppInfo() {
|
PatchedApplication getAppInfo() {
|
||||||
return locator<PatcherViewModel>().selectedApp!;
|
return locator<PatcherViewModel>().selectedApp!;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isPatchNew(Patch patch) {
|
bool isPatchNew(Patch patch) {
|
||||||
final List<Patch> savedPatches =
|
if (savedPatchNames.isEmpty) {
|
||||||
_managerAPI.getSavedPatches(selectedApp!.packageName);
|
|
||||||
if (savedPatches.isEmpty) {
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return !savedPatches
|
return !savedPatchNames.contains(patch.name);
|
||||||
.any((p) => p.getSimpleName() == patch.getSimpleName());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool newPatchExists() {
|
|
||||||
return patches.any(
|
|
||||||
(patch) => isPatchNew(patch),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> getSupportedVersions(Patch patch) {
|
List<String> getSupportedVersions(Patch patch) {
|
||||||
final PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
|
final PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
|
||||||
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
||||||
@@ -281,7 +334,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
this.selectedPatches.addAll(
|
this.selectedPatches.addAll(
|
||||||
patches.where((patch) => selectedPatches.contains(patch.name)),
|
patches.where((patch) => selectedPatches.contains(patch.name)),
|
||||||
);
|
);
|
||||||
if (!_managerAPI.areExperimentalPatchesEnabled()) {
|
if (_managerAPI.isVersionCompatibilityCheckEnabled()) {
|
||||||
this.selectedPatches.removeWhere((patch) => !isPatchSupported(patch));
|
this.selectedPatches.removeWhere((patch) => !isPatchSupported(patch));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ class SManageApiUrl extends BaseViewModel {
|
|||||||
apiUrl = 'https://$apiUrl';
|
apiUrl = 'https://$apiUrl';
|
||||||
}
|
}
|
||||||
_managerAPI.setApiUrl(apiUrl);
|
_managerAPI.setApiUrl(apiUrl);
|
||||||
|
_toast.showBottom('settingsView.restartAppForChanges');
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -6,46 +6,98 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
|
||||||
|
|
||||||
final _settingViewModel = SettingsViewModel();
|
class SUpdateThemeUI extends StatefulWidget {
|
||||||
|
const SUpdateThemeUI({super.key});
|
||||||
|
|
||||||
// ignore: constant_identifier_names
|
@override
|
||||||
const int ANDROID_12_SDK_VERSION = 31;
|
State<SUpdateThemeUI> createState() => _SUpdateThemeUIState();
|
||||||
|
}
|
||||||
|
|
||||||
class SUpdateTheme extends BaseViewModel {
|
class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
|
||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
final ManagerAPI managerAPI = locator<ManagerAPI>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SettingsSection(
|
||||||
|
title: 'settingsView.appearanceSectionTitle',
|
||||||
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
title: I18nText(
|
||||||
|
'settingsView.themeModeLabel',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: CustomMaterialButton(
|
||||||
|
label: getThemeModeName(),
|
||||||
|
onPressed: () => {showThemeDialog(context)},
|
||||||
|
),
|
||||||
|
onTap: () => {showThemeDialog(context)},
|
||||||
|
),
|
||||||
|
if (managerAPI.isDynamicThemeAvailable)
|
||||||
|
SwitchListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
title: I18nText(
|
||||||
|
'settingsView.dynamicThemeLabel',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: I18nText('settingsView.dynamicThemeHint'),
|
||||||
|
value: getDynamicThemeStatus(),
|
||||||
|
onChanged: (value) => {
|
||||||
|
setUseDynamicTheme(
|
||||||
|
context,
|
||||||
|
value,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bool getDynamicThemeStatus() {
|
bool getDynamicThemeStatus() {
|
||||||
return _managerAPI.getUseDynamicTheme();
|
return managerAPI.getUseDynamicTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setUseDynamicTheme(BuildContext context, bool value) async {
|
Future<void> setUseDynamicTheme(BuildContext context, bool value) async {
|
||||||
await _managerAPI.setUseDynamicTheme(value);
|
await managerAPI.setUseDynamicTheme(value);
|
||||||
final int currentTheme = (DynamicTheme.of(context)!.themeId ~/ 2) * 2;
|
final int currentTheme = (DynamicTheme.of(context)!.themeId ~/ 2) * 2;
|
||||||
await DynamicTheme.of(context)!.setTheme(currentTheme + (value ? 1 : 0));
|
await DynamicTheme.of(context)!.setTheme(currentTheme + (value ? 1 : 0));
|
||||||
notifyListeners();
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
int getThemeMode() {
|
int getThemeMode() {
|
||||||
return _managerAPI.getThemeMode();
|
return managerAPI.getThemeMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setThemeMode(BuildContext context, int value) async {
|
Future<void> setThemeMode(BuildContext context, int value) async {
|
||||||
await _managerAPI.setThemeMode(value);
|
await managerAPI.setThemeMode(value);
|
||||||
final bool isDynamicTheme = DynamicTheme.of(context)!.themeId.isEven;
|
final bool isDynamicTheme = DynamicTheme.of(context)!.themeId.isEven;
|
||||||
await DynamicTheme.of(context)!.setTheme(value * 2 + (isDynamicTheme ? 0 : 1));
|
await DynamicTheme.of(context)!
|
||||||
final bool isLight = value != 2 && (value == 1 || DynamicTheme.of(context)!.theme.brightness == Brightness.light);
|
.setTheme(value * 2 + (isDynamicTheme ? 0 : 1));
|
||||||
|
final bool isLight = value != 2 &&
|
||||||
|
(value == 1 ||
|
||||||
|
DynamicTheme.of(context)!.theme.brightness == Brightness.light);
|
||||||
SystemChrome.setSystemUIOverlayStyle(
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
SystemUiOverlayStyle(
|
SystemUiOverlayStyle(
|
||||||
systemNavigationBarIconBrightness:
|
systemNavigationBarIconBrightness:
|
||||||
isLight ? Brightness.dark : Brightness.light,
|
isLight ? Brightness.dark : Brightness.light,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
notifyListeners();
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
I18nText getThemeModeName() {
|
I18nText getThemeModeName() {
|
||||||
@@ -131,63 +183,3 @@ class SUpdateTheme extends BaseViewModel {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final sUpdateTheme = SUpdateTheme();
|
|
||||||
class SUpdateThemeUI extends StatelessWidget {
|
|
||||||
const SUpdateThemeUI({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SettingsSection(
|
|
||||||
title: 'settingsView.appearanceSectionTitle',
|
|
||||||
children: <Widget>[
|
|
||||||
ListTile(
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
title: I18nText(
|
|
||||||
'settingsView.themeModeLabel',
|
|
||||||
child: const Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
trailing: CustomMaterialButton(
|
|
||||||
label: sUpdateTheme.getThemeModeName(),
|
|
||||||
onPressed: () => { sUpdateTheme.showThemeDialog(context) },
|
|
||||||
),
|
|
||||||
onTap: () => { sUpdateTheme.showThemeDialog(context) },
|
|
||||||
),
|
|
||||||
FutureBuilder<int>(
|
|
||||||
future: _settingViewModel.getSdkVersion(),
|
|
||||||
builder: (context, snapshot) => Visibility(
|
|
||||||
visible:
|
|
||||||
snapshot.hasData && snapshot.data! >= ANDROID_12_SDK_VERSION,
|
|
||||||
child: SwitchListTile(
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
title: I18nText(
|
|
||||||
'settingsView.dynamicThemeLabel',
|
|
||||||
child: const Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: I18nText('settingsView.dynamicThemeHint'),
|
|
||||||
value: _settingViewModel.sUpdateTheme.getDynamicThemeStatus(),
|
|
||||||
onChanged: (value) => {
|
|
||||||
_settingViewModel.sUpdateTheme.setUseDynamicTheme(
|
|
||||||
context,
|
|
||||||
value,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import 'package:google_fonts/google_fonts.dart';
|
|||||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_advanced_section.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_advanced_section.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_debug_section.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_export_section.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_export_section.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_info_section.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_team_section.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_team_section.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class SettingsView extends StatelessWidget {
|
class SettingsView extends StatelessWidget {
|
||||||
const SettingsView({Key? key}) : super(key: key);
|
const SettingsView({super.key});
|
||||||
|
|
||||||
static const _settingsDivider =
|
static const _settingsDivider =
|
||||||
Divider(thickness: 1.0, indent: 20.0, endIndent: 20.0);
|
Divider(thickness: 1.0, indent: 20.0, endIndent: 20.0);
|
||||||
@@ -40,16 +40,24 @@ class SettingsView extends StatelessWidget {
|
|||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildListDelegate.fixed(
|
delegate: SliverChildListDelegate.fixed(
|
||||||
<Widget>[
|
<Widget>[
|
||||||
SUpdateThemeUI(),
|
ListView(
|
||||||
// SUpdateLanguageUI(),
|
padding: EdgeInsets.zero,
|
||||||
// _settingsDivider,
|
shrinkWrap: true,
|
||||||
STeamSection(),
|
physics: NeverScrollableScrollPhysics(),
|
||||||
_settingsDivider,
|
children: const [
|
||||||
SAdvancedSection(),
|
SUpdateThemeUI(),
|
||||||
_settingsDivider,
|
// _settingsDivider,
|
||||||
SExportSection(),
|
// SUpdateLanguageUI(),
|
||||||
_settingsDivider,
|
_settingsDivider,
|
||||||
SInfoSection(),
|
SAdvancedSection(),
|
||||||
|
_settingsDivider,
|
||||||
|
SExportSection(),
|
||||||
|
_settingsDivider,
|
||||||
|
STeamSection(),
|
||||||
|
_settingsDivider,
|
||||||
|
SDebugSection(),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:cr_file_saver/file_saver.dart';
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:logcat/logcat.dart';
|
import 'package:logcat/logcat.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@@ -14,9 +12,8 @@ import 'package:revanced_manager/services/toast.dart';
|
|||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_language.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_language.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
import 'package:share_extend/share_extend.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
|
||||||
@@ -29,7 +26,6 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
final Toast _toast = locator<Toast>();
|
final Toast _toast = locator<Toast>();
|
||||||
|
|
||||||
final SUpdateLanguage sUpdateLanguage = SUpdateLanguage();
|
final SUpdateLanguage sUpdateLanguage = SUpdateLanguage();
|
||||||
final SUpdateTheme sUpdateTheme = SUpdateTheme();
|
|
||||||
|
|
||||||
void navigateToContributors() {
|
void navigateToContributors() {
|
||||||
_navigationService.navigateTo(Routes.contributorsView);
|
_navigationService.navigateTo(Routes.contributorsView);
|
||||||
@@ -135,15 +131,66 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool areExperimentalPatchesEnabled() {
|
bool isVersionCompatibilityCheckEnabled() {
|
||||||
return _managerAPI.areExperimentalPatchesEnabled();
|
return _managerAPI.isVersionCompatibilityCheckEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
void useExperimentalPatches(bool value) {
|
void useVersionCompatibilityCheck(bool value) {
|
||||||
_managerAPI.enableExperimentalPatchesStatus(value);
|
_managerAPI.enableVersionCompatibilityCheckStatus(value);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isRequireSuggestedAppVersionEnabled() {
|
||||||
|
return _managerAPI.isRequireSuggestedAppVersionEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void>? showRequireSuggestedAppVersionDialog(
|
||||||
|
BuildContext context, bool value,) {
|
||||||
|
if (!value) {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
title: I18nText('warning'),
|
||||||
|
content: I18nText(
|
||||||
|
'settingsView.requireSuggestedAppVersionDialogText',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
CustomMaterialButton(
|
||||||
|
isFilled: false,
|
||||||
|
label: I18nText('yesButton'),
|
||||||
|
onPressed: () {
|
||||||
|
_managerAPI.enableRequireSuggestedAppVersionStatus(false);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CustomMaterialButton(
|
||||||
|
label: I18nText('noButton'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_managerAPI.enableRequireSuggestedAppVersionStatus(true);
|
||||||
|
|
||||||
|
if (!_managerAPI.suggestedAppVersionSelected) {
|
||||||
|
_patcherViewModel.selectedApp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void deleteKeystore() {
|
void deleteKeystore() {
|
||||||
_managerAPI.deleteKeystore();
|
_managerAPI.deleteKeystore();
|
||||||
_toast.showBottom('settingsView.regeneratedKeystore');
|
_toast.showBottom('settingsView.regeneratedKeystore');
|
||||||
@@ -162,10 +209,10 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
if (outFile.existsSync()) {
|
if (outFile.existsSync()) {
|
||||||
final String dateTime =
|
final String dateTime =
|
||||||
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
|
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
|
||||||
await CRFileSaver.saveFileWithDialog(
|
await FlutterFileDialog.saveFile(
|
||||||
SaveFileDialogParams(
|
params: SaveFileDialogParams(
|
||||||
sourceFilePath: outFile.path,
|
sourceFilePath: outFile.path,
|
||||||
destinationFileName: 'selected_patches_$dateTime.json',
|
fileName: 'selected_patches_$dateTime.json',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_toast.showBottom('settingsView.exportedPatches');
|
_toast.showBottom('settingsView.exportedPatches');
|
||||||
@@ -182,12 +229,13 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
Future<void> importPatches(BuildContext context) async {
|
Future<void> importPatches(BuildContext context) async {
|
||||||
if (isPatchesChangeEnabled()) {
|
if (isPatchesChangeEnabled()) {
|
||||||
try {
|
try {
|
||||||
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
final String? result = await FlutterFileDialog.pickFile(
|
||||||
type: FileType.custom,
|
params: const OpenFileDialogParams(
|
||||||
allowedExtensions: ['json'],
|
fileExtensionsFilter: ['json'],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (result != null && result.files.single.path != null) {
|
if (result != null) {
|
||||||
final File inFile = File(result.files.single.path!);
|
final File inFile = File(result);
|
||||||
inFile.copySync(_managerAPI.storedPatchesFile);
|
inFile.copySync(_managerAPI.storedPatchesFile);
|
||||||
inFile.delete();
|
inFile.delete();
|
||||||
if (_patcherViewModel.selectedApp != null) {
|
if (_patcherViewModel.selectedApp != null) {
|
||||||
@@ -212,10 +260,10 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
if (outFile.existsSync()) {
|
if (outFile.existsSync()) {
|
||||||
final String dateTime =
|
final String dateTime =
|
||||||
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
|
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
|
||||||
await CRFileSaver.saveFileWithDialog(
|
await FlutterFileDialog.saveFile(
|
||||||
SaveFileDialogParams(
|
params: SaveFileDialogParams(
|
||||||
sourceFilePath: outFile.path,
|
sourceFilePath: outFile.path,
|
||||||
destinationFileName: 'keystore_$dateTime.keystore',
|
fileName: 'keystore_$dateTime.keystore',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_toast.showBottom('settingsView.exportedKeystore');
|
_toast.showBottom('settingsView.exportedKeystore');
|
||||||
@@ -231,9 +279,9 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
|
|
||||||
Future<void> importKeystore() async {
|
Future<void> importKeystore() async {
|
||||||
try {
|
try {
|
||||||
final FilePickerResult? result = await FilePicker.platform.pickFiles();
|
final String? result = await FlutterFileDialog.pickFile();
|
||||||
if (result != null && result.files.single.path != null) {
|
if (result != null) {
|
||||||
final File inFile = File(result.files.single.path!);
|
final File inFile = File(result);
|
||||||
inFile.copySync(_managerAPI.keystoreFile);
|
inFile.copySync(_managerAPI.keystoreFile);
|
||||||
|
|
||||||
_toast.showBottom('settingsView.importedKeystore');
|
_toast.showBottom('settingsView.importedKeystore');
|
||||||
@@ -256,11 +304,6 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
_toast.showBottom('settingsView.resetStoredPatches');
|
_toast.showBottom('settingsView.resetStoredPatches');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> getSdkVersion() async {
|
|
||||||
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
|
|
||||||
return info.version.sdkInt;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteLogs() async {
|
Future<void> deleteLogs() async {
|
||||||
final Directory appCacheDir = await getTemporaryDirectory();
|
final Directory appCacheDir = await getTemporaryDirectory();
|
||||||
final Directory logsDir = Directory('${appCacheDir.path}/logs');
|
final Directory logsDir = Directory('${appCacheDir.path}/logs');
|
||||||
@@ -284,6 +327,6 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
File('${logDir.path}/revanced-manager_logcat_$dateTime.log');
|
File('${logDir.path}/revanced-manager_logcat_$dateTime.log');
|
||||||
final String logs = await Logcat.execute();
|
final String logs = await Logcat.execute();
|
||||||
logcat.writeAsStringSync(logs);
|
logcat.writeAsStringSync(logs);
|
||||||
ShareExtend.share(logcat.path, 'file');
|
await Share.shareXFiles([XFile(logcat.path)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import 'package:stacked/stacked.dart';
|
|||||||
|
|
||||||
class AppInfoView extends StatelessWidget {
|
class AppInfoView extends StatelessWidget {
|
||||||
const AppInfoView({
|
const AppInfoView({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.app,
|
required this.app,
|
||||||
}) : super(key: key);
|
});
|
||||||
final PatchedApplication app;
|
final PatchedApplication app;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
|||||||
import 'package:skeletons/skeletons.dart';
|
import 'package:skeletons/skeletons.dart';
|
||||||
|
|
||||||
class AppSkeletonLoader extends StatelessWidget {
|
class AppSkeletonLoader extends StatelessWidget {
|
||||||
const AppSkeletonLoader({Key? key}) : super(key: key);
|
const AppSkeletonLoader({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
|||||||
|
|
||||||
class InstalledAppItem extends StatefulWidget {
|
class InstalledAppItem extends StatefulWidget {
|
||||||
const InstalledAppItem({
|
const InstalledAppItem({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.pkgName,
|
required this.pkgName,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
@@ -13,7 +13,9 @@ class InstalledAppItem extends StatefulWidget {
|
|||||||
required this.suggestedVersion,
|
required this.suggestedVersion,
|
||||||
required this.installedVersion,
|
required this.installedVersion,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
}) : super(key: key);
|
this.onLinkTap,
|
||||||
|
});
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final String pkgName;
|
final String pkgName;
|
||||||
final Uint8List icon;
|
final Uint8List icon;
|
||||||
@@ -21,6 +23,7 @@ class InstalledAppItem extends StatefulWidget {
|
|||||||
final String suggestedVersion;
|
final String suggestedVersion;
|
||||||
final String installedVersion;
|
final String installedVersion;
|
||||||
final Function()? onTap;
|
final Function()? onTap;
|
||||||
|
final Function()? onLinkTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<InstalledAppItem> createState() => _InstalledAppItemState();
|
State<InstalledAppItem> createState() => _InstalledAppItemState();
|
||||||
@@ -71,17 +74,52 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Wrap(
|
Wrap(
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
I18nText(
|
Material(
|
||||||
'suggested',
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
translationParams: {
|
borderRadius:
|
||||||
'version': widget.suggestedVersion.isEmpty
|
const BorderRadius.all(Radius.circular(8)),
|
||||||
? FlutterI18n.translate(
|
child: InkWell(
|
||||||
context,
|
onTap: widget.onLinkTap,
|
||||||
'appSelectorCard.allVersions',
|
borderRadius:
|
||||||
)
|
const BorderRadius.all(Radius.circular(8)),
|
||||||
: 'v${widget.suggestedVersion}',
|
child: Container(
|
||||||
},
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
I18nText(
|
||||||
|
'suggested',
|
||||||
|
translationParams: {
|
||||||
|
'version': widget.suggestedVersion.isEmpty
|
||||||
|
? FlutterI18n.translate(
|
||||||
|
context,
|
||||||
|
'appSelectorCard.allVersions',
|
||||||
|
)
|
||||||
|
: 'v${widget.suggestedVersion}',
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Icon(
|
||||||
|
Icons.search,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -4,16 +4,19 @@ import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
|||||||
|
|
||||||
class NotInstalledAppItem extends StatefulWidget {
|
class NotInstalledAppItem extends StatefulWidget {
|
||||||
const NotInstalledAppItem({
|
const NotInstalledAppItem({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.patchesCount,
|
required this.patchesCount,
|
||||||
required this.suggestedVersion,
|
required this.suggestedVersion,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
}) : super(key: key);
|
this.onLinkTap,
|
||||||
|
});
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final int patchesCount;
|
final int patchesCount;
|
||||||
final String suggestedVersion;
|
final String suggestedVersion;
|
||||||
final Function()? onTap;
|
final Function()? onTap;
|
||||||
|
final Function()? onLinkTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<NotInstalledAppItem> createState() => _NotInstalledAppItem();
|
State<NotInstalledAppItem> createState() => _NotInstalledAppItem();
|
||||||
@@ -65,17 +68,52 @@ class _NotInstalledAppItem extends State<NotInstalledAppItem> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Wrap(
|
Wrap(
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
I18nText(
|
Material(
|
||||||
'suggested',
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
translationParams: {
|
borderRadius:
|
||||||
'version': widget.suggestedVersion.isEmpty
|
const BorderRadius.all(Radius.circular(8)),
|
||||||
? FlutterI18n.translate(
|
child: InkWell(
|
||||||
context,
|
onTap: widget.onLinkTap,
|
||||||
'appSelectorCard.allVersions',
|
borderRadius:
|
||||||
)
|
const BorderRadius.all(Radius.circular(8)),
|
||||||
: 'v${widget.suggestedVersion}',
|
child: Container(
|
||||||
},
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
I18nText(
|
||||||
|
'suggested',
|
||||||
|
translationParams: {
|
||||||
|
'version': widget.suggestedVersion.isEmpty
|
||||||
|
? FlutterI18n.translate(
|
||||||
|
context,
|
||||||
|
'appSelectorCard.allVersions',
|
||||||
|
)
|
||||||
|
: 'v${widget.suggestedVersion}',
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Icon(
|
||||||
|
Icons.search,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_cache_manager/file.dart';
|
import 'package:flutter_cache_manager/file.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
|
import 'package:revanced_manager/services/download_manager.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class ContributorsCard extends StatefulWidget {
|
class ContributorsCard extends StatefulWidget {
|
||||||
const ContributorsCard({
|
const ContributorsCard({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.contributors,
|
required this.contributors,
|
||||||
}) : super(key: key);
|
});
|
||||||
final String title;
|
final String title;
|
||||||
final List<dynamic> contributors;
|
final List<dynamic> contributors;
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ class _ContributorsCardState extends State<ContributorsCard> {
|
|||||||
mode: LaunchMode.externalApplication,
|
mode: LaunchMode.externalApplication,
|
||||||
),
|
),
|
||||||
child: FutureBuilder<File?>(
|
child: FutureBuilder<File?>(
|
||||||
future: DefaultCacheManager().getSingleFile(
|
future: DownloadManager().getSingleFile(
|
||||||
widget.contributors[index]['avatar_url'],
|
widget.contributors[index]['avatar_url'],
|
||||||
),
|
),
|
||||||
builder: (context, snapshot) => snapshot.hasData
|
builder: (context, snapshot) => snapshot.hasData
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
|||||||
|
|
||||||
//ignore: must_be_immutable
|
//ignore: must_be_immutable
|
||||||
class InstalledAppsCard extends StatelessWidget {
|
class InstalledAppsCard extends StatelessWidget {
|
||||||
InstalledAppsCard({Key? key}) : super(key: key);
|
InstalledAppsCard({super.key});
|
||||||
|
|
||||||
List<PatchedApplication> apps = locator<HomeViewModel>().patchedInstalledApps;
|
List<PatchedApplication> apps = locator<HomeViewModel>().patchedInstalledApps;
|
||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|||||||
|
|
||||||
class LatestCommitCard extends StatefulWidget {
|
class LatestCommitCard extends StatefulWidget {
|
||||||
const LatestCommitCard({
|
const LatestCommitCard({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.model,
|
required this.model,
|
||||||
required this.parentContext,
|
required this.parentContext,
|
||||||
}) : super(key: key);
|
});
|
||||||
final HomeViewModel model;
|
final HomeViewModel model;
|
||||||
final BuildContext parentContext;
|
final BuildContext parentContext;
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
|
|||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// ReVanced Patches
|
// Patches
|
||||||
CustomCard(
|
CustomCard(
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@@ -84,7 +84,7 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
const Text('ReVanced Patches'),
|
const Text('Patches'),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
|||||||
|
|
||||||
class AppSelectorCard extends StatelessWidget {
|
class AppSelectorCard extends StatelessWidget {
|
||||||
const AppSelectorCard({
|
const AppSelectorCard({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
final Function() onPressed;
|
final Function() onPressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -61,11 +62,52 @@ class AppSelectorCard extends StatelessWidget {
|
|||||||
Container()
|
Container()
|
||||||
else
|
else
|
||||||
Column(
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
locator<PatcherViewModel>()
|
locator<PatcherViewModel>().getCurrentVersionString(context),
|
||||||
.getSuggestedVersionString(context),
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Material(
|
||||||
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
locator<PatcherViewModel>()
|
||||||
|
.searchSuggestedVersionOnWeb();
|
||||||
|
},
|
||||||
|
borderRadius:
|
||||||
|
const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
locator<PatcherViewModel>()
|
||||||
|
.getSuggestedVersionString(context),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Icon(
|
||||||
|
Icons.search,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
|||||||
|
|
||||||
class PatchSelectorCard extends StatelessWidget {
|
class PatchSelectorCard extends StatelessWidget {
|
||||||
const PatchSelectorCard({
|
const PatchSelectorCard({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : super(key: key);
|
});
|
||||||
final Function() onPressed;
|
final Function() onPressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class PatchItem extends StatefulWidget {
|
class PatchItem extends StatefulWidget {
|
||||||
PatchItem({
|
PatchItem({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.simpleName,
|
required this.simpleName,
|
||||||
required this.description,
|
required this.description,
|
||||||
@@ -23,7 +23,7 @@ class PatchItem extends StatefulWidget {
|
|||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
required this.navigateToOptions,
|
required this.navigateToOptions,
|
||||||
required this.isChangeEnabled,
|
required this.isChangeEnabled,
|
||||||
}) : super(key: key);
|
});
|
||||||
final String name;
|
final String name;
|
||||||
final String simpleName;
|
final String simpleName;
|
||||||
final String description;
|
final String description;
|
||||||
@@ -48,13 +48,13 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
widget.isSelected = widget.isSelected &&
|
widget.isSelected = widget.isSelected &&
|
||||||
(!widget.isUnsupported ||
|
(!widget.isUnsupported ||
|
||||||
widget._managerAPI.areExperimentalPatchesEnabled()) &&
|
!widget._managerAPI.isVersionCompatibilityCheckEnabled()) &&
|
||||||
!widget.hasUnsupportedPatchOption;
|
!widget.hasUnsupportedPatchOption;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: widget.isUnsupported &&
|
opacity: widget.isUnsupported &&
|
||||||
widget._managerAPI.areExperimentalPatchesEnabled() == false
|
widget._managerAPI.isVersionCompatibilityCheckEnabled() == true
|
||||||
? 0.5
|
? 0.5
|
||||||
: 1,
|
: 1,
|
||||||
child: CustomCard(
|
child: CustomCard(
|
||||||
@@ -66,7 +66,7 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (widget.isUnsupported &&
|
if (widget.isUnsupported &&
|
||||||
!widget._managerAPI.areExperimentalPatchesEnabled()) {
|
widget._managerAPI.isVersionCompatibilityCheckEnabled()) {
|
||||||
widget.isSelected = false;
|
widget.isSelected = false;
|
||||||
widget.toast.showBottom('patchItem.unsupportedPatchVersion');
|
widget.toast.showBottom('patchItem.unsupportedPatchVersion');
|
||||||
} else if (widget.isChangeEnabled) {
|
} else if (widget.isChangeEnabled) {
|
||||||
@@ -80,7 +80,7 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
if (!widget.isUnsupported ||
|
if (!widget.isUnsupported ||
|
||||||
widget._managerAPI.areExperimentalPatchesEnabled()) {
|
!widget._managerAPI.isVersionCompatibilityCheckEnabled()) {
|
||||||
widget.onChanged(widget.isSelected);
|
widget.onChanged(widget.isSelected);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -99,7 +99,8 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
),
|
),
|
||||||
onChanged: (newValue) {
|
onChanged: (newValue) {
|
||||||
if (widget.isUnsupported &&
|
if (widget.isUnsupported &&
|
||||||
!widget._managerAPI.areExperimentalPatchesEnabled()) {
|
widget._managerAPI
|
||||||
|
.isVersionCompatibilityCheckEnabled()) {
|
||||||
widget.isSelected = false;
|
widget.isSelected = false;
|
||||||
widget.toast.showBottom(
|
widget.toast.showBottom(
|
||||||
'patchItem.unsupportedPatchVersion',
|
'patchItem.unsupportedPatchVersion',
|
||||||
@@ -115,7 +116,8 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
if (!widget.isUnsupported ||
|
if (!widget.isUnsupported ||
|
||||||
widget._managerAPI.areExperimentalPatchesEnabled()) {
|
!widget._managerAPI
|
||||||
|
.isVersionCompatibilityCheckEnabled()) {
|
||||||
widget.onChanged(widget.isSelected);
|
widget.onChanged(widget.isSelected);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -155,8 +157,8 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
runSpacing: 4,
|
runSpacing: 4,
|
||||||
children: [
|
children: [
|
||||||
if (widget.isUnsupported &&
|
if (widget.isUnsupported &&
|
||||||
widget._managerAPI
|
!widget._managerAPI
|
||||||
.areExperimentalPatchesEnabled())
|
.isVersionCompatibilityCheckEnabled())
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
@@ -59,13 +59,27 @@ class IntAndStringPatchOption extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ValueNotifier patchOptionValue = ValueNotifier(patchOption.value);
|
final ValueNotifier patchOptionValue = ValueNotifier(patchOption.value);
|
||||||
|
String getKey() {
|
||||||
|
if (patchOption.value != null && patchOption.values != null) {
|
||||||
|
final List values = patchOption.values!.entries
|
||||||
|
.where((e) => e.value == patchOption.value)
|
||||||
|
.toList();
|
||||||
|
if (values.isNotEmpty) {
|
||||||
|
return values.first.key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
return PatchOption(
|
return PatchOption(
|
||||||
widget: Column(
|
widget: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
TextFieldForPatchOption(
|
TextFieldForPatchOption(
|
||||||
value: patchOption.value,
|
value: patchOption.value,
|
||||||
optionType: patchOption.optionClassType,
|
values: patchOption.values,
|
||||||
|
optionType: patchOption.valueType,
|
||||||
|
selectedKey: getKey(),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
patchOptionValue.value = value;
|
patchOptionValue.value = value;
|
||||||
onChanged(value, patchOption);
|
onChanged(value, patchOption);
|
||||||
@@ -77,7 +91,7 @@ class IntAndStringPatchOption extends StatelessWidget {
|
|||||||
if (patchOption.required && value == null) {
|
if (patchOption.required && value == null) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children:[
|
children: [
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
I18nText(
|
I18nText(
|
||||||
'patchOptionsView.requiredOption',
|
'patchOptionsView.requiredOption',
|
||||||
@@ -119,17 +133,41 @@ class IntStringLongListPatchOption extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final String type = patchOption.optionClassType;
|
final List<dynamic> values = List.from(patchOption.value ?? []);
|
||||||
final List<dynamic> values = patchOption.value ?? [];
|
|
||||||
final ValueNotifier patchOptionValue = ValueNotifier(values);
|
final ValueNotifier patchOptionValue = ValueNotifier(values);
|
||||||
|
final String type = patchOption.valueType;
|
||||||
|
|
||||||
|
String getKey(dynamic value) {
|
||||||
|
if (value != null && patchOption.values != null) {
|
||||||
|
final List values = patchOption.values!.entries
|
||||||
|
.where((e) => e.value.toString() == value)
|
||||||
|
.toList();
|
||||||
|
if (values.isNotEmpty) {
|
||||||
|
return values.first.key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isCustomValue() {
|
||||||
|
if (values.length == 1 && patchOption.values != null) {
|
||||||
|
if (getKey(values[0]) != '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isTextFieldVisible = isCustomValue();
|
||||||
|
|
||||||
return PatchOption(
|
return PatchOption(
|
||||||
widget: Column(
|
widget: ValueListenableBuilder(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
valueListenable: patchOptionValue,
|
||||||
children: [
|
builder: (context, value, child) {
|
||||||
ValueListenableBuilder(
|
return Column(
|
||||||
valueListenable: patchOptionValue,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
builder: (context, value, child) {
|
children: [
|
||||||
return ListView.builder(
|
ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: value.length,
|
itemCount: value.length,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
@@ -137,54 +175,89 @@ class IntStringLongListPatchOption extends StatelessWidget {
|
|||||||
final e = values[index];
|
final e = values[index];
|
||||||
return TextFieldForPatchOption(
|
return TextFieldForPatchOption(
|
||||||
value: e.toString(),
|
value: e.toString(),
|
||||||
|
values: patchOption.values,
|
||||||
optionType: type,
|
optionType: type,
|
||||||
|
selectedKey: value.length > 1 ? '' : getKey(e),
|
||||||
|
showDropdown: index == 0,
|
||||||
onChanged: (newValue) {
|
onChanged: (newValue) {
|
||||||
values[index] = type == 'StringListPatchOption' ? newValue : type == 'IntListPatchOption' ? int.parse(newValue) : num.parse(newValue);
|
if (newValue is List) {
|
||||||
|
values.clear();
|
||||||
|
isTextFieldVisible = false;
|
||||||
|
values.add(newValue.toString());
|
||||||
|
} else {
|
||||||
|
isTextFieldVisible = true;
|
||||||
|
if (values.length == 1 &&
|
||||||
|
values[0].toString().startsWith('[') &&
|
||||||
|
type.contains('Array')) {
|
||||||
|
values.clear();
|
||||||
|
values.addAll(patchOption.value);
|
||||||
|
} else {
|
||||||
|
values[index] = type == 'StringArray'
|
||||||
|
? newValue
|
||||||
|
: type == 'IntArray'
|
||||||
|
? int.parse(
|
||||||
|
newValue.toString().isEmpty
|
||||||
|
? '0'
|
||||||
|
: newValue.toString(),
|
||||||
|
)
|
||||||
|
: num.parse(
|
||||||
|
newValue.toString().isEmpty
|
||||||
|
? '0'
|
||||||
|
: newValue.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
patchOptionValue.value = List.from(values);
|
||||||
onChanged(values, patchOption);
|
onChanged(values, patchOption);
|
||||||
},
|
},
|
||||||
removeValue: (value) {
|
removeValue: () {
|
||||||
patchOptionValue.value = List.from(patchOptionValue.value)..removeAt(index);
|
patchOptionValue.value = List.from(patchOptionValue.value)
|
||||||
|
..removeAt(index);
|
||||||
values.removeAt(index);
|
values.removeAt(index);
|
||||||
onChanged(values, patchOption);
|
onChanged(values, patchOption);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
if (type == 'StringListPatchOption') {
|
|
||||||
patchOptionValue.value = List.from(patchOptionValue.value)..add('');
|
|
||||||
values.add('');
|
|
||||||
} else {
|
|
||||||
patchOptionValue.value = List.from(patchOptionValue.value)..add(0);
|
|
||||||
values.add(0);
|
|
||||||
}
|
|
||||||
onChanged(values, patchOption);
|
|
||||||
},
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.add, size: 20),
|
|
||||||
I18nText(
|
|
||||||
'add',
|
|
||||||
child: const Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
if (isTextFieldVisible) ...[
|
||||||
],
|
const SizedBox(height: 4),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (type == 'StringArray') {
|
||||||
|
patchOptionValue.value =
|
||||||
|
List.from(patchOptionValue.value)..add('');
|
||||||
|
values.add('');
|
||||||
|
} else {
|
||||||
|
patchOptionValue.value =
|
||||||
|
List.from(patchOptionValue.value)..add(0);
|
||||||
|
values.add(0);
|
||||||
|
}
|
||||||
|
onChanged(values, patchOption);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.add, size: 20),
|
||||||
|
I18nText(
|
||||||
|
'add',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
patchOption: patchOption,
|
patchOption: patchOption,
|
||||||
removeOption: (Option option) {
|
removeOption: (Option option) {
|
||||||
@@ -196,6 +269,7 @@ class IntStringLongListPatchOption extends StatelessWidget {
|
|||||||
|
|
||||||
class UnsupportedPatchOption extends StatelessWidget {
|
class UnsupportedPatchOption extends StatelessWidget {
|
||||||
const UnsupportedPatchOption({super.key, required this.patchOption});
|
const UnsupportedPatchOption({super.key, required this.patchOption});
|
||||||
|
|
||||||
final Option patchOption;
|
final Option patchOption;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -239,7 +313,6 @@ class PatchOption extends StatelessWidget {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: CustomCard(
|
child: CustomCard(
|
||||||
onTap: () {},
|
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -296,14 +369,20 @@ class TextFieldForPatchOption extends StatefulWidget {
|
|||||||
const TextFieldForPatchOption({
|
const TextFieldForPatchOption({
|
||||||
super.key,
|
super.key,
|
||||||
required this.value,
|
required this.value,
|
||||||
|
required this.values,
|
||||||
this.removeValue,
|
this.removeValue,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
required this.optionType,
|
required this.optionType,
|
||||||
|
required this.selectedKey,
|
||||||
|
this.showDropdown = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String? value;
|
final String? value;
|
||||||
|
final Map<String, dynamic>? values;
|
||||||
final String optionType;
|
final String optionType;
|
||||||
final void Function(dynamic value)? removeValue;
|
final String selectedKey;
|
||||||
|
final bool showDropdown;
|
||||||
|
final void Function()? removeValue;
|
||||||
final void Function(dynamic value) onChanged;
|
final void Function(dynamic value) onChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -313,75 +392,155 @@ class TextFieldForPatchOption extends StatefulWidget {
|
|||||||
|
|
||||||
class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
|
class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
|
||||||
final TextEditingController controller = TextEditingController();
|
final TextEditingController controller = TextEditingController();
|
||||||
|
String? selectedKey;
|
||||||
|
String? defaultValue;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final bool isStringOption = widget.optionType.contains('String');
|
final bool isStringOption = widget.optionType.contains('String');
|
||||||
final bool isListOption = widget.optionType.contains('List');
|
final bool isArrayOption = widget.optionType.contains('Array');
|
||||||
controller.text = widget.value ?? '';
|
selectedKey ??= widget.selectedKey;
|
||||||
return TextFormField(
|
controller.text = !isStringOption && isArrayOption && selectedKey == '' &&
|
||||||
inputFormatters: [
|
(widget.value != null && widget.value.toString().startsWith('['))
|
||||||
if (widget.optionType.contains('Int'))
|
? ''
|
||||||
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
|
: widget.value ?? '';
|
||||||
if (widget.optionType.contains('Long'))
|
defaultValue ??= controller.text;
|
||||||
FilteringTextInputFormatter.allow(RegExp(r'^[0-9]*\.?[0-9]*')),
|
return Column(
|
||||||
],
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
controller: controller,
|
children: [
|
||||||
keyboardType: isStringOption ? TextInputType.text : TextInputType.number,
|
if (widget.showDropdown && (widget.values?.isNotEmpty ?? false))
|
||||||
decoration: InputDecoration(
|
DropdownButton<String>(
|
||||||
suffixIcon: PopupMenuButton(
|
style: const TextStyle(
|
||||||
tooltip: FlutterI18n.translate(
|
fontSize: 16,
|
||||||
context,
|
),
|
||||||
'patchOptionsView.tooltip',
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
dropdownColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
isExpanded: true,
|
||||||
|
value: selectedKey,
|
||||||
|
items: widget.values!.entries
|
||||||
|
.map(
|
||||||
|
(e) => DropdownMenuItem(
|
||||||
|
value: e.key,
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
text: e.key,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: ' ${e.value}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSecondaryContainer
|
||||||
|
.withOpacity(0.6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList()
|
||||||
|
..add(
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: '',
|
||||||
|
child: I18nText(
|
||||||
|
'patchOptionsView.customValue',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == '') {
|
||||||
|
controller.text = defaultValue!;
|
||||||
|
widget.onChanged(controller.text);
|
||||||
|
} else {
|
||||||
|
controller.text = widget.values![value].toString();
|
||||||
|
widget.onChanged(
|
||||||
|
isArrayOption ? widget.values![value] : controller.text,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
selectedKey = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
itemBuilder: (BuildContext context) {
|
if (selectedKey == '')
|
||||||
return [
|
TextFormField(
|
||||||
if (isListOption)
|
inputFormatters: [
|
||||||
PopupMenuItem(
|
if (widget.optionType.contains('Int'))
|
||||||
value: 'remove',
|
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
|
||||||
child: I18nText('remove'),
|
if (widget.optionType.contains('Long'))
|
||||||
|
FilteringTextInputFormatter.allow(RegExp(r'^[0-9]*\.?[0-9]*')),
|
||||||
|
],
|
||||||
|
controller: controller,
|
||||||
|
keyboardType:
|
||||||
|
isStringOption ? TextInputType.text : TextInputType.number,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
suffixIcon: PopupMenuButton(
|
||||||
|
tooltip: FlutterI18n.translate(
|
||||||
|
context,
|
||||||
|
'patchOptionsView.tooltip',
|
||||||
),
|
),
|
||||||
if (isStringOption && !isListOption) ...[
|
itemBuilder: (BuildContext context) {
|
||||||
PopupMenuItem(
|
return [
|
||||||
value: 'patchOptionsView.selectFilePath',
|
if (isArrayOption)
|
||||||
child: I18nText('patchOptionsView.selectFilePath'),
|
PopupMenuItem(
|
||||||
),
|
value: 'remove',
|
||||||
PopupMenuItem(
|
child: I18nText('remove'),
|
||||||
value: 'patchOptionsView.selectFolder',
|
),
|
||||||
child: I18nText('patchOptionsView.selectFolder'),
|
if (isStringOption) ...[
|
||||||
),
|
PopupMenuItem(
|
||||||
],
|
value: 'patchOptionsView.selectFilePath',
|
||||||
];
|
child: I18nText('patchOptionsView.selectFilePath'),
|
||||||
},
|
),
|
||||||
onSelected: (String selection) async {
|
PopupMenuItem(
|
||||||
switch (selection) {
|
value: 'patchOptionsView.selectFolder',
|
||||||
case 'patchOptionsView.selectFilePath':
|
child: I18nText('patchOptionsView.selectFolder'),
|
||||||
final result = await FilePicker.platform.pickFiles();
|
),
|
||||||
if (result != null && result.files.single.path != null) {
|
],
|
||||||
controller.text = result.files.single.path.toString();
|
];
|
||||||
widget.onChanged(controller.text);
|
},
|
||||||
}
|
onSelected: (String selection) async {
|
||||||
break;
|
switch (selection) {
|
||||||
case 'patchOptionsView.selectFolder':
|
case 'patchOptionsView.selectFilePath':
|
||||||
final result = await FilePicker.platform.getDirectoryPath();
|
final String? result = await FlutterFileDialog.pickFile();
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
controller.text = result;
|
controller.text = result;
|
||||||
widget.onChanged(controller.text);
|
widget.onChanged(controller.text);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'remove':
|
case 'patchOptionsView.selectFolder':
|
||||||
widget.removeValue!(widget.value);
|
final DirectoryLocation? result = await FlutterFileDialog.pickDirectory();
|
||||||
break;
|
if (result != null) {
|
||||||
}
|
controller.text = result.toString();
|
||||||
},
|
widget.onChanged(controller.text);
|
||||||
),
|
}
|
||||||
hintStyle: TextStyle(
|
break;
|
||||||
fontSize: 14,
|
case 'remove':
|
||||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
widget.removeValue!();
|
||||||
),
|
break;
|
||||||
),
|
}
|
||||||
onChanged: (String value) {
|
},
|
||||||
widget.onChanged(value);
|
),
|
||||||
},
|
hintStyle: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (String value) {
|
||||||
|
widget.onChanged(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
|||||||
import 'package:revanced_manager/utils/about_info.dart';
|
import 'package:revanced_manager/utils/about_info.dart';
|
||||||
|
|
||||||
class AboutWidget extends StatefulWidget {
|
class AboutWidget extends StatefulWidget {
|
||||||
const AboutWidget({Key? key, this.padding}) : super(key: key);
|
const AboutWidget({super.key, this.padding});
|
||||||
|
|
||||||
final EdgeInsetsGeometry? padding;
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
class CustomSwitch extends StatelessWidget {
|
class CustomSwitch extends StatelessWidget {
|
||||||
const CustomSwitch({
|
const CustomSwitch({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
required this.value,
|
required this.value,
|
||||||
}) : super(key: key);
|
});
|
||||||
final ValueChanged<bool> onChanged;
|
final ValueChanged<bool> onChanged;
|
||||||
final bool value;
|
final bool value;
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import 'package:revanced_manager/ui/widgets/settingsView/custom_switch.dart';
|
|||||||
|
|
||||||
class CustomSwitchTile extends StatelessWidget {
|
class CustomSwitchTile extends StatelessWidget {
|
||||||
const CustomSwitchTile({
|
const CustomSwitchTile({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.subtitle,
|
required this.subtitle,
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
this.padding,
|
this.padding,
|
||||||
}) : super(key: key);
|
});
|
||||||
final Widget title;
|
final Widget title;
|
||||||
final Widget subtitle;
|
final Widget subtitle;
|
||||||
final bool value;
|
final bool value;
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
class CustomTextField extends StatelessWidget {
|
class CustomTextField extends StatelessWidget {
|
||||||
const CustomTextField({
|
const CustomTextField({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.inputController,
|
required this.inputController,
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.hint,
|
required this.hint,
|
||||||
this.leadingIcon,
|
this.leadingIcon,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
}) : super(key: key);
|
});
|
||||||
final TextEditingController inputController;
|
final TextEditingController inputController;
|
||||||
final Widget label;
|
final Widget label;
|
||||||
final String hint;
|
final String hint;
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
// ignore_for_file: prefer_const_constructors
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_api_url.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_api_url.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_sources.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_sources.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_auto_update_patches.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_auto_update_patches.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_enable_patches_selection.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_enable_patches_selection.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_require_suggested_app_version.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_universal_patches.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_version_compatibility_check.dart';
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
|
||||||
|
|
||||||
class SAdvancedSection extends StatelessWidget {
|
class SAdvancedSection extends StatelessWidget {
|
||||||
const SAdvancedSection({super.key});
|
const SAdvancedSection({super.key});
|
||||||
@@ -21,85 +18,15 @@ class SAdvancedSection extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SettingsSection(
|
return SettingsSection(
|
||||||
title: 'settingsView.advancedSectionTitle',
|
title: 'settingsView.advancedSectionTitle',
|
||||||
children: <Widget>[
|
children: const <Widget>[
|
||||||
SManageApiUrlUI(),
|
|
||||||
SManageSourcesUI(),
|
|
||||||
// SManageKeystorePasswordUI(),
|
|
||||||
SAutoUpdatePatches(),
|
SAutoUpdatePatches(),
|
||||||
SEnablePatchesSelection(),
|
SEnablePatchesSelection(),
|
||||||
SExperimentalUniversalPatches(),
|
SRequireSuggestedAppVersion(),
|
||||||
SExperimentalPatches(),
|
SVersionCompatibilityCheck(),
|
||||||
ListTile(
|
SUniversalPatches(),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
SManageSourcesUI(),
|
||||||
title: I18nText(
|
SManageApiUrlUI(),
|
||||||
'settingsView.regenerateKeystoreLabel',
|
|
||||||
child: const Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: I18nText('settingsView.regenerateKeystoreHint'),
|
|
||||||
onTap: () => _showDeleteKeystoreDialog(context),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
title: I18nText(
|
|
||||||
'settingsView.deleteTempDirLabel',
|
|
||||||
child: const Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: I18nText('settingsView.deleteTempDirHint'),
|
|
||||||
onTap: () => _settingsViewModel.deleteTempDir(),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
title: I18nText(
|
|
||||||
'settingsView.deleteLogsLabel',
|
|
||||||
child: const Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: I18nText('settingsView.deleteLogsHint'),
|
|
||||||
onTap: () => _settingsViewModel.deleteLogs(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showDeleteKeystoreDialog(context) {
|
|
||||||
return showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: I18nText('settingsView.regenerateKeystoreDialogTitle'),
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: I18nText('settingsView.regenerateKeystoreDialogText'),
|
|
||||||
actions: <Widget>[
|
|
||||||
CustomMaterialButton(
|
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('noButton'),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
),
|
|
||||||
CustomMaterialButton(
|
|
||||||
label: I18nText('yesButton'),
|
|
||||||
onPressed: () => {
|
|
||||||
Navigator.of(context).pop(),
|
|
||||||
_settingsViewModel.deleteKeystore(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
68
lib/ui/widgets/settingsView/settings_debug_section.dart
Normal file
68
lib/ui/widgets/settingsView/settings_debug_section.dart
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||||
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/settingsView/about_widget.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||||
|
|
||||||
|
final _settingsViewModel = SettingsViewModel();
|
||||||
|
|
||||||
|
class SDebugSection extends StatelessWidget {
|
||||||
|
const SDebugSection({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SettingsSection(
|
||||||
|
title: 'settingsView.debugSectionTitle',
|
||||||
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
title: I18nText(
|
||||||
|
'settingsView.logsLabel',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: I18nText('settingsView.logsHint'),
|
||||||
|
onTap: () => _settingsViewModel.exportLogcatLogs(),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
title: I18nText(
|
||||||
|
'settingsView.deleteLogsLabel',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: I18nText('settingsView.deleteLogsHint'),
|
||||||
|
onTap: () => _settingsViewModel.deleteLogs(),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
title: I18nText(
|
||||||
|
'settingsView.deleteTempDirLabel',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: I18nText('settingsView.deleteTempDirHint'),
|
||||||
|
onTap: () => _settingsViewModel.deleteTempDir(),
|
||||||
|
),
|
||||||
|
const AboutWidget(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,42 +45,6 @@ class SExportSection extends StatelessWidget {
|
|||||||
subtitle: I18nText('settingsView.importPatchesHint'),
|
subtitle: I18nText('settingsView.importPatchesHint'),
|
||||||
onTap: () => _settingsViewModel.importPatches(context),
|
onTap: () => _settingsViewModel.importPatches(context),
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
title: I18nText(
|
|
||||||
'settingsView.exportKeystoreLabel',
|
|
||||||
child: const Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: I18nText('settingsView.exportKeystoreHint'),
|
|
||||||
onTap: () => _settingsViewModel.exportKeystore(),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
title: I18nText(
|
|
||||||
'settingsView.importKeystoreLabel',
|
|
||||||
child: const Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: I18nText('settingsView.importKeystoreHint'),
|
|
||||||
onTap: () async {
|
|
||||||
await _settingsViewModel.importKeystore();
|
|
||||||
final sManageKeystorePassword = SManageKeystorePassword();
|
|
||||||
if (context.mounted) {
|
|
||||||
sManageKeystorePassword.showKeystoreDialog(context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
@@ -121,6 +85,58 @@ class SExportSection extends StatelessWidget {
|
|||||||
_settingsViewModel.resetAllOptions,
|
_settingsViewModel.resetAllOptions,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
title: I18nText(
|
||||||
|
'settingsView.exportKeystoreLabel',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: I18nText('settingsView.exportKeystoreHint'),
|
||||||
|
onTap: () => _settingsViewModel.exportKeystore(),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
title: I18nText(
|
||||||
|
'settingsView.importKeystoreLabel',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: I18nText('settingsView.importKeystoreHint'),
|
||||||
|
onTap: () async {
|
||||||
|
await _settingsViewModel.importKeystore();
|
||||||
|
final sManageKeystorePassword = SManageKeystorePassword();
|
||||||
|
if (context.mounted) {
|
||||||
|
sManageKeystorePassword.showKeystoreDialog(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
title: I18nText(
|
||||||
|
'settingsView.regenerateKeystoreLabel',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: I18nText('settingsView.regenerateKeystoreHint'),
|
||||||
|
onTap: () => _showDeleteKeystoreDialog(context),
|
||||||
|
),
|
||||||
|
// SManageKeystorePasswordUI(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -154,4 +170,29 @@ class SExportSection extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _showDeleteKeystoreDialog(context) {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: I18nText('settingsView.regenerateKeystoreDialogTitle'),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
content: I18nText('settingsView.regenerateKeystoreDialogText'),
|
||||||
|
actions: <Widget>[
|
||||||
|
CustomMaterialButton(
|
||||||
|
isFilled: false,
|
||||||
|
label: I18nText('noButton'),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
CustomMaterialButton(
|
||||||
|
label: I18nText('yesButton'),
|
||||||
|
onPressed: () => {
|
||||||
|
Navigator.of(context).pop(),
|
||||||
|
_settingsViewModel.deleteKeystore(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/about_widget.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
|
||||||
|
|
||||||
class SInfoSection extends StatelessWidget {
|
|
||||||
const SInfoSection({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SettingsSection(
|
|
||||||
title: 'settingsView.infoSectionTitle',
|
|
||||||
children: <Widget>[
|
|
||||||
ListTile(
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
title: I18nText(
|
|
||||||
'settingsView.logsLabel',
|
|
||||||
child: const Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: I18nText('settingsView.logsHint'),
|
|
||||||
onTap: () => _settingsViewModel.exportLogcatLogs(),
|
|
||||||
),
|
|
||||||
const AboutWidget(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||||
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
|
|
||||||
|
class SRequireSuggestedAppVersion extends StatefulWidget {
|
||||||
|
const SRequireSuggestedAppVersion({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SRequireSuggestedAppVersion> createState() => _SRequireSuggestedAppVersionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
final _settingsViewModel = SettingsViewModel();
|
||||||
|
|
||||||
|
class _SRequireSuggestedAppVersionState extends State<SRequireSuggestedAppVersion> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SwitchListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
title: I18nText(
|
||||||
|
'settingsView.requireSuggestedAppVersionLabel',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: I18nText('settingsView.requireSuggestedAppVersionHint'),
|
||||||
|
value: _settingsViewModel.isRequireSuggestedAppVersionEnabled(),
|
||||||
|
onChanged: (value) async {
|
||||||
|
await _settingsViewModel.showRequireSuggestedAppVersionDialog(context, value,);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,10 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
|||||||
|
|
||||||
class SettingsSection extends StatelessWidget {
|
class SettingsSection extends StatelessWidget {
|
||||||
const SettingsSection({
|
const SettingsSection({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.children,
|
required this.children,
|
||||||
}) : super(key: key);
|
});
|
||||||
final String title;
|
final String title;
|
||||||
final List<Widget> children;
|
final List<Widget> children;
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
|||||||
|
|
||||||
class SettingsTileDialog extends StatelessWidget {
|
class SettingsTileDialog extends StatelessWidget {
|
||||||
const SettingsTileDialog({
|
const SettingsTileDialog({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.subtitle,
|
required this.subtitle,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
this.padding,
|
this.padding,
|
||||||
}) : super(key: key);
|
});
|
||||||
final String title;
|
final String title;
|
||||||
final String subtitle;
|
final String subtitle;
|
||||||
final Function()? onTap;
|
final Function()? onTap;
|
||||||
|
|||||||
@@ -4,26 +4,26 @@ import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
|||||||
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
|
|
||||||
class SExperimentalUniversalPatches extends StatefulWidget {
|
class SUniversalPatches extends StatefulWidget {
|
||||||
const SExperimentalUniversalPatches({super.key});
|
const SUniversalPatches({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SExperimentalUniversalPatches> createState() =>
|
State<SUniversalPatches> createState() =>
|
||||||
_SExperimentalUniversalPatchesState();
|
_SUniversalPatchesState();
|
||||||
}
|
}
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
final _settingsViewModel = SettingsViewModel();
|
||||||
final _patchesSelectorViewModel = PatchesSelectorViewModel();
|
final _patchesSelectorViewModel = PatchesSelectorViewModel();
|
||||||
final _patcherViewModel = PatcherViewModel();
|
final _patcherViewModel = PatcherViewModel();
|
||||||
|
|
||||||
class _SExperimentalUniversalPatchesState
|
class _SUniversalPatchesState
|
||||||
extends State<SExperimentalUniversalPatches> {
|
extends State<SUniversalPatches> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SwitchListTile(
|
return SwitchListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'settingsView.experimentalUniversalPatchesLabel',
|
'settingsView.universalPatchesLabel',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'',
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -32,7 +32,7 @@ class _SExperimentalUniversalPatchesState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: I18nText('settingsView.experimentalUniversalPatchesHint'),
|
subtitle: I18nText('settingsView.universalPatchesHint'),
|
||||||
value: _settingsViewModel.areUniversalPatchesEnabled(),
|
value: _settingsViewModel.areUniversalPatchesEnabled(),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -5,24 +5,24 @@ import 'package:revanced_manager/ui/views/patches_selector/patches_selector_view
|
|||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||||
|
|
||||||
class SExperimentalPatches extends StatefulWidget {
|
class SVersionCompatibilityCheck extends StatefulWidget {
|
||||||
const SExperimentalPatches({super.key});
|
const SVersionCompatibilityCheck({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SExperimentalPatches> createState() => _SExperimentalPatchesState();
|
State<SVersionCompatibilityCheck> createState() => _SVersionCompatibilityCheckState();
|
||||||
}
|
}
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
final _settingsViewModel = SettingsViewModel();
|
||||||
final _patchesSelectorViewModel = PatchesSelectorViewModel();
|
final _patchesSelectorViewModel = PatchesSelectorViewModel();
|
||||||
final _patcherViewModel = PatcherViewModel();
|
final _patcherViewModel = PatcherViewModel();
|
||||||
|
|
||||||
class _SExperimentalPatchesState extends State<SExperimentalPatches> {
|
class _SVersionCompatibilityCheckState extends State<SVersionCompatibilityCheck> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SwitchListTile(
|
return SwitchListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'settingsView.experimentalPatchesLabel',
|
'settingsView.versionCompatibilityCheckLabel',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'',
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -31,11 +31,11 @@ class _SExperimentalPatchesState extends State<SExperimentalPatches> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: I18nText('settingsView.experimentalPatchesHint'),
|
subtitle: I18nText('settingsView.versionCompatibilityCheckHint'),
|
||||||
value: _settingsViewModel.areExperimentalPatchesEnabled(),
|
value: _settingsViewModel.isVersionCompatibilityCheckEnabled(),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_settingsViewModel.useExperimentalPatches(value);
|
_settingsViewModel.useVersionCompatibilityCheck(value);
|
||||||
});
|
});
|
||||||
if (!value) {
|
if (!value) {
|
||||||
_patcherViewModel.selectedPatches
|
_patcherViewModel.selectedPatches
|
||||||
@@ -3,12 +3,12 @@ import 'package:url_launcher/url_launcher.dart';
|
|||||||
|
|
||||||
class SocialMediaItem extends StatelessWidget {
|
class SocialMediaItem extends StatelessWidget {
|
||||||
const SocialMediaItem({
|
const SocialMediaItem({
|
||||||
Key? key,
|
super.key,
|
||||||
this.icon,
|
this.icon,
|
||||||
required this.title,
|
required this.title,
|
||||||
this.subtitle,
|
this.subtitle,
|
||||||
this.url,
|
this.url,
|
||||||
}) : super(key: key);
|
});
|
||||||
final Widget? icon;
|
final Widget? icon;
|
||||||
final Widget title;
|
final Widget title;
|
||||||
final Widget? subtitle;
|
final Widget? subtitle;
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
|||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/social_media_item.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/social_media_item.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/shared/custom_icon.dart';
|
||||||
|
|
||||||
class SocialMediaWidget extends StatelessWidget {
|
class SocialMediaWidget extends StatelessWidget {
|
||||||
const SocialMediaWidget({
|
const SocialMediaWidget({
|
||||||
Key? key,
|
super.key,
|
||||||
this.padding,
|
this.padding,
|
||||||
}) : super(key: key);
|
});
|
||||||
final EdgeInsetsGeometry? padding;
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -42,11 +43,17 @@ class SocialMediaWidget extends StatelessWidget {
|
|||||||
child: const CustomCard(
|
child: const CustomCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
SocialMediaItem(
|
||||||
|
icon: Icon(CustomIcon.revancedIcon),
|
||||||
|
title: Text('Website'),
|
||||||
|
subtitle: Text('revanced.app'),
|
||||||
|
url: 'https://revanced.app',
|
||||||
|
),
|
||||||
SocialMediaItem(
|
SocialMediaItem(
|
||||||
icon: FaIcon(FontAwesomeIcons.github),
|
icon: FaIcon(FontAwesomeIcons.github),
|
||||||
title: Text('GitHub'),
|
title: Text('GitHub'),
|
||||||
subtitle: Text('github.com/revanced'),
|
subtitle: Text('github.com/ReVanced'),
|
||||||
url: 'https://github.com/revanced',
|
url: 'https://github.com/ReVanced',
|
||||||
),
|
),
|
||||||
SocialMediaItem(
|
SocialMediaItem(
|
||||||
icon: FaIcon(FontAwesomeIcons.discord),
|
icon: FaIcon(FontAwesomeIcons.discord),
|
||||||
@@ -67,10 +74,10 @@ class SocialMediaWidget extends StatelessWidget {
|
|||||||
url: 'https://reddit.com/r/revancedapp',
|
url: 'https://reddit.com/r/revancedapp',
|
||||||
),
|
),
|
||||||
SocialMediaItem(
|
SocialMediaItem(
|
||||||
icon: FaIcon(FontAwesomeIcons.twitter),
|
icon: FaIcon(FontAwesomeIcons.xTwitter),
|
||||||
title: Text('Twitter'),
|
title: Text('X'),
|
||||||
subtitle: Text('@revancedapp'),
|
subtitle: Text('@revancedapp'),
|
||||||
url: 'https://twitter.com/revancedapp',
|
url: 'https://x.com/revancedapp',
|
||||||
),
|
),
|
||||||
SocialMediaItem(
|
SocialMediaItem(
|
||||||
icon: FaIcon(FontAwesomeIcons.youtube),
|
icon: FaIcon(FontAwesomeIcons.youtube),
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import 'package:timeago/timeago.dart';
|
|||||||
|
|
||||||
class ApplicationItem extends StatefulWidget {
|
class ApplicationItem extends StatefulWidget {
|
||||||
const ApplicationItem({
|
const ApplicationItem({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.patchDate,
|
required this.patchDate,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : super(key: key);
|
});
|
||||||
final Uint8List icon;
|
final Uint8List icon;
|
||||||
final String name;
|
final String name;
|
||||||
final DateTime patchDate;
|
final DateTime patchDate;
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
class CustomCard extends StatelessWidget {
|
class CustomCard extends StatelessWidget {
|
||||||
const CustomCard({
|
const CustomCard({
|
||||||
Key? key,
|
super.key,
|
||||||
this.isFilled = true,
|
this.isFilled = true,
|
||||||
required this.child,
|
required this.child,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.padding,
|
this.padding,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
}) : super(key: key);
|
});
|
||||||
final bool isFilled;
|
final bool isFilled;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final Function()? onTap;
|
final Function()? onTap;
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
class CustomChip extends StatelessWidget {
|
class CustomChip extends StatelessWidget {
|
||||||
const CustomChip({
|
const CustomChip({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.label,
|
required this.label,
|
||||||
this.isSelected = false,
|
this.isSelected = false,
|
||||||
this.onSelected,
|
this.onSelected,
|
||||||
}) : super(key: key);
|
});
|
||||||
final Widget label;
|
final Widget label;
|
||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
final Function(bool)? onSelected;
|
final Function(bool)? onSelected;
|
||||||
|
|||||||
9
lib/ui/widgets/shared/custom_icon.dart
Normal file
9
lib/ui/widgets/shared/custom_icon.dart
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class CustomIcon {
|
||||||
|
CustomIcon._();
|
||||||
|
|
||||||
|
static const _kFontFam = 'CustomIcon';
|
||||||
|
|
||||||
|
static const IconData revancedIcon = IconData(0xe800, fontFamily: _kFontFam);
|
||||||
|
}
|
||||||
@@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
class CustomMaterialButton extends StatelessWidget {
|
class CustomMaterialButton extends StatelessWidget {
|
||||||
const CustomMaterialButton({
|
const CustomMaterialButton({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.label,
|
required this.label,
|
||||||
this.isFilled = true,
|
this.isFilled = true,
|
||||||
this.isExpanded = false,
|
this.isExpanded = false,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : super(key: key);
|
});
|
||||||
final Widget label;
|
final Widget label;
|
||||||
final bool isFilled;
|
final bool isFilled;
|
||||||
final bool isExpanded;
|
final bool isExpanded;
|
||||||
@@ -49,13 +49,13 @@ class CustomMaterialButton extends StatelessWidget {
|
|||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class TimerButton extends StatefulWidget {
|
class TimerButton extends StatefulWidget {
|
||||||
TimerButton({
|
TimerButton({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.seconds,
|
required this.seconds,
|
||||||
required this.isRunning,
|
required this.isRunning,
|
||||||
required this.onTimerEnd,
|
required this.onTimerEnd,
|
||||||
this.label = const Text(''),
|
this.label = const Text(''),
|
||||||
this.isFilled = true,
|
this.isFilled = true,
|
||||||
}) : super(key: key);
|
});
|
||||||
Widget label;
|
Widget label;
|
||||||
bool isFilled;
|
bool isFilled;
|
||||||
int seconds;
|
int seconds;
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class CustomPopupMenu extends StatelessWidget {
|
|
||||||
const CustomPopupMenu({
|
|
||||||
Key? key,
|
|
||||||
required this.onSelected,
|
|
||||||
required this.children,
|
|
||||||
}) : super(key: key);
|
|
||||||
final Function(dynamic) onSelected;
|
|
||||||
final Map<int, Widget> children;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Theme(
|
|
||||||
data: Theme.of(context).copyWith(useMaterial3: false),
|
|
||||||
child: PopupMenuButton<int>(
|
|
||||||
icon: Icon(
|
|
||||||
Icons.more_vert,
|
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
|
||||||
onSelected: onSelected,
|
|
||||||
itemBuilder: (context) => children.entries
|
|
||||||
.map(
|
|
||||||
(entry) => PopupMenuItem<int>(
|
|
||||||
padding: const EdgeInsets.all(16.0).copyWith(right: 20),
|
|
||||||
value: entry.key,
|
|
||||||
child: entry.value,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
),
|
|
||||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
position: PopupMenuPosition.under,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,13 +2,13 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
class CustomSliverAppBar extends StatelessWidget {
|
class CustomSliverAppBar extends StatelessWidget {
|
||||||
const CustomSliverAppBar({
|
const CustomSliverAppBar({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
this.actions,
|
this.actions,
|
||||||
this.bottom,
|
this.bottom,
|
||||||
this.isMainView = false,
|
this.isMainView = false,
|
||||||
this.onBackButtonPressed,
|
this.onBackButtonPressed,
|
||||||
}) : super(key: key);
|
});
|
||||||
final Widget title;
|
final Widget title;
|
||||||
final List<Widget>? actions;
|
final List<Widget>? actions;
|
||||||
final PreferredSizeWidget? bottom;
|
final PreferredSizeWidget? bottom;
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
class OpenContainerWrapper extends StatelessWidget {
|
class OpenContainerWrapper extends StatelessWidget {
|
||||||
const OpenContainerWrapper({
|
const OpenContainerWrapper({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.openBuilder,
|
required this.openBuilder,
|
||||||
required this.closedBuilder,
|
required this.closedBuilder,
|
||||||
}) : super(key: key);
|
});
|
||||||
final OpenContainerBuilder openBuilder;
|
final OpenContainerBuilder openBuilder;
|
||||||
final CloseContainerBuilder closedBuilder;
|
final CloseContainerBuilder closedBuilder;
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
class SearchBar extends StatefulWidget {
|
class SearchBar extends StatefulWidget {
|
||||||
const SearchBar({
|
const SearchBar({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.hintText,
|
required this.hintText,
|
||||||
this.showSelectIcon = false,
|
this.showSelectIcon = false,
|
||||||
this.onSelectAll,
|
this.onSelectAll,
|
||||||
required this.onQueryChanged,
|
required this.onQueryChanged,
|
||||||
}) : super(key: key);
|
});
|
||||||
final String? hintText;
|
final String? hintText;
|
||||||
final bool showSelectIcon;
|
final bool showSelectIcon;
|
||||||
final Function(bool)? onSelectAll;
|
final Function(bool)? onSelectAll;
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ bool isPatchSupported(Patch patch) {
|
|||||||
bool hasUnsupportedRequiredOption(List<Option> options, Patch patch) {
|
bool hasUnsupportedRequiredOption(List<Option> options, Patch patch) {
|
||||||
final List<String> requiredOptionsType = [];
|
final List<String> requiredOptionsType = [];
|
||||||
final List<String> supportedOptionsType = [
|
final List<String> supportedOptionsType = [
|
||||||
'StringPatchOption',
|
'String',
|
||||||
'BooleanPatchOption',
|
'Boolean',
|
||||||
'IntPatchOption',
|
'Int',
|
||||||
'StringListPatchOption',
|
'StringArray',
|
||||||
'IntListPatchOption',
|
'IntArray',
|
||||||
'LongListPatchOption',
|
'LongArray',
|
||||||
];
|
];
|
||||||
for (final Option option in options) {
|
for (final Option option in options) {
|
||||||
if (option.required &&
|
if (option.required &&
|
||||||
@@ -33,7 +33,7 @@ bool hasUnsupportedRequiredOption(List<Option> options, Patch patch) {
|
|||||||
patch.name,
|
patch.name,
|
||||||
option.key,
|
option.key,
|
||||||
) == null) {
|
) == null) {
|
||||||
requiredOptionsType.add(option.optionClassType);
|
requiredOptionsType.add(option.valueType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (final String optionType in requiredOptionsType) {
|
for (final String optionType in requiredOptionsType) {
|
||||||
|
|||||||
7179
package-lock.json
generated
7179
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"devDependencies": {
|
|
||||||
"@saithodev/semantic-release-backmerge": "^3.1.0",
|
|
||||||
"@semantic-release/changelog": "^6.0.3",
|
|
||||||
"@semantic-release/git": "^10.0.1",
|
|
||||||
"semantic-release": "^21.0.1",
|
|
||||||
"semantic-release-export-data": "^1.0.1",
|
|
||||||
"semantic-release-flutter-plugin": "^1.1.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
75
pubspec.yaml
75
pubspec.yaml
@@ -1,10 +1,10 @@
|
|||||||
name: revanced_manager
|
name: revanced_manager
|
||||||
description: Patch your favorite apps, right on your device.
|
description: Patch your favorite apps, right on your device.
|
||||||
homepage: https://github.com/revanced/revanced-manager
|
homepage: https://github.com/ReVanced/revanced-manager
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.12.0+101200000
|
version: 1.17.2+101700200
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
@@ -12,84 +12,89 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
animations: ^2.0.7
|
animations: ^2.0.7
|
||||||
collection: ^1.17.0
|
collection: ^1.17.0
|
||||||
cross_connectivity: ^3.0.5
|
|
||||||
cr_file_saver:
|
|
||||||
git:
|
|
||||||
url: https://github.com/dhruvanbhalara/cr_file_saver
|
|
||||||
ref: "fix/incorrect_file_name"
|
|
||||||
device_apps:
|
device_apps:
|
||||||
git:
|
git: # switch back to ponces fork once https://github.com/ponces/flutter_plugin_device_apps/pull/1 is merged
|
||||||
url: https://github.com/ponces/flutter_plugin_device_apps
|
url: https://github.com/BenjaminHalko/flutter_plugin_device_apps
|
||||||
ref: revanced-manager
|
ref: 0efbeba41657158a66bbc92c55d1226df56d0f1b # Branch: revanced-manager
|
||||||
device_info_plus: ^8.1.0
|
device_info_plus: ^9.1.0
|
||||||
dynamic_color: ^1.6.3
|
dynamic_color: ^1.6.3
|
||||||
dio: ^5.0.0
|
dio: ^5.0.0
|
||||||
dynamic_themes: ^1.1.0
|
dynamic_themes: ^1.1.0
|
||||||
expandable: ^5.0.1
|
expandable: ^5.0.1
|
||||||
file_picker:
|
|
||||||
git:
|
|
||||||
url: https://github.com/alexmercerind/flutter_file_picker
|
|
||||||
ref: master
|
|
||||||
flex_color_scheme: ^7.0.1
|
flex_color_scheme: ^7.0.1
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_background: ^1.2.0
|
flutter_background:
|
||||||
|
git: # remove once https://github.com/JulianAssmann/flutter_background/pull/79 is merged
|
||||||
|
url: https://github.com/BenjaminHalko/flutter_background
|
||||||
|
ref: 560d21c4148b53933313573e7eafca0b0eb9aadf # Branch: specify-namespace
|
||||||
flutter_cache_manager: ^3.3.0
|
flutter_cache_manager: ^3.3.0
|
||||||
flutter_i18n: ^0.33.0
|
flutter_i18n: ^0.34.0
|
||||||
flutter_local_notifications: ^13.0.0
|
flutter_local_notifications: ^16.1.0
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_svg: ^2.0.4
|
flutter_svg: ^2.0.4
|
||||||
fluttertoast: ^8.2.1
|
fluttertoast: ^8.2.4
|
||||||
font_awesome_flutter: ^10.4.0
|
font_awesome_flutter: ^10.4.0
|
||||||
get_it: 7.2.0
|
get_it: ^7.6.4
|
||||||
google_fonts: ^4.0.3
|
google_fonts: ^6.1.0
|
||||||
http: ^0.13.5
|
http: ^1.1.0
|
||||||
injectable: ^2.1.1
|
injectable: ^2.1.1
|
||||||
intl: ^0.18.0
|
intl: ^0.18.0
|
||||||
json_annotation: ^4.8.0
|
json_annotation: ^4.8.0
|
||||||
logcat:
|
logcat:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/SuaMusica/logcat
|
url: https://github.com/BenjaminHalko/logcat
|
||||||
ref: feature/nullSafe
|
ref: 4a6d5e0e22292c8eb160cfb9365b9ea29735fd43 # Branch: master
|
||||||
package_info_plus: ^3.0.3
|
package_info_plus: ^4.2.0
|
||||||
path_provider: ^2.0.14
|
path_provider: ^2.0.14
|
||||||
permission_handler: ^10.2.0
|
permission_handler: ^11.0.1
|
||||||
pull_to_refresh: ^2.0.0
|
pull_to_refresh: ^2.0.0
|
||||||
root:
|
root:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/EvadeMaster/root
|
url: https://github.com/validcube/root
|
||||||
ref: 82803aa40f63cddff81c3e4d27ce8ce3e7c83f60
|
ref: 68e5678a535a2a3344828a14a39017fa74b9098c # Branch: libsu-521
|
||||||
share_extend: ^2.0.0
|
|
||||||
shared_preferences: ^2.1.0
|
shared_preferences: ^2.1.0
|
||||||
skeletons: ^0.0.3
|
skeletons: ^0.0.3
|
||||||
stacked: ^3.2.0
|
stacked: ^3.2.0
|
||||||
stacked_generator: ^1.1.0
|
stacked_generator: ^1.1.0
|
||||||
stacked_services: ^1.0.0
|
stacked_services: ^1.0.0
|
||||||
stacked_themes: ^0.3.10
|
|
||||||
timeago: ^3.3.0
|
timeago: ^3.3.0
|
||||||
timezone: ^0.9.0
|
timezone: ^0.9.0
|
||||||
url_launcher: ^6.1.10
|
url_launcher: ^6.1.10
|
||||||
wakelock: ^0.6.2
|
|
||||||
flutter_dotenv: ^5.0.2
|
flutter_dotenv: ^5.0.2
|
||||||
flutter_markdown: ^0.6.14
|
flutter_markdown: ^0.6.14
|
||||||
dio_cache_interceptor: ^3.4.0
|
dio_cache_interceptor: ^3.4.0
|
||||||
install_plugin: ^2.1.0
|
install_plugin:
|
||||||
screenshot_callback: ^3.0.1
|
git: # remove once https://github.com/hui-z/flutter_install_plugin/pull/67 is merged
|
||||||
|
url: https://github.com/BenjaminHalko/flutter_install_plugin
|
||||||
|
ref: 5f9b1a8c956fc3355ae655eefcbcadb457bd10f7 # Branch: master
|
||||||
|
screenshot_callback:
|
||||||
|
git: # remove once https://github.com/flutter-moum/flutter_screenshot_callback/pull/81 is merged
|
||||||
|
url: https://github.com/BenjaminHalko/flutter_screenshot_callback
|
||||||
|
ref: 1a1616ac91e16cd1f3dd170a81febf27ffce3587 # Branch: master
|
||||||
synchronized: ^3.1.0
|
synchronized: ^3.1.0
|
||||||
|
connectivity_plus: ^5.0.1
|
||||||
|
flutter_file_dialog: ^3.0.2
|
||||||
|
wakelock_plus: ^1.1.3
|
||||||
|
share_plus: ^7.2.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
json_serializable: ^6.6.1
|
json_serializable: ^6.6.1
|
||||||
build_runner: any
|
build_runner: any
|
||||||
flutter_launcher_icons: ^0.13.0
|
flutter_launcher_icons: ^0.13.0
|
||||||
flutter_lints: ^2.0.1
|
flutter_lints: ^3.0.1
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
injectable_generator: ^2.1.5
|
injectable_generator: ^2.1.5
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
fonts:
|
||||||
|
- family: CustomIcon
|
||||||
|
fonts:
|
||||||
|
- asset: fonts/custom-icons.ttf
|
||||||
assets:
|
assets:
|
||||||
- assets/i18n/
|
- assets/i18n/
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
include ':build.gradle'
|
|
||||||
project(':build.gradle').projectDir = new File(rootDir, 'android/build.gradle')
|
|
||||||
Reference in New Issue
Block a user