Compare commits

...

73 Commits

Author SHA1 Message Date
semantic-release-bot
7104d6d6dd chore(release): 1.19.0-dev.2 [skip ci]
# [1.19.0-dev.2](https://github.com/ReVanced/revanced-manager/compare/v1.19.0-dev.1...v1.19.0-dev.2) (2024-01-22)

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Performance Improvements

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

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

Signed-off-by: validcube <pun.butrach@gmail.com>
2023-12-11 19:02:13 +07:00
Pun Butrach
637641cf54 feat: output suggested version into patch log (#1557)
Signed-off-by: validcube <pun.butrach@gmail.com>
2023-12-11 11:26:52 +07:00
Pun Butrach
d9a316abbb docs(contributing): update branding 2023-12-09 11:49:19 +07:00
Pun Butrach
093cfa5269 docs(contributing): use correct semantic 2023-12-09 11:48:20 +07:00
Pun Butrach
61a025de4d docs: correct styling issue 2023-12-09 11:47:26 +07:00
Dhruvan Bhalara
7df1ae7ed8 feat: append patch version to the APK name while sharing/exporting (#1547) 2023-12-05 12:58:32 +05:45
Pun Butrach
eb6e75b156 ci: use migrate setup-java to Node 20 (#1542) 2023-12-05 09:43:04 +07:00
validcube
8480b3ac3d chore: update repository's owner name 2023-12-02 17:39:02 +07:00
validcube
6339a31fec build(dart-dependency): bump FlutterToast to v8.2.4 2023-12-02 17:35:35 +07:00
validcube
71b5bb3f8f build(dart-dependency): prefer immutable for dependency using git 2023-12-02 17:33:42 +07:00
Ushie
a9878dbbdf chore: Merge dev to main (#1544) 2023-12-02 03:46:17 +03:00
Ushie
b1fb9dd7d3 build: Bump version to v1.17.1 2023-12-02 03:44:51 +03:00
Dhruvan Bhalara
de51fbd7be fix: Incorrect duplicate filename handling when exporting files (#1541) 2023-12-01 20:31:46 +05:45
Ax333l
c9412a97d0 build(dependency): Bump patcher to 19.1.0 2023-12-01 13:22:38 +01:00
Ushie
63c29bdd75 chore: Merge dev to main (#1529) 2023-11-27 11:06:43 +03:00
Ushie
011eddbbc5 chore: Merge dev to main (#1478) 2023-11-21 17:37:40 +03:00
77 changed files with 8426 additions and 1320 deletions

View File

@@ -45,10 +45,10 @@ jobs:
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
run: | run: |
gh repo clone ${{ github.repository }} gh repo clone "${{ github.repository }}"
cd revanced-manager cd revanced-manager
gh repo set-default ${{ github.repository }} gh repo set-default "${{ github.repository }}"
gh pr checkout ${{ inputs.pr-number }} gh pr checkout "${{ inputs.pr-number }}"
echo "DATETIME=$( TZ='UTC+0' date --rfc-email )" >> $GITHUB_ENV echo "DATETIME=$( TZ='UTC+0' date --rfc-email )" >> $GITHUB_ENV
echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
@@ -59,7 +59,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Setup JDK - name: Setup JDK
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: '17'
distribution: 'zulu' distribution: 'zulu'
@@ -83,7 +83,7 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
flutter build apk --${{ inputs.app-flavour }}; flutter build apk --"${{ inputs.app-flavour }}";
- name: Prepare to comment - name: Prepare to comment
run: | run: |

View File

@@ -1,36 +1,61 @@
name: "Release Build" name: "Release Build"
on: on:
workflow_dispatch:
push: push:
tags: branches:
- "v*" - main
- dev
jobs: jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: "17" java-version: "17"
distribution: "zulu" distribution: "zulu"
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
channel: "stable" channel: "stable"
- name: Set up Flutter
run: flutter pub get - name: Cache Node modules
- name: Generate files with Builder uses: actions/cache@v3
run: dart run build_runner build --delete-conflicting-outputs with:
- name: Build with Flutter path: |
node_modules
key: npm-${{ hashFiles('package-lock.json') }}
- name: Setup semantic-release
run: npm install
- name: Get version code
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }} run: |
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} npx semantic-release --extends ./.releaserc.getversion.json --no-ci --dry-run
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} echo "RELEASE_VERSION=v$(cat .VERSION)" >> $GITHUB_ENV
- name: Bump pubspec version
run: |
VERSION=$(echo "${RELEASE_VERSION}" | sed 's/v//')
IFS='.' read -r -a nums <<< "${VERSION/-dev/}.0"
VERSIONCODE=$((nums[0] * 100000000 + nums[1] * 100000 + nums[2] * 100 + nums[3]))
sed -i "/^version/c\\version: $VERSION+$VERSIONCODE" pubspec.yaml
- name: Set up Flutter
run: flutter pub get
- name: Generate files with Builder
run: dart run build_runner build --delete-conflicting-outputs
- name: Build with Flutter
run: flutter build apk run: flutter build apk
- name: Sign APK - name: Sign APK
id: sign_apk id: sign_apk
uses: ilharp/sign-android-release@v1 uses: ilharp/sign-android-release@v1
@@ -40,11 +65,11 @@ jobs:
keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }} keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }}
keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }} keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
- name: Add version to APK
- name: Rename APK
run: mv ${{steps.sign_apk.outputs.signedFile}} revanced-manager-${{ env.RELEASE_VERSION }}.apk run: mv ${{steps.sign_apk.outputs.signedFile}} revanced-manager-${{ env.RELEASE_VERSION }}.apk
- name: Publish release APK - name: Publish release APK
uses: "marvinpinto/action-automatic-releases@latest" env:
with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
repo_token: "${{ secrets.GITHUB_TOKEN }}" run: npx semantic-release --extends ./.releaserc.release.json
prerelease: false
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@
.buildlog/ .buildlog/
.history .history
.svn/ .svn/
local.properties
# IntelliJ related # IntelliJ related
*.iml *.iml

View File

@@ -0,0 +1,24 @@
{
"branches": [
"main",
{
"name": "dev",
"prerelease": true
}
],
"plugins": [
[
"@semantic-release/commit-analyzer", {
"releaseRules": [
{ "type": "build", "scope": "Needs bump", "release": "patch" }
]
}
],
[
"@semantic-release/exec",
{
"verifyReleaseCmd": "echo ${nextRelease.version} > .VERSION"
}
]
]
}

46
.releaserc.release.json Normal file
View File

@@ -0,0 +1,46 @@
{
"branches": [
"main",
{
"name": "dev",
"prerelease": true
}
],
"plugins": [
[
"@semantic-release/commit-analyzer", {
"releaseRules": [
{ "type": "build", "scope": "Needs bump", "release": "patch" }
]
}
],
"@semantic-release/changelog",
"@semantic-release/release-notes-generator",
[
"@semantic-release/git",
{
"assets": [
"pubspec.yaml"
]
}
],
[
"@semantic-release/github",
{
"assets": [
{
"path": "revanced-manager-v*.apk"
}
],
"successComment": false
}
],
[
"@saithodev/semantic-release-backmerge",
{
"backmergeBranches": [{"from": "main", "to": "dev"}],
"clearWorkspace": true
}
]
]
}

View File

@@ -12,7 +12,10 @@
</picture> </picture>
<br> <br>
<a href="https://revanced.app/"> <a href="https://revanced.app/">
<img height="24px" src="assets/revanced-logo/revanced-logo-round.svg" /> <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>&nbsp;&nbsp;&nbsp; </a>&nbsp;&nbsp;&nbsp;
<a href="https://github.com/ReVanced"> <a href="https://github.com/ReVanced">
<picture> <picture>
@@ -21,13 +24,22 @@
</picture> </picture>
</a>&nbsp;&nbsp;&nbsp; </a>&nbsp;&nbsp;&nbsp;
<a href="http://revanced.app/discord"> <a href="http://revanced.app/discord">
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" /> <picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp; </a>&nbsp;&nbsp;&nbsp;
<a href="https://reddit.com/r/revancedapp"> <a href="https://reddit.com/r/revancedapp">
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" /> <picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp; </a>&nbsp;&nbsp;&nbsp;
<a href="https://t.me/app_revanced"> <a href="https://t.me/app_revanced">
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" /> <picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp; </a>&nbsp;&nbsp;&nbsp;
<a href="https://x.com/revancedapp"> <a href="https://x.com/revancedapp">
<picture> <picture>
@@ -36,7 +48,10 @@
</picture> </picture>
</a>&nbsp;&nbsp;&nbsp; </a>&nbsp;&nbsp;&nbsp;
<a href="https://www.youtube.com/@ReVanced"> <a href="https://www.youtube.com/@ReVanced">
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" /> <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> </a>
<br> <br>
<br> <br>
@@ -69,7 +84,7 @@ If you encounter a bug while using the ReVanced Manager app, open an issue using
## 📝 How to contribute ## 📝 How to contribute
> [!NOTE] > [!TIP]
> We recommend that you discuss your changes with > We recommend that you discuss your changes with
> the maintainers of ReVanced Manager before contributing. > the maintainers of ReVanced Manager before contributing.
> This will help you determine whether your change is acceptable. > This will help you determine whether your change is acceptable.

View File

@@ -67,7 +67,7 @@ ReVanced Manager is an Android application that uses ReVanced Patcher to add, re
## 💪 Features ## 💪 Features
We provide the some of the features are: Some of the features we provide are:
* 📱 **Portable**: ReVanced Patcher that fit in your pocket; * 📱 **Portable**: ReVanced Patcher that fit in your pocket;
* 🤗 **Intuitive UI**: Help you manage your patched applications with easy-to-use interface; * 🤗 **Intuitive UI**: Help you manage your patched applications with easy-to-use interface;

78
SECURITY.md Normal file
View File

@@ -0,0 +1,78 @@
<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>&nbsp;&nbsp;&nbsp;
<a href="https://github.com/ReVanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="http://revanced.app/discord">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://reddit.com/r/revancedapp">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://t.me/app_revanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://x.com/revancedapp">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://www.youtube.com/@ReVanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
</picture>
</a>
<br>
<br>
Continuing the legacy of Vanced
</p>
# 🔒 Security Policy
This document describes how to report security vulnerabilities for ReVanced Manager.
## 🚨 Reporting a Vulnerability
Please open an issue in our [advisory tracker](https://github.com/ReVanced/revanced-manager/security/advisories/new) or reach out privately to us on [Discord](https://discord.gg/revanced).
If a vulnerability is confirmed and accepted, you can join our [Discord](https://discord.gg/revanced) server to receive a special contributor role.
### ⏳ Supported Versions
| Version | Branch | Supported |
| ------- | ------------|------------------- |
| v1.19.0 | main | :white_check_mark: |
| latest | dev | :white_check_mark: |
| latest | compose-dev | :white_check_mark: |

View File

@@ -21,7 +21,6 @@ linter:
- always_declare_return_types - always_declare_return_types
- require_trailing_commas - require_trailing_commas
- always_put_control_body_on_new_line - always_put_control_body_on_new_line
- always_require_non_null_named_parameters
- always_use_package_imports # we do this commonly - always_use_package_imports # we do this commonly
- annotate_overrides - annotate_overrides
- avoid_bool_literals_in_conditional_expressions - avoid_bool_literals_in_conditional_expressions
@@ -40,8 +39,6 @@ linter:
- avoid_relative_lib_imports - avoid_relative_lib_imports
- avoid_renaming_method_parameters - avoid_renaming_method_parameters
- avoid_return_types_on_setters - avoid_return_types_on_setters
- avoid_returning_null
- avoid_returning_null_for_future
- avoid_returning_null_for_void - avoid_returning_null_for_void
- avoid_setters_without_getters - avoid_setters_without_getters
- avoid_shadowing_type_parameters - avoid_shadowing_type_parameters
@@ -126,6 +123,7 @@ linter:
- slash_for_doc_comments - slash_for_doc_comments
- sort_child_properties_last - sort_child_properties_last
- sort_constructors_first - sort_constructors_first
- sort_pub_dependencies
- sort_unnamed_constructors_first - sort_unnamed_constructors_first
- test_types_in_equals - test_types_in_equals
- throw_in_finally - throw_in_finally

View File

@@ -99,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:19.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")

View File

@@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
@@ -32,17 +33,17 @@
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<meta-data <meta-data
android:name="io.flutter.embedding.android.NormalTheme" android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"/> android:resource="@style/NormalTheme" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".ExportSettingsActivity" android:name=".ExportSettingsActivity"
android:exported="true"> android:exported="true">
</activity> </activity>
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
@@ -55,5 +56,22 @@
android:name="android.support.FILE_PROVIDER_PATHS" android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" /> android:resource="@xml/file_paths" />
</provider> </provider>
<receiver
android:name=".utils.packageInstaller.InstallerReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="APP_INSTALL_ACTION" />
</intent-filter>
</receiver>
<receiver
android:name=".utils.packageInstaller.UninstallerReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="APP_UNINSTALL_ACTION" />
</intent-filter>
</receiver>
</application> </application>
</manifest> </manifest>

View File

@@ -1,11 +1,16 @@
package app.revanced.manager.flutter package app.revanced.manager.flutter
import android.app.PendingIntent
import android.app.SearchManager import android.app.SearchManager
import android.content.Intent import android.content.Intent
import android.content.pm.PackageInstaller
import android.os.Build
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
import app.revanced.manager.flutter.utils.aligning.ZipAligner import app.revanced.manager.flutter.utils.aligning.ZipAligner
import app.revanced.manager.flutter.utils.packageInstaller.InstallerReceiver
import app.revanced.manager.flutter.utils.packageInstaller.UninstallerReceiver
import app.revanced.manager.flutter.utils.signing.Signer import app.revanced.manager.flutter.utils.signing.Signer
import app.revanced.manager.flutter.utils.zip.ZipFile import app.revanced.manager.flutter.utils.zip.ZipFile
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
@@ -184,12 +189,24 @@ class MainActivity : FlutterActivity() {
}.toString().let(result::success) }.toString().let(result::success)
} }
"installApk" -> {
val apkPath = call.argument<String>("apkPath")!!
PackageInstallerManager.result = result
installApk(apkPath)
}
"uninstallApp" -> {
val packageName = call.argument<String>("packageName")!!
uninstallApp(packageName)
PackageInstallerManager.result = result
}
else -> result.notImplemented() else -> result.notImplemented()
} }
} }
} }
fun openBrowser(query: String?) { private fun openBrowser(query: String?) {
val intent = Intent(Intent.ACTION_WEB_SEARCH).apply { val intent = Intent(Intent.ACTION_WEB_SEARCH).apply {
putExtra(SearchManager.QUERY, query) putExtra(SearchManager.QUERY, query)
} }
@@ -349,7 +366,7 @@ class MainActivity : FlutterActivity() {
return@Thread return@Thread
} }
updateProgress(0.8, "Building...", "") updateProgress(0.75, "Building...", "")
val res = patcher.get() val res = patcher.get()
patcher.close() patcher.close()
@@ -382,7 +399,7 @@ class MainActivity : FlutterActivity() {
return@Thread return@Thread
} }
updateProgress(0.9, "Signing...", "Signing APK") updateProgress(0.8, "Signing...", "Signing APK")
try { try {
Signer("ReVanced", keystorePassword) Signer("ReVanced", keystorePassword)
@@ -392,7 +409,7 @@ class MainActivity : FlutterActivity() {
e.printStackTrace() e.printStackTrace()
} }
updateProgress(1.0, "Patched", "Patched") updateProgress(.85, "Patched", "Patched APK")
} catch (ex: Throwable) { } catch (ex: Throwable) {
if (!cancel) { if (!cancel) {
val stack = ex.stackTraceToString() val stack = ex.stackTraceToString()
@@ -407,4 +424,44 @@ class MainActivity : FlutterActivity() {
handler.post { result.success(null) } handler.post { result.success(null) }
}.start() }.start()
} }
private fun installApk(apkPath: String) {
val packageInstaller: PackageInstaller = applicationContext.packageManager.packageInstaller
val sessionParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val sessionId: Int = packageInstaller.createSession(sessionParams)
val session: PackageInstaller.Session = packageInstaller.openSession(sessionId)
session.use { activeSession ->
val sessionOutputStream = activeSession.openWrite(applicationContext.packageName, 0, -1)
sessionOutputStream.use { outputStream ->
val apkFile = File(apkPath)
apkFile.inputStream().use { inputStream ->
inputStream.copyTo(outputStream)
}
}
}
val receiverIntent = Intent(applicationContext, InstallerReceiver::class.java).apply {
action = "APP_INSTALL_ACTION"
}
val receiverPendingIntent = PendingIntent.getBroadcast(context, sessionId, receiverIntent, PackageInstallerManager.flags)
session.commit(receiverPendingIntent.intentSender)
session.close()
}
private fun uninstallApp(packageName: String) {
val packageInstaller: PackageInstaller = applicationContext.packageManager.packageInstaller
val receiverIntent = Intent(applicationContext, UninstallerReceiver::class.java).apply {
action = "APP_UNINSTALL_ACTION"
}
val receiverPendingIntent = PendingIntent.getBroadcast(context, 0, receiverIntent, PackageInstallerManager.flags)
packageInstaller.uninstall(packageName, receiverPendingIntent.intentSender)
}
object PackageInstallerManager {
var result: MethodChannel.Result? = null
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
}
} }

View File

@@ -0,0 +1,32 @@
package app.revanced.manager.flutter.utils.packageInstaller
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import app.revanced.manager.flutter.MainActivity
class InstallerReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
if (confirmationIntent != null) {
context.startActivity(confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
}
else -> {
val packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
val otherPackageName = intent.getStringExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME)
MainActivity.PackageInstallerManager.result!!.success(mapOf(
"status" to status,
"packageName" to packageName,
"message" to message,
"otherPackageName" to otherPackageName
))
}
}
}
}

View File

@@ -0,0 +1,24 @@
package app.revanced.manager.flutter.utils.packageInstaller
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import app.revanced.manager.flutter.MainActivity
class UninstallerReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
if (confirmationIntent != null) {
context.startActivity(confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
}
else -> {
MainActivity.PackageInstallerManager.result!!.success(status)
}
}
}
}

View File

@@ -1,6 +1,8 @@
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 distributionSha256Sum=9d926787066a081739e8200858338b4a69e837c3a821a33aca9db09dd4a41026
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -72,22 +72,21 @@
"widgetTitle": "Patcher", "widgetTitle": "Patcher",
"patchButton": "Patch", "patchButton": "Patch",
"armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Proceed anyways?", "armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Continue anyways?",
"removedPatchesWarningDialogText": "The following patches have been removed since the last time you used them.\n\n{patches}\n\nProceed anyways?", "removedPatchesWarningDialogText": "The following patches have been removed since the last time you used them.\n\n{patches}\n\nContinue anyways?",
"requiredOptionDialogText" : "Some patch options have to be set." "requiredOptionDialogText" : "Some patch options have to be set."
}, },
"appSelectorCard": { "appSelectorCard": {
"widgetTitle": "Select an application", "widgetTitle": "Select an app",
"widgetTitleSelected": "Selected application", "widgetTitleSelected": "Selected app",
"widgetSubtitle": "No application selected", "widgetSubtitle": "No app selected",
"noAppsLabel": "No applications found", "noAppsLabel": "No applications found",
"notInstalled":"App not installed",
"currentVersion": "Current", "currentVersion": "Current",
"suggestedVersion": "Suggested", "suggestedVersion": "Suggested",
"allVersions": "All versions" "anyVersion": "Any version"
}, },
"patchSelectorCard": { "patchSelectorCard": {
"widgetTitle": "Select patches", "widgetTitle": "Select patches",
@@ -101,20 +100,20 @@
"widgetSubtitle": "We are online!" "widgetSubtitle": "We are online!"
}, },
"appSelectorView": { "appSelectorView": {
"viewTitle": "Select an application", "viewTitle": "Select an app",
"searchBarHint": "Search applications", "searchBarHint": "Search app",
"storageButton": "Storage", "storageButton": "Storage",
"selectFromStorageButton": "Select from storage", "selectFromStorageButton": "Select from storage",
"errorMessage": "Unable to use selected application", "errorMessage": "Unable to use selected application",
"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.", "requireSuggestedAppVersionDialogText": "The version of the app you have selected does not match the suggested version which can lead to unexpected issues. Please use the suggested version.\n\nSelected version: {selected}\nSuggested version: {suggested}\n\nTo continue 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 app is a split APK and can only be patched and installed reliably by mounting with root permissions. However, you can patch and install a full APK by selecting it from storage."
}, },
"patchesSelectorView": { "patchesSelectorView": {
"viewTitle": "Select patches", "viewTitle": "Select patches",
@@ -164,16 +163,18 @@
"installerView": { "installerView": {
"widgetTitle": "Installer", "widgetTitle": "Installer",
"installType": "Select install type", "installType": "Select install type",
"installTypeDescription": "Select the installation type to proceed with.", "installTypeDescription": "Select the installation type to continue with.",
"installButton": "Install", "installButton": "Install",
"installRootType": "Mount", "installRootType": "Mount",
"installNonRootType": "Normal", "installNonRootType": "Regular",
"warning": "Disable auto updates for the patched app to avoid unexpected issues.",
"pressBackAgain": "Press back again to cancel", "pressBackAgain": "Press back again to cancel",
"openButton": "Open", "openButton": "Open",
"shareButton": "Share file", "shareButton": "Share file",
"notificationTitle": "ReVanced Manager is patching", "notificationTitle": "ReVanced Manager is patching",
"notificationText": "Tap to return to the installer", "notificationText": "Tap to return to the installer",
@@ -206,7 +207,7 @@
"englishOption": "English", "englishOption": "English",
"sourcesLabel": "Sources", "sourcesLabel": "Sources",
"sourcesLabelHint": "Configure your sources", "sourcesLabelHint": "Configure the source of patches and integrations",
"sourcesIntegrationsLabel": "Integrations source", "sourcesIntegrationsLabel": "Integrations source",
"sourcesResetDialogTitle": "Reset", "sourcesResetDialogTitle": "Reset",
"sourcesResetDialogText": "Are you sure you want to reset your sources to their default values?", "sourcesResetDialogText": "Are you sure you want to reset your sources to their default values?",
@@ -214,13 +215,13 @@
"sourcesUpdateNote": "Note: 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 API URL", "apiURLHint": "Configure the URL of the API to use",
"selectApiURL": "API URL", "selectApiURL": "API URL",
"hostRepositoryLabel": "Repository API", "hostRepositoryLabel": "Repository API",
"orgPatchesLabel": "Patches organization", "orgPatchesLabel": "Patches organization",
"sourcesPatchesLabel": "Patches source", "sourcesPatchesLabel": "Patches source",
"orgIntegrationsLabel": "Integrations organization", "orgIntegrationsLabel": "Integrations organization",
"contributorsLabel": "Contributors", "contributorsLabel": "Contributors",
"contributorsHint": "A list of contributors of ReVanced", "contributorsHint": "A list of contributors of ReVanced",
@@ -228,7 +229,7 @@
"logsHint": "Share ReVanced Manager logs", "logsHint": "Share ReVanced Manager logs",
"enablePatchesSelectionLabel": "Allow changing patch selection", "enablePatchesSelectionLabel": "Allow changing patch selection",
"enablePatchesSelectionHint": "Allow changing the selection of patches", "enablePatchesSelectionHint": "Do not prevent selecting or deselecting patches",
"enablePatchesSelectionWarningText": "Changing the 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?",
@@ -238,9 +239,10 @@
"universalPatchesHint": "Display all apps and universal patches (may slow down the app list)", "universalPatchesHint": "Display all apps and universal patches (may slow down the app list)",
"versionCompatibilityCheckLabel": "Version compatibility check", "versionCompatibilityCheckLabel": "Version compatibility check",
"versionCompatibilityCheckHint": "Restricts patches to supported app versions", "versionCompatibilityCheckHint": "Prevent selecting patches that are not compatible with the selected app version",
"requireSuggestedAppVersionLabel": "Require suggested app version", "requireSuggestedAppVersionLabel": "Require suggested app version",
"requireSuggestedAppVersionHint": "Enforce selection of suggested app version", "requireSuggestedAppVersionHint": "Prevent selecting an app with a version that is not the suggested",
"requireSuggestedAppVersionDialogText": "Selecting an app that is not the suggested version may cause unexpected issues.\n\nDo you want to proceed anyways?", "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",
@@ -302,22 +304,23 @@
"widgetTitle": "App info", "widgetTitle": "App info",
"openButton": "Open", "openButton": "Open",
"uninstallButton": "Uninstall", "uninstallButton": "Uninstall",
"unpatchButton": "Unpatch", "unmountButton": "Unmount",
"rootDialogTitle": "Error", "rootDialogTitle": "Error",
"unpatchDialogText": "Are you sure you want to unpatch this app?", "unmountDialogText": "Are you sure you want to unmount this app?",
"uninstallDialogText": "Are you sure you want to uninstall this app?",
"rootDialogText": "App was installed with superuser permissions, but currently ReVanced Manager has no permissions.\nPlease grant superuser permissions first.", "rootDialogText": "App was installed with superuser permissions, but currently ReVanced Manager has no permissions.\nPlease grant superuser permissions first.",
"packageNameLabel": "Package name", "packageNameLabel": "Package name",
"installTypeLabel": "Installation type", "installTypeLabel": "Installation type",
"rootTypeLabel": "Root", "mountTypeLabel": "Mount",
"nonRootTypeLabel": "Non-root", "regularTypeLabel": "Regular",
"patchedDateLabel": "Patched date", "patchedDateLabel": "Patched date",
"appliedPatchesLabel": "Applied patches", "appliedPatchesLabel": "Applied patches",
"patchedDateHint": "{date} at {time}", "patchedDateHint": "{date} at {time}",
"appliedPatchesHint": "{quantity} applied patches", "appliedPatchesHint": "{quantity} applied patches",
"updateNotImplemented": "This feature has not been implemented yet" "updateNotImplemented": "This feature has not been implemented yet"
}, },
"contributorsView": { "contributorsView": {
@@ -327,5 +330,34 @@
"integrationsContributors": "Integrations contributors", "integrationsContributors": "Integrations contributors",
"cliContributors": "CLI contributors", "cliContributors": "CLI contributors",
"managerContributors": "Manager contributors" "managerContributors": "Manager contributors"
},
"installErrorDialog": {
"mount_version_mismatch": "Version mismatch",
"mount_no_root": "No root access",
"mount_missing_installation": "Installation not found",
"status_failure_blocked": "Installation blocked",
"install_failed_verification_failure": "Verification failed",
"status_failure_invalid": "Installation invalid",
"install_failed_version_downgrade": "Can't downgrade",
"status_failure_conflict": "Installation conflict",
"status_failure_storage": "Installation storage issue",
"status_failure_incompatible": "Installation incompatible",
"status_failure_timeout": "Installation timeout",
"status_unknown": "Installation failed",
"mount_version_mismatch_description": "The installation failed due to the installed app being a different version than the patched app.\n\nInstall the version of the app you are mounting and try again.",
"mount_no_root_description": "The installation failed due to root access not being granted.\n\nGrant root access to ReVanced Manager and try again.",
"mount_missing_installation_description": "The installation failed due to the unpatched app not being installed on this device in order to mount over it.\n\nInstall the unpatched app before mounting and try again.",
"status_failure_timeout_description": "The installation took too long to finish.\n\nWould you like to try again?",
"status_failure_storage_description": "The installation failed due to insufficient storage.\n\nFree up some space and try again.",
"status_failure_invalid_description": "The installation failed due to the patched app being invalid.\n\nUninstall the app and try again?",
"status_failure_incompatible_description": "The app is incompatible with this device.\n\nContact the developer of the app and ask for support.",
"status_failure_conflict_description": "The installation was prevented by an existing installation of the app.\n\nUninstall the installed app and try again?",
"status_failure_blocked_description": "The installation was blocked by {packageName}.\n\nAdjust your security settings and try again.",
"install_failed_verification_failure_description": "The installation failed due to a verification issue.\n\nAdjust your security settings and try again.",
"install_failed_version_downgrade_description": "The installation failed due to the patched app being a lower version than the installed app.\n\nUninstall the app and try again?",
"status_unknown_description": "The installation failed due to an unknown reason. Please try again."
} }
} }

View File

@@ -5,14 +5,16 @@ In case you encounter any issues while using ReVanced Manager, please refer to t
- 💉 Patching fails with an error - 💉 Patching fails with an error
Make sure ReVanced Manager is up to date by following [🔄 Updating ReVanced Manager](2_3_updating.md) and select the **Default** button when choosing patches. Make sure ReVanced Manager is up to date by following [🔄 Updating ReVanced Manager](2_3_updating.md) and select the **Default** button when choosing patches.
- 🚫 App not installed as package conflicts with an existing package - 🚫 App not installed as package conflicts with an existing package
An existing installation of the app you're trying to patch is conflicting with the patched app. Uninstall the existing app before installing the patched app. An existing installation of the app you're trying to patch conflicts with the patched app (i.e., signature mismatch or downgrade). Uninstall the existing app before installing the patched app.
- ❗️ Error code `135`, `139` or `1` when patching the app - ❗️ Error code `135`, `139` or `1` when patching the app
Your device is not supported. Refer to the [Prerequisites](0_prerequisites.md) page for supported devices. You may be trying to patch a split APK[^1]. This may not work under certain circumstances. In such a case, patch a full APK.
Your device may otherwise be unsupported. Please look at the [Prerequisites](0_prerequisites.md) page for supported devices.
Alternatively, you can use [ReVanced CLI](https://github.com/revanced/revanced-cli) to patch the app. Alternatively, you can use [ReVanced CLI](https://github.com/revanced/revanced-cli) to patch the app.
@@ -25,3 +27,5 @@ In case you encounter any issues while using ReVanced Manager, please refer to t
The next page will teach you how to build ReVanced Manager from source. The next page will teach you how to build ReVanced Manager from source.
Continue: [🔨 Building from source](4_building.md) Continue: [🔨 Building from source](4_building.md)
[^1]: https://developer.android.com/guide/app-bundle/app-bundle-format

View File

@@ -2,29 +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\. Get dependencies
```sh ```sh
flutter pub get flutter pub get
``` ```
4. Delete conflicting outputs 4\. Delete conflicting outputs
> [!TIP]
> Must be run every time you sync your local repository with the remote repository.
```sh ```sh
dart 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.
5. Build the APK
5\. Build the APK
```sh ```sh
flutter build apk flutter build apk

View File

@@ -8,6 +8,7 @@ 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/services/root_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';
@@ -24,6 +25,13 @@ Future main() async {
final String repoUrl = locator<ManagerAPI>().getRepoUrl(); final String repoUrl = locator<ManagerAPI>().getRepoUrl();
locator<GithubAPI>().initialize(repoUrl); locator<GithubAPI>().initialize(repoUrl);
tz.initializeTimeZones(); tz.initializeTimeZones();
// TODO(aAbed): remove in the future, keep it for now during migration.
final rootAPI = RootAPI();
if (await rootAPI.hasRootPermissions()) {
await rootAPI.removeOrphanedFiles();
}
prefs = await SharedPreferences.getInstance(); prefs = await SharedPreferences.getInstance();
runApp(const MyApp()); runApp(const MyApp());

View File

@@ -75,9 +75,8 @@ class Option {
if (json['valueType'] == null) { if (json['valueType'] == null) {
final type = json['optionClassType']; final type = json['optionClassType'];
if (type is String) { if (type is String) {
json['valueType'] = type json['valueType'] =
.replaceAll('PatchOption', '') type.replaceAll('PatchOption', '').replaceAll('List', 'Array');
.replaceAll('List', 'Array');
json['optionClassType'] = null; json['optionClassType'] = null;
} }

View File

@@ -19,7 +19,8 @@ class DownloadManager {
); );
Future<void> initialize() async { Future<void> initialize() async {
_userAgent = 'ReVanced-Manager/${await _managerAPI.getCurrentManagerVersion()}'; _userAgent =
'ReVanced-Manager/${await _managerAPI.getCurrentManagerVersion()}';
} }
Dio initDio(String url) { Dio initDio(String url) {
@@ -72,4 +73,3 @@ class DownloadManager {
); );
} }
} }

View File

@@ -15,7 +15,8 @@ import 'package:revanced_manager/services/github_api.dart';
import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/services/revanced_api.dart'; import 'package:revanced_manager/services/revanced_api.dart';
import 'package:revanced_manager/services/root_api.dart'; import 'package:revanced_manager/services/root_api.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_checkbox_list_tile.dart';
import 'package:revanced_manager/utils/check_for_supported_patch.dart'; import 'package:revanced_manager/utils/check_for_supported_patch.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:timeago/timeago.dart'; import 'package:timeago/timeago.dart';
@@ -24,6 +25,7 @@ import 'package:timeago/timeago.dart';
class ManagerAPI { class ManagerAPI {
final RevancedAPI _revancedAPI = locator<RevancedAPI>(); final RevancedAPI _revancedAPI = locator<RevancedAPI>();
final GithubAPI _githubAPI = locator<GithubAPI>(); final GithubAPI _githubAPI = locator<GithubAPI>();
final Toast _toast = locator<Toast>();
final RootAPI _rootAPI = RootAPI(); final RootAPI _rootAPI = RootAPI();
final String patcherRepo = 'revanced-patcher'; final String patcherRepo = 'revanced-patcher';
final String cliRepo = 'revanced-cli'; final String cliRepo = 'revanced-cli';
@@ -51,20 +53,31 @@ class ManagerAPI {
String? integrationsVersion = ''; String? integrationsVersion = '';
bool isDefaultPatchesRepo() { bool isDefaultPatchesRepo() {
return getPatchesRepo().toLowerCase() == 'revanced/revanced-patches'; return getPatchesRepo().toLowerCase() == defaultPatchesRepo;
} }
bool isDefaultIntegrationsRepo() { bool isDefaultIntegrationsRepo() {
return getIntegrationsRepo().toLowerCase() == return getIntegrationsRepo().toLowerCase() ==
'revanced/revanced-integrations'; defaultIntegrationsRepo;
} }
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 isDynamicThemeAvailable =
(await getSdkVersion()) >= 31; // ANDROID_12_SDK_VERSION = 31
storedPatchesFile = storedPatchesFile =
(await getApplicationDocumentsDirectory()).path + storedPatchesFile; (await getApplicationDocumentsDirectory()).path + storedPatchesFile;
// Migrate to new API URL if not done yet as the old one is sunset.
final bool hasMigrated = _prefs.getBool('migratedToNewApiUrl') ?? false;
if (!hasMigrated) {
final String apiUrl = getApiUrl().toLowerCase();
if (apiUrl.contains('releases.revanced.app')) {
await setApiUrl(''); // Reset to default.
_prefs.setBool('migratedToNewApiUrl', true);
}
}
} }
Future<int> getSdkVersion() async { Future<int> getSdkVersion() async {
@@ -82,6 +95,7 @@ class ManagerAPI {
} }
await _revancedAPI.clearAllCache(); await _revancedAPI.clearAllCache();
await _prefs.setString('apiUrl', url); await _prefs.setString('apiUrl', url);
_toast.showBottom('settingsView.restartAppForChanges');
} }
String getRepoUrl() { String getRepoUrl() {
@@ -585,7 +599,6 @@ class ManagerAPI {
builder: (context) => PopScope( builder: (context) => PopScope(
canPop: false, canPop: false,
child: AlertDialog( child: AlertDialog(
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
title: I18nText('warning'), title: I18nText('warning'),
content: ValueListenableBuilder( content: ValueListenableBuilder(
valueListenable: noShow, valueListenable: noShow,
@@ -605,7 +618,7 @@ class ManagerAPI {
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
CheckboxListTile( HapticCheckboxListTile(
value: value, value: value,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
title: I18nText( title: I18nText(
@@ -620,12 +633,12 @@ class ManagerAPI {
}, },
), ),
actions: [ actions: [
CustomMaterialButton( FilledButton(
label: I18nText('okButton'),
onPressed: () { onPressed: () {
setPatchesChangeWarning(noShow.value); setPatchesChangeWarning(noShow.value);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('okButton'),
), ),
], ],
), ),
@@ -633,7 +646,7 @@ class ManagerAPI {
); );
} }
Future<void> reAssessSavedApps() async { Future<void> rePatchedSavedApps() async {
final List<PatchedApplication> patchedApps = getPatchedApps(); final List<PatchedApplication> patchedApps = getPatchedApps();
// Remove apps that are not installed anymore. // Remove apps that are not installed anymore.

View File

@@ -3,10 +3,11 @@ import 'dart:io';
import 'package:collection/collection.dart'; import 'package:collection/collection.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/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_file_dialog/flutter_file_dialog.dart'; import 'package:flutter_file_dialog/flutter_file_dialog.dart';
import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:install_plugin/install_plugin.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.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';
@@ -18,7 +19,7 @@ import 'package:share_plus/share_plus.dart';
@lazySingleton @lazySingleton
class PatcherAPI { class PatcherAPI {
static const patcherChannel = static const patcherChannel =
MethodChannel('app.revanced.manager.flutter/patcher'); MethodChannel('app.revanced.manager.flutter/patcher');
final ManagerAPI _managerAPI = locator<ManagerAPI>(); final ManagerAPI _managerAPI = locator<ManagerAPI>();
final RootAPI _rootAPI = RootAPI(); final RootAPI _rootAPI = RootAPI();
late Directory _dataDir; late Directory _dataDir;
@@ -79,7 +80,8 @@ class PatcherAPI {
} }
Future<List<ApplicationWithIcon>> getFilteredInstalledApps( Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
bool showUniversalPatches,) async { bool showUniversalPatches,
) async {
final List<ApplicationWithIcon> filteredApps = []; final List<ApplicationWithIcon> filteredApps = [];
final bool allAppsIncluded = final bool allAppsIncluded =
_universalPatches.isNotEmpty && showUniversalPatches; _universalPatches.isNotEmpty && showUniversalPatches;
@@ -121,11 +123,11 @@ class PatcherAPI {
final List<Patch> patches = _patches final List<Patch> patches = _patches
.where( .where(
(patch) => (patch) =>
patch.compatiblePackages.isEmpty || patch.compatiblePackages.isEmpty ||
!patch.name.contains('settings') && !patch.name.contains('settings') &&
patch.compatiblePackages patch.compatiblePackages
.any((pack) => pack.name == packageName), .any((pack) => pack.name == packageName),
) )
.toList(); .toList();
if (!_managerAPI.areUniversalPatchesEnabled()) { if (!_managerAPI.areUniversalPatchesEnabled()) {
filteredPatches[packageName] = patches filteredPatches[packageName] = patches
@@ -137,22 +139,27 @@ class PatcherAPI {
return filteredPatches[packageName]; return filteredPatches[packageName];
} }
Future<List<Patch>> getAppliedPatches(List<String> appliedPatches,) async { Future<List<Patch>> getAppliedPatches(
List<String> appliedPatches,
) async {
return _patches return _patches
.where((patch) => appliedPatches.contains(patch.name)) .where((patch) => appliedPatches.contains(patch.name))
.toList(); .toList();
} }
Future<void> runPatcher(String packageName, Future<void> runPatcher(
String apkFilePath, String packageName,
List<Patch> selectedPatches,) async { String apkFilePath,
List<Patch> selectedPatches,
) async {
final File? integrationsFile = await _managerAPI.downloadIntegrations(); final File? integrationsFile = await _managerAPI.downloadIntegrations();
final Map<String, Map<String, dynamic>> options = {}; final Map<String, Map<String, dynamic>> options = {};
for (final patch in selectedPatches) { for (final patch in selectedPatches) {
if (patch.options.isNotEmpty) { if (patch.options.isNotEmpty) {
final Map<String, dynamic> patchOptions = {}; final Map<String, dynamic> patchOptions = {};
for (final option in patch.options) { for (final option in patch.options) {
final patchOption = _managerAPI.getPatchOption(packageName, patch.name, option.key); final patchOption =
_managerAPI.getPatchOption(packageName, patch.name, option.key);
if (patchOption != null) { if (patchOption != null) {
patchOptions[patchOption.key] = patchOption.value; patchOptions[patchOption.key] = patchOption.value;
} }
@@ -194,131 +201,315 @@ class PatcherAPI {
} }
} }
} }
} }
Future<void> stopPatcher() async { Future<void> stopPatcher() async {
try { try {
await patcherChannel.invokeMethod('stopPatcher'); await patcherChannel.invokeMethod('stopPatcher');
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
print(e); print(e);
}
} }
} }
}
Future<bool> installPatchedFile(PatchedApplication patchedApp) async { Future<int> installPatchedFile(
if (outFile != null) { BuildContext context,
try { PatchedApplication patchedApp,
if (patchedApp.isRooted) { ) async {
final bool hasRootPermissions = await _rootAPI.hasRootPermissions(); if (outFile != null) {
if (hasRootPermissions) { _managerAPI.ctx = context;
return _rootAPI.installApp( try {
patchedApp.packageName, if (patchedApp.isRooted) {
patchedApp.apkFilePath, final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
outFile!.path, final packageVersion = await DeviceApps.getApp(patchedApp.packageName)
); .then((app) => app?.versionName);
if (!hasRootPermissions) {
installErrorDialog(1);
} else if (packageVersion == null) {
installErrorDialog(1.2);
} else if (packageVersion == patchedApp.version) {
return await _rootAPI.install(
patchedApp.packageName,
patchedApp.apkFilePath,
outFile!.path,
)
? 0
: 1;
} else {
installErrorDialog(1.1);
}
} else {
if (await _rootAPI.hasRootPermissions()) {
await _rootAPI.uninstall(patchedApp.packageName);
}
if (context.mounted) {
return await installApk(
context,
outFile!.path,
);
}
} }
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
}
return 1;
}
Future<int> installApk(
BuildContext context,
String apkPath,
) async {
try {
final status = await patcherChannel.invokeMethod('installApk', {
'apkPath': apkPath,
});
final int statusCode = status['status'];
final String message = status['message'];
final bool hasExtra =
message.contains('INSTALL_FAILED_VERIFICATION_FAILURE') ||
message.contains('INSTALL_FAILED_VERSION_DOWNGRADE');
if (statusCode == 0 || (statusCode == 3 && !hasExtra)) {
return statusCode;
} else { } else {
final install = await InstallPlugin.installApk(outFile!.path); _managerAPI.ctx = context;
return install['isSuccess']; return await installErrorDialog(
statusCode,
status,
hasExtra,
);
} }
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
print(e); print(e);
} }
return false; return 3;
} }
} }
return false;
}
void exportPatchedFile(String appName, String version) { Future<int> installErrorDialog(
try { num statusCode, [
if (outFile != null) { status,
final String newName = _getFileName(appName, version); bool hasExtra = false,
FlutterFileDialog.saveFile( ]) async {
params: SaveFileDialogParams( final String statusValue = InstallStatus.byCode(
sourceFilePath: outFile!.path, hasExtra ? double.parse('$statusCode.1') : statusCode,
fileName: newName,
),
);
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
}
void sharePatchedFile(String appName, String version) {
try {
if (outFile != null) {
final String newName = _getFileName(appName, version);
final int lastSeparator = outFile!.path.lastIndexOf('/');
final String newPath =
outFile!.path.substring(0, lastSeparator + 1) + newName;
final File shareFile = outFile!.copySync(newPath);
Share.shareXFiles([XFile(shareFile.path)]);
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
}
String _getFileName(String appName, String version) {
final String prefix = appName.toLowerCase().replaceAll(' ', '-');
final String newName = '$prefix-revanced_v$version.apk';
return newName;
}
Future<void> exportPatcherLog(String logs) async {
final Directory appCache = await getTemporaryDirectory();
final Directory logDir = Directory('${appCache.path}/logs');
logDir.createSync();
final String dateTime = DateTime.now()
.toIso8601String()
.replaceAll('-', '')
.replaceAll(':', '')
.replaceAll('T', '')
.replaceAll('.', '');
final String fileName = 'revanced-manager_patcher_$dateTime.txt';
final File log = File('${logDir.path}/$fileName');
log.writeAsStringSync(logs);
FlutterFileDialog.saveFile(
params: SaveFileDialogParams(
sourceFilePath: log.path,
fileName: fileName,
),
);
}
String getSuggestedVersion(String packageName) {
final Map<String, int> versions = {};
for (final Patch patch in _patches) {
final Package? package = patch.compatiblePackages.firstWhereOrNull(
(pack) => pack.name == packageName,
); );
if (package != null) { bool cleanInstall = false;
for (final String version in package.versions) { final bool isFixable = statusCode == 4 || statusCode == 5;
versions.update( await showDialog(
version, context: _managerAPI.ctx!,
(value) => versions[version]! + 1, builder: (context) => AlertDialog(
ifAbsent: () => 1, backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
title: I18nText('installErrorDialog.$statusValue'),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
I18nText(
'installErrorDialog.${statusValue}_description',
translationParams: statusCode == 2
? {
'packageName': status['otherPackageName'],
}
: null,
),
],
),
actions: (status == null)
? <Widget>[
FilledButton(
onPressed: () async {
Navigator.pop(context);
},
child: I18nText('okButton'),
),
]
: <Widget>[
if (!isFixable)
FilledButton(
onPressed: () {
Navigator.pop(context);
},
child: I18nText('cancelButton'),
)
else
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: I18nText('cancelButton'),
),
if (isFixable)
FilledButton(
onPressed: () async {
final int response = await patcherChannel.invokeMethod(
'uninstallApp',
{'packageName': status['packageName']},
);
if (response == 0 && context.mounted) {
cleanInstall = true;
Navigator.pop(context);
}
},
child: I18nText('okButton'),
),
],
),
);
return cleanInstall ? 10 : 1;
}
void exportPatchedFile(String appName, String version) {
try {
if (outFile != null) {
final String newName = _getFileName(appName, version);
FlutterFileDialog.saveFile(
params: SaveFileDialogParams(
sourceFilePath: outFile!.path,
fileName: newName,
mimeTypesFilter: ['application/vnd.android.package-archive'],
),
); );
} }
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} }
} }
if (versions.isNotEmpty) {
final entries = versions.entries.toList() void sharePatchedFile(String appName, String version) {
..sort((a, b) => a.value.compareTo(b.value)); try {
versions if (outFile != null) {
..clear() final String newName = _getFileName(appName, version);
..addEntries(entries); final int lastSeparator = outFile!.path.lastIndexOf('/');
versions.removeWhere((key, value) => value != versions.values.last); final String newPath =
return (versions.keys.toList() outFile!.path.substring(0, lastSeparator + 1) + newName;
..sort()).last; final File shareFile = outFile!.copySync(newPath);
Share.shareXFiles([XFile(shareFile.path)]);
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
} }
return '';
}} String _getFileName(String appName, String version) {
final String patchVersion = _managerAPI.patchesVersion!;
final String prefix = appName.toLowerCase().replaceAll(' ', '-');
final String newName =
'$prefix-revanced_v$version-patches_$patchVersion.apk';
return newName;
}
Future<void> exportPatcherLog(String logs) async {
final Directory appCache = await getTemporaryDirectory();
final Directory logDir = Directory('${appCache.path}/logs');
logDir.createSync();
final String dateTime = DateTime.now()
.toIso8601String()
.replaceAll('-', '')
.replaceAll(':', '')
.replaceAll('T', '')
.replaceAll('.', '');
final String fileName = 'revanced-manager_patcher_$dateTime.txt';
final File log = File('${logDir.path}/$fileName');
log.writeAsStringSync(logs);
FlutterFileDialog.saveFile(
params: SaveFileDialogParams(
sourceFilePath: log.path,
fileName: fileName,
),
);
}
String getSuggestedVersion(String packageName) {
final Map<String, int> versions = {};
for (final Patch patch in _patches) {
final Package? package = patch.compatiblePackages.firstWhereOrNull(
(pack) => pack.name == packageName,
);
if (package != null) {
for (final String version in package.versions) {
versions.update(
version,
(value) => versions[version]! + 1,
ifAbsent: () => 1,
);
}
}
}
if (versions.isNotEmpty) {
final entries = versions.entries.toList()
..sort((a, b) => a.value.compareTo(b.value));
versions
..clear()
..addEntries(entries);
versions.removeWhere((key, value) => value != versions.values.last);
return (versions.keys.toList()..sort()).last;
}
return '';
}
}
enum InstallStatus {
mountNoRoot(1),
mountVersionMismatch(1.1),
mountMissingInstallation(1.2),
statusFailureBlocked(2),
installFailedVerificationFailure(3.1),
statusFailureInvalid(4),
installFailedVersionDowngrade(4.1),
statusFailureConflict(5),
statusFailureStorage(6),
statusFailureIncompatible(7),
statusFailureTimeout(8);
const InstallStatus(this.statusCode);
final double statusCode;
static String byCode(num code) {
try {
return InstallStatus.values
.firstWhere((flag) => flag.statusCode == code)
.status;
} catch (e) {
return 'status_unknown';
}
}
}
extension InstallStatusExtension on InstallStatus {
String get status {
switch (this) {
case InstallStatus.mountNoRoot:
return 'mount_no_root';
case InstallStatus.mountVersionMismatch:
return 'mount_version_mismatch';
case InstallStatus.mountMissingInstallation:
return 'mount_missing_installation';
case InstallStatus.statusFailureBlocked:
return 'status_failure_blocked';
case InstallStatus.installFailedVerificationFailure:
return 'install_failed_verification_failure';
case InstallStatus.statusFailureInvalid:
return 'status_failure_invalid';
case InstallStatus.installFailedVersionDowngrade:
return 'install_failed_version_downgrade';
case InstallStatus.statusFailureConflict:
return 'status_failure_conflict';
case InstallStatus.statusFailureStorage:
return 'status_failure_storage';
case InstallStatus.statusFailureIncompatible:
return 'status_failure_incompatible';
case InstallStatus.statusFailureTimeout:
return 'status_failure_timeout';
}
}
}

View File

@@ -32,7 +32,7 @@ class RevancedAPI {
final response = await _dio.get('/contributors'); final response = await _dio.get('/contributors');
final List<dynamic> repositories = response.data['repositories']; final List<dynamic> repositories = response.data['repositories'];
for (final Map<String, dynamic> repo in repositories) { for (final Map<String, dynamic> repo in repositories) {
final String name = repo['name']; final String name = repo['name'].toLowerCase();
contributors[name] = repo['contributors']; contributors[name] = repo['contributors'];
} }
} on Exception catch (e) { } on Exception catch (e) {
@@ -54,7 +54,7 @@ class RevancedAPI {
final List<dynamic> tools = response.data['tools']; final List<dynamic> tools = response.data['tools'];
return tools.firstWhereOrNull( return tools.firstWhereOrNull(
(t) => (t) =>
t['repository'] == repoName && (t['repository'] as String).toLowerCase() == repoName.toLowerCase() &&
(t['name'] as String).endsWith(extension), (t['name'] as String).endsWith(extension),
); );
} on Exception catch (e) { } on Exception catch (e) {

View File

@@ -2,10 +2,10 @@ import 'package:flutter/foundation.dart';
import 'package:root/root.dart'; import 'package:root/root.dart';
class RootAPI { class RootAPI {
// TODO(ponces): remove in the future, keep it for now during migration. // TODO(aAbed): remove in the future, keep it for now during migration.
final String _revancedOldDirPath = '/data/local/tmp/revanced-manager';
final String _revancedDirPath = '/data/adb/revanced';
final String _postFsDataDirPath = '/data/adb/post-fs-data.d'; final String _postFsDataDirPath = '/data/adb/post-fs-data.d';
final String _revancedDirPath = '/data/adb/revanced';
final String _serviceDDirPath = '/data/adb/service.d'; final String _serviceDDirPath = '/data/adb/service.d';
Future<bool> isRooted() async { Future<bool> isRooted() async {
@@ -43,21 +43,22 @@ class RootAPI {
String filePath, String filePath,
) async { ) async {
try { try {
final StringBuffer commands = StringBuffer();
if (permissions.isNotEmpty) { if (permissions.isNotEmpty) {
await Root.exec( commands.writeln('chmod $permissions $filePath');
cmd: 'chmod $permissions "$filePath"',
);
} }
if (ownerGroup.isNotEmpty) { if (ownerGroup.isNotEmpty) {
await Root.exec( commands.writeln('chown $ownerGroup $filePath');
cmd: 'chown $ownerGroup "$filePath"',
);
} }
if (seLinux.isNotEmpty) { if (seLinux.isNotEmpty) {
await Root.exec( commands.writeln('chcon $seLinux $filePath');
cmd: 'chcon $seLinux "$filePath"',
);
} }
await Root.exec(
cmd: commands.toString(),
);
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
print(e); print(e);
@@ -75,18 +76,7 @@ class RootAPI {
Future<List<String>> getInstalledApps() async { Future<List<String>> getInstalledApps() async {
final List<String> apps = List.empty(growable: true); final List<String> apps = List.empty(growable: true);
try { try {
String? res = await Root.exec( final String? res = await Root.exec(cmd: 'ls $_revancedDirPath');
cmd: 'ls "$_revancedDirPath"',
);
if (res != null) {
final List<String> list = res.split('\n');
list.removeWhere((pack) => pack.isEmpty);
apps.addAll(list.map((pack) => pack.trim()).toList());
}
// TODO(ponces): remove in the future, keep it for now during migration.
res = await Root.exec(
cmd: 'ls "$_revancedOldDirPath"',
);
if (res != null) { if (res != null) {
final List<String> list = res.split('\n'); final List<String> list = res.split('\n');
list.removeWhere((pack) => pack.isEmpty); list.removeWhere((pack) => pack.isEmpty);
@@ -100,49 +90,45 @@ class RootAPI {
return apps; return apps;
} }
Future<void> deleteApp(String packageName, String originalFilePath) async { Future<void> uninstall(String packageName) async {
await Root.exec( await Root.exec(
cmd: 'am force-stop "$packageName"', cmd: '''
); grep $packageName /proc/mounts | while read -r line; do echo \$line | cut -d " " -f 2 | sed "s/apk.*/apk/" | xargs -r umount -l; done
await Root.exec( rm -rf $_revancedDirPath/$packageName $_serviceDDirPath/$packageName.sh
cmd: 'su -mm -c "umount -l $originalFilePath"', ''',
);
// TODO(ponces): remove in the future, keep it for now during migration.
await Root.exec(
cmd: 'rm -rf "$_revancedOldDirPath/$packageName"',
);
await Root.exec(
cmd: 'rm -rf "$_revancedDirPath/$packageName"',
);
await Root.exec(
cmd: 'rm -rf "$_serviceDDirPath/$packageName.sh"',
);
await Root.exec(
cmd: 'rm -rf "$_postFsDataDirPath/$packageName.sh"',
); );
} }
Future<bool> installApp( // TODO(aAbed): remove in the future, keep it for now during migration.
Future<void> removeOrphanedFiles() async {
await Root.exec(
cmd: '''
find $_revancedDirPath -type f -name original.apk -delete
for file in "$_serviceDDirPath"/*; do
filename=\$(basename "\$file")
if [ -f "$_postFsDataDirPath/\$filename" ]; then
rm "$_postFsDataDirPath/\$filename"
fi
done
''',
);
}
Future<bool> install(
String packageName, String packageName,
String originalFilePath, String originalFilePath,
String patchedFilePath, String patchedFilePath,
) async { ) async {
try { try {
await deleteApp(packageName, originalFilePath);
await Root.exec(
cmd: 'mkdir -p "$_revancedDirPath/$packageName"',
);
await setPermissions( await setPermissions(
'0755', '0755',
'shell:shell', 'shell:shell',
'', '',
'$_revancedDirPath/$packageName', '$_revancedDirPath/$packageName',
); );
await saveOriginalFilePath(packageName, originalFilePath); await installPatchedApk(packageName, patchedFilePath);
await installServiceDScript(packageName); await installServiceDScript(packageName);
await installPostFsDataScript(packageName); await runMountScript(packageName);
await installApk(packageName, patchedFilePath);
await mountApk(packageName, originalFilePath);
return true; return true;
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
@@ -154,38 +140,48 @@ class RootAPI {
Future<void> installServiceDScript(String packageName) async { Future<void> installServiceDScript(String packageName) async {
await Root.exec( await Root.exec(
cmd: 'mkdir -p "$_serviceDDirPath"', cmd: 'mkdir -p $_serviceDDirPath',
); );
final String content = '#!/system/bin/sh\n' final String mountScript = '''
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 3; done\n' #!/system/bin/sh
'base_path=$_revancedDirPath/$packageName/base.apk\n' # Mount using Magisk mirror, if available.
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n' MAGISKTMP="\$( magisk --path )" || MAGISKTMP=/sbin
r'[ ! -z $stock_path ] && mount -o bind $base_path $stock_path'; MIRROR="\$MAGISKTMP/.magisk/mirror"
if [ ! -f \$MIRROR ]; then
MIRROR=""
fi
until [ "\$(getprop sys.boot_completed)" = 1 ]; do sleep 3; done
until [ -d "/sdcard/Android" ]; do sleep 1; done
# Unmount any existing installation to prevent multiple unnecessary mounts.
grep $packageName /proc/mounts | while read -r line; do echo \$line | cut -d " " -f 2 | sed "s/apk.*/apk/" | xargs -r umount -l; done
base_path=$_revancedDirPath/$packageName/base.apk
stock_path=\$(pm path $packageName | grep base | sed "s/package://g" )
chcon u:object_r:apk_data_file:s0 \$base_path
mount -o bind \$MIRROR\$base_path \$stock_path
# Kill the app to force it to restart the mounted APK in case it is already running
am force-stop $packageName
'''
.trimMultilineString();
final String scriptFilePath = '$_serviceDDirPath/$packageName.sh'; final String scriptFilePath = '$_serviceDDirPath/$packageName.sh';
await Root.exec( await Root.exec(
cmd: 'echo \'$content\' > "$scriptFilePath"', cmd: 'echo \'$mountScript\' > "$scriptFilePath"',
); );
await setPermissions('0744', '', '', scriptFilePath); await setPermissions('0744', '', '', scriptFilePath);
} }
Future<void> installPostFsDataScript(String packageName) async { Future<void> installPatchedApk(
await Root.exec( String packageName, String patchedFilePath,) async {
cmd: 'mkdir -p "$_postFsDataDirPath"',
);
final String content = '#!/system/bin/sh\n'
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
r'[ ! -z $stock_path ] && umount -l $stock_path';
final String scriptFilePath = '$_postFsDataDirPath/$packageName.sh';
await Root.exec(
cmd: 'echo \'$content\' > "$scriptFilePath"',
);
await setPermissions('0744', '', '', scriptFilePath);
}
Future<void> installApk(String packageName, String patchedFilePath) async {
final String newPatchedFilePath = '$_revancedDirPath/$packageName/base.apk'; final String newPatchedFilePath = '$_revancedDirPath/$packageName/base.apk';
await Root.exec( await Root.exec(
cmd: 'cp "$patchedFilePath" "$newPatchedFilePath"', cmd: '''
mkdir -p $_revancedDirPath/$packageName
cp "$patchedFilePath" $newPatchedFilePath
''',
); );
await setPermissions( await setPermissions(
'0644', '0644',
@@ -195,50 +191,10 @@ class RootAPI {
); );
} }
Future<void> mountApk(String packageName, String originalFilePath) async { Future<void> runMountScript(
final String newPatchedFilePath = '$_revancedDirPath/$packageName/base.apk';
await Root.exec(
cmd: 'am force-stop "$packageName"',
);
await Root.exec(
cmd: 'su -mm -c "umount -l $originalFilePath"',
);
await Root.exec(
cmd: 'su -mm -c "mount -o bind $newPatchedFilePath $originalFilePath"',
);
}
Future<bool> isMounted(String packageName) async {
final String? res = await Root.exec(
cmd: 'cat /proc/mounts | grep $packageName',
);
return res != null && res.isNotEmpty;
}
Future<void> saveOriginalFilePath(
String packageName, String packageName,
String originalFilePath,
) async { ) async {
final String originalRootPath = await Root.exec(cmd: '.$_serviceDDirPath/$packageName.sh');
'$_revancedDirPath/$packageName/original.apk';
await Root.exec(
cmd: 'mkdir -p "$_revancedDirPath/$packageName"',
);
await setPermissions(
'0755',
'shell:shell',
'',
'$_revancedDirPath/$packageName',
);
await Root.exec(
cmd: 'cp "$originalFilePath" "$originalRootPath"',
);
await setPermissions(
'0644',
'shell:shell',
'u:object_r:apk_data_file:s0',
originalFilePath,
);
} }
Future<bool> fileExists(String path) async { Future<bool> fileExists(String path) async {
@@ -255,3 +211,10 @@ class RootAPI {
} }
} }
} }
// Remove leading spaces manually until
// https://github.com/dart-lang/language/issues/559 is closed
extension StringExtension on String {
String trimMultilineString() =>
split('\n').map((line) => line.trim()).join('\n').trim();
}

View File

@@ -25,7 +25,8 @@ class DynamicThemeBuilder extends StatefulWidget {
State<DynamicThemeBuilder> createState() => _DynamicThemeBuilderState(); State<DynamicThemeBuilder> createState() => _DynamicThemeBuilderState();
} }
class _DynamicThemeBuilderState extends State<DynamicThemeBuilder> with WidgetsBindingObserver { class _DynamicThemeBuilderState extends State<DynamicThemeBuilder>
with WidgetsBindingObserver {
Brightness brightness = PlatformDispatcher.instance.platformBrightness; Brightness brightness = PlatformDispatcher.instance.platformBrightness;
final ManagerAPI _managerAPI = locator<ManagerAPI>(); final ManagerAPI _managerAPI = locator<ManagerAPI>();
@@ -43,8 +44,9 @@ class _DynamicThemeBuilderState extends State<DynamicThemeBuilder> with WidgetsB
if (_managerAPI.getThemeMode() < 2) { if (_managerAPI.getThemeMode() < 2) {
SystemChrome.setSystemUIOverlayStyle( SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle( SystemUiOverlayStyle(
systemNavigationBarIconBrightness: systemNavigationBarIconBrightness: brightness == Brightness.light
brightness == Brightness.light ? Brightness.dark : Brightness.light, ? Brightness.dark
: Brightness.light,
), ),
); );
} }
@@ -83,24 +85,31 @@ class _DynamicThemeBuilderState extends State<DynamicThemeBuilder> with WidgetsB
return DynamicTheme( return DynamicTheme(
themeCollection: ThemeCollection( themeCollection: ThemeCollection(
themes: { themes: {
0: brightness == Brightness.light ? lightCustomTheme : darkCustomTheme, 0: brightness == Brightness.light
1: brightness == Brightness.light ? lightDynamicTheme : darkDynamicTheme, ? lightCustomTheme
: darkCustomTheme,
1: brightness == Brightness.light
? lightDynamicTheme
: darkDynamicTheme,
2: lightCustomTheme, 2: lightCustomTheme,
3: lightDynamicTheme, 3: lightDynamicTheme,
4: darkCustomTheme, 4: darkCustomTheme,
5: darkDynamicTheme, 5: darkDynamicTheme,
}, },
fallbackTheme: PlatformDispatcher.instance.platformBrightness == Brightness.light ? lightCustomTheme : darkCustomTheme, fallbackTheme: PlatformDispatcher.instance.platformBrightness ==
Brightness.light
? lightCustomTheme
: darkCustomTheme,
), ),
builder: (context, theme) => MaterialApp( builder: (context, theme) => MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: widget.title, title: widget.title,
navigatorKey: StackedService.navigatorKey, navigatorKey: StackedService.navigatorKey,
onGenerateRoute: StackedRouter().onGenerateRoute, onGenerateRoute: StackedRouter().onGenerateRoute,
theme: theme, theme: theme,
home: widget.home, home: widget.home,
localizationsDelegates: widget.localizationsDelegates, localizationsDelegates: widget.localizationsDelegates,
), ),
); );
}, },
); );

View File

@@ -4,6 +4,7 @@ import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.da
import 'package:revanced_manager/ui/widgets/appSelectorView/app_skeleton_loader.dart'; import 'package:revanced_manager/ui/widgets/appSelectorView/app_skeleton_loader.dart';
import 'package:revanced_manager/ui/widgets/appSelectorView/installed_app_item.dart'; import 'package:revanced_manager/ui/widgets/appSelectorView/installed_app_item.dart';
import 'package:revanced_manager/ui/widgets/appSelectorView/not_installed_app_item.dart'; import 'package:revanced_manager/ui/widgets/appSelectorView/not_installed_app_item.dart';
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_floating_action_button_extended.dart';
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart'; import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
import 'package:stacked/stacked.dart' hide SkeletonLoader; import 'package:stacked/stacked.dart' hide SkeletonLoader;
@@ -23,7 +24,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
onViewModelReady: (model) => model.initialize(), onViewModelReady: (model) => model.initialize(),
viewModelBuilder: () => AppSelectorViewModel(), viewModelBuilder: () => AppSelectorViewModel(),
builder: (context, model, child) => Scaffold( builder: (context, model, child) => Scaffold(
floatingActionButton: FloatingActionButton.extended( floatingActionButton: HapticFloatingActionButtonExtended(
label: I18nText('appSelectorView.storageButton'), label: I18nText('appSelectorView.storageButton'),
icon: const Icon(Icons.sd_storage), icon: const Icon(Icons.sd_storage),
onPressed: () { onPressed: () {
@@ -40,8 +41,8 @@ class _AppSelectorViewState extends State<AppSelectorView> {
), ),
titleTextStyle: TextStyle( titleTextStyle: TextStyle(
fontSize: 22.0, 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,
@@ -94,9 +95,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
), ),
child: Column( child: Column(
children: [ children: [
...model ...model.getFilteredApps(_query).map(
.getFilteredApps(_query)
.map(
(app) => InstalledAppItem( (app) => InstalledAppItem(
name: app.appName, name: app.appName,
pkgName: app.packageName, pkgName: app.packageName,
@@ -117,11 +116,8 @@ class _AppSelectorViewState extends State<AppSelectorView> {
packageName: app.packageName, packageName: app.packageName,
), ),
), ),
) ),
, ...model.getFilteredAppsNames(_query).map(
...model
.getFilteredAppsNames(_query)
.map(
(app) => NotInstalledAppItem( (app) => NotInstalledAppItem(
name: app, name: app,
patchesCount: model.patchesCount(app), patchesCount: model.patchesCount(app),
@@ -135,8 +131,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
packageName: app, packageName: app,
), ),
), ),
) ),
,
const SizedBox(height: 70.0), const SizedBox(height: 70.0),
], ],
), ),

View File

@@ -13,7 +13,6 @@ 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/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';
@@ -77,7 +76,7 @@ class AppSelectorViewModel extends BaseViewModel {
final String suggestedVersion = getSuggestedVersion(packageName); final String suggestedVersion = getSuggestedVersion(packageName);
if (suggestedVersion.isNotEmpty) { if (suggestedVersion.isNotEmpty) {
await openDefaultBrowser('$packageName apk version v$suggestedVersion'); await openDefaultBrowser('$packageName apk version $suggestedVersion');
} else { } else {
await openDefaultBrowser('$packageName apk'); await openDefaultBrowser('$packageName apk');
} }
@@ -105,7 +104,8 @@ class AppSelectorViewModel extends BaseViewModel {
]) async { ]) async {
final String suggestedVersion = final String suggestedVersion =
getSuggestedVersion(application.packageName); getSuggestedVersion(application.packageName);
if (application.versionName != suggestedVersion && suggestedVersion.isNotEmpty) { if (application.versionName != suggestedVersion &&
suggestedVersion.isNotEmpty) {
_managerAPI.suggestedAppVersionSelected = false; _managerAPI.suggestedAppVersionSelected = false;
if (_managerAPI.isRequireSuggestedAppVersionEnabled() && if (_managerAPI.isRequireSuggestedAppVersionEnabled() &&
context.mounted) { context.mounted) {
@@ -168,7 +168,6 @@ class AppSelectorViewModel extends BaseViewModel {
return showDialog( return showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
title: I18nText('warning'), title: I18nText('warning'),
content: I18nText( content: I18nText(
'appSelectorView.requireSuggestedAppVersionDialogText', 'appSelectorView.requireSuggestedAppVersionDialogText',
@@ -185,9 +184,9 @@ class AppSelectorViewModel extends BaseViewModel {
), ),
), ),
actions: [ actions: [
CustomMaterialButton( FilledButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: I18nText('okButton'),
), ),
], ],
), ),
@@ -232,12 +231,12 @@ class AppSelectorViewModel extends BaseViewModel {
), ),
), ),
const SizedBox(height: 30), const SizedBox(height: 30),
CustomMaterialButton( FilledButton(
onPressed: () async { onPressed: () async {
Navigator.pop(innerContext); Navigator.pop(innerContext);
await selectAppFromStorage(context); await selectAppFromStorage(context);
}, },
label: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon(Icons.sd_card), const Icon(Icons.sd_card),
@@ -247,12 +246,11 @@ class AppSelectorViewModel extends BaseViewModel {
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
CustomMaterialButton( TextButton(
isFilled: false,
onPressed: () { onPressed: () {
Navigator.pop(innerContext); Navigator.pop(innerContext);
}, },
label: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const SizedBox(width: 10), const SizedBox(width: 10),

View File

@@ -14,9 +14,9 @@ class ContributorsViewModel extends BaseViewModel {
final Map<String, List<dynamic>> contributors = final Map<String, List<dynamic>> contributors =
await _managerAPI.getContributors(); await _managerAPI.getContributors();
patcherContributors = contributors[_managerAPI.defaultPatcherRepo] ?? []; patcherContributors = contributors[_managerAPI.defaultPatcherRepo] ?? [];
patchesContributors = contributors[_managerAPI.getPatchesRepo()] ?? []; patchesContributors = contributors[_managerAPI.getPatchesRepo().toLowerCase()] ?? [];
integrationsContributors = integrationsContributors =
contributors[_managerAPI.getIntegrationsRepo()] ?? []; contributors[_managerAPI.getIntegrationsRepo().toLowerCase()] ?? [];
cliContributors = contributors[_managerAPI.defaultCliRepo] ?? []; cliContributors = contributors[_managerAPI.defaultCliRepo] ?? [];
managerContributors = contributors[_managerAPI.defaultManagerRepo] ?? []; managerContributors = contributors[_managerAPI.defaultManagerRepo] ?? [];
notifyListeners(); notifyListeners();

View File

@@ -8,7 +8,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:install_plugin/install_plugin.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/app/app.router.dart'; import 'package:revanced_manager/app/app.router.dart';
@@ -21,7 +20,7 @@ import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart'; import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.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/homeView/update_confirmation_dialog.dart'; import 'package:revanced_manager/ui/widgets/homeView/update_confirmation_dialog.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_checkbox_list_tile.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';
@@ -40,11 +39,15 @@ class HomeViewModel extends BaseViewModel {
File? downloadedApk; File? downloadedApk;
Future<void> initialize(BuildContext context) async { Future<void> initialize(BuildContext context) async {
_managerAPI.rePatchedSavedApps().then((_) => _getPatchedApps());
_latestManagerVersion = await _managerAPI.getLatestManagerVersion(); _latestManagerVersion = await _managerAPI.getLatestManagerVersion();
if (!_managerAPI.getPatchesConsent()) { if (!_managerAPI.getPatchesConsent()) {
await showPatchesConsent(context); await showPatchesConsent(context);
} }
await _patcherAPI.initialize(); await _patcherAPI.initialize();
await flutterLocalNotificationsPlugin.initialize( await flutterLocalNotificationsPlugin.initialize(
const InitializationSettings( const InitializationSettings(
android: AndroidInitializationSettings('ic_notification'), android: AndroidInitializationSettings('ic_notification'),
@@ -54,7 +57,7 @@ class HomeViewModel extends BaseViewModel {
_toast.showBottom('homeView.installingMessage'); _toast.showBottom('homeView.installingMessage');
final File? managerApk = await _managerAPI.downloadManager(); final File? managerApk = await _managerAPI.downloadManager();
if (managerApk != null) { if (managerApk != null) {
await InstallPlugin.installApk(managerApk.path); await _patcherAPI.installApk(context, managerApk.path);
} else { } else {
_toast.showBottom('homeView.errorDownloadMessage'); _toast.showBottom('homeView.errorDownloadMessage');
} }
@@ -65,24 +68,24 @@ class HomeViewModel extends BaseViewModel {
.resolvePlatformSpecificImplementation< .resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>() AndroidFlutterLocalNotificationsPlugin>()
?.requestNotificationsPermission(); ?.requestNotificationsPermission();
final bool isConnected = await Connectivity().checkConnectivity() !=
ConnectivityResult.none; final bool isConnected =
await Connectivity().checkConnectivity() != ConnectivityResult.none;
if (!isConnected) { if (!isConnected) {
_toast.showBottom('homeView.noConnection'); _toast.showBottom('homeView.noConnection');
} }
final NotificationAppLaunchDetails? notificationAppLaunchDetails = final NotificationAppLaunchDetails? notificationAppLaunchDetails =
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(); await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) { if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
_toast.showBottom('homeView.installingMessage'); _toast.showBottom('homeView.installingMessage');
final File? managerApk = await _managerAPI.downloadManager(); final File? managerApk = await _managerAPI.downloadManager();
if (managerApk != null) { if (managerApk != null) {
await InstallPlugin.installApk(managerApk.path); await _patcherAPI.installApk(context, managerApk.path);
} else { } else {
_toast.showBottom('homeView.errorDownloadMessage'); _toast.showBottom('homeView.errorDownloadMessage');
} }
} }
_managerAPI.reAssessSavedApps().then((_) => _getPatchedApps());
} }
void navigateToAppInfo(PatchedApplication app) { void navigateToAppInfo(PatchedApplication app) {
@@ -205,7 +208,7 @@ class HomeViewModel extends BaseViewModel {
), ),
), ),
), ),
CheckboxListTile( HapticCheckboxListTile(
value: value, value: value,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
title: I18nText( title: I18nText(
@@ -223,21 +226,20 @@ class HomeViewModel extends BaseViewModel {
}, },
), ),
actions: [ actions: [
CustomMaterialButton( TextButton(
isFilled: false,
onPressed: () async { onPressed: () async {
await _managerAPI.setPatchesConsent(false); await _managerAPI.setPatchesConsent(false);
SystemNavigator.pop(); SystemNavigator.pop();
}, },
label: I18nText('quitButton'), child: I18nText('quitButton'),
), ),
CustomMaterialButton( FilledButton(
onPressed: () async { onPressed: () async {
await _managerAPI.setPatchesConsent(true); await _managerAPI.setPatchesConsent(true);
await _managerAPI.setPatchesAutoUpdate(autoUpdate.value); await _managerAPI.setPatchesAutoUpdate(autoUpdate.value);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
label: I18nText('okButton'), child: I18nText('okButton'),
), ),
], ],
), ),
@@ -270,6 +272,7 @@ class HomeViewModel extends BaseViewModel {
valueListenable: downloaded, valueListenable: downloaded,
builder: (context, value, child) { builder: (context, value, child) {
return SimpleDialog( return SimpleDialog(
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
contentPadding: const EdgeInsets.all(16.0), contentPadding: const EdgeInsets.all(16.0),
title: I18nText( title: I18nText(
!value !value
@@ -324,12 +327,12 @@ class HomeViewModel extends BaseViewModel {
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: CustomMaterialButton( child: FilledButton(
label: I18nText('cancelButton'),
onPressed: () { onPressed: () {
_revancedAPI.disposeManagerUpdateProgress(); _revancedAPI.disposeManagerUpdateProgress();
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('cancelButton'),
), ),
), ),
], ],
@@ -355,24 +358,24 @@ class HomeViewModel extends BaseViewModel {
children: [ children: [
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: CustomMaterialButton( child: TextButton(
isFilled: false,
label: I18nText('cancelButton'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('cancelButton'),
), ),
), ),
const SizedBox(width: 8.0), const SizedBox(width: 8.0),
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: CustomMaterialButton( child: FilledButton(
label: I18nText('updateButton'),
onPressed: () async { onPressed: () async {
await InstallPlugin.installApk( await _patcherAPI.installApk(
context,
downloadedApk!.path, downloadedApk!.path,
); );
}, },
child: I18nText('updateButton'),
), ),
), ),
], ],
@@ -415,7 +418,7 @@ class HomeViewModel extends BaseViewModel {
// UILocalNotificationDateInterpretation.absoluteTime, // UILocalNotificationDateInterpretation.absoluteTime,
// ); // );
_toast.showBottom('homeView.installingMessage'); _toast.showBottom('homeView.installingMessage');
await InstallPlugin.installApk(managerApk.path); await _patcherAPI.installApk(context, managerApk.path);
} else { } else {
_toast.showBottom('homeView.errorDownloadMessage'); _toast.showBottom('homeView.errorDownloadMessage');
} }

View File

@@ -5,6 +5,7 @@ import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/installerView/gradient_progress_indicator.dart'; import 'package:revanced_manager/ui/widgets/installerView/gradient_progress_indicator.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_sliver_app_bar.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_floating_action_button_extended.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class InstallerView extends StatelessWidget { class InstallerView extends StatelessWidget {
@@ -16,14 +17,13 @@ class InstallerView extends StatelessWidget {
onViewModelReady: (model) => model.initialize(context), onViewModelReady: (model) => model.initialize(context),
viewModelBuilder: () => InstallerViewModel(), viewModelBuilder: () => InstallerViewModel(),
builder: (context, model, child) => PopScope( builder: (context, model, child) => PopScope(
onPopInvoked: (bool didPop) => model.onPopInvoked(context, didPop),
child: SafeArea( child: SafeArea(
top: false, top: false,
bottom: model.isPatching, bottom: model.isPatching,
child: Scaffold( child: Scaffold(
floatingActionButton: Visibility( floatingActionButton: Visibility(
visible: !model.isPatching && !model.hasErrors, visible: !model.isPatching && !model.hasErrors,
child: FloatingActionButton.extended( child: HapticFloatingActionButtonExtended(
label: I18nText( label: I18nText(
model.isInstalled model.isInstalled
? 'installerView.openButton' ? 'installerView.openButton'
@@ -84,7 +84,7 @@ class InstallerView extends StatelessWidget {
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
onBackButtonPressed: () => model.onBackButtonInvoked(context), onBackButtonPressed: () => model.onWillPop(context),
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: const Size(double.infinity, 1.0), preferredSize: const Size(double.infinity, 1.0),
child: GradientProgressIndicator(progress: model.progress), child: GradientProgressIndicator(progress: model.progress),
@@ -112,6 +112,7 @@ class InstallerView extends StatelessWidget {
), ),
), ),
), ),
onPopInvoked: (bool didPop) => model.onWillPop(context),
), ),
); );
} }

View File

@@ -13,8 +13,8 @@ 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/root_api.dart'; import 'package:revanced_manager/services/root_api.dart';
import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/views/home/home_viewmodel.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/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';
@@ -104,7 +104,7 @@ class InstallerViewModel extends BaseViewModel {
isPatching = true; isPatching = true;
isInstalled = false; isInstalled = false;
hasErrors = false; hasErrors = false;
} else if (value == 1.0) { } else if (value == .85) {
isPatching = false; isPatching = false;
hasErrors = false; hasErrors = false;
await _managerAPI.savePatches( await _managerAPI.savePatches(
@@ -115,6 +115,7 @@ class InstallerViewModel extends BaseViewModel {
} else if (value == -100.0) { } else if (value == -100.0) {
isPatching = false; isPatching = false;
hasErrors = true; hasErrors = true;
progress = 0.0;
} }
if (header.isNotEmpty) { if (header.isNotEmpty) {
headerLogs = header; headerLogs = header;
@@ -127,10 +128,10 @@ class InstallerViewModel extends BaseViewModel {
if (logs[logs.length - 1] == '\n') { if (logs[logs.length - 1] == '\n') {
logs = logs.substring(0, logs.length - 1); logs = logs.substring(0, logs.length - 1);
} }
Future.delayed(const Duration(milliseconds: 500)).then((value) { Future.delayed(const Duration(milliseconds: 100)).then((value) {
scrollController.animateTo( scrollController.animateTo(
scrollController.position.maxScrollExtent, scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 100),
curve: Curves.fastOutSlowIn, curve: Curves.fastOutSlowIn,
); );
}); });
@@ -183,13 +184,17 @@ class InstallerViewModel extends BaseViewModel {
final lineCount = logLines.where((line) => line.endsWith(keyword)).length; final lineCount = logLines.where((line) => line.endsWith(keyword)).length;
final index = logLines.indexWhere((line) => line.endsWith(keyword)); final index = logLines.indexWhere((line) => line.endsWith(keyword));
if (newString != null && lineCount > 0) { if (newString != null && lineCount > 0) {
logLines.insert(index, newString.replaceAll('{lineCount}', lineCount.toString())); logLines.insert(
index,
newString.replaceAll('{lineCount}', lineCount.toString()),
);
} }
logLines.removeWhere((lines) => lines.endsWith(keyword)); logLines.removeWhere((lines) => lines.endsWith(keyword));
} }
dynamic _getPatchOptionValue(String patchName, Option option) { dynamic _getPatchOptionValue(String patchName, Option option) {
final Option? savedOption = _managerAPI.getPatchOption(_app.packageName, patchName, option.key); final Option? savedOption =
_managerAPI.getPatchOption(_app.packageName, patchName, option.key);
if (savedOption != null) { if (savedOption != null) {
return savedOption.value; return savedOption.value;
} else { } else {
@@ -197,11 +202,26 @@ class InstallerViewModel extends BaseViewModel {
} }
} }
String _formatPatches(List<Patch> patches) { String _formatPatches(List<Patch> patches, String noneString) {
if (patches.isEmpty) { return patches.isEmpty
return 'None'; ? noneString
: patches.map((p) {
final optionsChanged = p.options
.where((o) => _getPatchOptionValue(p.name, o) != o.value)
.toList();
return p.name +
(optionsChanged.isEmpty
? ''
: ' [${optionsChanged.map((o) => '${o.title}: ${_getPatchOptionValue(p.name, o)}').join(", ")}]');
}).join(', ');
}
String _getSuggestedVersion(String packageName) {
String suggestedVersion = _patcherAPI.getSuggestedVersion(_app.packageName);
if (suggestedVersion.isEmpty) {
suggestedVersion = 'Any';
} }
return patches.map((p) => p.name + (p.options.isEmpty ? '' : ' [${p.options.map((o) => '${o.title}: ${_getPatchOptionValue(p.name, o)}').join(", ")}]')).toList().join(', '); return suggestedVersion;
} }
Future<void> copyLogs() async { Future<void> copyLogs() async {
@@ -213,37 +233,48 @@ class InstallerViewModel extends BaseViewModel {
_trimLogs(logsTrimmed, '.dex', 'Compiled {lineCount} dex files'); _trimLogs(logsTrimmed, '.dex', 'Compiled {lineCount} dex files');
// Get patches added / removed // Get patches added / removed
final defaultPatches = _patcherAPI.getFilteredPatches(_app.packageName).where((p) => !p.excluded).toList(); final defaultPatches = _patcherAPI
final patchesAdded = _patches.where((p) => !defaultPatches.contains(p)).toList(); .getFilteredPatches(_app.packageName)
final patchesRemoved = defaultPatches.where((p) => !_patches.contains(p)).toList(); .where((p) => !p.excluded)
.toList();
final appliedPatchesNames = _patches.map((p) => p.name).toList();
// Options changed final patchesAdded = _patches.where((p) => p.excluded).toList();
final patchesChanged = defaultPatches.where((p) => _patches.contains(p) && p.options.any((o) => _getPatchOptionValue(p.name, o) != o.value)).toList(); final patchesRemoved = defaultPatches
.where((p) => !appliedPatchesNames.contains(p.name))
.map((p) => p.name)
.toList();
final patchesOptionsChanged = defaultPatches
.where(
(p) =>
appliedPatchesNames.contains(p.name) &&
p.options.any((o) => _getPatchOptionValue(p.name, o) != o.value),
)
.toList();
// Add Info // Add Info
final formattedLogs = [ final formattedLogs = [
'- Device Info', '- Device Info',
'ReVanced Manager: ${info['version']}', 'ReVanced Manager: ${info['version']}',
'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'}', '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 added: ${_formatPatches(patchesAdded)}', 'Patches added: ${_formatPatches(patchesAdded, 'Default')}',
'Patches removed: ${_formatPatches(patchesRemoved)}', 'Patches removed: ${patchesRemoved.isEmpty ? 'None' : patchesRemoved.join(', ')}',
'Options changed: ${_formatPatches(patchesChanged)}', 'Default patch options changed: ${_formatPatches(patchesOptionsChanged, 'None')}', //
'\n- Settings', '\n- Settings',
'Allow changing patch selection: ${_managerAPI.isPatchesChangeEnabled()}', 'Allow changing patch selection: ${_managerAPI.isPatchesChangeEnabled()}',
'Version compatibility check: ${_managerAPI.isVersionCompatibilityCheckEnabled()}', 'Version compatibility check: ${_managerAPI.isVersionCompatibilityCheckEnabled()}',
'Show universal patches: ${_managerAPI.areUniversalPatchesEnabled()}', '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',
logsTrimmed.join('\n'), logsTrimmed.join('\n'),
]; ];
@@ -259,26 +290,24 @@ class InstallerViewModel extends BaseViewModel {
title: I18nText( title: I18nText(
'warning', 'warning',
), ),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
icon: const Icon(Icons.warning), icon: const Icon(Icons.warning),
content: SingleChildScrollView( content: SingleChildScrollView(
child: I18nText('installerView.screenshotDetected'), child: I18nText('installerView.screenshotDetected'),
), ),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('noButton'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('noButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('yesButton'),
onPressed: () { onPressed: () {
copyLogs(); copyLogs();
showPopupScreenshotWarning = true; showPopupScreenshotWarning = true;
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('yesButton'),
), ),
], ],
), ),
@@ -291,11 +320,10 @@ class InstallerViewModel extends BaseViewModel {
await showDialog( await showDialog(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
builder: (context) => AlertDialog( builder: (innerContext) => AlertDialog(
title: I18nText( title: I18nText(
'installerView.installType', 'installerView.installType',
), ),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
icon: const Icon(Icons.file_download_outlined), icon: const Icon(Icons.file_download_outlined),
contentPadding: const EdgeInsets.symmetric(vertical: 16), contentPadding: const EdgeInsets.symmetric(vertical: 16),
content: SingleChildScrollView( content: SingleChildScrollView(
@@ -343,31 +371,68 @@ class InstallerViewModel extends BaseViewModel {
installType.value = selected!; installType.value = selected!;
}, },
), ),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: I18nText(
'installerView.warning',
child: Text(
'',
style: TextStyle(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.error,
),
),
),
),
], ],
); );
}, },
), ),
), ),
actions: [ actions: [
CustomMaterialButton( TextButton(
label: I18nText('cancelButton'),
isFilled: false,
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(innerContext).pop();
}, },
child: I18nText('cancelButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('installerView.installButton'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(innerContext).pop();
installResult(context, installType.value == 1); installResult(context, installType.value == 1);
}, },
child: I18nText('installerView.installButton'),
), ),
], ],
), ),
); );
} else { } else {
installResult(context, false); await showDialog(
context: context,
barrierDismissible: false,
builder: (innerContext) => AlertDialog(
title: I18nText(
'warning',
),
contentPadding: const EdgeInsets.all(16),
content: I18nText('installerView.warning'),
actions: [
TextButton(
onPressed: () {
Navigator.of(innerContext).pop();
},
child: I18nText('cancelButton'),
),
FilledButton(
onPressed: () {
Navigator.of(innerContext).pop();
installResult(context, false);
},
child: I18nText('installerView.installButton'),
),
],
),
);
} }
} }
@@ -388,15 +453,16 @@ class InstallerViewModel extends BaseViewModel {
Future<void> installResult(BuildContext context, bool installAsRoot) async { Future<void> installResult(BuildContext context, bool installAsRoot) async {
try { try {
_app.isRooted = installAsRoot; _app.isRooted = installAsRoot;
update( if (headerLogs != 'Installing...') {
1.0, update(
'Installing...', .85,
_app.isRooted 'Installing...',
? 'Installing patched file using root method' _app.isRooted ? 'Mounting patched app' : 'Installing patched app',
: 'Installing patched file using nonroot method', );
); }
isInstalled = await _patcherAPI.installPatchedFile(_app); final int response = await _patcherAPI.installPatchedFile(context, _app);
if (isInstalled) { if (response == 0) {
isInstalled = true;
_app.isFromStorage = false; _app.isFromStorage = false;
_app.patchDate = DateTime.now(); _app.patchDate = DateTime.now();
_app.appliedPatches = _patches.map((p) => p.name).toList(); _app.appliedPatches = _patches.map((p) => p.name).toList();
@@ -411,10 +477,23 @@ class InstallerViewModel extends BaseViewModel {
} }
await _managerAPI.savePatchedApp(_app); await _managerAPI.savePatchedApp(_app);
await locator<HomeViewModel>().initialize(context);
update(1.0, 'Installed!', 'Installed!'); update(1.0, 'Installed', 'Installed');
} else if (response == 3) {
update(
.85,
'Installation canceled',
'Installation canceled',
);
} else if (response == 10) {
installResult(context, installAsRoot);
} else { } else {
// TODO(aabed): Show error message. update(
.85,
'Installation failed',
'Installation failed',
);
} }
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
@@ -461,38 +540,25 @@ class InstallerViewModel extends BaseViewModel {
} }
} }
bool canPop() { Future<bool> onWillPop(BuildContext context) async {
return !isPatching; if (isPatching) {
}
void onBackButtonInvoked(BuildContext context) {
if (canPop()) {
onPopInvoked(context, true);
} else {
onPopInvoked(context, false);
}
}
Future<void> onPopInvoked(BuildContext context, bool didPop) async {
if (didPop) {
if (!cancel) { if (!cancel) {
cleanPatcher(); cancel = true;
_toast.showBottom('installerView.pressBackAgain');
} else if (!isCanceled) {
await stopPatcher();
} else { } else {
_patcherAPI.cleanPatcher(); _toast.showBottom('installerView.noExit');
}
screenshotCallback.dispose();
Navigator.of(context).pop();
} else {
if (isPatching) {
if (!cancel) {
cancel = true;
_toast.showBottom('installerView.pressBackAgain');
} else if (!isCanceled) {
await stopPatcher();
} else {
_toast.showBottom('installerView.noExit');
}
} }
return false;
} }
if (!cancel) {
cleanPatcher();
} else {
_patcherAPI.cleanPatcher();
}
screenshotCallback.dispose();
Navigator.of(context).pop();
return true;
} }
} }

View File

@@ -15,10 +15,8 @@ class NavigationView extends StatelessWidget {
viewModelBuilder: () => locator<NavigationViewModel>(), viewModelBuilder: () => locator<NavigationViewModel>(),
builder: (context, model, child) => PopScope( builder: (context, model, child) => PopScope(
canPop: model.currentIndex == 0, canPop: model.currentIndex == 0,
onPopInvoked: (bool didPop) { onPopInvoked: (bool didPop) => {
if (!didPop) { if (!didPop) model.setIndex(0),
model.setIndex(0);
}
}, },
child: Scaffold( child: Scaffold(
body: PageTransitionSwitcher( body: PageTransitionSwitcher(

View File

@@ -4,7 +4,7 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/ui/views/patch_options/patch_options_viewmodel.dart'; import 'package:revanced_manager/ui/views/patch_options/patch_options_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_options_fields.dart'; import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_options_fields.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_floating_action_button_extended.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class PatchOptionsView extends StatelessWidget { class PatchOptionsView extends StatelessWidget {
@@ -18,7 +18,7 @@ class PatchOptionsView extends StatelessWidget {
builder: (context, model, child) => GestureDetector( builder: (context, model, child) => GestureDetector(
onTap: () => FocusScope.of(context).unfocus(), onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold( child: Scaffold(
floatingActionButton: FloatingActionButton.extended( floatingActionButton: HapticFloatingActionButtonExtended(
onPressed: () async { onPressed: () async {
final bool saved = model.saveOptions(context); final bool saved = model.saveOptions(context);
if (saved && context.mounted) { if (saved && context.mounted) {
@@ -82,8 +82,7 @@ class PatchOptionsView extends StatelessWidget {
model.modifyOptions(value, option); model.modifyOptions(value, option);
}, },
) )
else if (option.valueType == else if (option.valueType == 'StringArray' ||
'StringArray' ||
option.valueType == 'IntArray' || option.valueType == 'IntArray' ||
option.valueType == 'LongArray') option.valueType == 'LongArray')
IntStringLongListPatchOption( IntStringLongListPatchOption(
@@ -104,11 +103,11 @@ class PatchOptionsView extends StatelessWidget {
const SizedBox( const SizedBox(
height: 8, height: 8,
), ),
CustomMaterialButton( FilledButton(
onPressed: () { onPressed: () {
model.showAddOptionDialog(context); model.showAddOptionDialog(context);
}, },
label: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon(Icons.add), const Icon(Icons.add),

View File

@@ -6,7 +6,6 @@ import 'package:revanced_manager/services/manager_api.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/widgets/shared/custom_card.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class PatchOptionsViewModel extends BaseViewModel { class PatchOptionsViewModel extends BaseViewModel {
@@ -32,13 +31,11 @@ class PatchOptionsViewModel extends BaseViewModel {
if (savedOptions.isNotEmpty) { if (savedOptions.isNotEmpty) {
visibleOptions = [ visibleOptions = [
...savedOptions, ...savedOptions,
...options ...options.where(
.where( (option) =>
(option) => option.required &&
option.required && !savedOptions.any((sOption) => sOption.key == option.key),
!savedOptions.any((sOption) => sOption.key == option.key), ),
)
,
]; ];
} else { } else {
visibleOptions = [ visibleOptions = [
@@ -136,7 +133,6 @@ class PatchOptionsViewModel extends BaseViewModel {
await showDialog( await showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
title: Column( title: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -154,11 +150,11 @@ class PatchOptionsViewModel extends BaseViewModel {
], ],
), ),
actions: [ actions: [
CustomMaterialButton( FilledButton(
label: I18nText('cancelButton'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('cancelButton'),
), ),
], ],
contentPadding: const EdgeInsets.all(8), contentPadding: const EdgeInsets.all(8),
@@ -188,8 +184,9 @@ class PatchOptionsViewModel extends BaseViewModel {
children: [ children: [
Text( Text(
e.title, e.title,
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: Theme.of(context).colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@@ -227,14 +224,9 @@ Future<void> showRequiredOptionNullDialog(
await showDialog( await showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
title: I18nText('notice'), title: I18nText('notice'),
actions: [ actions: [
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText(
'patchOptionsView.deselectPatch',
),
onPressed: () async { onPressed: () async {
if (managerAPI.isPatchesChangeEnabled()) { if (managerAPI.isPatchesChangeEnabled()) {
locator<PatcherViewModel>() locator<PatcherViewModel>()
@@ -256,12 +248,13 @@ Future<void> showRequiredOptionNullDialog(
PatchesSelectorViewModel().showPatchesChangeDialog(context); PatchesSelectorViewModel().showPatchesChangeDialog(context);
} }
}, },
child: I18nText('patchOptionsView.deselectPatch'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('okButton'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('okButton'),
), ),
], ],
content: I18nText( content: I18nText(

View File

@@ -6,6 +6,7 @@ import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/patcherView/app_selector_card.dart'; import 'package:revanced_manager/ui/widgets/patcherView/app_selector_card.dart';
import 'package:revanced_manager/ui/widgets/patcherView/patch_selector_card.dart'; import 'package:revanced_manager/ui/widgets/patcherView/patch_selector_card.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:revanced_manager/ui/widgets/shared/haptics/haptic_floating_action_button_extended.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class PatcherView extends StatelessWidget { class PatcherView extends StatelessWidget {
@@ -19,10 +20,10 @@ class PatcherView extends StatelessWidget {
builder: (context, model, child) => Scaffold( builder: (context, model, child) => Scaffold(
floatingActionButton: Visibility( floatingActionButton: Visibility(
visible: model.showPatchButton(), visible: model.showPatchButton(),
child: FloatingActionButton.extended( child: HapticFloatingActionButtonExtended(
label: I18nText('patcherView.patchButton'), label: I18nText('patcherView.patchButton'),
icon: const Icon(Icons.build), icon: const Icon(Icons.build),
onPressed: () async{ onPressed: () async {
if (model.checkRequiredPatchOption(context)) { if (model.checkRequiredPatchOption(context)) {
final bool proceed = model.showRemovedPatchesDialog(context); final bool proceed = model.showRemovedPatchesDialog(context);
if (proceed && context.mounted) { if (proceed && context.mounted) {

View File

@@ -13,7 +13,6 @@ 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/patcher_api.dart'; import 'package:revanced_manager/services/patcher_api.dart';
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: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 +23,7 @@ class PatcherViewModel extends BaseViewModel {
final NavigationService _navigationService = locator<NavigationService>(); final NavigationService _navigationService = locator<NavigationService>();
final ManagerAPI _managerAPI = locator<ManagerAPI>(); final ManagerAPI _managerAPI = locator<ManagerAPI>();
final PatcherAPI _patcherAPI = locator<PatcherAPI>(); final PatcherAPI _patcherAPI = locator<PatcherAPI>();
Set<String> savedPatchNames = {};
PatchedApplication? selectedApp; PatchedApplication? selectedApp;
BuildContext? ctx; BuildContext? ctx;
List<Patch> selectedPatches = []; List<Patch> selectedPatches = [];
@@ -55,25 +55,23 @@ class PatcherViewModel extends BaseViewModel {
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: I18nText('notice'), title: I18nText('notice'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText( content: I18nText(
'patcherView.removedPatchesWarningDialogText', 'patcherView.removedPatchesWarningDialogText',
translationParams: {'patches': removedPatches.join('\n')}, translationParams: {'patches': removedPatches.join('\n')},
), ),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('noButton'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('noButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('yesButton'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
showArmv7WarningDialog(context); showArmv7WarningDialog(context);
}, },
child: I18nText('yesButton'),
), ),
], ],
), ),
@@ -97,22 +95,20 @@ class PatcherViewModel extends BaseViewModel {
context: context ?? ctx, context: context ?? ctx,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: I18nText('notice'), title: I18nText('notice'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText('patcherView.requiredOptionDialogText'), content: I18nText('patcherView.requiredOptionDialogText'),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('cancelButton'),
onPressed: () => { onPressed: () => {
Navigator.of(context).pop(), Navigator.of(context).pop(),
}, },
child: I18nText('cancelButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('okButton'),
onPressed: () => { onPressed: () => {
Navigator.pop(context), Navigator.pop(context),
navigateToPatchesSelector(), navigateToPatchesSelector(),
}, },
child: I18nText('okButton'),
), ),
], ],
), ),
@@ -130,20 +126,18 @@ class PatcherViewModel extends BaseViewModel {
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: I18nText('warning'), title: I18nText('warning'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText('patcherView.armv7WarningDialogText'), content: I18nText('patcherView.armv7WarningDialogText'),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( FilledButton(
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: I18nText('noButton'),
), ),
CustomMaterialButton( TextButton(
label: I18nText('yesButton'),
isFilled: false,
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
navigateToInstaller(); navigateToInstaller();
}, },
child: I18nText('yesButton'),
), ),
], ],
), ),
@@ -154,51 +148,17 @@ class PatcherViewModel extends BaseViewModel {
} }
String getAppSelectionString() { String getAppSelectionString() {
String text = '${selectedApp!.name} (${selectedApp!.packageName})'; return '${selectedApp!.name} ${selectedApp!.version}';
if (text.length > 32) {
text = '${text.substring(0, 32)}...)';
}
return text;
} }
String getCurrentVersionString(BuildContext context) { Future<void> queryVersion(String suggestedVersion) async {
return '${FlutterI18n.translate( await openDefaultBrowser(
context, '${selectedApp!.packageName} apk version $suggestedVersion',
'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 = return _patcherAPI.getSuggestedVersion(selectedApp!.packageName);
_patcherAPI.getSuggestedVersion(selectedApp!.packageName);
if (suggestedVersion.isEmpty) {
suggestedVersion = FlutterI18n.translate(
context,
'appSelectorCard.allVersions',
);
} else {
suggestedVersion = 'v$suggestedVersion';
}
return '${FlutterI18n.translate(
context,
'appSelectorCard.suggestedVersion',
)}: $suggestedVersion';
} }
Future<void> openDefaultBrowser(String query) async { Future<void> openDefaultBrowser(String query) async {
@@ -216,6 +176,20 @@ class PatcherViewModel extends BaseViewModel {
} }
} }
bool isPatchNew(Patch patch) {
if (savedPatchNames.isEmpty) {
savedPatchNames = _managerAPI
.getSavedPatches(selectedApp!.packageName)
.map((p) => p.name)
.toSet();
}
if (savedPatchNames.isEmpty) {
return false;
} else {
return !savedPatchNames.contains(patch.name);
}
}
Future<void> loadLastSelectedPatches() async { Future<void> loadLastSelectedPatches() async {
this.selectedPatches.clear(); this.selectedPatches.clear();
removedPatches.clear(); removedPatches.clear();
@@ -238,13 +212,24 @@ class PatcherViewModel extends BaseViewModel {
.selectedPatches .selectedPatches
.removeWhere((patch) => patch.compatiblePackages.isEmpty); .removeWhere((patch) => patch.compatiblePackages.isEmpty);
} }
this.selectedPatches.addAll(
patches.where(
(patch) =>
isPatchNew(patch) &&
!patch.excluded &&
!this.selectedPatches.contains(patch),
),
);
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( _managerAPI.clearPatchOption(
selectedApp!.packageName, patch.name, option.key); selectedApp!.packageName,
patch.name,
option.key,
);
} }
} }
} }

View File

@@ -3,6 +3,7 @@ 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/shared/haptics/haptic_floating_action_button_extended.dart';
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart'; import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
@@ -36,7 +37,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
builder: (context, model, child) => Scaffold( builder: (context, model, child) => Scaffold(
floatingActionButton: Visibility( floatingActionButton: Visibility(
visible: model.patches.isNotEmpty, visible: model.patches.isNotEmpty,
child: FloatingActionButton.extended( child: HapticFloatingActionButtonExtended(
label: Row( label: Row(
children: <Widget>[ children: <Widget>[
I18nText('patchesSelectorView.doneButton'), I18nText('patchesSelectorView.doneButton'),
@@ -180,11 +181,16 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
), ),
], ],
), ),
if (model.getQueriedPatches(_query).any((patch) => model.isPatchNew(patch))) if (model
.getQueriedPatches(_query)
.any((patch) => model.isPatchNew(patch)))
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
model.getPatchCategory(context, 'patchesSelectorView.newPatches'), model.getPatchCategory(
context,
'patchesSelectorView.newPatches',
),
...model.getQueriedPatches(_query).map((patch) { ...model.getQueriedPatches(_query).map((patch) {
if (model.isPatchNew(patch)) { if (model.isPatchNew(patch)) {
return model.getPatchItem(context, patch); return model.getPatchItem(context, patch);
@@ -192,26 +198,40 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
return Container(); return Container();
} }
}), }),
if (model.getQueriedPatches(_query).any((patch) => !model.isPatchNew(patch) && patch.compatiblePackages.isNotEmpty)) if (model.getQueriedPatches(_query).any(
model.getPatchCategory(context, 'patchesSelectorView.patches'), (patch) =>
!model.isPatchNew(patch) &&
patch.compatiblePackages.isNotEmpty,
))
model.getPatchCategory(
context,
'patchesSelectorView.patches',
),
], ],
), ),
...model.getQueriedPatches(_query).map( ...model.getQueriedPatches(_query).map(
(patch) { (patch) {
if (patch.compatiblePackages.isNotEmpty && !model.isPatchNew(patch)) { if (patch.compatiblePackages.isNotEmpty &&
!model.isPatchNew(patch)) {
return model.getPatchItem(context, patch); return model.getPatchItem(context, patch);
} else { } else {
return Container(); return Container();
} }
}, },
), ),
if (model.getQueriedPatches(_query).any((patch) => patch.compatiblePackages.isEmpty)) if (model
.getQueriedPatches(_query)
.any((patch) => patch.compatiblePackages.isEmpty))
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
model.getPatchCategory(context, 'patchesSelectorView.universalPatches'), model.getPatchCategory(
context,
'patchesSelectorView.universalPatches',
),
...model.getQueriedPatches(_query).map((patch) { ...model.getQueriedPatches(_query).map((patch) {
if (patch.compatiblePackages.isEmpty && !model.isPatchNew(patch)) { if (patch.compatiblePackages.isEmpty &&
!model.isPatchNew(patch)) {
return model.getPatchItem(context, patch); return model.getPatchItem(context, patch);
} else { } else {
return Container(); return Container();

View File

@@ -10,7 +10,6 @@ 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/patchesSelectorView/patch_item.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';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
@@ -25,6 +24,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
locator<PatcherViewModel>().selectedPatches; locator<PatcherViewModel>().selectedPatches;
PatchedApplication? selectedApp = locator<PatcherViewModel>().selectedApp; PatchedApplication? selectedApp = locator<PatcherViewModel>().selectedApp;
String? patchesVersion = ''; String? patchesVersion = '';
bool isDefaultPatchesRepo() { bool isDefaultPatchesRepo() {
return _managerAPI.getPatchesRepo() == 'revanced/revanced-patches'; return _managerAPI.getPatchesRepo() == 'revanced/revanced-patches';
} }
@@ -48,6 +48,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
}); });
currentSelection.clear(); currentSelection.clear();
currentSelection.addAll(selectedPatches); currentSelection.addAll(selectedPatches);
notifyListeners(); notifyListeners();
} }
@@ -92,7 +93,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: I18nText('notice'), title: I18nText('notice'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText( content: I18nText(
'patchesSelectorView.setRequiredOption', 'patchesSelectorView.setRequiredOption',
translationParams: { translationParams: {
@@ -100,11 +100,11 @@ class PatchesSelectorViewModel extends BaseViewModel {
}, },
), ),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( FilledButton(
label: I18nText('okButton'),
onPressed: () => { onPressed: () => {
Navigator.of(context).pop(), Navigator.of(context).pop(),
}, },
child: I18nText('okButton'),
), ),
], ],
), ),
@@ -128,7 +128,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
return showDialog( return showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
title: I18nText('warning'), title: I18nText('warning'),
content: I18nText( content: I18nText(
'patchItem.patchesChangeWarningDialogText', 'patchItem.patchesChangeWarningDialogText',
@@ -141,18 +140,17 @@ class PatchesSelectorViewModel extends BaseViewModel {
), ),
), ),
actions: [ actions: [
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: I18nText('okButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('patchItem.patchesChangeWarningDialogButton'),
onPressed: () { onPressed: () {
Navigator.of(context) Navigator.of(context)
..pop() ..pop()
..pop(); ..pop();
}, },
child: I18nText('patchItem.patchesChangeWarningDialogButton'),
), ),
], ],
), ),
@@ -186,10 +184,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
void selectPatches() { void selectPatches() {
locator<PatcherViewModel>().selectedPatches = selectedPatches; locator<PatcherViewModel>().selectedPatches = selectedPatches;
saveSelectedPatches(); saveSelectedPatches();
if (_managerAPI.ctx != null) {
Navigator.pop(_managerAPI.ctx!);
_managerAPI.ctx = null;
}
locator<PatcherViewModel>().notifyListeners(); locator<PatcherViewModel>().notifyListeners();
} }
@@ -281,14 +275,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
} }
bool isPatchNew(Patch patch) { bool isPatchNew(Patch patch) {
final List<Patch> savedPatches = return locator<PatcherViewModel>().isPatchNew(patch);
_managerAPI.getSavedPatches(selectedApp!.packageName);
if (savedPatches.isEmpty) {
return false;
} else {
return !savedPatches
.any((p) => p.getSimpleName() == patch.getSimpleName());
}
} }
List<String> getSupportedVersions(Patch patch) { List<String> getSupportedVersions(Patch patch) {

View File

@@ -4,15 +4,11 @@ import 'package:flutter/material.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/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class SManageApiUrl extends BaseViewModel { class SManageApiUrl extends BaseViewModel {
final ManagerAPI _managerAPI = locator<ManagerAPI>(); final ManagerAPI _managerAPI = locator<ManagerAPI>();
final Toast _toast = locator<Toast>();
final TextEditingController _apiUrlController = TextEditingController(); final TextEditingController _apiUrlController = TextEditingController();
@@ -33,43 +29,47 @@ class SManageApiUrl extends BaseViewModel {
), ),
], ],
), ),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: SingleChildScrollView( content: SingleChildScrollView(
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
CustomTextField( TextField(
leadingIcon: Icon( controller: _apiUrlController,
Icons.api_outlined, autocorrect: false,
color: Theme.of(context).colorScheme.secondary,
),
inputController: _apiUrlController,
label: I18nText('settingsView.selectApiURL'),
hint: apiUrl,
onChanged: (value) => notifyListeners(), onChanged: (value) => notifyListeners(),
decoration: InputDecoration(
icon: Icon(
Icons.api_outlined,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
border: const OutlineInputBorder(),
labelText: FlutterI18n.translate(
context,
'settingsView.selectApiURL',
),
hintText: apiUrl,
),
), ),
], ],
), ),
), ),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('cancelButton'),
onPressed: () { onPressed: () {
_apiUrlController.clear(); _apiUrlController.clear();
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('cancelButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('okButton'),
onPressed: () { onPressed: () {
String apiUrl = _apiUrlController.text; String apiUrl = _apiUrlController.text;
if (!apiUrl.startsWith('https')) { if (!apiUrl.startsWith('https')) {
apiUrl = 'https://$apiUrl'; apiUrl = 'https://$apiUrl';
} }
_managerAPI.setApiUrl(apiUrl); _managerAPI.setApiUrl(apiUrl);
_toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('okButton'),
), ),
], ],
), ),
@@ -81,23 +81,20 @@ class SManageApiUrl extends BaseViewModel {
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: I18nText('settingsView.sourcesResetDialogTitle'), title: I18nText('settingsView.sourcesResetDialogTitle'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText('settingsView.apiURLResetDialogText'), content: I18nText('settingsView.apiURLResetDialogText'),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: I18nText('noButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('yesButton'),
onPressed: () { onPressed: () {
_managerAPI.setApiUrl(''); _managerAPI.setApiUrl('');
_toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context) Navigator.of(context)
..pop() ..pop()
..pop(); ..pop();
}, },
child: I18nText('yesButton'),
), ),
], ],
), ),

View File

@@ -4,9 +4,7 @@ import 'package:flutter/material.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/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class SManageKeystorePassword extends BaseViewModel { class SManageKeystorePassword extends BaseViewModel {
@@ -33,35 +31,40 @@ class SManageKeystorePassword extends BaseViewModel {
), ),
], ],
), ),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: SingleChildScrollView( content: SingleChildScrollView(
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
CustomTextField( TextField(
inputController: _keystorePasswordController, controller: _keystorePasswordController,
label: I18nText('settingsView.selectKeystorePassword'), autocorrect: false,
hint: '', obscureText: true,
onChanged: (value) => notifyListeners(), onChanged: (value) => notifyListeners(),
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: FlutterI18n.translate(
context,
'settingsView.selectKeystorePassword',
),
),
), ),
], ],
), ),
), ),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('cancelButton'),
onPressed: () { onPressed: () {
_keystorePasswordController.clear(); _keystorePasswordController.clear();
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('cancelButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('okButton'),
onPressed: () { onPressed: () {
final String passwd = _keystorePasswordController.text; final String passwd = _keystorePasswordController.text;
_managerAPI.setKeystorePassword(passwd); _managerAPI.setKeystorePassword(passwd);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('okButton'),
), ),
], ],
), ),

View File

@@ -5,9 +5,7 @@ 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/services/toast.dart'; import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class SManageSources extends BaseViewModel { class SManageSources extends BaseViewModel {
@@ -43,63 +41,105 @@ class SManageSources extends BaseViewModel {
), ),
], ],
), ),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: SingleChildScrollView( content: SingleChildScrollView(
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
CustomTextField( /*
leadingIcon: const Icon( API for accessing the specified repositories
Icons.extension_outlined, If default is used, will use the ReVanced API
color: Colors.transparent, */
), TextField(
inputController: _hostSourceController, controller: _hostSourceController,
label: I18nText('settingsView.hostRepositoryLabel'), autocorrect: false,
hint: hostRepository,
onChanged: (value) => notifyListeners(), onChanged: (value) => notifyListeners(),
), decoration: InputDecoration(
const SizedBox(height: 20), icon: Icon(
CustomTextField( Icons.rocket_launch_outlined,
leadingIcon: Icon( color: Theme.of(context).colorScheme.onSurfaceVariant,
Icons.extension_outlined, ),
color: Theme.of(context).colorScheme.secondary, border: const OutlineInputBorder(),
labelText: FlutterI18n.translate(
context,
'settingsView.hostRepositoryLabel',
),
hintText: hostRepository,
), ),
inputController: _orgPatSourceController,
label: I18nText('settingsView.orgPatchesLabel'),
hint: patchesRepo.split('/')[0],
onChanged: (value) => notifyListeners(),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
CustomTextField( // Patches owner's name
leadingIcon: const Icon( TextField(
Icons.extension_outlined, controller: _orgPatSourceController,
color: Colors.transparent, autocorrect: false,
),
inputController: _patSourceController,
label: I18nText('settingsView.sourcesPatchesLabel'),
hint: patchesRepo.split('/')[1],
onChanged: (value) => notifyListeners(), onChanged: (value) => notifyListeners(),
), decoration: InputDecoration(
const SizedBox(height: 20), icon: Icon(
CustomTextField( Icons.extension_outlined,
leadingIcon: Icon( color: Theme.of(context).colorScheme.onSurfaceVariant,
Icons.merge_outlined, ),
color: Theme.of(context).colorScheme.secondary, border: const OutlineInputBorder(),
labelText: FlutterI18n.translate(
context,
'settingsView.orgPatchesLabel',
),
hintText: patchesRepo.split('/')[0],
), ),
inputController: _orgIntSourceController,
label: I18nText('settingsView.orgIntegrationsLabel'),
hint: integrationsRepo.split('/')[0],
onChanged: (value) => notifyListeners(),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
CustomTextField( // Patches repository's name
leadingIcon: const Icon( TextField(
Icons.merge_outlined, controller: _patSourceController,
color: Colors.transparent, autocorrect: false,
),
inputController: _intSourceController,
label: I18nText('settingsView.sourcesIntegrationsLabel'),
hint: integrationsRepo.split('/')[1],
onChanged: (value) => notifyListeners(), onChanged: (value) => notifyListeners(),
decoration: InputDecoration(
icon: const Icon(
Icons.extension_outlined,
color: Colors.transparent,
),
border: const OutlineInputBorder(),
labelText: FlutterI18n.translate(
context,
'settingsView.sourcesPatchesLabel',
),
hintText: patchesRepo.split('/')[1],
),
),
const SizedBox(height: 8),
// Integrations owner's name
TextField(
controller: _orgIntSourceController,
autocorrect: false,
onChanged: (value) => notifyListeners(),
decoration: InputDecoration(
icon: Icon(
Icons.merge_outlined,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
border: const OutlineInputBorder(),
labelText: FlutterI18n.translate(
context,
'settingsView.orgIntegrationsLabel',
),
hintText: integrationsRepo.split('/')[0],
),
),
const SizedBox(height: 8),
// Integrations repository's name
TextField(
controller: _intSourceController,
autocorrect: false,
onChanged: (value) => notifyListeners(),
decoration: InputDecoration(
icon: const Icon(
Icons.merge_outlined,
color: Colors.transparent,
),
border: const OutlineInputBorder(),
labelText: FlutterI18n.translate(
context,
'settingsView.sourcesIntegrationsLabel',
),
hintText: integrationsRepo.split('/')[1],
),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
I18nText('settingsView.sourcesUpdateNote'), I18nText('settingsView.sourcesUpdateNote'),
@@ -107,9 +147,7 @@ class SManageSources extends BaseViewModel {
), ),
), ),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('cancelButton'),
onPressed: () { onPressed: () {
_orgPatSourceController.clear(); _orgPatSourceController.clear();
_patSourceController.clear(); _patSourceController.clear();
@@ -117,9 +155,9 @@ class SManageSources extends BaseViewModel {
_intSourceController.clear(); _intSourceController.clear();
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('cancelButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('okButton'),
onPressed: () { onPressed: () {
_managerAPI.setRepoUrl(_hostSourceController.text.trim()); _managerAPI.setRepoUrl(_hostSourceController.text.trim());
_managerAPI.setPatchesRepo( _managerAPI.setPatchesRepo(
@@ -133,6 +171,7 @@ class SManageSources extends BaseViewModel {
_toast.showBottom('settingsView.restartAppForChanges'); _toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('okButton'),
), ),
], ],
), ),
@@ -144,16 +183,13 @@ class SManageSources extends BaseViewModel {
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: I18nText('settingsView.sourcesResetDialogTitle'), title: I18nText('settingsView.sourcesResetDialogTitle'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText('settingsView.sourcesResetDialogText'), content: I18nText('settingsView.sourcesResetDialogText'),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: I18nText('noButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('yesButton'),
onPressed: () { onPressed: () {
_managerAPI.setRepoUrl(''); _managerAPI.setRepoUrl('');
_managerAPI.setPatchesRepo(''); _managerAPI.setPatchesRepo('');
@@ -165,6 +201,7 @@ class SManageSources extends BaseViewModel {
..pop() ..pop()
..pop(); ..pop();
}, },
child: I18nText('yesButton'),
), ),
], ],
), ),

View File

@@ -51,7 +51,6 @@ class SUpdateLanguage extends BaseViewModel {
context: parentContext, context: parentContext,
builder: (context) => SimpleDialog( builder: (context) => SimpleDialog(
title: I18nText('settingsView.languageLabel'), title: I18nText('settingsView.languageLabel'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
children: [ children: [
SizedBox( SizedBox(
height: 500, height: 500,

View File

@@ -7,7 +7,8 @@ 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/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/haptics/haptic_radio_list_tile.dart';
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_switch_list_tile.dart';
class SUpdateThemeUI extends StatefulWidget { class SUpdateThemeUI extends StatefulWidget {
const SUpdateThemeUI({super.key}); const SUpdateThemeUI({super.key});
@@ -36,14 +37,14 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
), ),
), ),
), ),
trailing: CustomMaterialButton( trailing: FilledButton(
label: getThemeModeName(),
onPressed: () => {showThemeDialog(context)}, onPressed: () => {showThemeDialog(context)},
child: getThemeModeName(),
), ),
onTap: () => {showThemeDialog(context)}, onTap: () => {showThemeDialog(context)},
), ),
if (managerAPI.isDynamicThemeAvailable) if (managerAPI.isDynamicThemeAvailable)
SwitchListTile( HapticSwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText( title: I18nText(
'settingsView.dynamicThemeLabel', 'settingsView.dynamicThemeLabel',
@@ -122,7 +123,6 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
title: I18nText('settingsView.themeModeLabel'), title: I18nText('settingsView.themeModeLabel'),
icon: const Icon(Icons.palette), icon: const Icon(Icons.palette),
contentPadding: const EdgeInsets.symmetric(vertical: 16), contentPadding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: SingleChildScrollView( content: SingleChildScrollView(
child: ValueListenableBuilder( child: ValueListenableBuilder(
valueListenable: newTheme, valueListenable: newTheme,
@@ -131,7 +131,7 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
RadioListTile( HapticRadioListTile(
title: I18nText('settingsView.systemThemeLabel'), title: I18nText('settingsView.systemThemeLabel'),
contentPadding: const EdgeInsets.symmetric(horizontal: 16), contentPadding: const EdgeInsets.symmetric(horizontal: 16),
value: 0, value: 0,
@@ -140,7 +140,7 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
newTheme.value = value!; newTheme.value = value!;
}, },
), ),
RadioListTile( HapticRadioListTile(
title: I18nText('settingsView.lightThemeLabel'), title: I18nText('settingsView.lightThemeLabel'),
contentPadding: const EdgeInsets.symmetric(horizontal: 16), contentPadding: const EdgeInsets.symmetric(horizontal: 16),
value: 1, value: 1,
@@ -149,7 +149,7 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
newTheme.value = value!; newTheme.value = value!;
}, },
), ),
RadioListTile( HapticRadioListTile(
title: I18nText('settingsView.darkThemeLabel'), title: I18nText('settingsView.darkThemeLabel'),
contentPadding: const EdgeInsets.symmetric(horizontal: 16), contentPadding: const EdgeInsets.symmetric(horizontal: 16),
value: 2, value: 2,
@@ -164,19 +164,18 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
), ),
), ),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('cancelButton'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('cancelButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('okButton'),
onPressed: () { onPressed: () {
setThemeMode(context, newTheme.value); setThemeMode(context, newTheme.value);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('okButton'),
), ),
], ],
), ),

View File

@@ -12,7 +12,6 @@ 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/widgets/shared/custom_material_button.dart';
import 'package:share_plus/share_plus.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';
@@ -52,7 +51,6 @@ class SettingsViewModel extends BaseViewModel {
return showDialog( return showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
title: I18nText('warning'), title: I18nText('warning'),
content: I18nText( content: I18nText(
'settingsView.enablePatchesSelectionWarningText', 'settingsView.enablePatchesSelectionWarningText',
@@ -65,20 +63,19 @@ class SettingsViewModel extends BaseViewModel {
), ),
), ),
actions: [ actions: [
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('yesButton'),
onPressed: () { onPressed: () {
_managerAPI.setChangingToggleModified(true); _managerAPI.setChangingToggleModified(true);
_managerAPI.setPatchesChangeEnabled(true); _managerAPI.setPatchesChangeEnabled(true);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('yesButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('noButton'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('noButton'),
), ),
], ],
), ),
@@ -87,7 +84,6 @@ class SettingsViewModel extends BaseViewModel {
return showDialog( return showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
title: I18nText('warning'), title: I18nText('warning'),
content: I18nText( content: I18nText(
'settingsView.disablePatchesSelectionWarningText', 'settingsView.disablePatchesSelectionWarningText',
@@ -100,21 +96,20 @@ class SettingsViewModel extends BaseViewModel {
), ),
), ),
actions: [ actions: [
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('noButton'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('noButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('yesButton'),
onPressed: () { onPressed: () {
_managerAPI.setChangingToggleModified(true); _managerAPI.setChangingToggleModified(true);
_patchesSelectorViewModel.selectDefaultPatches(); _patchesSelectorViewModel.selectDefaultPatches();
_managerAPI.setPatchesChangeEnabled(false); _managerAPI.setPatchesChangeEnabled(false);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('yesButton'),
), ),
], ],
), ),
@@ -145,12 +140,13 @@ class SettingsViewModel extends BaseViewModel {
} }
Future<void>? showRequireSuggestedAppVersionDialog( Future<void>? showRequireSuggestedAppVersionDialog(
BuildContext context, bool value,) { BuildContext context,
bool value,
) {
if (!value) { if (!value) {
return showDialog( return showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
title: I18nText('warning'), title: I18nText('warning'),
content: I18nText( content: I18nText(
'settingsView.requireSuggestedAppVersionDialogText', 'settingsView.requireSuggestedAppVersionDialogText',
@@ -163,19 +159,18 @@ class SettingsViewModel extends BaseViewModel {
), ),
), ),
actions: [ actions: [
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('yesButton'),
onPressed: () { onPressed: () {
_managerAPI.enableRequireSuggestedAppVersionStatus(false); _managerAPI.enableRequireSuggestedAppVersionStatus(false);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('yesButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('noButton'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('noButton'),
), ),
], ],
), ),

View File

@@ -184,7 +184,7 @@ class AppInfoView extends StatelessWidget {
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
I18nText( I18nText(
'appInfoView.unpatchButton', 'appInfoView.unmountButton',
child: Text( child: Text(
'', '',
style: TextStyle( style: TextStyle(
@@ -236,8 +236,8 @@ class AppInfoView extends StatelessWidget {
), ),
), ),
subtitle: app.isRooted subtitle: app.isRooted
? I18nText('appInfoView.rootTypeLabel') ? I18nText('appInfoView.mountTypeLabel')
: I18nText('appInfoView.nonRootTypeLabel'), : I18nText('appInfoView.regularTypeLabel'),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
ListTile( ListTile(

View File

@@ -12,7 +12,6 @@ import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart'; import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.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/shared/custom_material_button.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class AppInfoViewModel extends BaseViewModel { class AppInfoViewModel extends BaseViewModel {
@@ -26,18 +25,17 @@ class AppInfoViewModel extends BaseViewModel {
PatchedApplication app, PatchedApplication app,
bool onlyUnpatch, bool onlyUnpatch,
) async { ) async {
bool isUninstalled = true; var isUninstalled = onlyUnpatch;
if (app.isRooted) {
final bool hasRootPermissions = await _rootAPI.hasRootPermissions(); if (!onlyUnpatch) {
if (hasRootPermissions) { // TODO(Someone): Wait for the app to uninstall successfully.
await _rootAPI.deleteApp(app.packageName, app.apkFilePath);
if (!onlyUnpatch) {
await DeviceApps.uninstallApp(app.packageName);
}
}
} else {
isUninstalled = await DeviceApps.uninstallApp(app.packageName); isUninstalled = await DeviceApps.uninstallApp(app.packageName);
} }
if (isUninstalled && app.isRooted && await _rootAPI.hasRootPermissions()) {
await _rootAPI.uninstall(app.packageName);
}
if (isUninstalled) { if (isUninstalled) {
await _managerAPI.deletePatchedApp(app); await _managerAPI.deletePatchedApp(app);
locator<HomeViewModel>().initialize(context); locator<HomeViewModel>().initialize(context);
@@ -67,12 +65,11 @@ class AppInfoViewModel extends BaseViewModel {
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: I18nText('appInfoView.rootDialogTitle'), title: I18nText('appInfoView.rootDialogTitle'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText('appInfoView.rootDialogText'), content: I18nText('appInfoView.rootDialogText'),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( FilledButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: I18nText('okButton'),
), ),
], ],
), ),
@@ -83,32 +80,53 @@ class AppInfoViewModel extends BaseViewModel {
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: I18nText( title: I18nText(
'appInfoView.unpatchButton', 'appInfoView.unmountButton',
), ),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText( content: I18nText(
'appInfoView.unpatchDialogText', 'appInfoView.unmountDialogText',
), ),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: I18nText('noButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('yesButton'),
onPressed: () { onPressed: () {
uninstallApp(context, app, onlyUnpatch); uninstallApp(context, app, true);
Navigator.of(context).pop(); Navigator.of(context).pop();
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: I18nText('yesButton'),
), ),
], ],
), ),
); );
} else { } else {
uninstallApp(context, app, onlyUnpatch); return showDialog(
Navigator.of(context).pop(); context: context,
builder: (context) => AlertDialog(
title: I18nText(
'appInfoView.uninstallButton',
),
content: I18nText(
'appInfoView.uninstallDialogText',
),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: I18nText('noButton'),
),
FilledButton(
onPressed: () {
uninstallApp(context, app, false);
Navigator.of(context).pop();
Navigator.of(context).pop();
},
child: I18nText('yesButton'),
),
],
),
);
} }
} }
} }
@@ -131,14 +149,13 @@ class AppInfoViewModel extends BaseViewModel {
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: I18nText('appInfoView.appliedPatchesLabel'), title: I18nText('appInfoView.appliedPatchesLabel'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: SingleChildScrollView( content: SingleChildScrollView(
child: Text(getAppliedPatchesString(app.appliedPatches)), child: Text(getAppliedPatchesString(app.appliedPatches)),
), ),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( FilledButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: I18nText('okButton'),
), ),
], ],
), ),

View File

@@ -54,25 +54,43 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 4,
children: [
Text(
widget.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
),
),
Text(
widget.installedVersion,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
),
),
Text(
widget.patchesCount == 1
? '${widget.patchesCount} patch'
: '${widget.patchesCount} patches',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.secondary,
),
),
],
),
Text( Text(
widget.name, widget.pkgName,
maxLines: 2,
overflow: TextOverflow.visible,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
Text(widget.pkgName),
I18nText(
FlutterI18n.translate(
context,
'installed',
translationParams: {
'version': 'v${widget.installedVersion}',
},
),
), ),
const SizedBox(height: 4),
Wrap( Wrap(
crossAxisAlignment: WrapCrossAlignment.center, crossAxisAlignment: WrapCrossAlignment.center,
children: [ children: [
@@ -85,7 +103,7 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
borderRadius: borderRadius:
const BorderRadius.all(Radius.circular(8)), const BorderRadius.all(Radius.circular(8)),
child: Container( child: Container(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -95,18 +113,10 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
'version': widget.suggestedVersion.isEmpty 'version': widget.suggestedVersion.isEmpty
? FlutterI18n.translate( ? FlutterI18n.translate(
context, context,
'appSelectorCard.allVersions', 'appSelectorCard.anyVersion',
) )
: 'v${widget.suggestedVersion}', : widget.suggestedVersion,
}, },
child: Text(
'',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSecondaryContainer,
),
),
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Icon( Icon(
@@ -121,17 +131,6 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
), ),
), ),
), ),
const SizedBox(width: 4),
Text(
widget.patchesCount == 1
? '${widget.patchesCount} patch'
: '${widget.patchesCount} patches',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
),
),
], ],
), ),
], ],

View File

@@ -33,103 +33,91 @@ class _NotInstalledAppItem extends State<NotInstalledAppItem> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
Container( Container(
width: 48,
height: 48, height: 48,
padding: const EdgeInsets.symmetric(vertical: 4.0),
alignment: Alignment.center, alignment: Alignment.center,
child: const CircleAvatar( child: const CircleAvatar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
child: Icon( child: Icon(
Icons.square_rounded, Icons.square_rounded,
color: Colors.grey, color: Colors.grey,
size: 44, size: 48,
), ),
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( Wrap(
widget.name, crossAxisAlignment: WrapCrossAlignment.center,
style: const TextStyle( spacing: 4,
fontSize: 16, children: [
fontWeight: FontWeight.w500, Text(
widget.name,
style: const TextStyle(
fontSize: 16,
),
),
Text(
widget.patchesCount == 1
? '${widget.patchesCount} patch'
: '${widget.patchesCount} patches',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.secondary,
),
),
],
), ),
), const SizedBox(height: 4),
const SizedBox(height: 4), Wrap(
I18nText( crossAxisAlignment: WrapCrossAlignment.center,
'appSelectorCard.notInstalled', children: [
child: Text( Material(
'', color:
style: TextStyle( Theme.of(context).colorScheme.secondaryContainer,
color: Theme.of(context).textTheme.titleLarge!.color,
),
),
),
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Material(
color: Theme.of(context).colorScheme.secondaryContainer,
borderRadius:
const BorderRadius.all(Radius.circular(8)),
child: InkWell(
onTap: widget.onLinkTap,
borderRadius: borderRadius:
const BorderRadius.all(Radius.circular(8)), const BorderRadius.all(Radius.circular(8)),
child: Container( child: InkWell(
padding: const EdgeInsets.all(4), onTap: widget.onLinkTap,
child: Row( borderRadius:
mainAxisSize: MainAxisSize.min, const BorderRadius.all(Radius.circular(8)),
children: [ child: Container(
I18nText( padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
'suggested', child: Row(
translationParams: { mainAxisSize: MainAxisSize.min,
'version': widget.suggestedVersion.isEmpty children: [
? FlutterI18n.translate( I18nText(
context, 'suggested',
'appSelectorCard.allVersions', translationParams: {
) 'version': widget.suggestedVersion.isEmpty
: 'v${widget.suggestedVersion}', ? FlutterI18n.translate(
}, context,
child: Text( 'appSelectorCard.anyVersion',
'', )
style: TextStyle( : widget.suggestedVersion,
color: Theme.of(context) },
.colorScheme
.onSecondaryContainer,
),
), ),
), const SizedBox(width: 4),
const SizedBox(width: 4), Icon(
Icon( Icons.search,
Icons.search, size: 16,
size: 16, color: Theme.of(context)
color: Theme.of(context) .colorScheme
.colorScheme .onSecondaryContainer,
.onSecondaryContainer, ),
), ],
], ),
), ),
), ),
), ),
), ],
const SizedBox(width: 4), ),
Text( ]),
widget.patchesCount == 1
? '${widget.patchesCount} patch'
: '${widget.patchesCount} patches',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
),
),
],
),
],
),
), ),
], ],
), ),

View File

@@ -3,7 +3,6 @@ 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/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/views/home/home_viewmodel.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_material_button.dart';
class LatestCommitCard extends StatefulWidget { class LatestCommitCard extends StatefulWidget {
const LatestCommitCard({ const LatestCommitCard({
@@ -58,14 +57,14 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
initialData: false, initialData: false,
builder: (context, snapshot) => Opacity( builder: (context, snapshot) => Opacity(
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25, opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
child: CustomMaterialButton( child: FilledButton(
label: I18nText('updateButton'),
onPressed: snapshot.hasData && snapshot.data! onPressed: snapshot.hasData && snapshot.data!
? () => widget.model.showUpdateConfirmationDialog( ? () => widget.model.showUpdateConfirmationDialog(
widget.parentContext, widget.parentContext,
false, false,
) )
: () => {}, : () => {},
child: I18nText('updateButton'),
), ),
), ),
), ),
@@ -113,14 +112,14 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
initialData: false, initialData: false,
builder: (context, snapshot) => Opacity( builder: (context, snapshot) => Opacity(
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25, opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
child: CustomMaterialButton( child: FilledButton(
label: I18nText('updateButton'),
onPressed: snapshot.hasData && snapshot.data! onPressed: snapshot.hasData && snapshot.data!
? () => widget.model.showUpdateConfirmationDialog( ? () => widget.model.showUpdateConfirmationDialog(
widget.parentContext, widget.parentContext,
true, true,
) )
: () => {}, : () => {},
child: I18nText('updateButton'),
), ),
), ),
), ),

View File

@@ -3,7 +3,6 @@ import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
class UpdateConfirmationDialog extends StatelessWidget { class UpdateConfirmationDialog extends StatelessWidget {
const UpdateConfirmationDialog({super.key, required this.isPatches}); const UpdateConfirmationDialog({super.key, required this.isPatches});
@@ -86,15 +85,14 @@ class UpdateConfirmationDialog extends StatelessWidget {
], ],
), ),
), ),
CustomMaterialButton( FilledButton(
isExpanded: true,
label: I18nText('updateButton'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
isPatches isPatches
? model.updatePatches(context) ? model.updatePatches(context)
: model.updateManager(context); : model.updateManager(context);
}, },
child: I18nText('updateButton'),
), ),
], ],
), ),

View File

@@ -15,13 +15,21 @@ class AppSelectorCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final vm = locator<PatcherViewModel>();
String? suggestedVersion;
if (vm.selectedApp != null) {
suggestedVersion = vm.getSuggestedVersionString(context);
}
return CustomCard( return CustomCard(
onTap: onPressed, onTap: onPressed,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
I18nText( I18nText(
locator<PatcherViewModel>().selectedApp == null vm.selectedApp == null
? 'appSelectorCard.widgetTitle' ? 'appSelectorCard.widgetTitle'
: 'appSelectorCard.widgetTitleSelected', : 'appSelectorCard.widgetTitleSelected',
child: const Text( child: const Text(
@@ -33,7 +41,7 @@ class AppSelectorCard extends StatelessWidget {
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
if (locator<PatcherViewModel>().selectedApp == null) if (vm.selectedApp == null)
I18nText('appSelectorCard.widgetSubtitle') I18nText('appSelectorCard.widgetSubtitle')
else else
Row( Row(
@@ -42,9 +50,9 @@ class AppSelectorCard extends StatelessWidget {
height: 18.0, height: 18.0,
child: ClipOval( child: ClipOval(
child: Image.memory( child: Image.memory(
locator<PatcherViewModel>().selectedApp == null vm.selectedApp == null
? Uint8List(0) ? Uint8List(0)
: locator<PatcherViewModel>().selectedApp!.icon, : vm.selectedApp!.icon,
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
), ),
@@ -52,13 +60,13 @@ class AppSelectorCard extends StatelessWidget {
const SizedBox(width: 6), const SizedBox(width: 6),
Flexible( Flexible(
child: Text( child: Text(
locator<PatcherViewModel>().getAppSelectionString(), vm.getAppSelectionString(),
style: const TextStyle(fontWeight: FontWeight.w600), style: const TextStyle(fontWeight: FontWeight.w600),
), ),
), ),
], ],
), ),
if (locator<PatcherViewModel>().selectedApp == null) if (vm.selectedApp == null)
Container() Container()
else else
Column( Column(
@@ -66,49 +74,50 @@ class AppSelectorCard extends StatelessWidget {
children: [ children: [
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
locator<PatcherViewModel>().getCurrentVersionString(context), vm.selectedApp!.packageName,
), ),
Row( if (suggestedVersion!.isNotEmpty &&
children: [ suggestedVersion != vm.selectedApp!.version) ...[
Material( const SizedBox(height: 4),
color: Theme.of(context).colorScheme.secondaryContainer, Row(
borderRadius: const BorderRadius.all(Radius.circular(8)), children: [
child: InkWell( Material(
onTap: () { color: Theme.of(context).colorScheme.secondaryContainer,
locator<PatcherViewModel>()
.searchSuggestedVersionOnWeb();
},
borderRadius: borderRadius:
const BorderRadius.all(Radius.circular(8)), const BorderRadius.all(Radius.circular(8)),
child: Container( child: InkWell(
padding: const EdgeInsets.all(4), onTap: () {
child: Row( vm.queryVersion(suggestedVersion!);
mainAxisSize: MainAxisSize.min, },
children: [ borderRadius:
Text( const BorderRadius.all(Radius.circular(8)),
locator<PatcherViewModel>() child: Container(
.getSuggestedVersionString(context), padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
style: TextStyle( child: Row(
mainAxisSize: MainAxisSize.min,
children: [
I18nText(
'suggested',
translationParams: {
'version': suggestedVersion,
},
),
const SizedBox(width: 4),
Icon(
Icons.search,
size: 16,
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onSecondaryContainer, .onSecondaryContainer,
), ),
), ],
const SizedBox(width: 4), ),
Icon(
Icons.search,
size: 16,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer,
),
],
), ),
), ),
), ),
), ],
], ),
), ]
], ],
), ),
], ],

View File

@@ -58,7 +58,8 @@ class PatchSelectorCard extends StatelessWidget {
String _getPatchesSelection() { String _getPatchesSelection() {
String text = ''; String text = '';
final List<Patch> selectedPatches = locator<PatcherViewModel>().selectedPatches; final List<Patch> selectedPatches =
locator<PatcherViewModel>().selectedPatches;
selectedPatches.sort((a, b) => a.name.compareTo(b.name)); selectedPatches.sort((a, b) => a.name.compareTo(b.name));
for (final Patch p in selectedPatches) { for (final Patch p in selectedPatches) {
text += '${p.getSimpleName()}\n'; text += '${p.getSimpleName()}\n';

View File

@@ -4,8 +4,8 @@ import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart'; import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_checkbox.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_custom_card.dart';
// ignore: must_be_immutable // ignore: must_be_immutable
class PatchItem extends StatefulWidget { class PatchItem extends StatefulWidget {
@@ -57,7 +57,7 @@ class _PatchItemState extends State<PatchItem> {
widget._managerAPI.isVersionCompatibilityCheckEnabled() == true widget._managerAPI.isVersionCompatibilityCheckEnabled() == true
? 0.5 ? 0.5
: 1, : 1,
child: CustomCard( child: HapticCustomCard(
padding: EdgeInsets.only( padding: EdgeInsets.only(
top: 12, top: 12,
bottom: 16, bottom: 16,
@@ -89,7 +89,7 @@ class _PatchItemState extends State<PatchItem> {
children: [ children: [
Transform.scale( Transform.scale(
scale: 1.2, scale: 1.2,
child: Checkbox( child: HapticCheckbox(
value: widget.isSelected, value: widget.isSelected,
activeColor: Theme.of(context).colorScheme.primary, activeColor: Theme.of(context).colorScheme.primary,
checkColor: Theme.of(context).colorScheme.secondaryContainer, checkColor: Theme.of(context).colorScheme.secondaryContainer,
@@ -216,7 +216,6 @@ class _PatchItemState extends State<PatchItem> {
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: I18nText('warning'), title: I18nText('warning'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText( content: I18nText(
'patchItem.unsupportedDialogText', 'patchItem.unsupportedDialogText',
translationParams: { translationParams: {
@@ -226,9 +225,9 @@ class _PatchItemState extends State<PatchItem> {
}, },
), ),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( FilledButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: I18nText('okButton'),
), ),
], ],
), ),
@@ -240,14 +239,13 @@ class _PatchItemState extends State<PatchItem> {
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: I18nText('notice'), title: I18nText('notice'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText( content: I18nText(
'patchItem.unsupportedRequiredOption', 'patchItem.unsupportedRequiredOption',
), ),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( FilledButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: I18nText('okButton'),
), ),
], ],
), ),

View File

@@ -400,7 +400,9 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
final bool isStringOption = widget.optionType.contains('String'); final bool isStringOption = widget.optionType.contains('String');
final bool isArrayOption = widget.optionType.contains('Array'); final bool isArrayOption = widget.optionType.contains('Array');
selectedKey ??= widget.selectedKey; selectedKey ??= widget.selectedKey;
controller.text = !isStringOption && isArrayOption && selectedKey == '' && controller.text = !isStringOption &&
isArrayOption &&
selectedKey == '' &&
(widget.value != null && widget.value.toString().startsWith('[')) (widget.value != null && widget.value.toString().startsWith('['))
? '' ? ''
: widget.value ?? ''; : widget.value ?? '';
@@ -424,8 +426,11 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
text: e.key, text: e.key,
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer,
), ),
children: [ children: [
TextSpan( TextSpan(
@@ -519,7 +524,8 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
} }
break; break;
case 'patchOptionsView.selectFolder': case 'patchOptionsView.selectFolder':
final DirectoryLocation? result = await FlutterFileDialog.pickDirectory(); final DirectoryLocation? result =
await FlutterFileDialog.pickDirectory();
if (result != null) { if (result != null) {
controller.text = result.toString(); controller.text = result.toString();
widget.onChanged(controller.text); widget.onChanged(controller.text);

View File

@@ -1,63 +0,0 @@
import 'package:flutter/material.dart';
class CustomSwitch extends StatelessWidget {
const CustomSwitch({
super.key,
required this.onChanged,
required this.value,
});
final ValueChanged<bool> onChanged;
final bool value;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => onChanged(!value),
child: SizedBox(
height: 25,
width: 50,
child: Stack(
children: <Widget>[
AnimatedContainer(
height: 25,
width: 50,
curve: Curves.ease,
duration: const Duration(milliseconds: 400),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(25.0),
),
color: value
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.secondary,
),
),
AnimatedAlign(
curve: Curves.ease,
duration: const Duration(milliseconds: 400),
alignment: !value ? Alignment.centerLeft : Alignment.centerRight,
child: Container(
height: 20,
width: 20,
margin: const EdgeInsets.symmetric(horizontal: 3),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: value
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surface,
boxShadow: [
BoxShadow(
color: Colors.black12.withOpacity(0.1),
spreadRadius: 0.5,
blurRadius: 1,
),
],
),
),
),
],
),
),
);
}
}

View File

@@ -1,32 +0,0 @@
import 'package:flutter/material.dart';
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch.dart';
class CustomSwitchTile extends StatelessWidget {
const CustomSwitchTile({
super.key,
required this.title,
required this.subtitle,
required this.value,
required this.onTap,
this.padding,
});
final Widget title;
final Widget subtitle;
final bool value;
final Function(bool) onTap;
final EdgeInsetsGeometry? padding;
@override
Widget build(BuildContext context) {
return ListTile(
contentPadding: padding ?? EdgeInsets.zero,
title: title,
subtitle: subtitle,
onTap: () => onTap(!value),
trailing: CustomSwitch(
value: value,
onChanged: onTap,
),
);
}
}

View File

@@ -1,73 +0,0 @@
import 'package:flutter/material.dart';
class CustomTextField extends StatelessWidget {
const CustomTextField({
super.key,
required this.inputController,
required this.label,
required this.hint,
this.leadingIcon,
required this.onChanged,
});
final TextEditingController inputController;
final Widget label;
final String hint;
final Widget? leadingIcon;
final Function(String)? onChanged;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 4.0),
child: TextField(
controller: inputController,
onChanged: onChanged,
keyboardType: TextInputType.text,
decoration: InputDecoration(
icon: leadingIcon,
label: label,
filled: true,
fillColor: Theme.of(context).colorScheme.secondaryContainer,
hintText: hint,
hintStyle: TextStyle(
color: Theme.of(context).colorScheme.secondary,
),
floatingLabelStyle: MaterialStateTextStyle.resolveWith(
(states) => states.contains(MaterialState.focused)
? TextStyle(color: Theme.of(context).colorScheme.primary)
: TextStyle(color: Theme.of(context).colorScheme.secondary),
),
contentPadding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 16.0,
),
border: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
),
borderRadius: BorderRadius.circular(10),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 2.0,
),
borderRadius: BorderRadius.circular(10),
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.error,
),
borderRadius: BorderRadius.circular(10),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
),
borderRadius: BorderRadius.circular(10),
),
),
),
);
}
}

View File

@@ -10,7 +10,6 @@ import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_universal_patches.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_universal_patches.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_version_compatibility_check.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_version_compatibility_check.dart';
class SAdvancedSection extends StatelessWidget { class SAdvancedSection extends StatelessWidget {
const SAdvancedSection({super.key}); const SAdvancedSection({super.key});

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_i18n/widgets/I18nText.dart'; import 'package:flutter_i18n/widgets/I18nText.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/shared/haptics/haptic_switch_list_tile.dart';
class SAutoUpdatePatches extends StatefulWidget { class SAutoUpdatePatches extends StatefulWidget {
const SAutoUpdatePatches({super.key}); const SAutoUpdatePatches({super.key});
@@ -14,7 +15,7 @@ final _settingsViewModel = SettingsViewModel();
class _SAutoUpdatePatchesState extends State<SAutoUpdatePatches> { class _SAutoUpdatePatchesState extends State<SAutoUpdatePatches> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SwitchListTile( return HapticSwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText( title: I18nText(
'settingsView.autoUpdatePatchesLabel', 'settingsView.autoUpdatePatchesLabel',

View File

@@ -1,12 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_i18n/widgets/I18nText.dart'; import 'package:flutter_i18n/widgets/I18nText.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/shared/haptics/haptic_switch_list_tile.dart';
class SEnablePatchesSelection extends StatefulWidget { class SEnablePatchesSelection extends StatefulWidget {
const SEnablePatchesSelection({super.key}); const SEnablePatchesSelection({super.key});
@override @override
State<SEnablePatchesSelection> createState() => _SEnablePatchesSelectionState(); State<SEnablePatchesSelection> createState() =>
_SEnablePatchesSelectionState();
} }
final _settingsViewModel = SettingsViewModel(); final _settingsViewModel = SettingsViewModel();
@@ -14,7 +16,7 @@ final _settingsViewModel = SettingsViewModel();
class _SEnablePatchesSelectionState extends State<SEnablePatchesSelection> { class _SEnablePatchesSelectionState extends State<SEnablePatchesSelection> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SwitchListTile( return HapticSwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText( title: I18nText(
'settingsView.enablePatchesSelectionLabel', 'settingsView.enablePatchesSelectionLabel',

View File

@@ -3,7 +3,6 @@ import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_keystore_password.dart'; import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_keystore_password.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_section.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
final _settingsViewModel = SettingsViewModel(); final _settingsViewModel = SettingsViewModel();
@@ -151,20 +150,18 @@ class SExportSection extends StatelessWidget {
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: I18nText(dialogTitle), title: I18nText(dialogTitle),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText(dialogText), content: I18nText(dialogText),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: I18nText('noButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('yesButton'),
onPressed: () => { onPressed: () => {
Navigator.of(context).pop(), Navigator.of(context).pop(),
dialogAction(), dialogAction(),
}, },
child: I18nText('yesButton'),
), ),
], ],
), ),
@@ -176,20 +173,18 @@ class SExportSection extends StatelessWidget {
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: I18nText('settingsView.regenerateKeystoreDialogTitle'), title: I18nText('settingsView.regenerateKeystoreDialogTitle'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText('settingsView.regenerateKeystoreDialogText'), content: I18nText('settingsView.regenerateKeystoreDialogText'),
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( TextButton(
isFilled: false,
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: I18nText('noButton'),
), ),
CustomMaterialButton( FilledButton(
label: I18nText('yesButton'),
onPressed: () => { onPressed: () => {
Navigator.of(context).pop(), Navigator.of(context).pop(),
_settingsViewModel.deleteKeystore(), _settingsViewModel.deleteKeystore(),
}, },
child: I18nText('yesButton'),
), ),
], ],
), ),

View File

@@ -1,20 +1,23 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_i18n/widgets/I18nText.dart'; import 'package:flutter_i18n/widgets/I18nText.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/shared/haptics/haptic_switch_list_tile.dart';
class SRequireSuggestedAppVersion extends StatefulWidget { class SRequireSuggestedAppVersion extends StatefulWidget {
const SRequireSuggestedAppVersion({super.key}); const SRequireSuggestedAppVersion({super.key});
@override @override
State<SRequireSuggestedAppVersion> createState() => _SRequireSuggestedAppVersionState(); State<SRequireSuggestedAppVersion> createState() =>
_SRequireSuggestedAppVersionState();
} }
final _settingsViewModel = SettingsViewModel(); final _settingsViewModel = SettingsViewModel();
class _SRequireSuggestedAppVersionState extends State<SRequireSuggestedAppVersion> { class _SRequireSuggestedAppVersionState
extends State<SRequireSuggestedAppVersion> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SwitchListTile( return HapticSwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText( title: I18nText(
'settingsView.requireSuggestedAppVersionLabel', 'settingsView.requireSuggestedAppVersionLabel',
@@ -29,8 +32,11 @@ class _SRequireSuggestedAppVersionState extends State<SRequireSuggestedAppVersio
subtitle: I18nText('settingsView.requireSuggestedAppVersionHint'), subtitle: I18nText('settingsView.requireSuggestedAppVersionHint'),
value: _settingsViewModel.isRequireSuggestedAppVersionEnabled(), value: _settingsViewModel.isRequireSuggestedAppVersionEnabled(),
onChanged: (value) async { onChanged: (value) async {
await _settingsViewModel.showRequireSuggestedAppVersionDialog(context, value,); await _settingsViewModel.showRequireSuggestedAppVersionDialog(
setState(() {}); context,
value,
);
setState(() {});
}, },
); );
} }

View File

@@ -3,24 +3,23 @@ import 'package:flutter_i18n/widgets/I18nText.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/settings_viewmodel.dart'; import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_switch_list_tile.dart';
class SUniversalPatches extends StatefulWidget { class SUniversalPatches extends StatefulWidget {
const SUniversalPatches({super.key}); const SUniversalPatches({super.key});
@override @override
State<SUniversalPatches> createState() => State<SUniversalPatches> createState() => _SUniversalPatchesState();
_SUniversalPatchesState();
} }
final _settingsViewModel = SettingsViewModel(); final _settingsViewModel = SettingsViewModel();
final _patchesSelectorViewModel = PatchesSelectorViewModel(); final _patchesSelectorViewModel = PatchesSelectorViewModel();
final _patcherViewModel = PatcherViewModel(); final _patcherViewModel = PatcherViewModel();
class _SUniversalPatchesState class _SUniversalPatchesState extends State<SUniversalPatches> {
extends State<SUniversalPatches> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SwitchListTile( return HapticSwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText( title: I18nText(
'settingsView.universalPatchesLabel', 'settingsView.universalPatchesLabel',

View File

@@ -3,23 +3,26 @@ import 'package:flutter_i18n/widgets/I18nText.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/settings_viewmodel.dart'; import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_switch_list_tile.dart';
import 'package:revanced_manager/utils/check_for_supported_patch.dart'; import 'package:revanced_manager/utils/check_for_supported_patch.dart';
class SVersionCompatibilityCheck extends StatefulWidget { class SVersionCompatibilityCheck extends StatefulWidget {
const SVersionCompatibilityCheck({super.key}); const SVersionCompatibilityCheck({super.key});
@override @override
State<SVersionCompatibilityCheck> createState() => _SVersionCompatibilityCheckState(); State<SVersionCompatibilityCheck> createState() =>
_SVersionCompatibilityCheckState();
} }
final _settingsViewModel = SettingsViewModel(); final _settingsViewModel = SettingsViewModel();
final _patchesSelectorViewModel = PatchesSelectorViewModel(); final _patchesSelectorViewModel = PatchesSelectorViewModel();
final _patcherViewModel = PatcherViewModel(); final _patcherViewModel = PatcherViewModel();
class _SVersionCompatibilityCheckState extends State<SVersionCompatibilityCheck> { class _SVersionCompatibilityCheckState
extends State<SVersionCompatibilityCheck> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SwitchListTile( return HapticSwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText( title: I18nText(
'settingsView.versionCompatibilityCheckLabel', 'settingsView.versionCompatibilityCheckLabel',

View File

@@ -3,7 +3,6 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_i18n/flutter_i18n.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_material_button.dart';
import 'package:timeago/timeago.dart'; import 'package:timeago/timeago.dart';
class ApplicationItem extends StatefulWidget { class ApplicationItem extends StatefulWidget {
@@ -24,7 +23,6 @@ class ApplicationItem extends StatefulWidget {
} }
class _ApplicationItemState extends State<ApplicationItem> { class _ApplicationItemState extends State<ApplicationItem> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -81,9 +79,9 @@ class _ApplicationItemState extends State<ApplicationItem> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[ children: <Widget>[
CustomMaterialButton( FilledButton(
label: I18nText('applicationItem.infoButton'),
onPressed: widget.onPressed, onPressed: widget.onPressed,
child: I18nText('applicationItem.infoButton'),
), ),
], ],
), ),

View File

@@ -1,126 +0,0 @@
import 'package:flutter/material.dart';
class CustomMaterialButton extends StatelessWidget {
const CustomMaterialButton({
super.key,
required this.label,
this.isFilled = true,
this.isExpanded = false,
required this.onPressed,
});
final Widget label;
final bool isFilled;
final bool isExpanded;
final Function()? onPressed;
@override
Widget build(BuildContext context) {
return TextButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(
isExpanded
? const EdgeInsets.symmetric(horizontal: 24, vertical: 12)
: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
shape: MaterialStateProperty.all(
StadiumBorder(
side: isFilled
? BorderSide.none
: BorderSide(
color: Theme.of(context).colorScheme.primary,
),
),
),
backgroundColor: MaterialStateProperty.all(
isFilled ? Theme.of(context).colorScheme.primary : Colors.transparent,
),
foregroundColor: MaterialStateProperty.all(
isFilled
? Theme.of(context).colorScheme.surface
: Theme.of(context).colorScheme.primary,
),
),
onPressed: onPressed,
child: label,
);
}
}
// ignore: must_be_immutable
class TimerButton extends StatefulWidget {
TimerButton({
super.key,
required this.seconds,
required this.isRunning,
required this.onTimerEnd,
this.label = const Text(''),
this.isFilled = true,
});
Widget label;
bool isFilled;
int seconds;
final bool isRunning;
final Function()? onTimerEnd;
@override
State<TimerButton> createState() => _TimerButtonState();
}
class _TimerButtonState extends State<TimerButton> {
void timer(int seconds) {
Future.delayed(const Duration(seconds: 1), () {
if (seconds > 0) {
setState(() {
seconds--;
});
timer(seconds);
} else {
widget.onTimerEnd!();
}
});
}
@override
void initState() {
//decrement seconds
if (widget.isRunning) {
timer(widget.seconds);
}
super.initState();
}
@override
Widget build(BuildContext build) {
return TextButton(
style: ButtonStyle(
shape: MaterialStateProperty.all(
StadiumBorder(
side: widget.isFilled
? BorderSide.none
: BorderSide(
color: Theme.of(context).colorScheme.primary,
),
),
),
backgroundColor: MaterialStateProperty.all(
widget.isFilled
? Theme.of(context).colorScheme.primary
: Colors.transparent,
),
foregroundColor: MaterialStateProperty.all(
widget.isFilled
? Theme.of(context).colorScheme.surface
: Theme.of(context).colorScheme.primary,
),
),
onPressed: widget.isRunning ? null : widget.onTimerEnd,
child: Text(
widget.isRunning ? '${widget.seconds}' : 'Install',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
);
}
}

View File

@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class HapticCheckbox extends StatelessWidget {
const HapticCheckbox({
super.key,
required this.value,
required this.onChanged,
this.activeColor,
this.checkColor,
this.side,
});
final bool value;
final Function(bool?)? onChanged;
final Color? activeColor;
final Color? checkColor;
final BorderSide? side;
@override
Widget build(BuildContext context) {
return Checkbox(
value: value,
onChanged: (value) => {
HapticFeedback.selectionClick(),
if (onChanged != null) onChanged!(value),
},
activeColor: activeColor,
checkColor: checkColor,
side: side,
);
}
}

View File

@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class HapticCheckboxListTile extends StatelessWidget {
const HapticCheckboxListTile({
super.key,
required this.value,
required this.onChanged,
this.title,
this.subtitle,
this.contentPadding,
});
final bool value;
final Function(bool?)? onChanged;
final Widget? title;
final Widget? subtitle;
final EdgeInsetsGeometry? contentPadding;
@override
Widget build(BuildContext context) {
return CheckboxListTile(
contentPadding: contentPadding ?? EdgeInsets.zero,
title: title,
subtitle: subtitle,
value: value,
onChanged: (value) => {
HapticFeedback.lightImpact(),
if (onChanged != null) onChanged!(value),
},
);
}
}

View File

@@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
class HapticCustomCard extends StatelessWidget {
const HapticCustomCard({
super.key,
this.isFilled = true,
required this.child,
this.onTap,
this.padding,
this.backgroundColor,
});
final bool isFilled;
final Widget child;
final Function()? onTap;
final EdgeInsetsGeometry? padding;
final Color? backgroundColor;
@override
Widget build(BuildContext context) {
return CustomCard(
isFilled: isFilled,
onTap: () => {
HapticFeedback.selectionClick(),
if (onTap != null) onTap!(),
},
padding: padding,
backgroundColor: backgroundColor,
child: child,
);
}
}

View File

@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class HapticFloatingActionButtonExtended extends StatelessWidget {
const HapticFloatingActionButtonExtended({
super.key,
required this.onPressed,
required this.label,
this.icon,
this.elevation,
});
final Function()? onPressed;
final Widget label;
final Widget? icon;
final double? elevation;
@override
Widget build(BuildContext context) {
return FloatingActionButton.extended(
onPressed: () => {
HapticFeedback.lightImpact(),
if (onPressed != null) onPressed!(),
},
label: label,
icon: icon,
elevation: elevation,
);
}
}

View File

@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class HapticRadioListTile extends StatelessWidget {
const HapticRadioListTile({
super.key,
required this.title,
required this.value,
required this.groupValue,
this.subtitle,
this.onChanged,
this.contentPadding,
});
final Widget title;
final Widget? subtitle;
final int value;
final Function(int?)? onChanged;
final int groupValue;
final EdgeInsetsGeometry? contentPadding;
@override
Widget build(BuildContext context) {
return RadioListTile(
contentPadding: contentPadding ?? EdgeInsets.zero,
title: title,
subtitle: subtitle,
value: value,
groupValue: groupValue,
onChanged: (val) => {
if (val == value) {
HapticFeedback.lightImpact(),
},
if (onChanged != null) onChanged!(val),
},
);
}
}

View File

@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class HapticSwitchListTile extends StatelessWidget {
const HapticSwitchListTile({
super.key,
required this.value,
required this.onChanged,
this.title,
this.subtitle,
this.contentPadding,
});
final bool value;
final Function(bool)? onChanged;
final Widget? title;
final Widget? subtitle;
final EdgeInsetsGeometry? contentPadding;
@override
Widget build(BuildContext context) {
return SwitchListTile(
contentPadding: contentPadding ?? EdgeInsets.zero,
title: title,
subtitle: subtitle,
value: value,
onChanged: (value) => {
if (value) {
HapticFeedback.mediumImpact(),
} else {
HapticFeedback.lightImpact(),
},
if (onChanged != null) onChanged!(value),
},
);
}
}

View File

@@ -27,12 +27,12 @@ bool hasUnsupportedRequiredOption(List<Option> options, Patch patch) {
for (final Option option in options) { for (final Option option in options) {
if (option.required && if (option.required &&
option.value == null && option.value == null &&
locator<ManagerAPI>() locator<ManagerAPI>().getPatchOption(
.getPatchOption( locator<PatcherViewModel>().selectedApp!.packageName,
locator<PatcherViewModel>().selectedApp!.packageName, patch.name,
patch.name, option.key,
option.key, ) ==
) == null) { null) {
requiredOptionsType.add(option.valueType); requiredOptionsType.add(option.valueType);
} }
} }
@@ -52,7 +52,8 @@ List<Option> getNullRequiredOptions(List<Patch> patches, String packageName) {
patchOption.required && patchOption.required &&
patchOption.value == null && patchOption.value == null &&
locator<ManagerAPI>() locator<ManagerAPI>()
.getPatchOption(packageName, patch.name, patchOption.key) == null) { .getPatchOption(packageName, patch.name, patchOption.key) ==
null) {
requiredNullOptions.add(patchOption); requiredNullOptions.add(patchOption);
} }
} }

6573
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

9
package.json Normal file
View File

@@ -0,0 +1,9 @@
{
"devDependencies": {
"@saithodev/semantic-release-backmerge": "^4.0.1",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"semantic-release": "^22.0.12"
}
}

View File

@@ -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.17.0+101700000 version: 1.19.0-dev.2+101900002
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'
@@ -12,86 +12,68 @@ environment:
dependencies: dependencies:
animations: ^2.0.7 animations: ^2.0.7
collection: ^1.17.0 collection: ^1.17.0
connectivity_plus: ^5.0.1
device_apps: device_apps:
git: # switch back to ponces fork once https://github.com/ponces/flutter_plugin_device_apps/pull/1 is merged git: # switch back to ponces fork once https://github.com/ponces/flutter_plugin_device_apps/pull/1 is merged
url: https://github.com/BenjaminHalko/flutter_plugin_device_apps url: https://github.com/BenjaminHalko/flutter_plugin_device_apps
ref: revanced-manager ref: 0efbeba41657158a66bbc92c55d1226df56d0f1b # Branch: revanced-manager
device_info_plus: ^9.1.0 device_info_plus: ^9.1.0
dynamic_color: ^1.6.3
dio: ^5.0.0 dio: ^5.0.0
dio_cache_interceptor: ^3.4.0
dynamic_color: ^1.6.3
dynamic_themes: ^1.1.0 dynamic_themes: ^1.1.0
expandable: ^5.0.1 expandable: ^5.0.1
flex_color_scheme: ^7.0.1
flutter: flutter:
sdk: flutter sdk: flutter
flutter_background: flutter_background:
git: # remove once https://github.com/JulianAssmann/flutter_background/pull/79 is merged git: # remove once https://github.com/JulianAssmann/flutter_background/pull/79 is merged
url: https://github.com/BenjaminHalko/flutter_background url: https://github.com/BenjaminHalko/flutter_background
ref: specify-namespace ref: 560d21c4148b53933313573e7eafca0b0eb9aadf # Branch: specify-namespace
flutter_cache_manager: ^3.3.0 flutter_cache_manager: ^3.3.0
flutter_file_dialog: ^3.0.2
flutter_i18n: ^0.34.0 flutter_i18n: ^0.34.0
flutter_local_notifications: ^16.1.0 flutter_local_notifications: ^16.1.0
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
flutter_svg: ^2.0.4 flutter_markdown: ^0.6.14
fluttertoast: fluttertoast: ^8.2.4
git: # remove once the next fluttertoast version is release (> 8.2.2)
url: https://github.com/ponnamkarthik/FlutterToast
ref: f4e7b4e1afc8c760eb5bac80f6a2e299906d83ca
font_awesome_flutter: ^10.4.0 font_awesome_flutter: ^10.4.0
get_it: ^7.6.4
google_fonts: ^6.1.0 google_fonts: ^6.1.0
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/BenjaminHalko/logcat url: https://github.com/BenjaminHalko/logcat
ref: master ref: 4a6d5e0e22292c8eb160cfb9365b9ea29735fd43 # Branch: master
package_info_plus: ^4.2.0 package_info_plus: ^5.0.1
path_provider: ^2.0.14 path_provider: ^2.0.14
permission_handler: ^11.0.1 permission_handler: ^11.0.1
pull_to_refresh: ^2.0.0
root: root:
git: git:
url: https://github.com/validcube/root url: https://github.com/validcube/root
ref: 68e5678a535a2a3344828a14a39017fa74b9098c ref: 68e5678a535a2a3344828a14a39017fa74b9098c # Branch: libsu-521
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
share_plus: ^7.2.1
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
synchronized: ^3.1.0
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
flutter_dotenv: ^5.0.2
flutter_markdown: ^0.6.14
dio_cache_interceptor: ^3.4.0
install_plugin:
git: # remove once https://github.com/hui-z/flutter_install_plugin/pull/67 is merged
url: https://github.com/BenjaminHalko/flutter_install_plugin
ref: 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: master
synchronized: ^3.1.0
connectivity_plus: ^5.0.1
flutter_file_dialog: ^3.0.2
wakelock_plus: ^1.1.3 wakelock_plus: ^1.1.3
share_plus: ^7.2.1
dev_dependencies: dev_dependencies:
json_serializable: ^6.6.1
build_runner: any build_runner: any
flutter_launcher_icons: ^0.13.0
flutter_lints: ^3.0.1 flutter_lints: ^3.0.1
flutter_test:
sdk: flutter
injectable_generator: ^2.1.5 injectable_generator: ^2.1.5
json_serializable: ^6.6.1
flutter: flutter:
uses-material-design: true uses-material-design: true